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