1use std::ops::{Add, AddAssign, Mul, Sub, SubAssign};
2
3use anyhow::Result;
4use indexmap::IndexMap;
5use kcl_error::SourceRange;
6use kittycad_modeling_cmds::{
7 self as kcmc, ModelingCmd, each_cmd as mcmd, length_unit::LengthUnit, units::UnitLength, websocket::ModelingCmdReq,
8};
9use parse_display::{Display, FromStr};
10use serde::{Deserialize, Serialize};
11use uuid::Uuid;
12
13use crate::{
14 engine::{DEFAULT_PLANE_INFO, PlaneName},
15 errors::{KclError, KclErrorDetails},
16 exec::KclValue,
17 execution::{
18 ArtifactId, ExecState, ExecutorContext, Metadata, TagEngineInfo, TagIdentifier, normalize_to_solver_unit,
19 types::{NumericType, adjust_length},
20 },
21 front::{ArcCtor, Freedom, LineCtor, ObjectId, PointCtor},
22 parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
23 std::{
24 Args,
25 args::TyF64,
26 sketch::{FaceTag, PlaneData},
27 },
28};
29
30type Point3D = kcmc::shared::Point3d<f64>;
31
32#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
34#[ts(export)]
35#[serde(tag = "type", rename_all = "camelCase")]
36pub struct GdtAnnotation {
37 pub id: uuid::Uuid,
39 #[serde(skip)]
40 pub meta: Vec<Metadata>,
41}
42
43#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
45#[ts(export)]
46#[serde(tag = "type")]
47#[allow(clippy::large_enum_variant)]
48pub enum Geometry {
49 Sketch(Sketch),
50 Solid(Solid),
51}
52
53impl Geometry {
54 pub fn id(&self) -> uuid::Uuid {
55 match self {
56 Geometry::Sketch(s) => s.id,
57 Geometry::Solid(e) => e.id,
58 }
59 }
60
61 pub fn original_id(&self) -> uuid::Uuid {
65 match self {
66 Geometry::Sketch(s) => s.original_id,
67 Geometry::Solid(e) => e.sketch.original_id,
68 }
69 }
70}
71
72#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
74#[ts(export)]
75#[serde(tag = "type")]
76#[allow(clippy::large_enum_variant)]
77pub enum GeometryWithImportedGeometry {
78 Sketch(Sketch),
79 Solid(Solid),
80 ImportedGeometry(Box<ImportedGeometry>),
81}
82
83impl GeometryWithImportedGeometry {
84 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
85 match self {
86 GeometryWithImportedGeometry::Sketch(s) => Ok(s.id),
87 GeometryWithImportedGeometry::Solid(e) => Ok(e.id),
88 GeometryWithImportedGeometry::ImportedGeometry(i) => {
89 let id = i.id(ctx).await?;
90 Ok(id)
91 }
92 }
93 }
94}
95
96#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
98#[ts(export)]
99#[serde(tag = "type")]
100#[allow(clippy::vec_box)]
101pub enum Geometries {
102 Sketches(Vec<Sketch>),
103 Solids(Vec<Solid>),
104}
105
106impl From<Geometry> for Geometries {
107 fn from(value: Geometry) -> Self {
108 match value {
109 Geometry::Sketch(x) => Self::Sketches(vec![x]),
110 Geometry::Solid(x) => Self::Solids(vec![x]),
111 }
112 }
113}
114
115#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
117#[ts(export)]
118#[serde(rename_all = "camelCase")]
119pub struct ImportedGeometry {
120 pub id: uuid::Uuid,
122 pub value: Vec<String>,
124 #[serde(skip)]
125 pub meta: Vec<Metadata>,
126 #[serde(skip)]
128 completed: bool,
129}
130
131impl ImportedGeometry {
132 pub fn new(id: uuid::Uuid, value: Vec<String>, meta: Vec<Metadata>) -> Self {
133 Self {
134 id,
135 value,
136 meta,
137 completed: false,
138 }
139 }
140
141 async fn wait_for_finish(&mut self, ctx: &ExecutorContext) -> Result<(), KclError> {
142 if self.completed {
143 return Ok(());
144 }
145
146 ctx.engine
147 .ensure_async_command_completed(self.id, self.meta.first().map(|m| m.source_range))
148 .await?;
149
150 self.completed = true;
151
152 Ok(())
153 }
154
155 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
156 if !self.completed {
157 self.wait_for_finish(ctx).await?;
158 }
159
160 Ok(self.id)
161 }
162}
163
164#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
166#[ts(export)]
167#[serde(tag = "type", rename_all = "camelCase")]
168#[allow(clippy::vec_box)]
169pub enum HideableGeometry {
170 ImportedGeometry(Box<ImportedGeometry>),
171 SolidSet(Vec<Solid>),
172 HelixSet(Vec<Helix>),
173 }
178
179impl From<HideableGeometry> for crate::execution::KclValue {
180 fn from(value: HideableGeometry) -> Self {
181 match value {
182 HideableGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
183 HideableGeometry::SolidSet(mut s) => {
184 if s.len() == 1
185 && let Some(s) = s.pop()
186 {
187 crate::execution::KclValue::Solid { value: Box::new(s) }
188 } else {
189 crate::execution::KclValue::HomArray {
190 value: s
191 .into_iter()
192 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
193 .collect(),
194 ty: crate::execution::types::RuntimeType::solid(),
195 }
196 }
197 }
198 HideableGeometry::HelixSet(mut s) => {
199 if s.len() == 1
200 && let Some(s) = s.pop()
201 {
202 crate::execution::KclValue::Helix { value: Box::new(s) }
203 } else {
204 crate::execution::KclValue::HomArray {
205 value: s
206 .into_iter()
207 .map(|s| crate::execution::KclValue::Helix { value: Box::new(s) })
208 .collect(),
209 ty: crate::execution::types::RuntimeType::helices(),
210 }
211 }
212 }
213 }
214 }
215}
216
217impl HideableGeometry {
218 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
219 match self {
220 HideableGeometry::ImportedGeometry(s) => {
221 let id = s.id(ctx).await?;
222
223 Ok(vec![id])
224 }
225 HideableGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
226 HideableGeometry::HelixSet(s) => Ok(s.iter().map(|s| s.value).collect()),
227 }
228 }
229}
230
231#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
233#[ts(export)]
234#[serde(tag = "type", rename_all = "camelCase")]
235#[allow(clippy::vec_box)]
236pub enum SolidOrSketchOrImportedGeometry {
237 ImportedGeometry(Box<ImportedGeometry>),
238 SolidSet(Vec<Solid>),
239 SketchSet(Vec<Sketch>),
240}
241
242impl From<SolidOrSketchOrImportedGeometry> for crate::execution::KclValue {
243 fn from(value: SolidOrSketchOrImportedGeometry) -> Self {
244 match value {
245 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
246 SolidOrSketchOrImportedGeometry::SolidSet(mut s) => {
247 if s.len() == 1
248 && let Some(s) = s.pop()
249 {
250 crate::execution::KclValue::Solid { value: Box::new(s) }
251 } else {
252 crate::execution::KclValue::HomArray {
253 value: s
254 .into_iter()
255 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
256 .collect(),
257 ty: crate::execution::types::RuntimeType::solid(),
258 }
259 }
260 }
261 SolidOrSketchOrImportedGeometry::SketchSet(mut s) => {
262 if s.len() == 1
263 && let Some(s) = s.pop()
264 {
265 crate::execution::KclValue::Sketch { value: Box::new(s) }
266 } else {
267 crate::execution::KclValue::HomArray {
268 value: s
269 .into_iter()
270 .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) })
271 .collect(),
272 ty: crate::execution::types::RuntimeType::sketch(),
273 }
274 }
275 }
276 }
277 }
278}
279
280impl SolidOrSketchOrImportedGeometry {
281 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
282 match self {
283 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => {
284 let id = s.id(ctx).await?;
285
286 Ok(vec![id])
287 }
288 SolidOrSketchOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
289 SolidOrSketchOrImportedGeometry::SketchSet(s) => Ok(s.iter().map(|s| s.id).collect()),
290 }
291 }
292}
293
294#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
296#[ts(export)]
297#[serde(tag = "type", rename_all = "camelCase")]
298#[allow(clippy::vec_box)]
299pub enum SolidOrImportedGeometry {
300 ImportedGeometry(Box<ImportedGeometry>),
301 SolidSet(Vec<Solid>),
302}
303
304impl From<SolidOrImportedGeometry> for crate::execution::KclValue {
305 fn from(value: SolidOrImportedGeometry) -> Self {
306 match value {
307 SolidOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
308 SolidOrImportedGeometry::SolidSet(mut s) => {
309 if s.len() == 1
310 && let Some(s) = s.pop()
311 {
312 crate::execution::KclValue::Solid { value: Box::new(s) }
313 } else {
314 crate::execution::KclValue::HomArray {
315 value: s
316 .into_iter()
317 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
318 .collect(),
319 ty: crate::execution::types::RuntimeType::solid(),
320 }
321 }
322 }
323 }
324 }
325}
326
327impl SolidOrImportedGeometry {
328 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
329 match self {
330 SolidOrImportedGeometry::ImportedGeometry(s) => {
331 let id = s.id(ctx).await?;
332
333 Ok(vec![id])
334 }
335 SolidOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
336 }
337 }
338}
339
340#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
342#[ts(export)]
343#[serde(rename_all = "camelCase")]
344pub struct Helix {
345 pub value: uuid::Uuid,
347 pub artifact_id: ArtifactId,
349 pub revolutions: f64,
351 pub angle_start: f64,
353 pub ccw: bool,
355 pub cylinder_id: Option<uuid::Uuid>,
357 pub units: UnitLength,
358 #[serde(skip)]
359 pub meta: Vec<Metadata>,
360}
361
362#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
363#[ts(export)]
364#[serde(rename_all = "camelCase")]
365pub struct Plane {
366 pub id: uuid::Uuid,
368 pub artifact_id: ArtifactId,
370 #[serde(skip_serializing_if = "Option::is_none")]
373 pub object_id: Option<ObjectId>,
374 pub kind: PlaneKind,
376 #[serde(flatten)]
378 pub info: PlaneInfo,
379 #[serde(skip)]
380 pub meta: Vec<Metadata>,
381}
382
383#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS)]
384#[ts(export)]
385#[serde(rename_all = "camelCase")]
386pub struct PlaneInfo {
387 pub origin: Point3d,
389 pub x_axis: Point3d,
391 pub y_axis: Point3d,
393 pub z_axis: Point3d,
395}
396
397impl PlaneInfo {
398 pub(crate) fn into_plane_data(self) -> PlaneData {
399 if self.origin.is_zero() {
400 match self {
401 Self {
402 origin:
403 Point3d {
404 x: 0.0,
405 y: 0.0,
406 z: 0.0,
407 units: Some(UnitLength::Millimeters),
408 },
409 x_axis:
410 Point3d {
411 x: 1.0,
412 y: 0.0,
413 z: 0.0,
414 units: _,
415 },
416 y_axis:
417 Point3d {
418 x: 0.0,
419 y: 1.0,
420 z: 0.0,
421 units: _,
422 },
423 z_axis: _,
424 } => return PlaneData::XY,
425 Self {
426 origin:
427 Point3d {
428 x: 0.0,
429 y: 0.0,
430 z: 0.0,
431 units: Some(UnitLength::Millimeters),
432 },
433 x_axis:
434 Point3d {
435 x: -1.0,
436 y: 0.0,
437 z: 0.0,
438 units: _,
439 },
440 y_axis:
441 Point3d {
442 x: 0.0,
443 y: 1.0,
444 z: 0.0,
445 units: _,
446 },
447 z_axis: _,
448 } => return PlaneData::NegXY,
449 Self {
450 origin:
451 Point3d {
452 x: 0.0,
453 y: 0.0,
454 z: 0.0,
455 units: Some(UnitLength::Millimeters),
456 },
457 x_axis:
458 Point3d {
459 x: 1.0,
460 y: 0.0,
461 z: 0.0,
462 units: _,
463 },
464 y_axis:
465 Point3d {
466 x: 0.0,
467 y: 0.0,
468 z: 1.0,
469 units: _,
470 },
471 z_axis: _,
472 } => return PlaneData::XZ,
473 Self {
474 origin:
475 Point3d {
476 x: 0.0,
477 y: 0.0,
478 z: 0.0,
479 units: Some(UnitLength::Millimeters),
480 },
481 x_axis:
482 Point3d {
483 x: -1.0,
484 y: 0.0,
485 z: 0.0,
486 units: _,
487 },
488 y_axis:
489 Point3d {
490 x: 0.0,
491 y: 0.0,
492 z: 1.0,
493 units: _,
494 },
495 z_axis: _,
496 } => return PlaneData::NegXZ,
497 Self {
498 origin:
499 Point3d {
500 x: 0.0,
501 y: 0.0,
502 z: 0.0,
503 units: Some(UnitLength::Millimeters),
504 },
505 x_axis:
506 Point3d {
507 x: 0.0,
508 y: 1.0,
509 z: 0.0,
510 units: _,
511 },
512 y_axis:
513 Point3d {
514 x: 0.0,
515 y: 0.0,
516 z: 1.0,
517 units: _,
518 },
519 z_axis: _,
520 } => return PlaneData::YZ,
521 Self {
522 origin:
523 Point3d {
524 x: 0.0,
525 y: 0.0,
526 z: 0.0,
527 units: Some(UnitLength::Millimeters),
528 },
529 x_axis:
530 Point3d {
531 x: 0.0,
532 y: -1.0,
533 z: 0.0,
534 units: _,
535 },
536 y_axis:
537 Point3d {
538 x: 0.0,
539 y: 0.0,
540 z: 1.0,
541 units: _,
542 },
543 z_axis: _,
544 } => return PlaneData::NegYZ,
545 _ => {}
546 }
547 }
548
549 PlaneData::Plane(Self {
550 origin: self.origin,
551 x_axis: self.x_axis,
552 y_axis: self.y_axis,
553 z_axis: self.z_axis,
554 })
555 }
556
557 pub(crate) fn is_right_handed(&self) -> bool {
558 let lhs = self
561 .x_axis
562 .axes_cross_product(&self.y_axis)
563 .axes_dot_product(&self.z_axis);
564 let rhs_x = self.x_axis.axes_dot_product(&self.x_axis);
565 let rhs_y = self.y_axis.axes_dot_product(&self.y_axis);
566 let rhs_z = self.z_axis.axes_dot_product(&self.z_axis);
567 let rhs = (rhs_x * rhs_y * rhs_z).sqrt();
568 (lhs - rhs).abs() <= 0.0001
570 }
571
572 #[cfg(test)]
573 pub(crate) fn is_left_handed(&self) -> bool {
574 !self.is_right_handed()
575 }
576
577 pub(crate) fn make_right_handed(self) -> Self {
578 if self.is_right_handed() {
579 return self;
580 }
581 Self {
583 origin: self.origin,
584 x_axis: self.x_axis.negated(),
585 y_axis: self.y_axis,
586 z_axis: self.z_axis,
587 }
588 }
589}
590
591impl TryFrom<PlaneData> for PlaneInfo {
592 type Error = KclError;
593
594 fn try_from(value: PlaneData) -> Result<Self, Self::Error> {
595 let name = match value {
596 PlaneData::XY => PlaneName::Xy,
597 PlaneData::NegXY => PlaneName::NegXy,
598 PlaneData::XZ => PlaneName::Xz,
599 PlaneData::NegXZ => PlaneName::NegXz,
600 PlaneData::YZ => PlaneName::Yz,
601 PlaneData::NegYZ => PlaneName::NegYz,
602 PlaneData::Plane(info) => {
603 return Ok(info);
604 }
605 };
606
607 let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
608 KclError::new_internal(KclErrorDetails::new(
609 format!("Plane {name} not found"),
610 Default::default(),
611 ))
612 })?;
613
614 Ok(info.clone())
615 }
616}
617
618impl From<&PlaneData> for PlaneKind {
619 fn from(value: &PlaneData) -> Self {
620 match value {
621 PlaneData::XY => PlaneKind::XY,
622 PlaneData::NegXY => PlaneKind::XY,
623 PlaneData::XZ => PlaneKind::XZ,
624 PlaneData::NegXZ => PlaneKind::XZ,
625 PlaneData::YZ => PlaneKind::YZ,
626 PlaneData::NegYZ => PlaneKind::YZ,
627 PlaneData::Plane(_) => PlaneKind::Custom,
628 }
629 }
630}
631
632impl From<&PlaneInfo> for PlaneKind {
633 fn from(value: &PlaneInfo) -> Self {
634 let data = PlaneData::Plane(value.clone());
635 PlaneKind::from(&data)
636 }
637}
638
639impl From<PlaneInfo> for PlaneKind {
640 fn from(value: PlaneInfo) -> Self {
641 let data = PlaneData::Plane(value);
642 PlaneKind::from(&data)
643 }
644}
645
646impl Plane {
647 #[cfg(test)]
648 pub(crate) fn from_plane_data_skipping_engine(
649 value: PlaneData,
650 exec_state: &mut ExecState,
651 ) -> Result<Self, KclError> {
652 let id = exec_state.next_uuid();
653 let kind = PlaneKind::from(&value);
654 Ok(Plane {
655 id,
656 artifact_id: id.into(),
657 info: PlaneInfo::try_from(value)?,
658 object_id: None,
659 kind,
660 meta: vec![],
661 })
662 }
663
664 pub fn is_initialized(&self) -> bool {
666 self.object_id.is_some()
667 }
668
669 pub fn is_uninitialized(&self) -> bool {
671 !self.is_initialized()
672 }
673
674 pub fn is_standard(&self) -> bool {
676 match &self.kind {
677 PlaneKind::XY | PlaneKind::YZ | PlaneKind::XZ => true,
678 PlaneKind::Custom => false,
679 }
680 }
681
682 pub fn project(&self, point: Point3d) -> Point3d {
685 let v = point - self.info.origin;
686 let dot = v.axes_dot_product(&self.info.z_axis);
687
688 point - self.info.z_axis * dot
689 }
690}
691
692#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
694#[ts(export)]
695#[serde(rename_all = "camelCase")]
696pub struct Face {
697 pub id: uuid::Uuid,
699 pub artifact_id: ArtifactId,
701 pub object_id: ObjectId,
703 pub value: String,
705 pub x_axis: Point3d,
707 pub y_axis: Point3d,
709 pub solid: Box<Solid>,
711 pub units: UnitLength,
712 #[serde(skip)]
713 pub meta: Vec<Metadata>,
714}
715
716#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS, FromStr, Display)]
718#[ts(export)]
719#[display(style = "camelCase")]
720pub enum PlaneKind {
721 #[serde(rename = "XY", alias = "xy")]
722 #[display("XY")]
723 XY,
724 #[serde(rename = "XZ", alias = "xz")]
725 #[display("XZ")]
726 XZ,
727 #[serde(rename = "YZ", alias = "yz")]
728 #[display("YZ")]
729 YZ,
730 #[display("Custom")]
732 Custom,
733}
734
735#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
736#[ts(export)]
737#[serde(tag = "type", rename_all = "camelCase")]
738pub struct Sketch {
739 pub id: uuid::Uuid,
741 pub paths: Vec<Path>,
745 #[serde(default, skip_serializing_if = "Vec::is_empty")]
747 pub inner_paths: Vec<Path>,
748 pub on: SketchSurface,
750 pub start: BasePath,
752 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
754 pub tags: IndexMap<String, TagIdentifier>,
755 pub artifact_id: ArtifactId,
758 #[ts(skip)]
759 pub original_id: uuid::Uuid,
760 #[serde(skip)]
762 pub mirror: Option<uuid::Uuid>,
763 #[serde(skip)]
765 pub clone: Option<uuid::Uuid>,
766 pub units: UnitLength,
767 #[serde(skip)]
769 pub meta: Vec<Metadata>,
770 #[serde(
773 default = "ProfileClosed::explicitly",
774 skip_serializing_if = "ProfileClosed::is_explicitly"
775 )]
776 pub is_closed: ProfileClosed,
777}
778
779impl ProfileClosed {
780 #[expect(dead_code, reason = "it's not actually dead, it's called by serde")]
781 fn explicitly() -> Self {
782 Self::Explicitly
783 }
784
785 fn is_explicitly(&self) -> bool {
786 matches!(self, ProfileClosed::Explicitly)
787 }
788}
789
790#[derive(Debug, Serialize, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd, ts_rs::TS)]
792#[serde(rename_all = "camelCase")]
793pub enum ProfileClosed {
794 No,
796 Maybe,
798 Implicitly,
800 Explicitly,
802}
803
804impl Sketch {
805 pub(crate) fn build_sketch_mode_cmds(
808 &self,
809 exec_state: &mut ExecState,
810 inner_cmd: ModelingCmdReq,
811 ) -> Vec<ModelingCmdReq> {
812 vec![
813 ModelingCmdReq {
816 cmd: ModelingCmd::from(
817 mcmd::EnableSketchMode::builder()
818 .animated(false)
819 .ortho(false)
820 .entity_id(self.on.id())
821 .adjust_camera(false)
822 .maybe_planar_normal(if let SketchSurface::Plane(plane) = &self.on {
823 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
825 Some(normal.into())
826 } else {
827 None
828 })
829 .build(),
830 ),
831 cmd_id: exec_state.next_uuid().into(),
832 },
833 inner_cmd,
834 ModelingCmdReq {
835 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::builder().build()),
836 cmd_id: exec_state.next_uuid().into(),
837 },
838 ]
839 }
840}
841
842#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
844#[ts(export)]
845#[serde(tag = "type", rename_all = "camelCase")]
846pub enum SketchSurface {
847 Plane(Box<Plane>),
848 Face(Box<Face>),
849}
850
851impl SketchSurface {
852 pub(crate) fn id(&self) -> uuid::Uuid {
853 match self {
854 SketchSurface::Plane(plane) => plane.id,
855 SketchSurface::Face(face) => face.id,
856 }
857 }
858 pub(crate) fn x_axis(&self) -> Point3d {
859 match self {
860 SketchSurface::Plane(plane) => plane.info.x_axis,
861 SketchSurface::Face(face) => face.x_axis,
862 }
863 }
864 pub(crate) fn y_axis(&self) -> Point3d {
865 match self {
866 SketchSurface::Plane(plane) => plane.info.y_axis,
867 SketchSurface::Face(face) => face.y_axis,
868 }
869 }
870
871 pub(crate) fn object_id(&self) -> Option<ObjectId> {
872 match self {
873 SketchSurface::Plane(plane) => plane.object_id,
874 SketchSurface::Face(face) => Some(face.object_id),
875 }
876 }
877
878 pub(crate) fn set_object_id(&mut self, object_id: ObjectId) {
879 match self {
880 SketchSurface::Plane(plane) => plane.object_id = Some(object_id),
881 SketchSurface::Face(face) => face.object_id = object_id,
882 }
883 }
884}
885
886#[derive(Debug, Clone, PartialEq)]
888pub enum Extrudable {
889 Sketch(Box<Sketch>),
891 Face(FaceTag),
893}
894
895impl Extrudable {
896 pub async fn id_to_extrude(
898 &self,
899 exec_state: &mut ExecState,
900 args: &Args,
901 must_be_planar: bool,
902 ) -> Result<uuid::Uuid, KclError> {
903 match self {
904 Extrudable::Sketch(sketch) => Ok(sketch.id),
905 Extrudable::Face(face_tag) => face_tag.get_face_id_from_tag(exec_state, args, must_be_planar).await,
906 }
907 }
908
909 pub fn as_sketch(&self) -> Option<Sketch> {
910 match self {
911 Extrudable::Sketch(sketch) => Some((**sketch).clone()),
912 Extrudable::Face(face_tag) => match face_tag.geometry() {
913 Some(Geometry::Sketch(sketch)) => Some(sketch),
914 Some(Geometry::Solid(solid)) => Some(solid.sketch),
915 None => None,
916 },
917 }
918 }
919
920 pub fn is_closed(&self) -> ProfileClosed {
921 match self {
922 Extrudable::Sketch(sketch) => sketch.is_closed,
923 Extrudable::Face(face_tag) => match face_tag.geometry() {
924 Some(Geometry::Sketch(sketch)) => sketch.is_closed,
925 Some(Geometry::Solid(solid)) => solid.sketch.is_closed,
926 _ => ProfileClosed::Maybe,
927 },
928 }
929 }
930}
931
932impl From<Sketch> for Extrudable {
933 fn from(value: Sketch) -> Self {
934 Extrudable::Sketch(Box::new(value))
935 }
936}
937
938#[derive(Debug, Clone)]
939pub(crate) enum GetTangentialInfoFromPathsResult {
940 PreviousPoint([f64; 2]),
941 Arc {
942 center: [f64; 2],
943 ccw: bool,
944 },
945 Circle {
946 center: [f64; 2],
947 ccw: bool,
948 radius: f64,
949 },
950 Ellipse {
951 center: [f64; 2],
952 ccw: bool,
953 major_axis: [f64; 2],
954 _minor_radius: f64,
955 },
956}
957
958impl GetTangentialInfoFromPathsResult {
959 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
960 match self {
961 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
962 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
963 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
964 }
965 GetTangentialInfoFromPathsResult::Circle {
968 center, radius, ccw, ..
969 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
970 GetTangentialInfoFromPathsResult::Ellipse {
971 center,
972 major_axis,
973 ccw,
974 ..
975 } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
976 }
977 }
978}
979
980impl Sketch {
981 pub(crate) fn add_tag(
982 &mut self,
983 tag: NodeRef<'_, TagDeclarator>,
984 current_path: &Path,
985 exec_state: &ExecState,
986 surface: Option<&ExtrudeSurface>,
987 ) {
988 let mut tag_identifier: TagIdentifier = tag.into();
989 let base = current_path.get_base();
990 let mut sketch_copy = self.clone();
991 sketch_copy.tags.clear();
992 tag_identifier.info.push((
993 exec_state.stack().current_epoch(),
994 TagEngineInfo {
995 id: base.geo_meta.id,
996 geometry: Geometry::Sketch(sketch_copy),
997 path: Some(current_path.clone()),
998 surface: surface.cloned(),
999 },
1000 ));
1001
1002 self.tags.insert(tag.name.to_string(), tag_identifier);
1003 }
1004
1005 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
1006 for t in tags {
1007 match self.tags.get_mut(&t.value) {
1008 Some(id) => {
1009 id.merge_info(t);
1010 }
1011 None => {
1012 self.tags.insert(t.value.clone(), t.clone());
1013 }
1014 }
1015 }
1016 }
1017
1018 pub(crate) fn latest_path(&self) -> Option<&Path> {
1020 self.paths.last()
1021 }
1022
1023 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
1027 let Some(path) = self.latest_path() else {
1028 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
1029 };
1030
1031 let to = path.get_base().to;
1032 Ok(Point2d::new(to[0], to[1], path.get_base().units))
1033 }
1034
1035 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
1036 let Some(path) = self.latest_path() else {
1037 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
1038 };
1039 path.get_tangential_info()
1040 }
1041}
1042
1043#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1044#[ts(export)]
1045#[serde(tag = "type", rename_all = "camelCase")]
1046pub struct Solid {
1047 pub id: uuid::Uuid,
1049 pub artifact_id: ArtifactId,
1051 pub value: Vec<ExtrudeSurface>,
1053 pub sketch: Sketch,
1055 pub start_cap_id: Option<uuid::Uuid>,
1057 pub end_cap_id: Option<uuid::Uuid>,
1059 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1061 pub edge_cuts: Vec<EdgeCut>,
1062 pub units: UnitLength,
1064 pub sectional: bool,
1066 #[serde(skip)]
1068 pub meta: Vec<Metadata>,
1069}
1070
1071impl Solid {
1072 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
1073 self.edge_cuts.iter().map(|foc| foc.id())
1074 }
1075}
1076
1077#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1079#[ts(export)]
1080#[serde(tag = "type", rename_all = "camelCase")]
1081pub enum EdgeCut {
1082 Fillet {
1084 id: uuid::Uuid,
1086 radius: TyF64,
1087 #[serde(rename = "edgeId")]
1089 edge_id: uuid::Uuid,
1090 tag: Box<Option<TagNode>>,
1091 },
1092 Chamfer {
1094 id: uuid::Uuid,
1096 length: TyF64,
1097 #[serde(rename = "edgeId")]
1099 edge_id: uuid::Uuid,
1100 tag: Box<Option<TagNode>>,
1101 },
1102}
1103
1104impl EdgeCut {
1105 pub fn id(&self) -> uuid::Uuid {
1106 match self {
1107 EdgeCut::Fillet { id, .. } => *id,
1108 EdgeCut::Chamfer { id, .. } => *id,
1109 }
1110 }
1111
1112 pub fn set_id(&mut self, id: uuid::Uuid) {
1113 match self {
1114 EdgeCut::Fillet { id: i, .. } => *i = id,
1115 EdgeCut::Chamfer { id: i, .. } => *i = id,
1116 }
1117 }
1118
1119 pub fn edge_id(&self) -> uuid::Uuid {
1120 match self {
1121 EdgeCut::Fillet { edge_id, .. } => *edge_id,
1122 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
1123 }
1124 }
1125
1126 pub fn set_edge_id(&mut self, id: uuid::Uuid) {
1127 match self {
1128 EdgeCut::Fillet { edge_id: i, .. } => *i = id,
1129 EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
1130 }
1131 }
1132
1133 pub fn tag(&self) -> Option<TagNode> {
1134 match self {
1135 EdgeCut::Fillet { tag, .. } => *tag.clone(),
1136 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
1137 }
1138 }
1139}
1140
1141#[derive(Debug, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
1142#[ts(export)]
1143pub struct Point2d {
1144 pub x: f64,
1145 pub y: f64,
1146 pub units: UnitLength,
1147}
1148
1149impl Point2d {
1150 pub const ZERO: Self = Self {
1151 x: 0.0,
1152 y: 0.0,
1153 units: UnitLength::Millimeters,
1154 };
1155
1156 pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
1157 Self { x, y, units }
1158 }
1159
1160 pub fn into_x(self) -> TyF64 {
1161 TyF64::new(self.x, self.units.into())
1162 }
1163
1164 pub fn into_y(self) -> TyF64 {
1165 TyF64::new(self.y, self.units.into())
1166 }
1167
1168 pub fn ignore_units(self) -> [f64; 2] {
1169 [self.x, self.y]
1170 }
1171}
1172
1173#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
1174#[ts(export)]
1175pub struct Point3d {
1176 pub x: f64,
1177 pub y: f64,
1178 pub z: f64,
1179 pub units: Option<UnitLength>,
1180}
1181
1182impl Point3d {
1183 pub const ZERO: Self = Self {
1184 x: 0.0,
1185 y: 0.0,
1186 z: 0.0,
1187 units: Some(UnitLength::Millimeters),
1188 };
1189
1190 pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
1191 Self { x, y, z, units }
1192 }
1193
1194 pub const fn is_zero(&self) -> bool {
1195 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
1196 }
1197
1198 pub fn axes_cross_product(&self, other: &Self) -> Self {
1203 Self {
1204 x: self.y * other.z - self.z * other.y,
1205 y: self.z * other.x - self.x * other.z,
1206 z: self.x * other.y - self.y * other.x,
1207 units: None,
1208 }
1209 }
1210
1211 pub fn axes_dot_product(&self, other: &Self) -> f64 {
1216 let x = self.x * other.x;
1217 let y = self.y * other.y;
1218 let z = self.z * other.z;
1219 x + y + z
1220 }
1221
1222 pub fn normalize(&self) -> Self {
1223 let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1224 Point3d {
1225 x: self.x / len,
1226 y: self.y / len,
1227 z: self.z / len,
1228 units: None,
1229 }
1230 }
1231
1232 pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1233 let p = [self.x, self.y, self.z];
1234 let u = self.units;
1235 (p, u)
1236 }
1237
1238 pub(crate) fn negated(self) -> Self {
1239 Self {
1240 x: -self.x,
1241 y: -self.y,
1242 z: -self.z,
1243 units: self.units,
1244 }
1245 }
1246}
1247
1248impl From<[TyF64; 3]> for Point3d {
1249 fn from(p: [TyF64; 3]) -> Self {
1250 Self {
1251 x: p[0].n,
1252 y: p[1].n,
1253 z: p[2].n,
1254 units: p[0].ty.as_length(),
1255 }
1256 }
1257}
1258
1259impl From<Point3d> for Point3D {
1260 fn from(p: Point3d) -> Self {
1261 Self { x: p.x, y: p.y, z: p.z }
1262 }
1263}
1264
1265impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1266 fn from(p: Point3d) -> Self {
1267 if let Some(units) = p.units {
1268 Self {
1269 x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1270 y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1271 z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1272 }
1273 } else {
1274 Self {
1275 x: LengthUnit(p.x),
1276 y: LengthUnit(p.y),
1277 z: LengthUnit(p.z),
1278 }
1279 }
1280 }
1281}
1282
1283impl Add for Point3d {
1284 type Output = Point3d;
1285
1286 fn add(self, rhs: Self) -> Self::Output {
1287 Point3d {
1289 x: self.x + rhs.x,
1290 y: self.y + rhs.y,
1291 z: self.z + rhs.z,
1292 units: self.units,
1293 }
1294 }
1295}
1296
1297impl AddAssign for Point3d {
1298 fn add_assign(&mut self, rhs: Self) {
1299 *self = *self + rhs
1300 }
1301}
1302
1303impl Sub for Point3d {
1304 type Output = Point3d;
1305
1306 fn sub(self, rhs: Self) -> Self::Output {
1307 let (x, y, z) = if rhs.units != self.units
1308 && let Some(sunits) = self.units
1309 && let Some(runits) = rhs.units
1310 {
1311 (
1312 adjust_length(runits, rhs.x, sunits).0,
1313 adjust_length(runits, rhs.y, sunits).0,
1314 adjust_length(runits, rhs.z, sunits).0,
1315 )
1316 } else {
1317 (rhs.x, rhs.y, rhs.z)
1318 };
1319 Point3d {
1320 x: self.x - x,
1321 y: self.y - y,
1322 z: self.z - z,
1323 units: self.units,
1324 }
1325 }
1326}
1327
1328impl SubAssign for Point3d {
1329 fn sub_assign(&mut self, rhs: Self) {
1330 *self = *self - rhs
1331 }
1332}
1333
1334impl Mul<f64> for Point3d {
1335 type Output = Point3d;
1336
1337 fn mul(self, rhs: f64) -> Self::Output {
1338 Point3d {
1339 x: self.x * rhs,
1340 y: self.y * rhs,
1341 z: self.z * rhs,
1342 units: self.units,
1343 }
1344 }
1345}
1346
1347#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1349#[ts(export)]
1350#[serde(rename_all = "camelCase")]
1351pub struct BasePath {
1352 #[ts(type = "[number, number]")]
1354 pub from: [f64; 2],
1355 #[ts(type = "[number, number]")]
1357 pub to: [f64; 2],
1358 pub units: UnitLength,
1359 pub tag: Option<TagNode>,
1361 #[serde(rename = "__geoMeta")]
1363 pub geo_meta: GeoMeta,
1364}
1365
1366impl BasePath {
1367 pub fn get_to(&self) -> [TyF64; 2] {
1368 let ty: NumericType = self.units.into();
1369 [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1370 }
1371
1372 pub fn get_from(&self) -> [TyF64; 2] {
1373 let ty: NumericType = self.units.into();
1374 [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1375 }
1376}
1377
1378#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1380#[ts(export)]
1381#[serde(rename_all = "camelCase")]
1382pub struct GeoMeta {
1383 pub id: uuid::Uuid,
1385 #[serde(flatten)]
1387 pub metadata: Metadata,
1388}
1389
1390#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1392#[ts(export)]
1393#[serde(tag = "type")]
1394pub enum Path {
1395 ToPoint {
1397 #[serde(flatten)]
1398 base: BasePath,
1399 },
1400 TangentialArcTo {
1402 #[serde(flatten)]
1403 base: BasePath,
1404 #[ts(type = "[number, number]")]
1406 center: [f64; 2],
1407 ccw: bool,
1409 },
1410 TangentialArc {
1412 #[serde(flatten)]
1413 base: BasePath,
1414 #[ts(type = "[number, number]")]
1416 center: [f64; 2],
1417 ccw: bool,
1419 },
1420 Circle {
1423 #[serde(flatten)]
1424 base: BasePath,
1425 #[ts(type = "[number, number]")]
1427 center: [f64; 2],
1428 radius: f64,
1430 ccw: bool,
1433 },
1434 CircleThreePoint {
1435 #[serde(flatten)]
1436 base: BasePath,
1437 #[ts(type = "[number, number]")]
1439 p1: [f64; 2],
1440 #[ts(type = "[number, number]")]
1442 p2: [f64; 2],
1443 #[ts(type = "[number, number]")]
1445 p3: [f64; 2],
1446 },
1447 ArcThreePoint {
1448 #[serde(flatten)]
1449 base: BasePath,
1450 #[ts(type = "[number, number]")]
1452 p1: [f64; 2],
1453 #[ts(type = "[number, number]")]
1455 p2: [f64; 2],
1456 #[ts(type = "[number, number]")]
1458 p3: [f64; 2],
1459 },
1460 Horizontal {
1462 #[serde(flatten)]
1463 base: BasePath,
1464 x: f64,
1466 },
1467 AngledLineTo {
1469 #[serde(flatten)]
1470 base: BasePath,
1471 x: Option<f64>,
1473 y: Option<f64>,
1475 },
1476 Base {
1478 #[serde(flatten)]
1479 base: BasePath,
1480 },
1481 Arc {
1483 #[serde(flatten)]
1484 base: BasePath,
1485 center: [f64; 2],
1487 radius: f64,
1489 ccw: bool,
1491 },
1492 Ellipse {
1493 #[serde(flatten)]
1494 base: BasePath,
1495 center: [f64; 2],
1496 major_axis: [f64; 2],
1497 minor_radius: f64,
1498 ccw: bool,
1499 },
1500 Conic {
1502 #[serde(flatten)]
1503 base: BasePath,
1504 },
1505 Bezier {
1507 #[serde(flatten)]
1508 base: BasePath,
1509 #[ts(type = "[number, number]")]
1511 control1: [f64; 2],
1512 #[ts(type = "[number, number]")]
1514 control2: [f64; 2],
1515 },
1516}
1517
1518impl Path {
1519 pub fn get_id(&self) -> uuid::Uuid {
1520 match self {
1521 Path::ToPoint { base } => base.geo_meta.id,
1522 Path::Horizontal { base, .. } => base.geo_meta.id,
1523 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1524 Path::Base { base } => base.geo_meta.id,
1525 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1526 Path::TangentialArc { base, .. } => base.geo_meta.id,
1527 Path::Circle { base, .. } => base.geo_meta.id,
1528 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1529 Path::Arc { base, .. } => base.geo_meta.id,
1530 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1531 Path::Ellipse { base, .. } => base.geo_meta.id,
1532 Path::Conic { base, .. } => base.geo_meta.id,
1533 Path::Bezier { base, .. } => base.geo_meta.id,
1534 }
1535 }
1536
1537 pub fn set_id(&mut self, id: uuid::Uuid) {
1538 match self {
1539 Path::ToPoint { base } => base.geo_meta.id = id,
1540 Path::Horizontal { base, .. } => base.geo_meta.id = id,
1541 Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1542 Path::Base { base } => base.geo_meta.id = id,
1543 Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1544 Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1545 Path::Circle { base, .. } => base.geo_meta.id = id,
1546 Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1547 Path::Arc { base, .. } => base.geo_meta.id = id,
1548 Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1549 Path::Ellipse { base, .. } => base.geo_meta.id = id,
1550 Path::Conic { base, .. } => base.geo_meta.id = id,
1551 Path::Bezier { base, .. } => base.geo_meta.id = id,
1552 }
1553 }
1554
1555 pub fn get_tag(&self) -> Option<TagNode> {
1556 match self {
1557 Path::ToPoint { base } => base.tag.clone(),
1558 Path::Horizontal { base, .. } => base.tag.clone(),
1559 Path::AngledLineTo { base, .. } => base.tag.clone(),
1560 Path::Base { base } => base.tag.clone(),
1561 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1562 Path::TangentialArc { base, .. } => base.tag.clone(),
1563 Path::Circle { base, .. } => base.tag.clone(),
1564 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1565 Path::Arc { base, .. } => base.tag.clone(),
1566 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1567 Path::Ellipse { base, .. } => base.tag.clone(),
1568 Path::Conic { base, .. } => base.tag.clone(),
1569 Path::Bezier { base, .. } => base.tag.clone(),
1570 }
1571 }
1572
1573 pub fn get_base(&self) -> &BasePath {
1574 match self {
1575 Path::ToPoint { base } => base,
1576 Path::Horizontal { base, .. } => base,
1577 Path::AngledLineTo { base, .. } => base,
1578 Path::Base { base } => base,
1579 Path::TangentialArcTo { base, .. } => base,
1580 Path::TangentialArc { base, .. } => base,
1581 Path::Circle { base, .. } => base,
1582 Path::CircleThreePoint { base, .. } => base,
1583 Path::Arc { base, .. } => base,
1584 Path::ArcThreePoint { base, .. } => base,
1585 Path::Ellipse { base, .. } => base,
1586 Path::Conic { base, .. } => base,
1587 Path::Bezier { base, .. } => base,
1588 }
1589 }
1590
1591 pub fn get_from(&self) -> [TyF64; 2] {
1593 let p = &self.get_base().from;
1594 let ty: NumericType = self.get_base().units.into();
1595 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1596 }
1597
1598 pub fn get_to(&self) -> [TyF64; 2] {
1600 let p = &self.get_base().to;
1601 let ty: NumericType = self.get_base().units.into();
1602 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1603 }
1604
1605 pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1607 let p = &self.get_base().from;
1608 let ty: NumericType = self.get_base().units.into();
1609 (*p, ty)
1610 }
1611
1612 pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1614 let p = &self.get_base().to;
1615 let ty: NumericType = self.get_base().units.into();
1616 (*p, ty)
1617 }
1618
1619 pub fn length(&self) -> Option<TyF64> {
1622 let n = match self {
1623 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1624 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1625 }
1626 Self::TangentialArc {
1627 base: _,
1628 center,
1629 ccw: _,
1630 }
1631 | Self::TangentialArcTo {
1632 base: _,
1633 center,
1634 ccw: _,
1635 } => {
1636 let radius = linear_distance(&self.get_base().from, center);
1639 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
1640 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1642 }
1643 Self::Circle { radius, .. } => Some(2.0 * std::f64::consts::PI * radius),
1644 Self::CircleThreePoint { .. } => {
1645 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1646 self.get_base().from,
1647 self.get_base().to,
1648 self.get_base().to,
1649 ]);
1650 let radius = linear_distance(
1651 &[circle_center.center[0], circle_center.center[1]],
1652 &self.get_base().from,
1653 );
1654 Some(2.0 * std::f64::consts::PI * radius)
1655 }
1656 Self::Arc { .. } => {
1657 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1659 }
1660 Self::ArcThreePoint { .. } => {
1661 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1663 }
1664 Self::Ellipse { .. } => {
1665 None
1667 }
1668 Self::Conic { .. } => {
1669 None
1671 }
1672 Self::Bezier { .. } => {
1673 None
1675 }
1676 };
1677 n.map(|n| TyF64::new(n, self.get_base().units.into()))
1678 }
1679
1680 pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
1681 match self {
1682 Path::ToPoint { base } => Some(base),
1683 Path::Horizontal { base, .. } => Some(base),
1684 Path::AngledLineTo { base, .. } => Some(base),
1685 Path::Base { base } => Some(base),
1686 Path::TangentialArcTo { base, .. } => Some(base),
1687 Path::TangentialArc { base, .. } => Some(base),
1688 Path::Circle { base, .. } => Some(base),
1689 Path::CircleThreePoint { base, .. } => Some(base),
1690 Path::Arc { base, .. } => Some(base),
1691 Path::ArcThreePoint { base, .. } => Some(base),
1692 Path::Ellipse { base, .. } => Some(base),
1693 Path::Conic { base, .. } => Some(base),
1694 Path::Bezier { base, .. } => Some(base),
1695 }
1696 }
1697
1698 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1699 match self {
1700 Path::TangentialArc { center, ccw, .. }
1701 | Path::TangentialArcTo { center, ccw, .. }
1702 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1703 center: *center,
1704 ccw: *ccw,
1705 },
1706 Path::ArcThreePoint { p1, p2, p3, .. } => {
1707 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1708 GetTangentialInfoFromPathsResult::Arc {
1709 center: circle.center,
1710 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
1711 }
1712 }
1713 Path::Circle {
1714 center, ccw, radius, ..
1715 } => GetTangentialInfoFromPathsResult::Circle {
1716 center: *center,
1717 ccw: *ccw,
1718 radius: *radius,
1719 },
1720 Path::CircleThreePoint { p1, p2, p3, .. } => {
1721 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1722 let center_point = [circle.center[0], circle.center[1]];
1723 GetTangentialInfoFromPathsResult::Circle {
1724 center: center_point,
1725 ccw: true,
1727 radius: circle.radius,
1728 }
1729 }
1730 Path::Ellipse {
1732 center,
1733 major_axis,
1734 minor_radius,
1735 ccw,
1736 ..
1737 } => GetTangentialInfoFromPathsResult::Ellipse {
1738 center: *center,
1739 major_axis: *major_axis,
1740 _minor_radius: *minor_radius,
1741 ccw: *ccw,
1742 },
1743 Path::Conic { .. }
1744 | Path::ToPoint { .. }
1745 | Path::Horizontal { .. }
1746 | Path::AngledLineTo { .. }
1747 | Path::Base { .. }
1748 | Path::Bezier { .. } => {
1749 let base = self.get_base();
1750 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1751 }
1752 }
1753 }
1754
1755 pub(crate) fn is_straight_line(&self) -> bool {
1757 matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
1758 }
1759}
1760
1761#[rustfmt::skip]
1763fn linear_distance(
1764 [x0, y0]: &[f64; 2],
1765 [x1, y1]: &[f64; 2]
1766) -> f64 {
1767 let y_sq = (y1 - y0).powi(2);
1768 let x_sq = (x1 - x0).powi(2);
1769 (y_sq + x_sq).sqrt()
1770}
1771
1772#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1774#[ts(export)]
1775#[serde(tag = "type", rename_all = "camelCase")]
1776pub enum ExtrudeSurface {
1777 ExtrudePlane(ExtrudePlane),
1779 ExtrudeArc(ExtrudeArc),
1780 Chamfer(ChamferSurface),
1781 Fillet(FilletSurface),
1782}
1783
1784#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1786#[ts(export)]
1787#[serde(rename_all = "camelCase")]
1788pub struct ChamferSurface {
1789 pub face_id: uuid::Uuid,
1791 pub tag: Option<Node<TagDeclarator>>,
1793 #[serde(flatten)]
1795 pub geo_meta: GeoMeta,
1796}
1797
1798#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1800#[ts(export)]
1801#[serde(rename_all = "camelCase")]
1802pub struct FilletSurface {
1803 pub face_id: uuid::Uuid,
1805 pub tag: Option<Node<TagDeclarator>>,
1807 #[serde(flatten)]
1809 pub geo_meta: GeoMeta,
1810}
1811
1812#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1814#[ts(export)]
1815#[serde(rename_all = "camelCase")]
1816pub struct ExtrudePlane {
1817 pub face_id: uuid::Uuid,
1819 pub tag: Option<Node<TagDeclarator>>,
1821 #[serde(flatten)]
1823 pub geo_meta: GeoMeta,
1824}
1825
1826#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1828#[ts(export)]
1829#[serde(rename_all = "camelCase")]
1830pub struct ExtrudeArc {
1831 pub face_id: uuid::Uuid,
1833 pub tag: Option<Node<TagDeclarator>>,
1835 #[serde(flatten)]
1837 pub geo_meta: GeoMeta,
1838}
1839
1840impl ExtrudeSurface {
1841 pub fn get_id(&self) -> uuid::Uuid {
1842 match self {
1843 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
1844 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
1845 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
1846 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
1847 }
1848 }
1849
1850 pub fn face_id(&self) -> uuid::Uuid {
1851 match self {
1852 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id,
1853 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id,
1854 ExtrudeSurface::Fillet(f) => f.face_id,
1855 ExtrudeSurface::Chamfer(c) => c.face_id,
1856 }
1857 }
1858
1859 pub fn set_face_id(&mut self, face_id: uuid::Uuid) {
1860 match self {
1861 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id = face_id,
1862 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id = face_id,
1863 ExtrudeSurface::Fillet(f) => f.face_id = face_id,
1864 ExtrudeSurface::Chamfer(c) => c.face_id = face_id,
1865 }
1866 }
1867
1868 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
1869 match self {
1870 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
1871 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
1872 ExtrudeSurface::Fillet(f) => f.tag.clone(),
1873 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
1874 }
1875 }
1876}
1877
1878#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ts_rs::TS)]
1879pub struct SketchVarId(pub usize);
1880
1881impl SketchVarId {
1882 pub fn to_constraint_id(self, range: SourceRange) -> Result<kcl_ezpz::Id, KclError> {
1883 self.0.try_into().map_err(|_| {
1884 KclError::new_type(KclErrorDetails::new(
1885 "Cannot convert to constraint ID since the sketch variable ID is too large".to_owned(),
1886 vec![range],
1887 ))
1888 })
1889 }
1890}
1891
1892#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1893#[ts(export_to = "Geometry.ts")]
1894#[serde(rename_all = "camelCase")]
1895pub struct SketchVar {
1896 pub id: SketchVarId,
1897 pub initial_value: f64,
1898 pub ty: NumericType,
1899 #[serde(skip)]
1900 pub meta: Vec<Metadata>,
1901}
1902
1903impl SketchVar {
1904 pub fn initial_value_to_solver_units(
1905 &self,
1906 exec_state: &mut ExecState,
1907 source_range: SourceRange,
1908 description: &str,
1909 ) -> Result<TyF64, KclError> {
1910 let x_initial_value = KclValue::Number {
1911 value: self.initial_value,
1912 ty: self.ty,
1913 meta: vec![source_range.into()],
1914 };
1915 let normalized_value = normalize_to_solver_unit(&x_initial_value, source_range, exec_state, description)?;
1916 normalized_value.as_ty_f64().ok_or_else(|| {
1917 let message = format!(
1918 "Expected number after coercion, but found {}",
1919 normalized_value.human_friendly_type()
1920 );
1921 debug_assert!(false, "{}", &message);
1922 KclError::new_internal(KclErrorDetails::new(message, vec![source_range]))
1923 })
1924 }
1925}
1926
1927#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1928#[ts(export_to = "Geometry.ts")]
1929#[serde(tag = "type")]
1930pub enum UnsolvedExpr {
1931 Known(TyF64),
1932 Unknown(SketchVarId),
1933}
1934
1935impl UnsolvedExpr {
1936 pub fn var(&self) -> Option<SketchVarId> {
1937 match self {
1938 UnsolvedExpr::Known(_) => None,
1939 UnsolvedExpr::Unknown(id) => Some(*id),
1940 }
1941 }
1942}
1943
1944pub type UnsolvedPoint2dExpr = [UnsolvedExpr; 2];
1945
1946#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1947#[ts(export_to = "Geometry.ts")]
1948#[serde(rename_all = "camelCase")]
1949pub struct ConstrainablePoint2d {
1950 pub vars: crate::front::Point2d<SketchVarId>,
1951 pub object_id: ObjectId,
1952}
1953
1954#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1955#[ts(export_to = "Geometry.ts")]
1956#[serde(rename_all = "camelCase")]
1957pub struct UnsolvedSegment {
1958 pub id: Uuid,
1960 pub object_id: ObjectId,
1961 pub kind: UnsolvedSegmentKind,
1962 #[serde(skip)]
1963 pub meta: Vec<Metadata>,
1964}
1965
1966#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1967#[ts(export_to = "Geometry.ts")]
1968#[serde(rename_all = "camelCase")]
1969pub enum UnsolvedSegmentKind {
1970 Point {
1971 position: UnsolvedPoint2dExpr,
1972 ctor: Box<PointCtor>,
1973 },
1974 Line {
1975 start: UnsolvedPoint2dExpr,
1976 end: UnsolvedPoint2dExpr,
1977 ctor: Box<LineCtor>,
1978 start_object_id: ObjectId,
1979 end_object_id: ObjectId,
1980 construction: bool,
1981 },
1982 Arc {
1983 start: UnsolvedPoint2dExpr,
1984 end: UnsolvedPoint2dExpr,
1985 center: UnsolvedPoint2dExpr,
1986 ctor: Box<ArcCtor>,
1987 start_object_id: ObjectId,
1988 end_object_id: ObjectId,
1989 center_object_id: ObjectId,
1990 construction: bool,
1991 },
1992}
1993
1994#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1995#[ts(export_to = "Geometry.ts")]
1996#[serde(rename_all = "camelCase")]
1997pub struct Segment {
1998 pub id: Uuid,
2000 pub object_id: ObjectId,
2001 pub kind: SegmentKind,
2002 #[serde(skip)]
2003 pub meta: Vec<Metadata>,
2004}
2005
2006impl Segment {
2007 pub fn is_construction(&self) -> bool {
2008 match &self.kind {
2009 SegmentKind::Point { .. } => true,
2010 SegmentKind::Line { construction, .. } => *construction,
2011 SegmentKind::Arc { construction, .. } => *construction,
2012 }
2013 }
2014}
2015
2016#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2017#[ts(export_to = "Geometry.ts")]
2018#[serde(rename_all = "camelCase")]
2019pub enum SegmentKind {
2020 Point {
2021 position: [TyF64; 2],
2022 ctor: Box<PointCtor>,
2023 #[serde(skip_serializing_if = "Option::is_none")]
2024 freedom: Option<Freedom>,
2025 },
2026 Line {
2027 start: [TyF64; 2],
2028 end: [TyF64; 2],
2029 ctor: Box<LineCtor>,
2030 start_object_id: ObjectId,
2031 end_object_id: ObjectId,
2032 #[serde(skip_serializing_if = "Option::is_none")]
2033 start_freedom: Option<Freedom>,
2034 #[serde(skip_serializing_if = "Option::is_none")]
2035 end_freedom: Option<Freedom>,
2036 construction: bool,
2037 },
2038 Arc {
2039 start: [TyF64; 2],
2040 end: [TyF64; 2],
2041 center: [TyF64; 2],
2042 ctor: Box<ArcCtor>,
2043 start_object_id: ObjectId,
2044 end_object_id: ObjectId,
2045 center_object_id: ObjectId,
2046 #[serde(skip_serializing_if = "Option::is_none")]
2047 start_freedom: Option<Freedom>,
2048 #[serde(skip_serializing_if = "Option::is_none")]
2049 end_freedom: Option<Freedom>,
2050 #[serde(skip_serializing_if = "Option::is_none")]
2051 center_freedom: Option<Freedom>,
2052 construction: bool,
2053 },
2054}
2055
2056#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2057#[ts(export_to = "Geometry.ts")]
2058#[serde(rename_all = "camelCase")]
2059pub struct AbstractSegment {
2060 pub repr: SegmentRepr,
2061 #[serde(skip)]
2062 pub meta: Vec<Metadata>,
2063}
2064
2065#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2066pub enum SegmentRepr {
2067 Unsolved { segment: UnsolvedSegment },
2068 Solved { segment: Segment },
2069}
2070
2071#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2072#[ts(export_to = "Geometry.ts")]
2073#[serde(rename_all = "camelCase")]
2074pub struct SketchConstraint {
2075 pub kind: SketchConstraintKind,
2076 #[serde(skip)]
2077 pub meta: Vec<Metadata>,
2078}
2079
2080#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2081#[ts(export_to = "Geometry.ts")]
2082#[serde(rename_all = "camelCase")]
2083pub enum SketchConstraintKind {
2084 Distance { points: [ConstrainablePoint2d; 2] },
2085 Radius { points: [ConstrainablePoint2d; 2] },
2086 Diameter { points: [ConstrainablePoint2d; 2] },
2087 HorizontalDistance { points: [ConstrainablePoint2d; 2] },
2088 VerticalDistance { points: [ConstrainablePoint2d; 2] },
2089}
2090
2091impl SketchConstraintKind {
2092 pub fn name(&self) -> &'static str {
2093 match self {
2094 SketchConstraintKind::Distance { .. } => "distance",
2095 SketchConstraintKind::Radius { .. } => "radius",
2096 SketchConstraintKind::Diameter { .. } => "diameter",
2097 SketchConstraintKind::HorizontalDistance { .. } => "horizontalDistance",
2098 SketchConstraintKind::VerticalDistance { .. } => "verticalDistance",
2099 }
2100 }
2101}