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