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.sketch.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, Copy, Serialize, PartialEq, Eq, ts_rs::TS, FromStr, Display)]
721#[ts(export)]
722#[display(style = "camelCase")]
723pub enum PlaneKind {
724 #[serde(rename = "XY", alias = "xy")]
725 #[display("XY")]
726 XY,
727 #[serde(rename = "XZ", alias = "xz")]
728 #[display("XZ")]
729 XZ,
730 #[serde(rename = "YZ", alias = "yz")]
731 #[display("YZ")]
732 YZ,
733 #[display("Custom")]
735 Custom,
736}
737
738#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
739#[ts(export)]
740#[serde(tag = "type", rename_all = "camelCase")]
741pub struct Sketch {
742 pub id: uuid::Uuid,
744 pub paths: Vec<Path>,
748 #[serde(default, skip_serializing_if = "Vec::is_empty")]
750 pub inner_paths: Vec<Path>,
751 pub on: SketchSurface,
753 pub start: BasePath,
755 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
757 pub tags: IndexMap<String, TagIdentifier>,
758 pub artifact_id: ArtifactId,
761 #[ts(skip)]
762 pub original_id: uuid::Uuid,
763 #[serde(skip)]
765 pub mirror: Option<uuid::Uuid>,
766 #[serde(skip)]
768 pub clone: Option<uuid::Uuid>,
769 pub units: UnitLength,
770 #[serde(skip)]
772 pub meta: Vec<Metadata>,
773 #[serde(
776 default = "ProfileClosed::explicitly",
777 skip_serializing_if = "ProfileClosed::is_explicitly"
778 )]
779 pub is_closed: ProfileClosed,
780}
781
782impl ProfileClosed {
783 #[expect(dead_code, reason = "it's not actually dead, it's called by serde")]
784 fn explicitly() -> Self {
785 Self::Explicitly
786 }
787
788 fn is_explicitly(&self) -> bool {
789 matches!(self, ProfileClosed::Explicitly)
790 }
791}
792
793#[derive(Debug, Serialize, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd, ts_rs::TS)]
795#[serde(rename_all = "camelCase")]
796pub enum ProfileClosed {
797 No,
799 Maybe,
801 Implicitly,
803 Explicitly,
805}
806
807impl Sketch {
808 pub(crate) fn build_sketch_mode_cmds(
811 &self,
812 exec_state: &mut ExecState,
813 inner_cmd: ModelingCmdReq,
814 ) -> Vec<ModelingCmdReq> {
815 vec![
816 ModelingCmdReq {
819 cmd: ModelingCmd::from(
820 mcmd::EnableSketchMode::builder()
821 .animated(false)
822 .ortho(false)
823 .entity_id(self.on.id())
824 .adjust_camera(false)
825 .maybe_planar_normal(if let SketchSurface::Plane(plane) = &self.on {
826 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
828 Some(normal.into())
829 } else {
830 None
831 })
832 .build(),
833 ),
834 cmd_id: exec_state.next_uuid().into(),
835 },
836 inner_cmd,
837 ModelingCmdReq {
838 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::builder().build()),
839 cmd_id: exec_state.next_uuid().into(),
840 },
841 ]
842 }
843}
844
845#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
847#[ts(export)]
848#[serde(tag = "type", rename_all = "camelCase")]
849pub enum SketchSurface {
850 Plane(Box<Plane>),
851 Face(Box<Face>),
852}
853
854impl SketchSurface {
855 pub(crate) fn id(&self) -> uuid::Uuid {
856 match self {
857 SketchSurface::Plane(plane) => plane.id,
858 SketchSurface::Face(face) => face.id,
859 }
860 }
861 pub(crate) fn x_axis(&self) -> Point3d {
862 match self {
863 SketchSurface::Plane(plane) => plane.info.x_axis,
864 SketchSurface::Face(face) => face.x_axis,
865 }
866 }
867 pub(crate) fn y_axis(&self) -> Point3d {
868 match self {
869 SketchSurface::Plane(plane) => plane.info.y_axis,
870 SketchSurface::Face(face) => face.y_axis,
871 }
872 }
873
874 pub(crate) fn object_id(&self) -> Option<ObjectId> {
875 match self {
876 SketchSurface::Plane(plane) => plane.object_id,
877 SketchSurface::Face(face) => Some(face.object_id),
878 }
879 }
880
881 pub(crate) fn set_object_id(&mut self, object_id: ObjectId) {
882 match self {
883 SketchSurface::Plane(plane) => plane.object_id = Some(object_id),
884 SketchSurface::Face(face) => face.object_id = object_id,
885 }
886 }
887}
888
889#[derive(Debug, Clone, PartialEq)]
891pub enum Extrudable {
892 Sketch(Box<Sketch>),
894 Face(FaceTag),
896}
897
898impl Extrudable {
899 pub async fn id_to_extrude(
901 &self,
902 exec_state: &mut ExecState,
903 args: &Args,
904 must_be_planar: bool,
905 ) -> Result<uuid::Uuid, KclError> {
906 match self {
907 Extrudable::Sketch(sketch) => Ok(sketch.id),
908 Extrudable::Face(face_tag) => face_tag.get_face_id_from_tag(exec_state, args, must_be_planar).await,
909 }
910 }
911
912 pub fn as_sketch(&self) -> Option<Sketch> {
913 match self {
914 Extrudable::Sketch(sketch) => Some((**sketch).clone()),
915 Extrudable::Face(face_tag) => match face_tag.geometry() {
916 Some(Geometry::Sketch(sketch)) => Some(sketch),
917 Some(Geometry::Solid(solid)) => Some(solid.sketch),
918 None => None,
919 },
920 }
921 }
922
923 pub fn is_closed(&self) -> ProfileClosed {
924 match self {
925 Extrudable::Sketch(sketch) => sketch.is_closed,
926 Extrudable::Face(face_tag) => match face_tag.geometry() {
927 Some(Geometry::Sketch(sketch)) => sketch.is_closed,
928 Some(Geometry::Solid(solid)) => solid.sketch.is_closed,
929 _ => ProfileClosed::Maybe,
930 },
931 }
932 }
933}
934
935impl From<Sketch> for Extrudable {
936 fn from(value: Sketch) -> Self {
937 Extrudable::Sketch(Box::new(value))
938 }
939}
940
941#[derive(Debug, Clone)]
942pub(crate) enum GetTangentialInfoFromPathsResult {
943 PreviousPoint([f64; 2]),
944 Arc {
945 center: [f64; 2],
946 ccw: bool,
947 },
948 Circle {
949 center: [f64; 2],
950 ccw: bool,
951 radius: f64,
952 },
953 Ellipse {
954 center: [f64; 2],
955 ccw: bool,
956 major_axis: [f64; 2],
957 _minor_radius: f64,
958 },
959}
960
961impl GetTangentialInfoFromPathsResult {
962 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
963 match self {
964 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
965 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
966 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
967 }
968 GetTangentialInfoFromPathsResult::Circle {
971 center, radius, ccw, ..
972 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
973 GetTangentialInfoFromPathsResult::Ellipse {
974 center,
975 major_axis,
976 ccw,
977 ..
978 } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
979 }
980 }
981}
982
983impl Sketch {
984 pub(crate) fn add_tag(
985 &mut self,
986 tag: NodeRef<'_, TagDeclarator>,
987 current_path: &Path,
988 exec_state: &ExecState,
989 surface: Option<&ExtrudeSurface>,
990 ) {
991 let mut tag_identifier: TagIdentifier = tag.into();
992 let base = current_path.get_base();
993 let mut sketch_copy = self.clone();
994 sketch_copy.tags.clear();
995 tag_identifier.info.push((
996 exec_state.stack().current_epoch(),
997 TagEngineInfo {
998 id: base.geo_meta.id,
999 geometry: Geometry::Sketch(sketch_copy),
1000 path: Some(current_path.clone()),
1001 surface: surface.cloned(),
1002 },
1003 ));
1004
1005 self.tags.insert(tag.name.to_string(), tag_identifier);
1006 }
1007
1008 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
1009 for t in tags {
1010 match self.tags.get_mut(&t.value) {
1011 Some(id) => {
1012 id.merge_info(t);
1013 }
1014 None => {
1015 self.tags.insert(t.value.clone(), t.clone());
1016 }
1017 }
1018 }
1019 }
1020
1021 pub(crate) fn latest_path(&self) -> Option<&Path> {
1023 self.paths.last()
1024 }
1025
1026 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
1030 let Some(path) = self.latest_path() else {
1031 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
1032 };
1033
1034 let to = path.get_base().to;
1035 Ok(Point2d::new(to[0], to[1], path.get_base().units))
1036 }
1037
1038 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
1039 let Some(path) = self.latest_path() else {
1040 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
1041 };
1042 path.get_tangential_info()
1043 }
1044}
1045
1046#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1047#[ts(export)]
1048#[serde(tag = "type", rename_all = "camelCase")]
1049pub struct Solid {
1050 pub id: uuid::Uuid,
1052 pub artifact_id: ArtifactId,
1054 pub value: Vec<ExtrudeSurface>,
1056 pub sketch: Sketch,
1058 pub start_cap_id: Option<uuid::Uuid>,
1060 pub end_cap_id: Option<uuid::Uuid>,
1062 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1064 pub edge_cuts: Vec<EdgeCut>,
1065 pub units: UnitLength,
1067 pub sectional: bool,
1069 #[serde(skip)]
1071 pub meta: Vec<Metadata>,
1072}
1073
1074impl Solid {
1075 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
1076 self.edge_cuts.iter().map(|foc| foc.id())
1077 }
1078}
1079
1080#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1082#[ts(export)]
1083#[serde(tag = "type", rename_all = "camelCase")]
1084pub enum EdgeCut {
1085 Fillet {
1087 id: uuid::Uuid,
1089 radius: TyF64,
1090 #[serde(rename = "edgeId")]
1092 edge_id: uuid::Uuid,
1093 tag: Box<Option<TagNode>>,
1094 },
1095 Chamfer {
1097 id: uuid::Uuid,
1099 length: TyF64,
1100 #[serde(rename = "edgeId")]
1102 edge_id: uuid::Uuid,
1103 tag: Box<Option<TagNode>>,
1104 },
1105}
1106
1107impl EdgeCut {
1108 pub fn id(&self) -> uuid::Uuid {
1109 match self {
1110 EdgeCut::Fillet { id, .. } => *id,
1111 EdgeCut::Chamfer { id, .. } => *id,
1112 }
1113 }
1114
1115 pub fn set_id(&mut self, id: uuid::Uuid) {
1116 match self {
1117 EdgeCut::Fillet { id: i, .. } => *i = id,
1118 EdgeCut::Chamfer { id: i, .. } => *i = id,
1119 }
1120 }
1121
1122 pub fn edge_id(&self) -> uuid::Uuid {
1123 match self {
1124 EdgeCut::Fillet { edge_id, .. } => *edge_id,
1125 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
1126 }
1127 }
1128
1129 pub fn set_edge_id(&mut self, id: uuid::Uuid) {
1130 match self {
1131 EdgeCut::Fillet { edge_id: i, .. } => *i = id,
1132 EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
1133 }
1134 }
1135
1136 pub fn tag(&self) -> Option<TagNode> {
1137 match self {
1138 EdgeCut::Fillet { tag, .. } => *tag.clone(),
1139 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
1140 }
1141 }
1142}
1143
1144#[derive(Debug, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
1145#[ts(export)]
1146pub struct Point2d {
1147 pub x: f64,
1148 pub y: f64,
1149 pub units: UnitLength,
1150}
1151
1152impl Point2d {
1153 pub const ZERO: Self = Self {
1154 x: 0.0,
1155 y: 0.0,
1156 units: UnitLength::Millimeters,
1157 };
1158
1159 pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
1160 Self { x, y, units }
1161 }
1162
1163 pub fn into_x(self) -> TyF64 {
1164 TyF64::new(self.x, self.units.into())
1165 }
1166
1167 pub fn into_y(self) -> TyF64 {
1168 TyF64::new(self.y, self.units.into())
1169 }
1170
1171 pub fn ignore_units(self) -> [f64; 2] {
1172 [self.x, self.y]
1173 }
1174}
1175
1176#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
1177#[ts(export)]
1178pub struct Point3d {
1179 pub x: f64,
1180 pub y: f64,
1181 pub z: f64,
1182 pub units: Option<UnitLength>,
1183}
1184
1185impl Point3d {
1186 pub const ZERO: Self = Self {
1187 x: 0.0,
1188 y: 0.0,
1189 z: 0.0,
1190 units: Some(UnitLength::Millimeters),
1191 };
1192
1193 pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
1194 Self { x, y, z, units }
1195 }
1196
1197 pub const fn is_zero(&self) -> bool {
1198 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
1199 }
1200
1201 pub fn axes_cross_product(&self, other: &Self) -> Self {
1206 Self {
1207 x: self.y * other.z - self.z * other.y,
1208 y: self.z * other.x - self.x * other.z,
1209 z: self.x * other.y - self.y * other.x,
1210 units: None,
1211 }
1212 }
1213
1214 pub fn axes_dot_product(&self, other: &Self) -> f64 {
1219 let x = self.x * other.x;
1220 let y = self.y * other.y;
1221 let z = self.z * other.z;
1222 x + y + z
1223 }
1224
1225 pub fn normalize(&self) -> Self {
1226 let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1227 Point3d {
1228 x: self.x / len,
1229 y: self.y / len,
1230 z: self.z / len,
1231 units: None,
1232 }
1233 }
1234
1235 pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1236 let p = [self.x, self.y, self.z];
1237 let u = self.units;
1238 (p, u)
1239 }
1240
1241 pub(crate) fn negated(self) -> Self {
1242 Self {
1243 x: -self.x,
1244 y: -self.y,
1245 z: -self.z,
1246 units: self.units,
1247 }
1248 }
1249}
1250
1251impl From<[TyF64; 3]> for Point3d {
1252 fn from(p: [TyF64; 3]) -> Self {
1253 Self {
1254 x: p[0].n,
1255 y: p[1].n,
1256 z: p[2].n,
1257 units: p[0].ty.as_length(),
1258 }
1259 }
1260}
1261
1262impl From<Point3d> for Point3D {
1263 fn from(p: Point3d) -> Self {
1264 Self { x: p.x, y: p.y, z: p.z }
1265 }
1266}
1267
1268impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1269 fn from(p: Point3d) -> Self {
1270 if let Some(units) = p.units {
1271 Self {
1272 x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1273 y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1274 z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1275 }
1276 } else {
1277 Self {
1278 x: LengthUnit(p.x),
1279 y: LengthUnit(p.y),
1280 z: LengthUnit(p.z),
1281 }
1282 }
1283 }
1284}
1285
1286impl Add for Point3d {
1287 type Output = Point3d;
1288
1289 fn add(self, rhs: Self) -> Self::Output {
1290 Point3d {
1292 x: self.x + rhs.x,
1293 y: self.y + rhs.y,
1294 z: self.z + rhs.z,
1295 units: self.units,
1296 }
1297 }
1298}
1299
1300impl AddAssign for Point3d {
1301 fn add_assign(&mut self, rhs: Self) {
1302 *self = *self + rhs
1303 }
1304}
1305
1306impl Sub for Point3d {
1307 type Output = Point3d;
1308
1309 fn sub(self, rhs: Self) -> Self::Output {
1310 let (x, y, z) = if rhs.units != self.units
1311 && let Some(sunits) = self.units
1312 && let Some(runits) = rhs.units
1313 {
1314 (
1315 adjust_length(runits, rhs.x, sunits).0,
1316 adjust_length(runits, rhs.y, sunits).0,
1317 adjust_length(runits, rhs.z, sunits).0,
1318 )
1319 } else {
1320 (rhs.x, rhs.y, rhs.z)
1321 };
1322 Point3d {
1323 x: self.x - x,
1324 y: self.y - y,
1325 z: self.z - z,
1326 units: self.units,
1327 }
1328 }
1329}
1330
1331impl SubAssign for Point3d {
1332 fn sub_assign(&mut self, rhs: Self) {
1333 *self = *self - rhs
1334 }
1335}
1336
1337impl Mul<f64> for Point3d {
1338 type Output = Point3d;
1339
1340 fn mul(self, rhs: f64) -> Self::Output {
1341 Point3d {
1342 x: self.x * rhs,
1343 y: self.y * rhs,
1344 z: self.z * rhs,
1345 units: self.units,
1346 }
1347 }
1348}
1349
1350#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1352#[ts(export)]
1353#[serde(rename_all = "camelCase")]
1354pub struct BasePath {
1355 #[ts(type = "[number, number]")]
1357 pub from: [f64; 2],
1358 #[ts(type = "[number, number]")]
1360 pub to: [f64; 2],
1361 pub units: UnitLength,
1362 pub tag: Option<TagNode>,
1364 #[serde(rename = "__geoMeta")]
1366 pub geo_meta: GeoMeta,
1367}
1368
1369impl BasePath {
1370 pub fn get_to(&self) -> [TyF64; 2] {
1371 let ty: NumericType = self.units.into();
1372 [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1373 }
1374
1375 pub fn get_from(&self) -> [TyF64; 2] {
1376 let ty: NumericType = self.units.into();
1377 [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1378 }
1379}
1380
1381#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1383#[ts(export)]
1384#[serde(rename_all = "camelCase")]
1385pub struct GeoMeta {
1386 pub id: uuid::Uuid,
1388 #[serde(flatten)]
1390 pub metadata: Metadata,
1391}
1392
1393#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1395#[ts(export)]
1396#[serde(tag = "type")]
1397pub enum Path {
1398 ToPoint {
1400 #[serde(flatten)]
1401 base: BasePath,
1402 },
1403 TangentialArcTo {
1405 #[serde(flatten)]
1406 base: BasePath,
1407 #[ts(type = "[number, number]")]
1409 center: [f64; 2],
1410 ccw: bool,
1412 },
1413 TangentialArc {
1415 #[serde(flatten)]
1416 base: BasePath,
1417 #[ts(type = "[number, number]")]
1419 center: [f64; 2],
1420 ccw: bool,
1422 },
1423 Circle {
1426 #[serde(flatten)]
1427 base: BasePath,
1428 #[ts(type = "[number, number]")]
1430 center: [f64; 2],
1431 radius: f64,
1433 ccw: bool,
1436 },
1437 CircleThreePoint {
1438 #[serde(flatten)]
1439 base: BasePath,
1440 #[ts(type = "[number, number]")]
1442 p1: [f64; 2],
1443 #[ts(type = "[number, number]")]
1445 p2: [f64; 2],
1446 #[ts(type = "[number, number]")]
1448 p3: [f64; 2],
1449 },
1450 ArcThreePoint {
1451 #[serde(flatten)]
1452 base: BasePath,
1453 #[ts(type = "[number, number]")]
1455 p1: [f64; 2],
1456 #[ts(type = "[number, number]")]
1458 p2: [f64; 2],
1459 #[ts(type = "[number, number]")]
1461 p3: [f64; 2],
1462 },
1463 Horizontal {
1465 #[serde(flatten)]
1466 base: BasePath,
1467 x: f64,
1469 },
1470 AngledLineTo {
1472 #[serde(flatten)]
1473 base: BasePath,
1474 x: Option<f64>,
1476 y: Option<f64>,
1478 },
1479 Base {
1481 #[serde(flatten)]
1482 base: BasePath,
1483 },
1484 Arc {
1486 #[serde(flatten)]
1487 base: BasePath,
1488 center: [f64; 2],
1490 radius: f64,
1492 ccw: bool,
1494 },
1495 Ellipse {
1496 #[serde(flatten)]
1497 base: BasePath,
1498 center: [f64; 2],
1499 major_axis: [f64; 2],
1500 minor_radius: f64,
1501 ccw: bool,
1502 },
1503 Conic {
1505 #[serde(flatten)]
1506 base: BasePath,
1507 },
1508 Bezier {
1510 #[serde(flatten)]
1511 base: BasePath,
1512 #[ts(type = "[number, number]")]
1514 control1: [f64; 2],
1515 #[ts(type = "[number, number]")]
1517 control2: [f64; 2],
1518 },
1519}
1520
1521impl Path {
1522 pub fn get_id(&self) -> uuid::Uuid {
1523 match self {
1524 Path::ToPoint { base } => base.geo_meta.id,
1525 Path::Horizontal { base, .. } => base.geo_meta.id,
1526 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1527 Path::Base { base } => base.geo_meta.id,
1528 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1529 Path::TangentialArc { base, .. } => base.geo_meta.id,
1530 Path::Circle { base, .. } => base.geo_meta.id,
1531 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1532 Path::Arc { base, .. } => base.geo_meta.id,
1533 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1534 Path::Ellipse { base, .. } => base.geo_meta.id,
1535 Path::Conic { base, .. } => base.geo_meta.id,
1536 Path::Bezier { base, .. } => base.geo_meta.id,
1537 }
1538 }
1539
1540 pub fn set_id(&mut self, id: uuid::Uuid) {
1541 match self {
1542 Path::ToPoint { base } => base.geo_meta.id = id,
1543 Path::Horizontal { base, .. } => base.geo_meta.id = id,
1544 Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1545 Path::Base { base } => base.geo_meta.id = id,
1546 Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1547 Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1548 Path::Circle { base, .. } => base.geo_meta.id = id,
1549 Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1550 Path::Arc { base, .. } => base.geo_meta.id = id,
1551 Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1552 Path::Ellipse { base, .. } => base.geo_meta.id = id,
1553 Path::Conic { base, .. } => base.geo_meta.id = id,
1554 Path::Bezier { base, .. } => base.geo_meta.id = id,
1555 }
1556 }
1557
1558 pub fn get_tag(&self) -> Option<TagNode> {
1559 match self {
1560 Path::ToPoint { base } => base.tag.clone(),
1561 Path::Horizontal { base, .. } => base.tag.clone(),
1562 Path::AngledLineTo { base, .. } => base.tag.clone(),
1563 Path::Base { base } => base.tag.clone(),
1564 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1565 Path::TangentialArc { base, .. } => base.tag.clone(),
1566 Path::Circle { base, .. } => base.tag.clone(),
1567 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1568 Path::Arc { base, .. } => base.tag.clone(),
1569 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1570 Path::Ellipse { base, .. } => base.tag.clone(),
1571 Path::Conic { base, .. } => base.tag.clone(),
1572 Path::Bezier { base, .. } => base.tag.clone(),
1573 }
1574 }
1575
1576 pub fn get_base(&self) -> &BasePath {
1577 match self {
1578 Path::ToPoint { base } => base,
1579 Path::Horizontal { base, .. } => base,
1580 Path::AngledLineTo { base, .. } => base,
1581 Path::Base { base } => base,
1582 Path::TangentialArcTo { base, .. } => base,
1583 Path::TangentialArc { base, .. } => base,
1584 Path::Circle { base, .. } => base,
1585 Path::CircleThreePoint { base, .. } => base,
1586 Path::Arc { base, .. } => base,
1587 Path::ArcThreePoint { base, .. } => base,
1588 Path::Ellipse { base, .. } => base,
1589 Path::Conic { base, .. } => base,
1590 Path::Bezier { base, .. } => base,
1591 }
1592 }
1593
1594 pub fn get_from(&self) -> [TyF64; 2] {
1596 let p = &self.get_base().from;
1597 let ty: NumericType = self.get_base().units.into();
1598 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1599 }
1600
1601 pub fn get_to(&self) -> [TyF64; 2] {
1603 let p = &self.get_base().to;
1604 let ty: NumericType = self.get_base().units.into();
1605 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1606 }
1607
1608 pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1610 let p = &self.get_base().from;
1611 let ty: NumericType = self.get_base().units.into();
1612 (*p, ty)
1613 }
1614
1615 pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1617 let p = &self.get_base().to;
1618 let ty: NumericType = self.get_base().units.into();
1619 (*p, ty)
1620 }
1621
1622 pub fn length(&self) -> Option<TyF64> {
1625 let n = match self {
1626 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1627 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1628 }
1629 Self::TangentialArc {
1630 base: _,
1631 center,
1632 ccw: _,
1633 }
1634 | Self::TangentialArcTo {
1635 base: _,
1636 center,
1637 ccw: _,
1638 } => {
1639 let radius = linear_distance(&self.get_base().from, center);
1642 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
1643 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1645 }
1646 Self::Circle { radius, .. } => Some(TAU * radius),
1647 Self::CircleThreePoint { .. } => {
1648 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1649 self.get_base().from,
1650 self.get_base().to,
1651 self.get_base().to,
1652 ]);
1653 let radius = linear_distance(
1654 &[circle_center.center[0], circle_center.center[1]],
1655 &self.get_base().from,
1656 );
1657 Some(TAU * radius)
1658 }
1659 Self::Arc { .. } => {
1660 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1662 }
1663 Self::ArcThreePoint { .. } => {
1664 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1666 }
1667 Self::Ellipse { .. } => {
1668 None
1670 }
1671 Self::Conic { .. } => {
1672 None
1674 }
1675 Self::Bezier { .. } => {
1676 None
1678 }
1679 };
1680 n.map(|n| TyF64::new(n, self.get_base().units.into()))
1681 }
1682
1683 pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
1684 match self {
1685 Path::ToPoint { base } => Some(base),
1686 Path::Horizontal { base, .. } => Some(base),
1687 Path::AngledLineTo { base, .. } => Some(base),
1688 Path::Base { base } => Some(base),
1689 Path::TangentialArcTo { base, .. } => Some(base),
1690 Path::TangentialArc { base, .. } => Some(base),
1691 Path::Circle { base, .. } => Some(base),
1692 Path::CircleThreePoint { base, .. } => Some(base),
1693 Path::Arc { base, .. } => Some(base),
1694 Path::ArcThreePoint { base, .. } => Some(base),
1695 Path::Ellipse { base, .. } => Some(base),
1696 Path::Conic { base, .. } => Some(base),
1697 Path::Bezier { base, .. } => Some(base),
1698 }
1699 }
1700
1701 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1702 match self {
1703 Path::TangentialArc { center, ccw, .. }
1704 | Path::TangentialArcTo { center, ccw, .. }
1705 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1706 center: *center,
1707 ccw: *ccw,
1708 },
1709 Path::ArcThreePoint { p1, p2, p3, .. } => {
1710 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1711 GetTangentialInfoFromPathsResult::Arc {
1712 center: circle.center,
1713 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
1714 }
1715 }
1716 Path::Circle {
1717 center, ccw, radius, ..
1718 } => GetTangentialInfoFromPathsResult::Circle {
1719 center: *center,
1720 ccw: *ccw,
1721 radius: *radius,
1722 },
1723 Path::CircleThreePoint { p1, p2, p3, .. } => {
1724 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1725 let center_point = [circle.center[0], circle.center[1]];
1726 GetTangentialInfoFromPathsResult::Circle {
1727 center: center_point,
1728 ccw: true,
1730 radius: circle.radius,
1731 }
1732 }
1733 Path::Ellipse {
1735 center,
1736 major_axis,
1737 minor_radius,
1738 ccw,
1739 ..
1740 } => GetTangentialInfoFromPathsResult::Ellipse {
1741 center: *center,
1742 major_axis: *major_axis,
1743 _minor_radius: *minor_radius,
1744 ccw: *ccw,
1745 },
1746 Path::Conic { .. }
1747 | Path::ToPoint { .. }
1748 | Path::Horizontal { .. }
1749 | Path::AngledLineTo { .. }
1750 | Path::Base { .. }
1751 | Path::Bezier { .. } => {
1752 let base = self.get_base();
1753 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1754 }
1755 }
1756 }
1757
1758 pub(crate) fn is_straight_line(&self) -> bool {
1760 matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
1761 }
1762}
1763
1764#[rustfmt::skip]
1766fn linear_distance(
1767 [x0, y0]: &[f64; 2],
1768 [x1, y1]: &[f64; 2]
1769) -> f64 {
1770 let y_sq = (y1 - y0).powi(2);
1771 let x_sq = (x1 - x0).powi(2);
1772 (y_sq + x_sq).sqrt()
1773}
1774
1775#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1777#[ts(export)]
1778#[serde(tag = "type", rename_all = "camelCase")]
1779pub enum ExtrudeSurface {
1780 ExtrudePlane(ExtrudePlane),
1782 ExtrudeArc(ExtrudeArc),
1783 Chamfer(ChamferSurface),
1784 Fillet(FilletSurface),
1785}
1786
1787#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1789#[ts(export)]
1790#[serde(rename_all = "camelCase")]
1791pub struct ChamferSurface {
1792 pub face_id: uuid::Uuid,
1794 pub tag: Option<Node<TagDeclarator>>,
1796 #[serde(flatten)]
1798 pub geo_meta: GeoMeta,
1799}
1800
1801#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1803#[ts(export)]
1804#[serde(rename_all = "camelCase")]
1805pub struct FilletSurface {
1806 pub face_id: uuid::Uuid,
1808 pub tag: Option<Node<TagDeclarator>>,
1810 #[serde(flatten)]
1812 pub geo_meta: GeoMeta,
1813}
1814
1815#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1817#[ts(export)]
1818#[serde(rename_all = "camelCase")]
1819pub struct ExtrudePlane {
1820 pub face_id: uuid::Uuid,
1822 pub tag: Option<Node<TagDeclarator>>,
1824 #[serde(flatten)]
1826 pub geo_meta: GeoMeta,
1827}
1828
1829#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1831#[ts(export)]
1832#[serde(rename_all = "camelCase")]
1833pub struct ExtrudeArc {
1834 pub face_id: uuid::Uuid,
1836 pub tag: Option<Node<TagDeclarator>>,
1838 #[serde(flatten)]
1840 pub geo_meta: GeoMeta,
1841}
1842
1843impl ExtrudeSurface {
1844 pub fn get_id(&self) -> uuid::Uuid {
1845 match self {
1846 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
1847 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
1848 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
1849 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
1850 }
1851 }
1852
1853 pub fn face_id(&self) -> uuid::Uuid {
1854 match self {
1855 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id,
1856 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id,
1857 ExtrudeSurface::Fillet(f) => f.face_id,
1858 ExtrudeSurface::Chamfer(c) => c.face_id,
1859 }
1860 }
1861
1862 pub fn set_face_id(&mut self, face_id: uuid::Uuid) {
1863 match self {
1864 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id = face_id,
1865 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id = face_id,
1866 ExtrudeSurface::Fillet(f) => f.face_id = face_id,
1867 ExtrudeSurface::Chamfer(c) => c.face_id = face_id,
1868 }
1869 }
1870
1871 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
1872 match self {
1873 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
1874 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
1875 ExtrudeSurface::Fillet(f) => f.tag.clone(),
1876 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
1877 }
1878 }
1879}
1880
1881#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ts_rs::TS)]
1882pub struct SketchVarId(pub usize);
1883
1884impl SketchVarId {
1885 pub fn to_constraint_id(self, range: SourceRange) -> Result<kcl_ezpz::Id, KclError> {
1886 self.0.try_into().map_err(|_| {
1887 KclError::new_type(KclErrorDetails::new(
1888 "Cannot convert to constraint ID since the sketch variable ID is too large".to_owned(),
1889 vec![range],
1890 ))
1891 })
1892 }
1893}
1894
1895#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1896#[ts(export_to = "Geometry.ts")]
1897#[serde(rename_all = "camelCase")]
1898pub struct SketchVar {
1899 pub id: SketchVarId,
1900 pub initial_value: f64,
1901 pub ty: NumericType,
1902 #[serde(skip)]
1903 pub meta: Vec<Metadata>,
1904}
1905
1906impl SketchVar {
1907 pub fn initial_value_to_solver_units(
1908 &self,
1909 exec_state: &mut ExecState,
1910 source_range: SourceRange,
1911 description: &str,
1912 ) -> Result<TyF64, KclError> {
1913 let x_initial_value = KclValue::Number {
1914 value: self.initial_value,
1915 ty: self.ty,
1916 meta: vec![source_range.into()],
1917 };
1918 let normalized_value = normalize_to_solver_unit(&x_initial_value, source_range, exec_state, description)?;
1919 normalized_value.as_ty_f64().ok_or_else(|| {
1920 let message = format!(
1921 "Expected number after coercion, but found {}",
1922 normalized_value.human_friendly_type()
1923 );
1924 debug_assert!(false, "{}", &message);
1925 KclError::new_internal(KclErrorDetails::new(message, vec![source_range]))
1926 })
1927 }
1928}
1929
1930#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1931#[ts(export_to = "Geometry.ts")]
1932#[serde(tag = "type")]
1933pub enum UnsolvedExpr {
1934 Known(TyF64),
1935 Unknown(SketchVarId),
1936}
1937
1938impl UnsolvedExpr {
1939 pub fn var(&self) -> Option<SketchVarId> {
1940 match self {
1941 UnsolvedExpr::Known(_) => None,
1942 UnsolvedExpr::Unknown(id) => Some(*id),
1943 }
1944 }
1945}
1946
1947pub type UnsolvedPoint2dExpr = [UnsolvedExpr; 2];
1948
1949#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1950#[ts(export_to = "Geometry.ts")]
1951#[serde(rename_all = "camelCase")]
1952pub struct ConstrainablePoint2d {
1953 pub vars: crate::front::Point2d<SketchVarId>,
1954 pub object_id: ObjectId,
1955}
1956
1957#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1958#[ts(export_to = "Geometry.ts")]
1959#[serde(rename_all = "camelCase")]
1960pub struct UnsolvedSegment {
1961 pub id: Uuid,
1963 pub object_id: ObjectId,
1964 pub kind: UnsolvedSegmentKind,
1965 #[serde(skip)]
1966 pub meta: Vec<Metadata>,
1967}
1968
1969#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1970#[ts(export_to = "Geometry.ts")]
1971#[serde(rename_all = "camelCase")]
1972pub enum UnsolvedSegmentKind {
1973 Point {
1974 position: UnsolvedPoint2dExpr,
1975 ctor: Box<PointCtor>,
1976 },
1977 Line {
1978 start: UnsolvedPoint2dExpr,
1979 end: UnsolvedPoint2dExpr,
1980 ctor: Box<LineCtor>,
1981 start_object_id: ObjectId,
1982 end_object_id: ObjectId,
1983 construction: bool,
1984 },
1985 Arc {
1986 start: UnsolvedPoint2dExpr,
1987 end: UnsolvedPoint2dExpr,
1988 center: UnsolvedPoint2dExpr,
1989 ctor: Box<ArcCtor>,
1990 start_object_id: ObjectId,
1991 end_object_id: ObjectId,
1992 center_object_id: ObjectId,
1993 construction: bool,
1994 },
1995}
1996
1997#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1998#[ts(export_to = "Geometry.ts")]
1999#[serde(rename_all = "camelCase")]
2000pub struct Segment {
2001 pub id: Uuid,
2003 pub object_id: ObjectId,
2004 pub kind: SegmentKind,
2005 #[serde(skip)]
2006 pub meta: Vec<Metadata>,
2007}
2008
2009impl Segment {
2010 pub fn is_construction(&self) -> bool {
2011 match &self.kind {
2012 SegmentKind::Point { .. } => true,
2013 SegmentKind::Line { construction, .. } => *construction,
2014 SegmentKind::Arc { construction, .. } => *construction,
2015 }
2016 }
2017}
2018
2019#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2020#[ts(export_to = "Geometry.ts")]
2021#[serde(rename_all = "camelCase")]
2022pub enum SegmentKind {
2023 Point {
2024 position: [TyF64; 2],
2025 ctor: Box<PointCtor>,
2026 #[serde(skip_serializing_if = "Option::is_none")]
2027 freedom: Option<Freedom>,
2028 },
2029 Line {
2030 start: [TyF64; 2],
2031 end: [TyF64; 2],
2032 ctor: Box<LineCtor>,
2033 start_object_id: ObjectId,
2034 end_object_id: ObjectId,
2035 #[serde(skip_serializing_if = "Option::is_none")]
2036 start_freedom: Option<Freedom>,
2037 #[serde(skip_serializing_if = "Option::is_none")]
2038 end_freedom: Option<Freedom>,
2039 construction: bool,
2040 },
2041 Arc {
2042 start: [TyF64; 2],
2043 end: [TyF64; 2],
2044 center: [TyF64; 2],
2045 ctor: Box<ArcCtor>,
2046 start_object_id: ObjectId,
2047 end_object_id: ObjectId,
2048 center_object_id: ObjectId,
2049 #[serde(skip_serializing_if = "Option::is_none")]
2050 start_freedom: Option<Freedom>,
2051 #[serde(skip_serializing_if = "Option::is_none")]
2052 end_freedom: Option<Freedom>,
2053 #[serde(skip_serializing_if = "Option::is_none")]
2054 center_freedom: Option<Freedom>,
2055 construction: bool,
2056 },
2057}
2058
2059#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2060#[ts(export_to = "Geometry.ts")]
2061#[serde(rename_all = "camelCase")]
2062pub struct AbstractSegment {
2063 pub repr: SegmentRepr,
2064 #[serde(skip)]
2065 pub meta: Vec<Metadata>,
2066}
2067
2068#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2069pub enum SegmentRepr {
2070 Unsolved { segment: UnsolvedSegment },
2071 Solved { segment: Segment },
2072}
2073
2074#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2075#[ts(export_to = "Geometry.ts")]
2076#[serde(rename_all = "camelCase")]
2077pub struct SketchConstraint {
2078 pub kind: SketchConstraintKind,
2079 #[serde(skip)]
2080 pub meta: Vec<Metadata>,
2081}
2082
2083#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2084#[ts(export_to = "Geometry.ts")]
2085#[serde(rename_all = "camelCase")]
2086pub enum SketchConstraintKind {
2087 Distance { points: [ConstrainablePoint2d; 2] },
2088 Radius { points: [ConstrainablePoint2d; 2] },
2089 Diameter { points: [ConstrainablePoint2d; 2] },
2090 HorizontalDistance { points: [ConstrainablePoint2d; 2] },
2091 VerticalDistance { points: [ConstrainablePoint2d; 2] },
2092}
2093
2094impl SketchConstraintKind {
2095 pub fn name(&self) -> &'static str {
2096 match self {
2097 SketchConstraintKind::Distance { .. } => "distance",
2098 SketchConstraintKind::Radius { .. } => "radius",
2099 SketchConstraintKind::Diameter { .. } => "diameter",
2100 SketchConstraintKind::HorizontalDistance { .. } => "horizontalDistance",
2101 SketchConstraintKind::VerticalDistance { .. } => "verticalDistance",
2102 }
2103 }
2104}