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 pub units: UnitLength,
662 #[serde(skip)]
664 pub meta: Vec<Metadata>,
665 #[serde(default = "very_true", skip_serializing_if = "is_true")]
667 pub is_closed: bool,
668}
669
670fn is_true(b: &bool) -> bool {
671 *b
672}
673
674impl Sketch {
675 pub(crate) fn build_sketch_mode_cmds(
678 &self,
679 exec_state: &mut ExecState,
680 inner_cmd: ModelingCmdReq,
681 ) -> Vec<ModelingCmdReq> {
682 vec![
683 ModelingCmdReq {
686 cmd: ModelingCmd::from(mcmd::EnableSketchMode {
687 animated: false,
688 ortho: false,
689 entity_id: self.on.id(),
690 adjust_camera: false,
691 planar_normal: if let SketchSurface::Plane(plane) = &self.on {
692 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
694 Some(normal.into())
695 } else {
696 None
697 },
698 }),
699 cmd_id: exec_state.next_uuid().into(),
700 },
701 inner_cmd,
702 ModelingCmdReq {
703 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
704 cmd_id: exec_state.next_uuid().into(),
705 },
706 ]
707 }
708}
709
710#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
712#[ts(export)]
713#[serde(tag = "type", rename_all = "camelCase")]
714pub enum SketchSurface {
715 Plane(Box<Plane>),
716 Face(Box<Face>),
717}
718
719impl SketchSurface {
720 pub(crate) fn id(&self) -> uuid::Uuid {
721 match self {
722 SketchSurface::Plane(plane) => plane.id,
723 SketchSurface::Face(face) => face.id,
724 }
725 }
726 pub(crate) fn x_axis(&self) -> Point3d {
727 match self {
728 SketchSurface::Plane(plane) => plane.info.x_axis,
729 SketchSurface::Face(face) => face.x_axis,
730 }
731 }
732 pub(crate) fn y_axis(&self) -> Point3d {
733 match self {
734 SketchSurface::Plane(plane) => plane.info.y_axis,
735 SketchSurface::Face(face) => face.y_axis,
736 }
737 }
738}
739
740#[derive(Debug, Clone)]
741pub(crate) enum GetTangentialInfoFromPathsResult {
742 PreviousPoint([f64; 2]),
743 Arc {
744 center: [f64; 2],
745 ccw: bool,
746 },
747 Circle {
748 center: [f64; 2],
749 ccw: bool,
750 radius: f64,
751 },
752 Ellipse {
753 center: [f64; 2],
754 ccw: bool,
755 major_axis: [f64; 2],
756 _minor_radius: f64,
757 },
758}
759
760impl GetTangentialInfoFromPathsResult {
761 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
762 match self {
763 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
764 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
765 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
766 }
767 GetTangentialInfoFromPathsResult::Circle {
770 center, radius, ccw, ..
771 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
772 GetTangentialInfoFromPathsResult::Ellipse {
773 center,
774 major_axis,
775 ccw,
776 ..
777 } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
778 }
779 }
780}
781
782impl Sketch {
783 pub(crate) fn add_tag(
784 &mut self,
785 tag: NodeRef<'_, TagDeclarator>,
786 current_path: &Path,
787 exec_state: &ExecState,
788 surface: Option<&ExtrudeSurface>,
789 ) {
790 let mut tag_identifier: TagIdentifier = tag.into();
791 let base = current_path.get_base();
792 tag_identifier.info.push((
793 exec_state.stack().current_epoch(),
794 TagEngineInfo {
795 id: base.geo_meta.id,
796 sketch: self.id,
797 path: Some(current_path.clone()),
798 surface: surface.cloned(),
799 },
800 ));
801
802 self.tags.insert(tag.name.to_string(), tag_identifier);
803 }
804
805 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
806 for t in tags {
807 match self.tags.get_mut(&t.value) {
808 Some(id) => {
809 id.merge_info(t);
810 }
811 None => {
812 self.tags.insert(t.value.clone(), t.clone());
813 }
814 }
815 }
816 }
817
818 pub(crate) fn latest_path(&self) -> Option<&Path> {
820 self.paths.last()
821 }
822
823 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
827 let Some(path) = self.latest_path() else {
828 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
829 };
830
831 let to = path.get_base().to;
832 Ok(Point2d::new(to[0], to[1], path.get_base().units))
833 }
834
835 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
836 let Some(path) = self.latest_path() else {
837 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
838 };
839 path.get_tangential_info()
840 }
841}
842
843#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
844#[ts(export)]
845#[serde(tag = "type", rename_all = "camelCase")]
846pub struct Solid {
847 pub id: uuid::Uuid,
849 pub artifact_id: ArtifactId,
851 pub value: Vec<ExtrudeSurface>,
853 pub sketch: Sketch,
855 pub start_cap_id: Option<uuid::Uuid>,
857 pub end_cap_id: Option<uuid::Uuid>,
859 #[serde(default, skip_serializing_if = "Vec::is_empty")]
861 pub edge_cuts: Vec<EdgeCut>,
862 pub units: UnitLength,
864 pub sectional: bool,
866 #[serde(skip)]
868 pub meta: Vec<Metadata>,
869}
870
871impl Solid {
872 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
873 self.edge_cuts.iter().map(|foc| foc.id())
874 }
875}
876
877#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
879#[ts(export)]
880#[serde(tag = "type", rename_all = "camelCase")]
881pub enum EdgeCut {
882 Fillet {
884 id: uuid::Uuid,
886 radius: TyF64,
887 #[serde(rename = "edgeId")]
889 edge_id: uuid::Uuid,
890 tag: Box<Option<TagNode>>,
891 },
892 Chamfer {
894 id: uuid::Uuid,
896 length: TyF64,
897 #[serde(rename = "edgeId")]
899 edge_id: uuid::Uuid,
900 tag: Box<Option<TagNode>>,
901 },
902}
903
904impl EdgeCut {
905 pub fn id(&self) -> uuid::Uuid {
906 match self {
907 EdgeCut::Fillet { id, .. } => *id,
908 EdgeCut::Chamfer { id, .. } => *id,
909 }
910 }
911
912 pub fn set_id(&mut self, id: uuid::Uuid) {
913 match self {
914 EdgeCut::Fillet { id: i, .. } => *i = id,
915 EdgeCut::Chamfer { id: i, .. } => *i = id,
916 }
917 }
918
919 pub fn edge_id(&self) -> uuid::Uuid {
920 match self {
921 EdgeCut::Fillet { edge_id, .. } => *edge_id,
922 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
923 }
924 }
925
926 pub fn set_edge_id(&mut self, id: uuid::Uuid) {
927 match self {
928 EdgeCut::Fillet { edge_id: i, .. } => *i = id,
929 EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
930 }
931 }
932
933 pub fn tag(&self) -> Option<TagNode> {
934 match self {
935 EdgeCut::Fillet { tag, .. } => *tag.clone(),
936 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
937 }
938 }
939}
940
941#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
942#[ts(export)]
943pub struct Point2d {
944 pub x: f64,
945 pub y: f64,
946 pub units: UnitLength,
947}
948
949impl Point2d {
950 pub const ZERO: Self = Self {
951 x: 0.0,
952 y: 0.0,
953 units: UnitLength::Millimeters,
954 };
955
956 pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
957 Self { x, y, units }
958 }
959
960 pub fn into_x(self) -> TyF64 {
961 TyF64::new(self.x, self.units.into())
962 }
963
964 pub fn into_y(self) -> TyF64 {
965 TyF64::new(self.y, self.units.into())
966 }
967
968 pub fn ignore_units(self) -> [f64; 2] {
969 [self.x, self.y]
970 }
971}
972
973#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
974#[ts(export)]
975pub struct Point3d {
976 pub x: f64,
977 pub y: f64,
978 pub z: f64,
979 pub units: Option<UnitLength>,
980}
981
982impl Point3d {
983 pub const ZERO: Self = Self {
984 x: 0.0,
985 y: 0.0,
986 z: 0.0,
987 units: Some(UnitLength::Millimeters),
988 };
989
990 pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
991 Self { x, y, z, units }
992 }
993
994 pub const fn is_zero(&self) -> bool {
995 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
996 }
997
998 pub fn axes_cross_product(&self, other: &Self) -> Self {
1003 Self {
1004 x: self.y * other.z - self.z * other.y,
1005 y: self.z * other.x - self.x * other.z,
1006 z: self.x * other.y - self.y * other.x,
1007 units: None,
1008 }
1009 }
1010
1011 pub fn axes_dot_product(&self, other: &Self) -> f64 {
1016 let x = self.x * other.x;
1017 let y = self.y * other.y;
1018 let z = self.z * other.z;
1019 x + y + z
1020 }
1021
1022 pub fn normalize(&self) -> Self {
1023 let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1024 Point3d {
1025 x: self.x / len,
1026 y: self.y / len,
1027 z: self.z / len,
1028 units: None,
1029 }
1030 }
1031
1032 pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1033 let p = [self.x, self.y, self.z];
1034 let u = self.units;
1035 (p, u)
1036 }
1037
1038 pub(crate) fn negated(self) -> Self {
1039 Self {
1040 x: -self.x,
1041 y: -self.y,
1042 z: -self.z,
1043 units: self.units,
1044 }
1045 }
1046}
1047
1048impl From<[TyF64; 3]> for Point3d {
1049 fn from(p: [TyF64; 3]) -> Self {
1050 Self {
1051 x: p[0].n,
1052 y: p[1].n,
1053 z: p[2].n,
1054 units: p[0].ty.as_length(),
1055 }
1056 }
1057}
1058
1059impl From<Point3d> for Point3D {
1060 fn from(p: Point3d) -> Self {
1061 Self { x: p.x, y: p.y, z: p.z }
1062 }
1063}
1064
1065impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1066 fn from(p: Point3d) -> Self {
1067 if let Some(units) = p.units {
1068 Self {
1069 x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1070 y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1071 z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1072 }
1073 } else {
1074 Self {
1075 x: LengthUnit(p.x),
1076 y: LengthUnit(p.y),
1077 z: LengthUnit(p.z),
1078 }
1079 }
1080 }
1081}
1082
1083impl Add for Point3d {
1084 type Output = Point3d;
1085
1086 fn add(self, rhs: Self) -> Self::Output {
1087 Point3d {
1089 x: self.x + rhs.x,
1090 y: self.y + rhs.y,
1091 z: self.z + rhs.z,
1092 units: self.units,
1093 }
1094 }
1095}
1096
1097impl AddAssign for Point3d {
1098 fn add_assign(&mut self, rhs: Self) {
1099 *self = *self + rhs
1100 }
1101}
1102
1103impl Sub for Point3d {
1104 type Output = Point3d;
1105
1106 fn sub(self, rhs: Self) -> Self::Output {
1107 let (x, y, z) = if rhs.units != self.units
1108 && let Some(sunits) = self.units
1109 && let Some(runits) = rhs.units
1110 {
1111 (
1112 adjust_length(runits, rhs.x, sunits).0,
1113 adjust_length(runits, rhs.y, sunits).0,
1114 adjust_length(runits, rhs.z, sunits).0,
1115 )
1116 } else {
1117 (rhs.x, rhs.y, rhs.z)
1118 };
1119 Point3d {
1120 x: self.x - x,
1121 y: self.y - y,
1122 z: self.z - z,
1123 units: self.units,
1124 }
1125 }
1126}
1127
1128impl SubAssign for Point3d {
1129 fn sub_assign(&mut self, rhs: Self) {
1130 *self = *self - rhs
1131 }
1132}
1133
1134impl Mul<f64> for Point3d {
1135 type Output = Point3d;
1136
1137 fn mul(self, rhs: f64) -> Self::Output {
1138 Point3d {
1139 x: self.x * rhs,
1140 y: self.y * rhs,
1141 z: self.z * rhs,
1142 units: self.units,
1143 }
1144 }
1145}
1146
1147#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1149#[ts(export)]
1150#[serde(rename_all = "camelCase")]
1151pub struct BasePath {
1152 #[ts(type = "[number, number]")]
1154 pub from: [f64; 2],
1155 #[ts(type = "[number, number]")]
1157 pub to: [f64; 2],
1158 pub units: UnitLength,
1159 pub tag: Option<TagNode>,
1161 #[serde(rename = "__geoMeta")]
1163 pub geo_meta: GeoMeta,
1164}
1165
1166impl BasePath {
1167 pub fn get_to(&self) -> [TyF64; 2] {
1168 let ty: NumericType = self.units.into();
1169 [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1170 }
1171
1172 pub fn get_from(&self) -> [TyF64; 2] {
1173 let ty: NumericType = self.units.into();
1174 [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1175 }
1176}
1177
1178#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1180#[ts(export)]
1181#[serde(rename_all = "camelCase")]
1182pub struct GeoMeta {
1183 pub id: uuid::Uuid,
1185 #[serde(flatten)]
1187 pub metadata: Metadata,
1188}
1189
1190#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1192#[ts(export)]
1193#[serde(tag = "type")]
1194pub enum Path {
1195 ToPoint {
1197 #[serde(flatten)]
1198 base: BasePath,
1199 },
1200 TangentialArcTo {
1202 #[serde(flatten)]
1203 base: BasePath,
1204 #[ts(type = "[number, number]")]
1206 center: [f64; 2],
1207 ccw: bool,
1209 },
1210 TangentialArc {
1212 #[serde(flatten)]
1213 base: BasePath,
1214 #[ts(type = "[number, number]")]
1216 center: [f64; 2],
1217 ccw: bool,
1219 },
1220 Circle {
1223 #[serde(flatten)]
1224 base: BasePath,
1225 #[ts(type = "[number, number]")]
1227 center: [f64; 2],
1228 radius: f64,
1230 ccw: bool,
1233 },
1234 CircleThreePoint {
1235 #[serde(flatten)]
1236 base: BasePath,
1237 #[ts(type = "[number, number]")]
1239 p1: [f64; 2],
1240 #[ts(type = "[number, number]")]
1242 p2: [f64; 2],
1243 #[ts(type = "[number, number]")]
1245 p3: [f64; 2],
1246 },
1247 ArcThreePoint {
1248 #[serde(flatten)]
1249 base: BasePath,
1250 #[ts(type = "[number, number]")]
1252 p1: [f64; 2],
1253 #[ts(type = "[number, number]")]
1255 p2: [f64; 2],
1256 #[ts(type = "[number, number]")]
1258 p3: [f64; 2],
1259 },
1260 Horizontal {
1262 #[serde(flatten)]
1263 base: BasePath,
1264 x: f64,
1266 },
1267 AngledLineTo {
1269 #[serde(flatten)]
1270 base: BasePath,
1271 x: Option<f64>,
1273 y: Option<f64>,
1275 },
1276 Base {
1278 #[serde(flatten)]
1279 base: BasePath,
1280 },
1281 Arc {
1283 #[serde(flatten)]
1284 base: BasePath,
1285 center: [f64; 2],
1287 radius: f64,
1289 ccw: bool,
1291 },
1292 Ellipse {
1293 #[serde(flatten)]
1294 base: BasePath,
1295 center: [f64; 2],
1296 major_axis: [f64; 2],
1297 minor_radius: f64,
1298 ccw: bool,
1299 },
1300 Conic {
1302 #[serde(flatten)]
1303 base: BasePath,
1304 },
1305}
1306
1307impl Path {
1308 pub fn get_id(&self) -> uuid::Uuid {
1309 match self {
1310 Path::ToPoint { base } => base.geo_meta.id,
1311 Path::Horizontal { base, .. } => base.geo_meta.id,
1312 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1313 Path::Base { base } => base.geo_meta.id,
1314 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1315 Path::TangentialArc { base, .. } => base.geo_meta.id,
1316 Path::Circle { base, .. } => base.geo_meta.id,
1317 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1318 Path::Arc { base, .. } => base.geo_meta.id,
1319 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1320 Path::Ellipse { base, .. } => base.geo_meta.id,
1321 Path::Conic { base, .. } => base.geo_meta.id,
1322 }
1323 }
1324
1325 pub fn set_id(&mut self, id: uuid::Uuid) {
1326 match self {
1327 Path::ToPoint { base } => base.geo_meta.id = id,
1328 Path::Horizontal { base, .. } => base.geo_meta.id = id,
1329 Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1330 Path::Base { base } => base.geo_meta.id = id,
1331 Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1332 Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1333 Path::Circle { base, .. } => base.geo_meta.id = id,
1334 Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1335 Path::Arc { base, .. } => base.geo_meta.id = id,
1336 Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1337 Path::Ellipse { base, .. } => base.geo_meta.id = id,
1338 Path::Conic { base, .. } => base.geo_meta.id = id,
1339 }
1340 }
1341
1342 pub fn get_tag(&self) -> Option<TagNode> {
1343 match self {
1344 Path::ToPoint { base } => base.tag.clone(),
1345 Path::Horizontal { base, .. } => base.tag.clone(),
1346 Path::AngledLineTo { base, .. } => base.tag.clone(),
1347 Path::Base { base } => base.tag.clone(),
1348 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1349 Path::TangentialArc { base, .. } => base.tag.clone(),
1350 Path::Circle { base, .. } => base.tag.clone(),
1351 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1352 Path::Arc { base, .. } => base.tag.clone(),
1353 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1354 Path::Ellipse { base, .. } => base.tag.clone(),
1355 Path::Conic { base, .. } => base.tag.clone(),
1356 }
1357 }
1358
1359 pub fn get_base(&self) -> &BasePath {
1360 match self {
1361 Path::ToPoint { base } => base,
1362 Path::Horizontal { base, .. } => base,
1363 Path::AngledLineTo { base, .. } => base,
1364 Path::Base { base } => base,
1365 Path::TangentialArcTo { base, .. } => base,
1366 Path::TangentialArc { base, .. } => base,
1367 Path::Circle { base, .. } => base,
1368 Path::CircleThreePoint { base, .. } => base,
1369 Path::Arc { base, .. } => base,
1370 Path::ArcThreePoint { base, .. } => base,
1371 Path::Ellipse { base, .. } => base,
1372 Path::Conic { base, .. } => base,
1373 }
1374 }
1375
1376 pub fn get_from(&self) -> [TyF64; 2] {
1378 let p = &self.get_base().from;
1379 let ty: NumericType = self.get_base().units.into();
1380 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1381 }
1382
1383 pub fn get_to(&self) -> [TyF64; 2] {
1385 let p = &self.get_base().to;
1386 let ty: NumericType = self.get_base().units.into();
1387 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1388 }
1389
1390 pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1392 let p = &self.get_base().from;
1393 let ty: NumericType = self.get_base().units.into();
1394 (*p, ty)
1395 }
1396
1397 pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1399 let p = &self.get_base().to;
1400 let ty: NumericType = self.get_base().units.into();
1401 (*p, ty)
1402 }
1403
1404 pub fn length(&self) -> Option<TyF64> {
1407 let n = match self {
1408 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1409 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1410 }
1411 Self::TangentialArc {
1412 base: _,
1413 center,
1414 ccw: _,
1415 }
1416 | Self::TangentialArcTo {
1417 base: _,
1418 center,
1419 ccw: _,
1420 } => {
1421 let radius = linear_distance(&self.get_base().from, center);
1424 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
1425 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1427 }
1428 Self::Circle { radius, .. } => Some(2.0 * std::f64::consts::PI * radius),
1429 Self::CircleThreePoint { .. } => {
1430 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1431 self.get_base().from,
1432 self.get_base().to,
1433 self.get_base().to,
1434 ]);
1435 let radius = linear_distance(
1436 &[circle_center.center[0], circle_center.center[1]],
1437 &self.get_base().from,
1438 );
1439 Some(2.0 * std::f64::consts::PI * radius)
1440 }
1441 Self::Arc { .. } => {
1442 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1444 }
1445 Self::ArcThreePoint { .. } => {
1446 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1448 }
1449 Self::Ellipse { .. } => {
1450 None
1452 }
1453 Self::Conic { .. } => {
1454 None
1456 }
1457 };
1458 n.map(|n| TyF64::new(n, self.get_base().units.into()))
1459 }
1460
1461 pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
1462 match self {
1463 Path::ToPoint { base } => Some(base),
1464 Path::Horizontal { base, .. } => Some(base),
1465 Path::AngledLineTo { base, .. } => Some(base),
1466 Path::Base { base } => Some(base),
1467 Path::TangentialArcTo { base, .. } => Some(base),
1468 Path::TangentialArc { base, .. } => Some(base),
1469 Path::Circle { base, .. } => Some(base),
1470 Path::CircleThreePoint { base, .. } => Some(base),
1471 Path::Arc { base, .. } => Some(base),
1472 Path::ArcThreePoint { base, .. } => Some(base),
1473 Path::Ellipse { base, .. } => Some(base),
1474 Path::Conic { base, .. } => Some(base),
1475 }
1476 }
1477
1478 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1479 match self {
1480 Path::TangentialArc { center, ccw, .. }
1481 | Path::TangentialArcTo { center, ccw, .. }
1482 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1483 center: *center,
1484 ccw: *ccw,
1485 },
1486 Path::ArcThreePoint { p1, p2, p3, .. } => {
1487 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1488 GetTangentialInfoFromPathsResult::Arc {
1489 center: circle.center,
1490 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
1491 }
1492 }
1493 Path::Circle {
1494 center, ccw, radius, ..
1495 } => GetTangentialInfoFromPathsResult::Circle {
1496 center: *center,
1497 ccw: *ccw,
1498 radius: *radius,
1499 },
1500 Path::CircleThreePoint { p1, p2, p3, .. } => {
1501 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1502 let center_point = [circle.center[0], circle.center[1]];
1503 GetTangentialInfoFromPathsResult::Circle {
1504 center: center_point,
1505 ccw: true,
1507 radius: circle.radius,
1508 }
1509 }
1510 Path::Ellipse {
1512 center,
1513 major_axis,
1514 minor_radius,
1515 ccw,
1516 ..
1517 } => GetTangentialInfoFromPathsResult::Ellipse {
1518 center: *center,
1519 major_axis: *major_axis,
1520 _minor_radius: *minor_radius,
1521 ccw: *ccw,
1522 },
1523 Path::Conic { .. }
1524 | Path::ToPoint { .. }
1525 | Path::Horizontal { .. }
1526 | Path::AngledLineTo { .. }
1527 | Path::Base { .. } => {
1528 let base = self.get_base();
1529 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1530 }
1531 }
1532 }
1533
1534 pub(crate) fn is_straight_line(&self) -> bool {
1536 matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
1537 }
1538}
1539
1540#[rustfmt::skip]
1542fn linear_distance(
1543 [x0, y0]: &[f64; 2],
1544 [x1, y1]: &[f64; 2]
1545) -> f64 {
1546 let y_sq = (y1 - y0).powi(2);
1547 let x_sq = (x1 - x0).powi(2);
1548 (y_sq + x_sq).sqrt()
1549}
1550
1551#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1553#[ts(export)]
1554#[serde(tag = "type", rename_all = "camelCase")]
1555pub enum ExtrudeSurface {
1556 ExtrudePlane(ExtrudePlane),
1558 ExtrudeArc(ExtrudeArc),
1559 Chamfer(ChamferSurface),
1560 Fillet(FilletSurface),
1561}
1562
1563#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1565#[ts(export)]
1566#[serde(rename_all = "camelCase")]
1567pub struct ChamferSurface {
1568 pub face_id: uuid::Uuid,
1570 pub tag: Option<Node<TagDeclarator>>,
1572 #[serde(flatten)]
1574 pub geo_meta: GeoMeta,
1575}
1576
1577#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1579#[ts(export)]
1580#[serde(rename_all = "camelCase")]
1581pub struct FilletSurface {
1582 pub face_id: uuid::Uuid,
1584 pub tag: Option<Node<TagDeclarator>>,
1586 #[serde(flatten)]
1588 pub geo_meta: GeoMeta,
1589}
1590
1591#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1593#[ts(export)]
1594#[serde(rename_all = "camelCase")]
1595pub struct ExtrudePlane {
1596 pub face_id: uuid::Uuid,
1598 pub tag: Option<Node<TagDeclarator>>,
1600 #[serde(flatten)]
1602 pub geo_meta: GeoMeta,
1603}
1604
1605#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1607#[ts(export)]
1608#[serde(rename_all = "camelCase")]
1609pub struct ExtrudeArc {
1610 pub face_id: uuid::Uuid,
1612 pub tag: Option<Node<TagDeclarator>>,
1614 #[serde(flatten)]
1616 pub geo_meta: GeoMeta,
1617}
1618
1619impl ExtrudeSurface {
1620 pub fn get_id(&self) -> uuid::Uuid {
1621 match self {
1622 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
1623 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
1624 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
1625 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
1626 }
1627 }
1628
1629 pub fn face_id(&self) -> uuid::Uuid {
1630 match self {
1631 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id,
1632 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id,
1633 ExtrudeSurface::Fillet(f) => f.face_id,
1634 ExtrudeSurface::Chamfer(c) => c.face_id,
1635 }
1636 }
1637
1638 pub fn set_face_id(&mut self, face_id: uuid::Uuid) {
1639 match self {
1640 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id = face_id,
1641 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id = face_id,
1642 ExtrudeSurface::Fillet(f) => f.face_id = face_id,
1643 ExtrudeSurface::Chamfer(c) => c.face_id = face_id,
1644 }
1645 }
1646
1647 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
1648 match self {
1649 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
1650 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
1651 ExtrudeSurface::Fillet(f) => f.tag.clone(),
1652 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
1653 }
1654 }
1655}
1656
1657#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ts_rs::TS)]
1658pub struct SketchVarId(pub usize);
1659
1660impl SketchVarId {
1661 pub fn to_constraint_id(self, range: SourceRange) -> Result<kcl_ezpz::Id, KclError> {
1662 self.0.try_into().map_err(|_| {
1663 KclError::new_type(KclErrorDetails::new(
1664 "Cannot convert to constraint ID since the sketch variable ID is too large".to_owned(),
1665 vec![range],
1666 ))
1667 })
1668 }
1669}
1670
1671#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1672#[ts(export_to = "Geometry.ts")]
1673#[serde(rename_all = "camelCase")]
1674pub struct SketchVar {
1675 pub id: SketchVarId,
1676 pub initial_value: f64,
1677 pub ty: NumericType,
1678 #[serde(skip)]
1679 pub meta: Vec<Metadata>,
1680}