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