1use std::ops::{Add, AddAssign, Mul, Sub, SubAssign};
2
3use anyhow::Result;
4use indexmap::IndexMap;
5use kcl_error::SourceRange;
6use kittycad_modeling_cmds as kcmc;
7use kittycad_modeling_cmds::{
8 ModelingCmd, each_cmd as mcmd, length_unit::LengthUnit, units::UnitLength, websocket::ModelingCmdReq,
9};
10use parse_display::{Display, FromStr};
11use serde::{Deserialize, Serialize};
12
13use crate::{
14 engine::{DEFAULT_PLANE_INFO, PlaneName},
15 errors::{KclError, KclErrorDetails},
16 exec::KclValue,
17 execution::{
18 ArtifactId, ExecState, ExecutorContext, Metadata, TagEngineInfo, TagIdentifier, normalize_to_solver_unit,
19 types::{NumericType, adjust_length},
20 },
21 front::{ArcCtor, Freedom, LineCtor, ObjectId, PointCtor},
22 parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
23 std::{args::TyF64, sketch::PlaneData},
24};
25
26type Point3D = kcmc::shared::Point3d<f64>;
27
28#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
30#[ts(export)]
31#[serde(tag = "type", rename_all = "camelCase")]
32pub struct GdtAnnotation {
33 pub id: uuid::Uuid,
35 #[serde(skip)]
36 pub meta: Vec<Metadata>,
37}
38
39#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
41#[ts(export)]
42#[serde(tag = "type")]
43#[allow(clippy::large_enum_variant)]
44pub enum Geometry {
45 Sketch(Sketch),
46 Solid(Solid),
47}
48
49impl Geometry {
50 pub fn id(&self) -> uuid::Uuid {
51 match self {
52 Geometry::Sketch(s) => s.id,
53 Geometry::Solid(e) => e.id,
54 }
55 }
56
57 pub fn original_id(&self) -> uuid::Uuid {
61 match self {
62 Geometry::Sketch(s) => s.original_id,
63 Geometry::Solid(e) => e.sketch.original_id,
64 }
65 }
66}
67
68#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
70#[ts(export)]
71#[serde(tag = "type")]
72#[allow(clippy::large_enum_variant)]
73pub enum GeometryWithImportedGeometry {
74 Sketch(Sketch),
75 Solid(Solid),
76 ImportedGeometry(Box<ImportedGeometry>),
77}
78
79impl GeometryWithImportedGeometry {
80 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
81 match self {
82 GeometryWithImportedGeometry::Sketch(s) => Ok(s.id),
83 GeometryWithImportedGeometry::Solid(e) => Ok(e.id),
84 GeometryWithImportedGeometry::ImportedGeometry(i) => {
85 let id = i.id(ctx).await?;
86 Ok(id)
87 }
88 }
89 }
90}
91
92#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
94#[ts(export)]
95#[serde(tag = "type")]
96#[allow(clippy::vec_box)]
97pub enum Geometries {
98 Sketches(Vec<Sketch>),
99 Solids(Vec<Solid>),
100}
101
102impl From<Geometry> for Geometries {
103 fn from(value: Geometry) -> Self {
104 match value {
105 Geometry::Sketch(x) => Self::Sketches(vec![x]),
106 Geometry::Solid(x) => Self::Solids(vec![x]),
107 }
108 }
109}
110
111#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
113#[ts(export)]
114#[serde(rename_all = "camelCase")]
115pub struct ImportedGeometry {
116 pub id: uuid::Uuid,
118 pub value: Vec<String>,
120 #[serde(skip)]
121 pub meta: Vec<Metadata>,
122 #[serde(skip)]
124 completed: bool,
125}
126
127impl ImportedGeometry {
128 pub fn new(id: uuid::Uuid, value: Vec<String>, meta: Vec<Metadata>) -> Self {
129 Self {
130 id,
131 value,
132 meta,
133 completed: false,
134 }
135 }
136
137 async fn wait_for_finish(&mut self, ctx: &ExecutorContext) -> Result<(), KclError> {
138 if self.completed {
139 return Ok(());
140 }
141
142 ctx.engine
143 .ensure_async_command_completed(self.id, self.meta.first().map(|m| m.source_range))
144 .await?;
145
146 self.completed = true;
147
148 Ok(())
149 }
150
151 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
152 if !self.completed {
153 self.wait_for_finish(ctx).await?;
154 }
155
156 Ok(self.id)
157 }
158}
159
160#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
162#[ts(export)]
163#[serde(tag = "type", rename_all = "camelCase")]
164#[allow(clippy::vec_box)]
165pub enum SolidOrSketchOrImportedGeometry {
166 ImportedGeometry(Box<ImportedGeometry>),
167 SolidSet(Vec<Solid>),
168 SketchSet(Vec<Sketch>),
169}
170
171impl From<SolidOrSketchOrImportedGeometry> for crate::execution::KclValue {
172 fn from(value: SolidOrSketchOrImportedGeometry) -> Self {
173 match value {
174 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
175 SolidOrSketchOrImportedGeometry::SolidSet(mut s) => {
176 if s.len() == 1
177 && let Some(s) = s.pop()
178 {
179 crate::execution::KclValue::Solid { value: Box::new(s) }
180 } else {
181 crate::execution::KclValue::HomArray {
182 value: s
183 .into_iter()
184 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
185 .collect(),
186 ty: crate::execution::types::RuntimeType::solid(),
187 }
188 }
189 }
190 SolidOrSketchOrImportedGeometry::SketchSet(mut s) => {
191 if s.len() == 1
192 && let Some(s) = s.pop()
193 {
194 crate::execution::KclValue::Sketch { value: Box::new(s) }
195 } else {
196 crate::execution::KclValue::HomArray {
197 value: s
198 .into_iter()
199 .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) })
200 .collect(),
201 ty: crate::execution::types::RuntimeType::sketch(),
202 }
203 }
204 }
205 }
206 }
207}
208
209impl SolidOrSketchOrImportedGeometry {
210 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
211 match self {
212 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => {
213 let id = s.id(ctx).await?;
214
215 Ok(vec![id])
216 }
217 SolidOrSketchOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
218 SolidOrSketchOrImportedGeometry::SketchSet(s) => Ok(s.iter().map(|s| s.id).collect()),
219 }
220 }
221}
222
223#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
225#[ts(export)]
226#[serde(tag = "type", rename_all = "camelCase")]
227#[allow(clippy::vec_box)]
228pub enum SolidOrImportedGeometry {
229 ImportedGeometry(Box<ImportedGeometry>),
230 SolidSet(Vec<Solid>),
231}
232
233impl From<SolidOrImportedGeometry> for crate::execution::KclValue {
234 fn from(value: SolidOrImportedGeometry) -> Self {
235 match value {
236 SolidOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
237 SolidOrImportedGeometry::SolidSet(mut s) => {
238 if s.len() == 1
239 && let Some(s) = s.pop()
240 {
241 crate::execution::KclValue::Solid { value: Box::new(s) }
242 } else {
243 crate::execution::KclValue::HomArray {
244 value: s
245 .into_iter()
246 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
247 .collect(),
248 ty: crate::execution::types::RuntimeType::solid(),
249 }
250 }
251 }
252 }
253 }
254}
255
256impl SolidOrImportedGeometry {
257 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
258 match self {
259 SolidOrImportedGeometry::ImportedGeometry(s) => {
260 let id = s.id(ctx).await?;
261
262 Ok(vec![id])
263 }
264 SolidOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
265 }
266 }
267}
268
269#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
271#[ts(export)]
272#[serde(rename_all = "camelCase")]
273pub struct Helix {
274 pub value: uuid::Uuid,
276 pub artifact_id: ArtifactId,
278 pub revolutions: f64,
280 pub angle_start: f64,
282 pub ccw: bool,
284 pub cylinder_id: Option<uuid::Uuid>,
286 pub units: UnitLength,
287 #[serde(skip)]
288 pub meta: Vec<Metadata>,
289}
290
291#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
292#[ts(export)]
293#[serde(rename_all = "camelCase")]
294pub struct Plane {
295 pub id: uuid::Uuid,
297 pub artifact_id: ArtifactId,
299 #[serde(skip_serializing_if = "Option::is_none")]
302 pub object_id: Option<ObjectId>,
303 pub kind: PlaneKind,
305 #[serde(flatten)]
307 pub info: PlaneInfo,
308 #[serde(skip)]
309 pub meta: Vec<Metadata>,
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS)]
313#[ts(export)]
314#[serde(rename_all = "camelCase")]
315pub struct PlaneInfo {
316 pub origin: Point3d,
318 pub x_axis: Point3d,
320 pub y_axis: Point3d,
322 pub z_axis: Point3d,
324}
325
326impl PlaneInfo {
327 pub(crate) fn into_plane_data(self) -> PlaneData {
328 if self.origin.is_zero() {
329 match self {
330 Self {
331 origin:
332 Point3d {
333 x: 0.0,
334 y: 0.0,
335 z: 0.0,
336 units: Some(UnitLength::Millimeters),
337 },
338 x_axis:
339 Point3d {
340 x: 1.0,
341 y: 0.0,
342 z: 0.0,
343 units: _,
344 },
345 y_axis:
346 Point3d {
347 x: 0.0,
348 y: 1.0,
349 z: 0.0,
350 units: _,
351 },
352 z_axis: _,
353 } => return PlaneData::XY,
354 Self {
355 origin:
356 Point3d {
357 x: 0.0,
358 y: 0.0,
359 z: 0.0,
360 units: Some(UnitLength::Millimeters),
361 },
362 x_axis:
363 Point3d {
364 x: -1.0,
365 y: 0.0,
366 z: 0.0,
367 units: _,
368 },
369 y_axis:
370 Point3d {
371 x: 0.0,
372 y: 1.0,
373 z: 0.0,
374 units: _,
375 },
376 z_axis: _,
377 } => return PlaneData::NegXY,
378 Self {
379 origin:
380 Point3d {
381 x: 0.0,
382 y: 0.0,
383 z: 0.0,
384 units: Some(UnitLength::Millimeters),
385 },
386 x_axis:
387 Point3d {
388 x: 1.0,
389 y: 0.0,
390 z: 0.0,
391 units: _,
392 },
393 y_axis:
394 Point3d {
395 x: 0.0,
396 y: 0.0,
397 z: 1.0,
398 units: _,
399 },
400 z_axis: _,
401 } => return PlaneData::XZ,
402 Self {
403 origin:
404 Point3d {
405 x: 0.0,
406 y: 0.0,
407 z: 0.0,
408 units: Some(UnitLength::Millimeters),
409 },
410 x_axis:
411 Point3d {
412 x: -1.0,
413 y: 0.0,
414 z: 0.0,
415 units: _,
416 },
417 y_axis:
418 Point3d {
419 x: 0.0,
420 y: 0.0,
421 z: 1.0,
422 units: _,
423 },
424 z_axis: _,
425 } => return PlaneData::NegXZ,
426 Self {
427 origin:
428 Point3d {
429 x: 0.0,
430 y: 0.0,
431 z: 0.0,
432 units: Some(UnitLength::Millimeters),
433 },
434 x_axis:
435 Point3d {
436 x: 0.0,
437 y: 1.0,
438 z: 0.0,
439 units: _,
440 },
441 y_axis:
442 Point3d {
443 x: 0.0,
444 y: 0.0,
445 z: 1.0,
446 units: _,
447 },
448 z_axis: _,
449 } => return PlaneData::YZ,
450 Self {
451 origin:
452 Point3d {
453 x: 0.0,
454 y: 0.0,
455 z: 0.0,
456 units: Some(UnitLength::Millimeters),
457 },
458 x_axis:
459 Point3d {
460 x: 0.0,
461 y: -1.0,
462 z: 0.0,
463 units: _,
464 },
465 y_axis:
466 Point3d {
467 x: 0.0,
468 y: 0.0,
469 z: 1.0,
470 units: _,
471 },
472 z_axis: _,
473 } => return PlaneData::NegYZ,
474 _ => {}
475 }
476 }
477
478 PlaneData::Plane(Self {
479 origin: self.origin,
480 x_axis: self.x_axis,
481 y_axis: self.y_axis,
482 z_axis: self.z_axis,
483 })
484 }
485
486 pub(crate) fn is_right_handed(&self) -> bool {
487 let lhs = self
490 .x_axis
491 .axes_cross_product(&self.y_axis)
492 .axes_dot_product(&self.z_axis);
493 let rhs_x = self.x_axis.axes_dot_product(&self.x_axis);
494 let rhs_y = self.y_axis.axes_dot_product(&self.y_axis);
495 let rhs_z = self.z_axis.axes_dot_product(&self.z_axis);
496 let rhs = (rhs_x * rhs_y * rhs_z).sqrt();
497 (lhs - rhs).abs() <= 0.0001
499 }
500
501 #[cfg(test)]
502 pub(crate) fn is_left_handed(&self) -> bool {
503 !self.is_right_handed()
504 }
505
506 pub(crate) fn make_right_handed(self) -> Self {
507 if self.is_right_handed() {
508 return self;
509 }
510 Self {
512 origin: self.origin,
513 x_axis: self.x_axis.negated(),
514 y_axis: self.y_axis,
515 z_axis: self.z_axis,
516 }
517 }
518}
519
520impl TryFrom<PlaneData> for PlaneInfo {
521 type Error = KclError;
522
523 fn try_from(value: PlaneData) -> Result<Self, Self::Error> {
524 let name = match value {
525 PlaneData::XY => PlaneName::Xy,
526 PlaneData::NegXY => PlaneName::NegXy,
527 PlaneData::XZ => PlaneName::Xz,
528 PlaneData::NegXZ => PlaneName::NegXz,
529 PlaneData::YZ => PlaneName::Yz,
530 PlaneData::NegYZ => PlaneName::NegYz,
531 PlaneData::Plane(info) => {
532 return Ok(info);
533 }
534 };
535
536 let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
537 KclError::new_internal(KclErrorDetails::new(
538 format!("Plane {name} not found"),
539 Default::default(),
540 ))
541 })?;
542
543 Ok(info.clone())
544 }
545}
546
547impl From<&PlaneData> for PlaneKind {
548 fn from(value: &PlaneData) -> Self {
549 match value {
550 PlaneData::XY => PlaneKind::XY,
551 PlaneData::NegXY => PlaneKind::XY,
552 PlaneData::XZ => PlaneKind::XZ,
553 PlaneData::NegXZ => PlaneKind::XZ,
554 PlaneData::YZ => PlaneKind::YZ,
555 PlaneData::NegYZ => PlaneKind::YZ,
556 PlaneData::Plane(_) => PlaneKind::Custom,
557 }
558 }
559}
560
561impl From<&PlaneInfo> for PlaneKind {
562 fn from(value: &PlaneInfo) -> Self {
563 let data = PlaneData::Plane(value.clone());
564 PlaneKind::from(&data)
565 }
566}
567
568impl From<PlaneInfo> for PlaneKind {
569 fn from(value: PlaneInfo) -> Self {
570 let data = PlaneData::Plane(value);
571 PlaneKind::from(&data)
572 }
573}
574
575impl Plane {
576 #[cfg(test)]
577 pub(crate) fn from_plane_data_skipping_engine(
578 value: PlaneData,
579 exec_state: &mut ExecState,
580 ) -> Result<Self, KclError> {
581 let id = exec_state.next_uuid();
582 let kind = PlaneKind::from(&value);
583 Ok(Plane {
584 id,
585 artifact_id: id.into(),
586 info: PlaneInfo::try_from(value)?,
587 object_id: None,
588 kind,
589 meta: vec![],
590 })
591 }
592
593 pub fn is_initialized(&self) -> bool {
595 self.object_id.is_some()
596 }
597
598 pub fn is_uninitialized(&self) -> bool {
600 !self.is_initialized()
601 }
602
603 pub fn is_standard(&self) -> bool {
605 match &self.kind {
606 PlaneKind::XY | PlaneKind::YZ | PlaneKind::XZ => true,
607 PlaneKind::Custom => false,
608 }
609 }
610
611 pub fn project(&self, point: Point3d) -> Point3d {
614 let v = point - self.info.origin;
615 let dot = v.axes_dot_product(&self.info.z_axis);
616
617 point - self.info.z_axis * dot
618 }
619}
620
621#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
623#[ts(export)]
624#[serde(rename_all = "camelCase")]
625pub struct Face {
626 pub id: uuid::Uuid,
628 pub artifact_id: ArtifactId,
630 pub object_id: ObjectId,
632 pub value: String,
634 pub x_axis: Point3d,
636 pub y_axis: Point3d,
638 pub solid: Box<Solid>,
640 pub units: UnitLength,
641 #[serde(skip)]
642 pub meta: Vec<Metadata>,
643}
644
645#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS, FromStr, Display)]
647#[ts(export)]
648#[display(style = "camelCase")]
649pub enum PlaneKind {
650 #[serde(rename = "XY", alias = "xy")]
651 #[display("XY")]
652 XY,
653 #[serde(rename = "XZ", alias = "xz")]
654 #[display("XZ")]
655 XZ,
656 #[serde(rename = "YZ", alias = "yz")]
657 #[display("YZ")]
658 YZ,
659 #[display("Custom")]
661 Custom,
662}
663
664#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
665#[ts(export)]
666#[serde(tag = "type", rename_all = "camelCase")]
667pub struct Sketch {
668 pub id: uuid::Uuid,
670 pub paths: Vec<Path>,
674 #[serde(default, skip_serializing_if = "Vec::is_empty")]
676 pub inner_paths: Vec<Path>,
677 pub on: SketchSurface,
679 pub start: BasePath,
681 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
683 pub tags: IndexMap<String, TagIdentifier>,
684 pub artifact_id: ArtifactId,
687 #[ts(skip)]
688 pub original_id: uuid::Uuid,
689 #[serde(skip)]
691 pub mirror: Option<uuid::Uuid>,
692 #[serde(skip)]
694 pub clone: Option<uuid::Uuid>,
695 pub units: UnitLength,
696 #[serde(skip)]
698 pub meta: Vec<Metadata>,
699 #[serde(
702 default = "ProfileClosed::explicitly",
703 skip_serializing_if = "ProfileClosed::is_explicitly"
704 )]
705 pub is_closed: ProfileClosed,
706}
707
708impl ProfileClosed {
709 #[expect(dead_code, reason = "it's not actually dead, it's called by serde")]
710 fn explicitly() -> Self {
711 Self::Explicitly
712 }
713
714 fn is_explicitly(&self) -> bool {
715 matches!(self, ProfileClosed::Explicitly)
716 }
717}
718
719#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd, ts_rs::TS)]
721#[serde(rename_all = "camelCase")]
722pub enum ProfileClosed {
723 No,
725 Maybe,
727 Implicitly,
729 Explicitly,
731}
732
733impl Sketch {
734 pub(crate) fn build_sketch_mode_cmds(
737 &self,
738 exec_state: &mut ExecState,
739 inner_cmd: ModelingCmdReq,
740 ) -> Vec<ModelingCmdReq> {
741 vec![
742 ModelingCmdReq {
745 cmd: ModelingCmd::from(if let SketchSurface::Plane(plane) = &self.on {
746 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
748 mcmd::EnableSketchMode::builder()
749 .animated(false)
750 .ortho(false)
751 .entity_id(self.on.id())
752 .adjust_camera(false)
753 .planar_normal(normal.into())
754 .build()
755 } else {
756 mcmd::EnableSketchMode::builder()
757 .animated(false)
758 .ortho(false)
759 .entity_id(self.on.id())
760 .adjust_camera(false)
761 .build()
762 }),
763 cmd_id: exec_state.next_uuid().into(),
764 },
765 inner_cmd,
766 ModelingCmdReq {
767 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
768 cmd_id: exec_state.next_uuid().into(),
769 },
770 ]
771 }
772}
773
774#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
776#[ts(export)]
777#[serde(tag = "type", rename_all = "camelCase")]
778pub enum SketchSurface {
779 Plane(Box<Plane>),
780 Face(Box<Face>),
781}
782
783impl SketchSurface {
784 pub(crate) fn id(&self) -> uuid::Uuid {
785 match self {
786 SketchSurface::Plane(plane) => plane.id,
787 SketchSurface::Face(face) => face.id,
788 }
789 }
790 pub(crate) fn x_axis(&self) -> Point3d {
791 match self {
792 SketchSurface::Plane(plane) => plane.info.x_axis,
793 SketchSurface::Face(face) => face.x_axis,
794 }
795 }
796 pub(crate) fn y_axis(&self) -> Point3d {
797 match self {
798 SketchSurface::Plane(plane) => plane.info.y_axis,
799 SketchSurface::Face(face) => face.y_axis,
800 }
801 }
802
803 pub(crate) fn object_id(&self) -> Option<ObjectId> {
804 match self {
805 SketchSurface::Plane(plane) => plane.object_id,
806 SketchSurface::Face(face) => Some(face.object_id),
807 }
808 }
809
810 pub(crate) fn set_object_id(&mut self, object_id: ObjectId) {
811 match self {
812 SketchSurface::Plane(plane) => plane.object_id = Some(object_id),
813 SketchSurface::Face(face) => face.object_id = object_id,
814 }
815 }
816}
817
818#[derive(Debug, Clone)]
819pub(crate) enum GetTangentialInfoFromPathsResult {
820 PreviousPoint([f64; 2]),
821 Arc {
822 center: [f64; 2],
823 ccw: bool,
824 },
825 Circle {
826 center: [f64; 2],
827 ccw: bool,
828 radius: f64,
829 },
830 Ellipse {
831 center: [f64; 2],
832 ccw: bool,
833 major_axis: [f64; 2],
834 _minor_radius: f64,
835 },
836}
837
838impl GetTangentialInfoFromPathsResult {
839 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
840 match self {
841 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
842 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
843 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
844 }
845 GetTangentialInfoFromPathsResult::Circle {
848 center, radius, ccw, ..
849 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
850 GetTangentialInfoFromPathsResult::Ellipse {
851 center,
852 major_axis,
853 ccw,
854 ..
855 } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
856 }
857 }
858}
859
860impl Sketch {
861 pub(crate) fn add_tag(
862 &mut self,
863 tag: NodeRef<'_, TagDeclarator>,
864 current_path: &Path,
865 exec_state: &ExecState,
866 surface: Option<&ExtrudeSurface>,
867 ) {
868 let mut tag_identifier: TagIdentifier = tag.into();
869 let base = current_path.get_base();
870 tag_identifier.info.push((
871 exec_state.stack().current_epoch(),
872 TagEngineInfo {
873 id: base.geo_meta.id,
874 sketch: self.id,
875 path: Some(current_path.clone()),
876 surface: surface.cloned(),
877 },
878 ));
879
880 self.tags.insert(tag.name.to_string(), tag_identifier);
881 }
882
883 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
884 for t in tags {
885 match self.tags.get_mut(&t.value) {
886 Some(id) => {
887 id.merge_info(t);
888 }
889 None => {
890 self.tags.insert(t.value.clone(), t.clone());
891 }
892 }
893 }
894 }
895
896 pub(crate) fn latest_path(&self) -> Option<&Path> {
898 self.paths.last()
899 }
900
901 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
905 let Some(path) = self.latest_path() else {
906 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
907 };
908
909 let to = path.get_base().to;
910 Ok(Point2d::new(to[0], to[1], path.get_base().units))
911 }
912
913 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
914 let Some(path) = self.latest_path() else {
915 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
916 };
917 path.get_tangential_info()
918 }
919}
920
921#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
922#[ts(export)]
923#[serde(tag = "type", rename_all = "camelCase")]
924pub struct Solid {
925 pub id: uuid::Uuid,
927 pub artifact_id: ArtifactId,
929 pub value: Vec<ExtrudeSurface>,
931 pub sketch: Sketch,
933 pub start_cap_id: Option<uuid::Uuid>,
935 pub end_cap_id: Option<uuid::Uuid>,
937 #[serde(default, skip_serializing_if = "Vec::is_empty")]
939 pub edge_cuts: Vec<EdgeCut>,
940 pub units: UnitLength,
942 pub sectional: bool,
944 #[serde(skip)]
946 pub meta: Vec<Metadata>,
947}
948
949impl Solid {
950 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
951 self.edge_cuts.iter().map(|foc| foc.id())
952 }
953}
954
955#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
957#[ts(export)]
958#[serde(tag = "type", rename_all = "camelCase")]
959pub enum EdgeCut {
960 Fillet {
962 id: uuid::Uuid,
964 radius: TyF64,
965 #[serde(rename = "edgeId")]
967 edge_id: uuid::Uuid,
968 tag: Box<Option<TagNode>>,
969 },
970 Chamfer {
972 id: uuid::Uuid,
974 length: TyF64,
975 #[serde(rename = "edgeId")]
977 edge_id: uuid::Uuid,
978 tag: Box<Option<TagNode>>,
979 },
980}
981
982impl EdgeCut {
983 pub fn id(&self) -> uuid::Uuid {
984 match self {
985 EdgeCut::Fillet { id, .. } => *id,
986 EdgeCut::Chamfer { id, .. } => *id,
987 }
988 }
989
990 pub fn set_id(&mut self, id: uuid::Uuid) {
991 match self {
992 EdgeCut::Fillet { id: i, .. } => *i = id,
993 EdgeCut::Chamfer { id: i, .. } => *i = id,
994 }
995 }
996
997 pub fn edge_id(&self) -> uuid::Uuid {
998 match self {
999 EdgeCut::Fillet { edge_id, .. } => *edge_id,
1000 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
1001 }
1002 }
1003
1004 pub fn set_edge_id(&mut self, id: uuid::Uuid) {
1005 match self {
1006 EdgeCut::Fillet { edge_id: i, .. } => *i = id,
1007 EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
1008 }
1009 }
1010
1011 pub fn tag(&self) -> Option<TagNode> {
1012 match self {
1013 EdgeCut::Fillet { tag, .. } => *tag.clone(),
1014 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
1015 }
1016 }
1017}
1018
1019#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
1020#[ts(export)]
1021pub struct Point2d {
1022 pub x: f64,
1023 pub y: f64,
1024 pub units: UnitLength,
1025}
1026
1027impl Point2d {
1028 pub const ZERO: Self = Self {
1029 x: 0.0,
1030 y: 0.0,
1031 units: UnitLength::Millimeters,
1032 };
1033
1034 pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
1035 Self { x, y, units }
1036 }
1037
1038 pub fn into_x(self) -> TyF64 {
1039 TyF64::new(self.x, self.units.into())
1040 }
1041
1042 pub fn into_y(self) -> TyF64 {
1043 TyF64::new(self.y, self.units.into())
1044 }
1045
1046 pub fn ignore_units(self) -> [f64; 2] {
1047 [self.x, self.y]
1048 }
1049}
1050
1051#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
1052#[ts(export)]
1053pub struct Point3d {
1054 pub x: f64,
1055 pub y: f64,
1056 pub z: f64,
1057 pub units: Option<UnitLength>,
1058}
1059
1060impl Point3d {
1061 pub const ZERO: Self = Self {
1062 x: 0.0,
1063 y: 0.0,
1064 z: 0.0,
1065 units: Some(UnitLength::Millimeters),
1066 };
1067
1068 pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
1069 Self { x, y, z, units }
1070 }
1071
1072 pub const fn is_zero(&self) -> bool {
1073 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
1074 }
1075
1076 pub fn axes_cross_product(&self, other: &Self) -> Self {
1081 Self {
1082 x: self.y * other.z - self.z * other.y,
1083 y: self.z * other.x - self.x * other.z,
1084 z: self.x * other.y - self.y * other.x,
1085 units: None,
1086 }
1087 }
1088
1089 pub fn axes_dot_product(&self, other: &Self) -> f64 {
1094 let x = self.x * other.x;
1095 let y = self.y * other.y;
1096 let z = self.z * other.z;
1097 x + y + z
1098 }
1099
1100 pub fn normalize(&self) -> Self {
1101 let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1102 Point3d {
1103 x: self.x / len,
1104 y: self.y / len,
1105 z: self.z / len,
1106 units: None,
1107 }
1108 }
1109
1110 pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1111 let p = [self.x, self.y, self.z];
1112 let u = self.units;
1113 (p, u)
1114 }
1115
1116 pub(crate) fn negated(self) -> Self {
1117 Self {
1118 x: -self.x,
1119 y: -self.y,
1120 z: -self.z,
1121 units: self.units,
1122 }
1123 }
1124}
1125
1126impl From<[TyF64; 3]> for Point3d {
1127 fn from(p: [TyF64; 3]) -> Self {
1128 Self {
1129 x: p[0].n,
1130 y: p[1].n,
1131 z: p[2].n,
1132 units: p[0].ty.as_length(),
1133 }
1134 }
1135}
1136
1137impl From<Point3d> for Point3D {
1138 fn from(p: Point3d) -> Self {
1139 Self { x: p.x, y: p.y, z: p.z }
1140 }
1141}
1142
1143impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1144 fn from(p: Point3d) -> Self {
1145 if let Some(units) = p.units {
1146 Self {
1147 x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1148 y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1149 z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1150 }
1151 } else {
1152 Self {
1153 x: LengthUnit(p.x),
1154 y: LengthUnit(p.y),
1155 z: LengthUnit(p.z),
1156 }
1157 }
1158 }
1159}
1160
1161impl Add for Point3d {
1162 type Output = Point3d;
1163
1164 fn add(self, rhs: Self) -> Self::Output {
1165 Point3d {
1167 x: self.x + rhs.x,
1168 y: self.y + rhs.y,
1169 z: self.z + rhs.z,
1170 units: self.units,
1171 }
1172 }
1173}
1174
1175impl AddAssign for Point3d {
1176 fn add_assign(&mut self, rhs: Self) {
1177 *self = *self + rhs
1178 }
1179}
1180
1181impl Sub for Point3d {
1182 type Output = Point3d;
1183
1184 fn sub(self, rhs: Self) -> Self::Output {
1185 let (x, y, z) = if rhs.units != self.units
1186 && let Some(sunits) = self.units
1187 && let Some(runits) = rhs.units
1188 {
1189 (
1190 adjust_length(runits, rhs.x, sunits).0,
1191 adjust_length(runits, rhs.y, sunits).0,
1192 adjust_length(runits, rhs.z, sunits).0,
1193 )
1194 } else {
1195 (rhs.x, rhs.y, rhs.z)
1196 };
1197 Point3d {
1198 x: self.x - x,
1199 y: self.y - y,
1200 z: self.z - z,
1201 units: self.units,
1202 }
1203 }
1204}
1205
1206impl SubAssign for Point3d {
1207 fn sub_assign(&mut self, rhs: Self) {
1208 *self = *self - rhs
1209 }
1210}
1211
1212impl Mul<f64> for Point3d {
1213 type Output = Point3d;
1214
1215 fn mul(self, rhs: f64) -> Self::Output {
1216 Point3d {
1217 x: self.x * rhs,
1218 y: self.y * rhs,
1219 z: self.z * rhs,
1220 units: self.units,
1221 }
1222 }
1223}
1224
1225#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1227#[ts(export)]
1228#[serde(rename_all = "camelCase")]
1229pub struct BasePath {
1230 #[ts(type = "[number, number]")]
1232 pub from: [f64; 2],
1233 #[ts(type = "[number, number]")]
1235 pub to: [f64; 2],
1236 pub units: UnitLength,
1237 pub tag: Option<TagNode>,
1239 #[serde(rename = "__geoMeta")]
1241 pub geo_meta: GeoMeta,
1242}
1243
1244impl BasePath {
1245 pub fn get_to(&self) -> [TyF64; 2] {
1246 let ty: NumericType = self.units.into();
1247 [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1248 }
1249
1250 pub fn get_from(&self) -> [TyF64; 2] {
1251 let ty: NumericType = self.units.into();
1252 [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1253 }
1254}
1255
1256#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1258#[ts(export)]
1259#[serde(rename_all = "camelCase")]
1260pub struct GeoMeta {
1261 pub id: uuid::Uuid,
1263 #[serde(flatten)]
1265 pub metadata: Metadata,
1266}
1267
1268#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1270#[ts(export)]
1271#[serde(tag = "type")]
1272pub enum Path {
1273 ToPoint {
1275 #[serde(flatten)]
1276 base: BasePath,
1277 },
1278 TangentialArcTo {
1280 #[serde(flatten)]
1281 base: BasePath,
1282 #[ts(type = "[number, number]")]
1284 center: [f64; 2],
1285 ccw: bool,
1287 },
1288 TangentialArc {
1290 #[serde(flatten)]
1291 base: BasePath,
1292 #[ts(type = "[number, number]")]
1294 center: [f64; 2],
1295 ccw: bool,
1297 },
1298 Circle {
1301 #[serde(flatten)]
1302 base: BasePath,
1303 #[ts(type = "[number, number]")]
1305 center: [f64; 2],
1306 radius: f64,
1308 ccw: bool,
1311 },
1312 CircleThreePoint {
1313 #[serde(flatten)]
1314 base: BasePath,
1315 #[ts(type = "[number, number]")]
1317 p1: [f64; 2],
1318 #[ts(type = "[number, number]")]
1320 p2: [f64; 2],
1321 #[ts(type = "[number, number]")]
1323 p3: [f64; 2],
1324 },
1325 ArcThreePoint {
1326 #[serde(flatten)]
1327 base: BasePath,
1328 #[ts(type = "[number, number]")]
1330 p1: [f64; 2],
1331 #[ts(type = "[number, number]")]
1333 p2: [f64; 2],
1334 #[ts(type = "[number, number]")]
1336 p3: [f64; 2],
1337 },
1338 Horizontal {
1340 #[serde(flatten)]
1341 base: BasePath,
1342 x: f64,
1344 },
1345 AngledLineTo {
1347 #[serde(flatten)]
1348 base: BasePath,
1349 x: Option<f64>,
1351 y: Option<f64>,
1353 },
1354 Base {
1356 #[serde(flatten)]
1357 base: BasePath,
1358 },
1359 Arc {
1361 #[serde(flatten)]
1362 base: BasePath,
1363 center: [f64; 2],
1365 radius: f64,
1367 ccw: bool,
1369 },
1370 Ellipse {
1371 #[serde(flatten)]
1372 base: BasePath,
1373 center: [f64; 2],
1374 major_axis: [f64; 2],
1375 minor_radius: f64,
1376 ccw: bool,
1377 },
1378 Conic {
1380 #[serde(flatten)]
1381 base: BasePath,
1382 },
1383}
1384
1385impl Path {
1386 pub fn get_id(&self) -> uuid::Uuid {
1387 match self {
1388 Path::ToPoint { base } => base.geo_meta.id,
1389 Path::Horizontal { base, .. } => base.geo_meta.id,
1390 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1391 Path::Base { base } => base.geo_meta.id,
1392 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1393 Path::TangentialArc { base, .. } => base.geo_meta.id,
1394 Path::Circle { base, .. } => base.geo_meta.id,
1395 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1396 Path::Arc { base, .. } => base.geo_meta.id,
1397 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1398 Path::Ellipse { base, .. } => base.geo_meta.id,
1399 Path::Conic { base, .. } => base.geo_meta.id,
1400 }
1401 }
1402
1403 pub fn set_id(&mut self, id: uuid::Uuid) {
1404 match self {
1405 Path::ToPoint { base } => base.geo_meta.id = id,
1406 Path::Horizontal { base, .. } => base.geo_meta.id = id,
1407 Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1408 Path::Base { base } => base.geo_meta.id = id,
1409 Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1410 Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1411 Path::Circle { base, .. } => base.geo_meta.id = id,
1412 Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1413 Path::Arc { base, .. } => base.geo_meta.id = id,
1414 Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1415 Path::Ellipse { base, .. } => base.geo_meta.id = id,
1416 Path::Conic { base, .. } => base.geo_meta.id = id,
1417 }
1418 }
1419
1420 pub fn get_tag(&self) -> Option<TagNode> {
1421 match self {
1422 Path::ToPoint { base } => base.tag.clone(),
1423 Path::Horizontal { base, .. } => base.tag.clone(),
1424 Path::AngledLineTo { base, .. } => base.tag.clone(),
1425 Path::Base { base } => base.tag.clone(),
1426 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1427 Path::TangentialArc { base, .. } => base.tag.clone(),
1428 Path::Circle { base, .. } => base.tag.clone(),
1429 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1430 Path::Arc { base, .. } => base.tag.clone(),
1431 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1432 Path::Ellipse { base, .. } => base.tag.clone(),
1433 Path::Conic { base, .. } => base.tag.clone(),
1434 }
1435 }
1436
1437 pub fn get_base(&self) -> &BasePath {
1438 match self {
1439 Path::ToPoint { base } => base,
1440 Path::Horizontal { base, .. } => base,
1441 Path::AngledLineTo { base, .. } => base,
1442 Path::Base { base } => base,
1443 Path::TangentialArcTo { base, .. } => base,
1444 Path::TangentialArc { base, .. } => base,
1445 Path::Circle { base, .. } => base,
1446 Path::CircleThreePoint { base, .. } => base,
1447 Path::Arc { base, .. } => base,
1448 Path::ArcThreePoint { base, .. } => base,
1449 Path::Ellipse { base, .. } => base,
1450 Path::Conic { base, .. } => base,
1451 }
1452 }
1453
1454 pub fn get_from(&self) -> [TyF64; 2] {
1456 let p = &self.get_base().from;
1457 let ty: NumericType = self.get_base().units.into();
1458 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1459 }
1460
1461 pub fn get_to(&self) -> [TyF64; 2] {
1463 let p = &self.get_base().to;
1464 let ty: NumericType = self.get_base().units.into();
1465 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1466 }
1467
1468 pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1470 let p = &self.get_base().from;
1471 let ty: NumericType = self.get_base().units.into();
1472 (*p, ty)
1473 }
1474
1475 pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1477 let p = &self.get_base().to;
1478 let ty: NumericType = self.get_base().units.into();
1479 (*p, ty)
1480 }
1481
1482 pub fn length(&self) -> Option<TyF64> {
1485 let n = match self {
1486 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1487 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1488 }
1489 Self::TangentialArc {
1490 base: _,
1491 center,
1492 ccw: _,
1493 }
1494 | Self::TangentialArcTo {
1495 base: _,
1496 center,
1497 ccw: _,
1498 } => {
1499 let radius = linear_distance(&self.get_base().from, center);
1502 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
1503 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1505 }
1506 Self::Circle { radius, .. } => Some(2.0 * std::f64::consts::PI * radius),
1507 Self::CircleThreePoint { .. } => {
1508 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1509 self.get_base().from,
1510 self.get_base().to,
1511 self.get_base().to,
1512 ]);
1513 let radius = linear_distance(
1514 &[circle_center.center[0], circle_center.center[1]],
1515 &self.get_base().from,
1516 );
1517 Some(2.0 * std::f64::consts::PI * radius)
1518 }
1519 Self::Arc { .. } => {
1520 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1522 }
1523 Self::ArcThreePoint { .. } => {
1524 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1526 }
1527 Self::Ellipse { .. } => {
1528 None
1530 }
1531 Self::Conic { .. } => {
1532 None
1534 }
1535 };
1536 n.map(|n| TyF64::new(n, self.get_base().units.into()))
1537 }
1538
1539 pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
1540 match self {
1541 Path::ToPoint { base } => Some(base),
1542 Path::Horizontal { base, .. } => Some(base),
1543 Path::AngledLineTo { base, .. } => Some(base),
1544 Path::Base { base } => Some(base),
1545 Path::TangentialArcTo { base, .. } => Some(base),
1546 Path::TangentialArc { base, .. } => Some(base),
1547 Path::Circle { base, .. } => Some(base),
1548 Path::CircleThreePoint { base, .. } => Some(base),
1549 Path::Arc { base, .. } => Some(base),
1550 Path::ArcThreePoint { base, .. } => Some(base),
1551 Path::Ellipse { base, .. } => Some(base),
1552 Path::Conic { base, .. } => Some(base),
1553 }
1554 }
1555
1556 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1557 match self {
1558 Path::TangentialArc { center, ccw, .. }
1559 | Path::TangentialArcTo { center, ccw, .. }
1560 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1561 center: *center,
1562 ccw: *ccw,
1563 },
1564 Path::ArcThreePoint { p1, p2, p3, .. } => {
1565 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1566 GetTangentialInfoFromPathsResult::Arc {
1567 center: circle.center,
1568 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
1569 }
1570 }
1571 Path::Circle {
1572 center, ccw, radius, ..
1573 } => GetTangentialInfoFromPathsResult::Circle {
1574 center: *center,
1575 ccw: *ccw,
1576 radius: *radius,
1577 },
1578 Path::CircleThreePoint { p1, p2, p3, .. } => {
1579 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1580 let center_point = [circle.center[0], circle.center[1]];
1581 GetTangentialInfoFromPathsResult::Circle {
1582 center: center_point,
1583 ccw: true,
1585 radius: circle.radius,
1586 }
1587 }
1588 Path::Ellipse {
1590 center,
1591 major_axis,
1592 minor_radius,
1593 ccw,
1594 ..
1595 } => GetTangentialInfoFromPathsResult::Ellipse {
1596 center: *center,
1597 major_axis: *major_axis,
1598 _minor_radius: *minor_radius,
1599 ccw: *ccw,
1600 },
1601 Path::Conic { .. }
1602 | Path::ToPoint { .. }
1603 | Path::Horizontal { .. }
1604 | Path::AngledLineTo { .. }
1605 | Path::Base { .. } => {
1606 let base = self.get_base();
1607 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1608 }
1609 }
1610 }
1611
1612 pub(crate) fn is_straight_line(&self) -> bool {
1614 matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
1615 }
1616}
1617
1618#[rustfmt::skip]
1620fn linear_distance(
1621 [x0, y0]: &[f64; 2],
1622 [x1, y1]: &[f64; 2]
1623) -> f64 {
1624 let y_sq = (y1 - y0).powi(2);
1625 let x_sq = (x1 - x0).powi(2);
1626 (y_sq + x_sq).sqrt()
1627}
1628
1629#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1631#[ts(export)]
1632#[serde(tag = "type", rename_all = "camelCase")]
1633pub enum ExtrudeSurface {
1634 ExtrudePlane(ExtrudePlane),
1636 ExtrudeArc(ExtrudeArc),
1637 Chamfer(ChamferSurface),
1638 Fillet(FilletSurface),
1639}
1640
1641#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1643#[ts(export)]
1644#[serde(rename_all = "camelCase")]
1645pub struct ChamferSurface {
1646 pub face_id: uuid::Uuid,
1648 pub tag: Option<Node<TagDeclarator>>,
1650 #[serde(flatten)]
1652 pub geo_meta: GeoMeta,
1653}
1654
1655#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1657#[ts(export)]
1658#[serde(rename_all = "camelCase")]
1659pub struct FilletSurface {
1660 pub face_id: uuid::Uuid,
1662 pub tag: Option<Node<TagDeclarator>>,
1664 #[serde(flatten)]
1666 pub geo_meta: GeoMeta,
1667}
1668
1669#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1671#[ts(export)]
1672#[serde(rename_all = "camelCase")]
1673pub struct ExtrudePlane {
1674 pub face_id: uuid::Uuid,
1676 pub tag: Option<Node<TagDeclarator>>,
1678 #[serde(flatten)]
1680 pub geo_meta: GeoMeta,
1681}
1682
1683#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1685#[ts(export)]
1686#[serde(rename_all = "camelCase")]
1687pub struct ExtrudeArc {
1688 pub face_id: uuid::Uuid,
1690 pub tag: Option<Node<TagDeclarator>>,
1692 #[serde(flatten)]
1694 pub geo_meta: GeoMeta,
1695}
1696
1697impl ExtrudeSurface {
1698 pub fn get_id(&self) -> uuid::Uuid {
1699 match self {
1700 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
1701 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
1702 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
1703 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
1704 }
1705 }
1706
1707 pub fn face_id(&self) -> uuid::Uuid {
1708 match self {
1709 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id,
1710 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id,
1711 ExtrudeSurface::Fillet(f) => f.face_id,
1712 ExtrudeSurface::Chamfer(c) => c.face_id,
1713 }
1714 }
1715
1716 pub fn set_face_id(&mut self, face_id: uuid::Uuid) {
1717 match self {
1718 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id = face_id,
1719 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id = face_id,
1720 ExtrudeSurface::Fillet(f) => f.face_id = face_id,
1721 ExtrudeSurface::Chamfer(c) => c.face_id = face_id,
1722 }
1723 }
1724
1725 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
1726 match self {
1727 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
1728 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
1729 ExtrudeSurface::Fillet(f) => f.tag.clone(),
1730 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
1731 }
1732 }
1733}
1734
1735#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ts_rs::TS)]
1736pub struct SketchVarId(pub usize);
1737
1738impl SketchVarId {
1739 pub fn to_constraint_id(self, range: SourceRange) -> Result<kcl_ezpz::Id, KclError> {
1740 self.0.try_into().map_err(|_| {
1741 KclError::new_type(KclErrorDetails::new(
1742 "Cannot convert to constraint ID since the sketch variable ID is too large".to_owned(),
1743 vec![range],
1744 ))
1745 })
1746 }
1747}
1748
1749#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1750#[ts(export_to = "Geometry.ts")]
1751#[serde(rename_all = "camelCase")]
1752pub struct SketchVar {
1753 pub id: SketchVarId,
1754 pub initial_value: f64,
1755 pub ty: NumericType,
1756 #[serde(skip)]
1757 pub meta: Vec<Metadata>,
1758}
1759
1760impl SketchVar {
1761 pub fn initial_value_to_solver_units(
1762 &self,
1763 exec_state: &mut ExecState,
1764 source_range: SourceRange,
1765 description: &str,
1766 ) -> Result<TyF64, KclError> {
1767 let x_initial_value = KclValue::Number {
1768 value: self.initial_value,
1769 ty: self.ty,
1770 meta: vec![source_range.into()],
1771 };
1772 let normalized_value = normalize_to_solver_unit(&x_initial_value, source_range, exec_state, description)?;
1773 normalized_value.as_ty_f64().ok_or_else(|| {
1774 let message = format!(
1775 "Expected number after coercion, but found {}",
1776 normalized_value.human_friendly_type()
1777 );
1778 debug_assert!(false, "{}", &message);
1779 KclError::new_internal(KclErrorDetails::new(message, vec![source_range]))
1780 })
1781 }
1782}
1783
1784#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1785#[ts(export_to = "Geometry.ts")]
1786#[serde(tag = "type")]
1787pub enum UnsolvedExpr {
1788 Known(TyF64),
1789 Unknown(SketchVarId),
1790}
1791
1792impl UnsolvedExpr {
1793 pub fn var(&self) -> Option<SketchVarId> {
1794 match self {
1795 UnsolvedExpr::Known(_) => None,
1796 UnsolvedExpr::Unknown(id) => Some(*id),
1797 }
1798 }
1799}
1800
1801pub type UnsolvedPoint2dExpr = [UnsolvedExpr; 2];
1802
1803#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1804#[ts(export_to = "Geometry.ts")]
1805#[serde(rename_all = "camelCase")]
1806pub struct ConstrainablePoint2d {
1807 pub vars: crate::front::Point2d<SketchVarId>,
1808 pub object_id: ObjectId,
1809}
1810
1811#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1812#[ts(export_to = "Geometry.ts")]
1813#[serde(rename_all = "camelCase")]
1814pub struct UnsolvedSegment {
1815 pub object_id: ObjectId,
1816 pub kind: UnsolvedSegmentKind,
1817 #[serde(skip)]
1818 pub meta: Vec<Metadata>,
1819}
1820
1821#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1822#[ts(export_to = "Geometry.ts")]
1823#[serde(rename_all = "camelCase")]
1824pub enum UnsolvedSegmentKind {
1825 Point {
1826 position: UnsolvedPoint2dExpr,
1827 ctor: Box<PointCtor>,
1828 },
1829 Line {
1830 start: UnsolvedPoint2dExpr,
1831 end: UnsolvedPoint2dExpr,
1832 ctor: Box<LineCtor>,
1833 start_object_id: ObjectId,
1834 end_object_id: ObjectId,
1835 },
1836 Arc {
1837 start: UnsolvedPoint2dExpr,
1838 end: UnsolvedPoint2dExpr,
1839 center: UnsolvedPoint2dExpr,
1840 ctor: Box<ArcCtor>,
1841 start_object_id: ObjectId,
1842 end_object_id: ObjectId,
1843 center_object_id: ObjectId,
1844 },
1845}
1846
1847#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1848#[ts(export_to = "Geometry.ts")]
1849#[serde(rename_all = "camelCase")]
1850pub struct Segment {
1851 pub object_id: ObjectId,
1852 pub kind: SegmentKind,
1853 #[serde(skip)]
1854 pub meta: Vec<Metadata>,
1855}
1856
1857#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1858#[ts(export_to = "Geometry.ts")]
1859#[serde(rename_all = "camelCase")]
1860pub enum SegmentKind {
1861 Point {
1862 position: [TyF64; 2],
1863 ctor: Box<PointCtor>,
1864 #[serde(skip_serializing_if = "Option::is_none")]
1865 freedom: Option<Freedom>,
1866 },
1867 Line {
1868 start: [TyF64; 2],
1869 end: [TyF64; 2],
1870 ctor: Box<LineCtor>,
1871 start_object_id: ObjectId,
1872 end_object_id: ObjectId,
1873 #[serde(skip_serializing_if = "Option::is_none")]
1874 start_freedom: Option<Freedom>,
1875 #[serde(skip_serializing_if = "Option::is_none")]
1876 end_freedom: Option<Freedom>,
1877 },
1878 Arc {
1879 start: [TyF64; 2],
1880 end: [TyF64; 2],
1881 center: [TyF64; 2],
1882 ctor: Box<ArcCtor>,
1883 start_object_id: ObjectId,
1884 end_object_id: ObjectId,
1885 center_object_id: ObjectId,
1886 #[serde(skip_serializing_if = "Option::is_none")]
1887 start_freedom: Option<Freedom>,
1888 #[serde(skip_serializing_if = "Option::is_none")]
1889 end_freedom: Option<Freedom>,
1890 #[serde(skip_serializing_if = "Option::is_none")]
1891 center_freedom: Option<Freedom>,
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 AbstractSegment {
1899 pub repr: SegmentRepr,
1900 #[serde(skip)]
1901 pub meta: Vec<Metadata>,
1902}
1903
1904#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1905pub enum SegmentRepr {
1906 Unsolved { segment: UnsolvedSegment },
1907 Solved { segment: Segment },
1908}
1909
1910#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1911#[ts(export_to = "Geometry.ts")]
1912#[serde(rename_all = "camelCase")]
1913pub struct SketchConstraint {
1914 pub kind: SketchConstraintKind,
1915 #[serde(skip)]
1916 pub meta: Vec<Metadata>,
1917}
1918
1919#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1920#[ts(export_to = "Geometry.ts")]
1921#[serde(rename_all = "camelCase")]
1922pub enum SketchConstraintKind {
1923 Distance { points: [ConstrainablePoint2d; 2] },
1924}