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 pub units: UnitLength,
806 #[serde(skip)]
808 pub meta: Vec<Metadata>,
809 #[serde(
812 default = "ProfileClosed::explicitly",
813 skip_serializing_if = "ProfileClosed::is_explicitly"
814 )]
815 pub is_closed: ProfileClosed,
816}
817
818impl ProfileClosed {
819 #[expect(dead_code, reason = "it's not actually dead, it's called by serde")]
820 fn explicitly() -> Self {
821 Self::Explicitly
822 }
823
824 fn is_explicitly(&self) -> bool {
825 matches!(self, ProfileClosed::Explicitly)
826 }
827}
828
829#[derive(Debug, Serialize, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd, ts_rs::TS)]
831#[serde(rename_all = "camelCase")]
832pub enum ProfileClosed {
833 No,
835 Maybe,
837 Implicitly,
839 Explicitly,
841}
842
843impl Sketch {
844 pub(crate) fn build_sketch_mode_cmds(
847 &self,
848 exec_state: &mut ExecState,
849 inner_cmd: ModelingCmdReq,
850 ) -> Vec<ModelingCmdReq> {
851 vec![
852 ModelingCmdReq {
855 cmd: ModelingCmd::from(
856 mcmd::EnableSketchMode::builder()
857 .animated(false)
858 .ortho(false)
859 .entity_id(self.on.id())
860 .adjust_camera(false)
861 .maybe_planar_normal(if let SketchSurface::Plane(plane) = &self.on {
862 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
864 Some(normal.into())
865 } else {
866 None
867 })
868 .build(),
869 ),
870 cmd_id: exec_state.next_uuid().into(),
871 },
872 inner_cmd,
873 ModelingCmdReq {
874 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::builder().build()),
875 cmd_id: exec_state.next_uuid().into(),
876 },
877 ]
878 }
879}
880
881#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
883#[ts(export)]
884#[serde(tag = "type", rename_all = "camelCase")]
885pub enum SketchSurface {
886 Plane(Box<Plane>),
887 Face(Box<Face>),
888}
889
890impl SketchSurface {
891 pub(crate) fn id(&self) -> uuid::Uuid {
892 match self {
893 SketchSurface::Plane(plane) => plane.id,
894 SketchSurface::Face(face) => face.id,
895 }
896 }
897 pub(crate) fn x_axis(&self) -> Point3d {
898 match self {
899 SketchSurface::Plane(plane) => plane.info.x_axis,
900 SketchSurface::Face(face) => face.x_axis,
901 }
902 }
903 pub(crate) fn y_axis(&self) -> Point3d {
904 match self {
905 SketchSurface::Plane(plane) => plane.info.y_axis,
906 SketchSurface::Face(face) => face.y_axis,
907 }
908 }
909
910 pub(crate) fn object_id(&self) -> Option<ObjectId> {
911 match self {
912 SketchSurface::Plane(plane) => plane.object_id,
913 SketchSurface::Face(face) => Some(face.object_id),
914 }
915 }
916
917 pub(crate) fn set_object_id(&mut self, object_id: ObjectId) {
918 match self {
919 SketchSurface::Plane(plane) => plane.object_id = Some(object_id),
920 SketchSurface::Face(face) => face.object_id = object_id,
921 }
922 }
923}
924
925#[derive(Debug, Clone, PartialEq)]
927pub enum Extrudable {
928 Sketch(Box<Sketch>),
930 Face(FaceTag),
932}
933
934impl Extrudable {
935 pub async fn id_to_extrude(
937 &self,
938 exec_state: &mut ExecState,
939 args: &Args,
940 must_be_planar: bool,
941 ) -> Result<uuid::Uuid, KclError> {
942 match self {
943 Extrudable::Sketch(sketch) => Ok(sketch.id),
944 Extrudable::Face(face_tag) => face_tag.get_face_id_from_tag(exec_state, args, must_be_planar).await,
945 }
946 }
947
948 pub fn as_sketch(&self) -> Option<Sketch> {
949 match self {
950 Extrudable::Sketch(sketch) => Some((**sketch).clone()),
951 Extrudable::Face(face_tag) => match face_tag.geometry() {
952 Some(Geometry::Sketch(sketch)) => Some(sketch),
953 Some(Geometry::Solid(solid)) => solid.sketch().cloned(),
954 None => None,
955 },
956 }
957 }
958
959 pub fn is_closed(&self) -> ProfileClosed {
960 match self {
961 Extrudable::Sketch(sketch) => sketch.is_closed,
962 Extrudable::Face(face_tag) => match face_tag.geometry() {
963 Some(Geometry::Sketch(sketch)) => sketch.is_closed,
964 Some(Geometry::Solid(solid)) => solid
965 .sketch()
966 .map(|sketch| sketch.is_closed)
967 .unwrap_or(ProfileClosed::Maybe),
968 _ => ProfileClosed::Maybe,
969 },
970 }
971 }
972}
973
974impl From<Sketch> for Extrudable {
975 fn from(value: Sketch) -> Self {
976 Extrudable::Sketch(Box::new(value))
977 }
978}
979
980#[derive(Debug, Clone)]
981pub(crate) enum GetTangentialInfoFromPathsResult {
982 PreviousPoint([f64; 2]),
983 Arc {
984 center: [f64; 2],
985 ccw: bool,
986 },
987 Circle {
988 center: [f64; 2],
989 ccw: bool,
990 radius: f64,
991 },
992 Ellipse {
993 center: [f64; 2],
994 ccw: bool,
995 major_axis: [f64; 2],
996 _minor_radius: f64,
997 },
998}
999
1000impl GetTangentialInfoFromPathsResult {
1001 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
1002 match self {
1003 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
1004 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
1005 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
1006 }
1007 GetTangentialInfoFromPathsResult::Circle {
1010 center, radius, ccw, ..
1011 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
1012 GetTangentialInfoFromPathsResult::Ellipse {
1013 center,
1014 major_axis,
1015 ccw,
1016 ..
1017 } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
1018 }
1019 }
1020}
1021
1022impl Sketch {
1023 pub(crate) fn add_tag(
1024 &mut self,
1025 tag: NodeRef<'_, TagDeclarator>,
1026 current_path: &Path,
1027 exec_state: &ExecState,
1028 surface: Option<&ExtrudeSurface>,
1029 ) {
1030 let mut tag_identifier: TagIdentifier = tag.into();
1031 let base = current_path.get_base();
1032 let mut sketch_copy = self.clone();
1033 sketch_copy.tags.clear();
1034 tag_identifier.info.push((
1035 exec_state.stack().current_epoch(),
1036 TagEngineInfo {
1037 id: base.geo_meta.id,
1038 geometry: Geometry::Sketch(sketch_copy),
1039 path: Some(current_path.clone()),
1040 surface: surface.cloned(),
1041 },
1042 ));
1043
1044 self.tags.insert(tag.name.to_string(), tag_identifier);
1045 }
1046
1047 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
1048 for t in tags {
1049 match self.tags.get_mut(&t.value) {
1050 Some(id) => {
1051 id.merge_info(t);
1052 }
1053 None => {
1054 self.tags.insert(t.value.clone(), t.clone());
1055 }
1056 }
1057 }
1058 }
1059
1060 pub(crate) fn latest_path(&self) -> Option<&Path> {
1062 self.paths.last()
1063 }
1064
1065 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
1069 let Some(path) = self.latest_path() else {
1070 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
1071 };
1072
1073 let to = path.get_base().to;
1074 Ok(Point2d::new(to[0], to[1], path.get_base().units))
1075 }
1076
1077 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
1078 let Some(path) = self.latest_path() else {
1079 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
1080 };
1081 path.get_tangential_info()
1082 }
1083}
1084
1085#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1086#[ts(export)]
1087#[serde(tag = "type", rename_all = "camelCase")]
1088pub struct Solid {
1089 pub id: uuid::Uuid,
1091 pub artifact_id: ArtifactId,
1093 pub value: Vec<ExtrudeSurface>,
1095 #[serde(rename = "sketch")]
1097 pub creator: SolidCreator,
1098 pub start_cap_id: Option<uuid::Uuid>,
1100 pub end_cap_id: Option<uuid::Uuid>,
1102 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1104 pub edge_cuts: Vec<EdgeCut>,
1105 pub units: UnitLength,
1107 pub sectional: bool,
1109 #[serde(skip)]
1111 pub meta: Vec<Metadata>,
1112}
1113
1114#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1115#[ts(export)]
1116pub struct CreatorFace {
1117 pub face_id: uuid::Uuid,
1119 pub solid_id: uuid::Uuid,
1121 pub sketch: Sketch,
1123}
1124
1125#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1127#[ts(export)]
1128#[serde(tag = "creatorType", rename_all = "camelCase")]
1129pub enum SolidCreator {
1130 Sketch(Sketch),
1132 Face(CreatorFace),
1134 Procedural,
1136}
1137
1138impl Solid {
1139 pub fn sketch(&self) -> Option<&Sketch> {
1140 match &self.creator {
1141 SolidCreator::Sketch(sketch) => Some(sketch),
1142 SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1143 SolidCreator::Procedural => None,
1144 }
1145 }
1146
1147 pub fn sketch_mut(&mut self) -> Option<&mut Sketch> {
1148 match &mut self.creator {
1149 SolidCreator::Sketch(sketch) => Some(sketch),
1150 SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1151 SolidCreator::Procedural => None,
1152 }
1153 }
1154
1155 pub fn sketch_id(&self) -> Option<uuid::Uuid> {
1156 self.sketch().map(|sketch| sketch.id)
1157 }
1158
1159 pub fn original_id(&self) -> uuid::Uuid {
1160 self.sketch().map(|sketch| sketch.original_id).unwrap_or(self.id)
1161 }
1162
1163 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
1164 self.edge_cuts.iter().map(|foc| foc.id())
1165 }
1166}
1167
1168#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1170#[ts(export)]
1171#[serde(tag = "type", rename_all = "camelCase")]
1172pub enum EdgeCut {
1173 Fillet {
1175 id: uuid::Uuid,
1177 radius: TyF64,
1178 #[serde(rename = "edgeId")]
1180 edge_id: uuid::Uuid,
1181 tag: Box<Option<TagNode>>,
1182 },
1183 Chamfer {
1185 id: uuid::Uuid,
1187 length: TyF64,
1188 #[serde(rename = "edgeId")]
1190 edge_id: uuid::Uuid,
1191 tag: Box<Option<TagNode>>,
1192 },
1193}
1194
1195impl EdgeCut {
1196 pub fn id(&self) -> uuid::Uuid {
1197 match self {
1198 EdgeCut::Fillet { id, .. } => *id,
1199 EdgeCut::Chamfer { id, .. } => *id,
1200 }
1201 }
1202
1203 pub fn set_id(&mut self, id: uuid::Uuid) {
1204 match self {
1205 EdgeCut::Fillet { id: i, .. } => *i = id,
1206 EdgeCut::Chamfer { id: i, .. } => *i = id,
1207 }
1208 }
1209
1210 pub fn edge_id(&self) -> uuid::Uuid {
1211 match self {
1212 EdgeCut::Fillet { edge_id, .. } => *edge_id,
1213 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
1214 }
1215 }
1216
1217 pub fn set_edge_id(&mut self, id: uuid::Uuid) {
1218 match self {
1219 EdgeCut::Fillet { edge_id: i, .. } => *i = id,
1220 EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
1221 }
1222 }
1223
1224 pub fn tag(&self) -> Option<TagNode> {
1225 match self {
1226 EdgeCut::Fillet { tag, .. } => *tag.clone(),
1227 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
1228 }
1229 }
1230}
1231
1232#[derive(Debug, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
1233#[ts(export)]
1234pub struct Point2d {
1235 pub x: f64,
1236 pub y: f64,
1237 pub units: UnitLength,
1238}
1239
1240impl Point2d {
1241 pub const ZERO: Self = Self {
1242 x: 0.0,
1243 y: 0.0,
1244 units: UnitLength::Millimeters,
1245 };
1246
1247 pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
1248 Self { x, y, units }
1249 }
1250
1251 pub fn into_x(self) -> TyF64 {
1252 TyF64::new(self.x, self.units.into())
1253 }
1254
1255 pub fn into_y(self) -> TyF64 {
1256 TyF64::new(self.y, self.units.into())
1257 }
1258
1259 pub fn ignore_units(self) -> [f64; 2] {
1260 [self.x, self.y]
1261 }
1262}
1263
1264#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
1265#[ts(export)]
1266pub struct Point3d {
1267 pub x: f64,
1268 pub y: f64,
1269 pub z: f64,
1270 pub units: Option<UnitLength>,
1271}
1272
1273impl Point3d {
1274 pub const ZERO: Self = Self {
1275 x: 0.0,
1276 y: 0.0,
1277 z: 0.0,
1278 units: Some(UnitLength::Millimeters),
1279 };
1280
1281 pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
1282 Self { x, y, z, units }
1283 }
1284
1285 pub const fn is_zero(&self) -> bool {
1286 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
1287 }
1288
1289 pub fn axes_cross_product(&self, other: &Self) -> Self {
1294 Self {
1295 x: self.y * other.z - self.z * other.y,
1296 y: self.z * other.x - self.x * other.z,
1297 z: self.x * other.y - self.y * other.x,
1298 units: None,
1299 }
1300 }
1301
1302 pub fn axes_dot_product(&self, other: &Self) -> f64 {
1307 let x = self.x * other.x;
1308 let y = self.y * other.y;
1309 let z = self.z * other.z;
1310 x + y + z
1311 }
1312
1313 pub fn normalize(&self) -> Self {
1314 let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1315 Point3d {
1316 x: self.x / len,
1317 y: self.y / len,
1318 z: self.z / len,
1319 units: None,
1320 }
1321 }
1322
1323 pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1324 let p = [self.x, self.y, self.z];
1325 let u = self.units;
1326 (p, u)
1327 }
1328
1329 pub(crate) fn negated(self) -> Self {
1330 Self {
1331 x: -self.x,
1332 y: -self.y,
1333 z: -self.z,
1334 units: self.units,
1335 }
1336 }
1337}
1338
1339impl From<[TyF64; 3]> for Point3d {
1340 fn from(p: [TyF64; 3]) -> Self {
1341 Self {
1342 x: p[0].n,
1343 y: p[1].n,
1344 z: p[2].n,
1345 units: p[0].ty.as_length(),
1346 }
1347 }
1348}
1349
1350impl From<Point3d> for Point3D {
1351 fn from(p: Point3d) -> Self {
1352 Self { x: p.x, y: p.y, z: p.z }
1353 }
1354}
1355
1356impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1357 fn from(p: Point3d) -> Self {
1358 if let Some(units) = p.units {
1359 Self {
1360 x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1361 y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1362 z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1363 }
1364 } else {
1365 Self {
1366 x: LengthUnit(p.x),
1367 y: LengthUnit(p.y),
1368 z: LengthUnit(p.z),
1369 }
1370 }
1371 }
1372}
1373
1374impl Add for Point3d {
1375 type Output = Point3d;
1376
1377 fn add(self, rhs: Self) -> Self::Output {
1378 Point3d {
1380 x: self.x + rhs.x,
1381 y: self.y + rhs.y,
1382 z: self.z + rhs.z,
1383 units: self.units,
1384 }
1385 }
1386}
1387
1388impl AddAssign for Point3d {
1389 fn add_assign(&mut self, rhs: Self) {
1390 *self = *self + rhs
1391 }
1392}
1393
1394impl Sub for Point3d {
1395 type Output = Point3d;
1396
1397 fn sub(self, rhs: Self) -> Self::Output {
1398 let (x, y, z) = if rhs.units != self.units
1399 && let Some(sunits) = self.units
1400 && let Some(runits) = rhs.units
1401 {
1402 (
1403 adjust_length(runits, rhs.x, sunits).0,
1404 adjust_length(runits, rhs.y, sunits).0,
1405 adjust_length(runits, rhs.z, sunits).0,
1406 )
1407 } else {
1408 (rhs.x, rhs.y, rhs.z)
1409 };
1410 Point3d {
1411 x: self.x - x,
1412 y: self.y - y,
1413 z: self.z - z,
1414 units: self.units,
1415 }
1416 }
1417}
1418
1419impl SubAssign for Point3d {
1420 fn sub_assign(&mut self, rhs: Self) {
1421 *self = *self - rhs
1422 }
1423}
1424
1425impl Mul<f64> for Point3d {
1426 type Output = Point3d;
1427
1428 fn mul(self, rhs: f64) -> Self::Output {
1429 Point3d {
1430 x: self.x * rhs,
1431 y: self.y * rhs,
1432 z: self.z * rhs,
1433 units: self.units,
1434 }
1435 }
1436}
1437
1438#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1440#[ts(export)]
1441#[serde(rename_all = "camelCase")]
1442pub struct BasePath {
1443 #[ts(type = "[number, number]")]
1445 pub from: [f64; 2],
1446 #[ts(type = "[number, number]")]
1448 pub to: [f64; 2],
1449 pub units: UnitLength,
1450 pub tag: Option<TagNode>,
1452 #[serde(rename = "__geoMeta")]
1454 pub geo_meta: GeoMeta,
1455}
1456
1457impl BasePath {
1458 pub fn get_to(&self) -> [TyF64; 2] {
1459 let ty: NumericType = self.units.into();
1460 [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1461 }
1462
1463 pub fn get_from(&self) -> [TyF64; 2] {
1464 let ty: NumericType = self.units.into();
1465 [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1466 }
1467}
1468
1469#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1471#[ts(export)]
1472#[serde(rename_all = "camelCase")]
1473pub struct GeoMeta {
1474 pub id: uuid::Uuid,
1476 #[serde(flatten)]
1478 pub metadata: Metadata,
1479}
1480
1481#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1483#[ts(export)]
1484#[serde(tag = "type")]
1485pub enum Path {
1486 ToPoint {
1488 #[serde(flatten)]
1489 base: BasePath,
1490 },
1491 TangentialArcTo {
1493 #[serde(flatten)]
1494 base: BasePath,
1495 #[ts(type = "[number, number]")]
1497 center: [f64; 2],
1498 ccw: bool,
1500 },
1501 TangentialArc {
1503 #[serde(flatten)]
1504 base: BasePath,
1505 #[ts(type = "[number, number]")]
1507 center: [f64; 2],
1508 ccw: bool,
1510 },
1511 Circle {
1514 #[serde(flatten)]
1515 base: BasePath,
1516 #[ts(type = "[number, number]")]
1518 center: [f64; 2],
1519 radius: f64,
1521 ccw: bool,
1524 },
1525 CircleThreePoint {
1526 #[serde(flatten)]
1527 base: BasePath,
1528 #[ts(type = "[number, number]")]
1530 p1: [f64; 2],
1531 #[ts(type = "[number, number]")]
1533 p2: [f64; 2],
1534 #[ts(type = "[number, number]")]
1536 p3: [f64; 2],
1537 },
1538 ArcThreePoint {
1539 #[serde(flatten)]
1540 base: BasePath,
1541 #[ts(type = "[number, number]")]
1543 p1: [f64; 2],
1544 #[ts(type = "[number, number]")]
1546 p2: [f64; 2],
1547 #[ts(type = "[number, number]")]
1549 p3: [f64; 2],
1550 },
1551 Horizontal {
1553 #[serde(flatten)]
1554 base: BasePath,
1555 x: f64,
1557 },
1558 AngledLineTo {
1560 #[serde(flatten)]
1561 base: BasePath,
1562 x: Option<f64>,
1564 y: Option<f64>,
1566 },
1567 Base {
1569 #[serde(flatten)]
1570 base: BasePath,
1571 },
1572 Arc {
1574 #[serde(flatten)]
1575 base: BasePath,
1576 center: [f64; 2],
1578 radius: f64,
1580 ccw: bool,
1582 },
1583 Ellipse {
1584 #[serde(flatten)]
1585 base: BasePath,
1586 center: [f64; 2],
1587 major_axis: [f64; 2],
1588 minor_radius: f64,
1589 ccw: bool,
1590 },
1591 Conic {
1593 #[serde(flatten)]
1594 base: BasePath,
1595 },
1596 Bezier {
1598 #[serde(flatten)]
1599 base: BasePath,
1600 #[ts(type = "[number, number]")]
1602 control1: [f64; 2],
1603 #[ts(type = "[number, number]")]
1605 control2: [f64; 2],
1606 },
1607}
1608
1609impl Path {
1610 pub fn get_id(&self) -> uuid::Uuid {
1611 match self {
1612 Path::ToPoint { base } => base.geo_meta.id,
1613 Path::Horizontal { base, .. } => base.geo_meta.id,
1614 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1615 Path::Base { base } => base.geo_meta.id,
1616 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1617 Path::TangentialArc { base, .. } => base.geo_meta.id,
1618 Path::Circle { base, .. } => base.geo_meta.id,
1619 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1620 Path::Arc { base, .. } => base.geo_meta.id,
1621 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1622 Path::Ellipse { base, .. } => base.geo_meta.id,
1623 Path::Conic { base, .. } => base.geo_meta.id,
1624 Path::Bezier { base, .. } => base.geo_meta.id,
1625 }
1626 }
1627
1628 pub fn set_id(&mut self, id: uuid::Uuid) {
1629 match self {
1630 Path::ToPoint { base } => base.geo_meta.id = id,
1631 Path::Horizontal { base, .. } => base.geo_meta.id = id,
1632 Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1633 Path::Base { base } => base.geo_meta.id = id,
1634 Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1635 Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1636 Path::Circle { base, .. } => base.geo_meta.id = id,
1637 Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1638 Path::Arc { base, .. } => base.geo_meta.id = id,
1639 Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1640 Path::Ellipse { base, .. } => base.geo_meta.id = id,
1641 Path::Conic { base, .. } => base.geo_meta.id = id,
1642 Path::Bezier { base, .. } => base.geo_meta.id = id,
1643 }
1644 }
1645
1646 pub fn get_tag(&self) -> Option<TagNode> {
1647 match self {
1648 Path::ToPoint { base } => base.tag.clone(),
1649 Path::Horizontal { base, .. } => base.tag.clone(),
1650 Path::AngledLineTo { base, .. } => base.tag.clone(),
1651 Path::Base { base } => base.tag.clone(),
1652 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1653 Path::TangentialArc { base, .. } => base.tag.clone(),
1654 Path::Circle { base, .. } => base.tag.clone(),
1655 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1656 Path::Arc { base, .. } => base.tag.clone(),
1657 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1658 Path::Ellipse { base, .. } => base.tag.clone(),
1659 Path::Conic { base, .. } => base.tag.clone(),
1660 Path::Bezier { base, .. } => base.tag.clone(),
1661 }
1662 }
1663
1664 pub fn get_base(&self) -> &BasePath {
1665 match self {
1666 Path::ToPoint { base } => base,
1667 Path::Horizontal { base, .. } => base,
1668 Path::AngledLineTo { base, .. } => base,
1669 Path::Base { base } => base,
1670 Path::TangentialArcTo { base, .. } => base,
1671 Path::TangentialArc { base, .. } => base,
1672 Path::Circle { base, .. } => base,
1673 Path::CircleThreePoint { base, .. } => base,
1674 Path::Arc { base, .. } => base,
1675 Path::ArcThreePoint { base, .. } => base,
1676 Path::Ellipse { base, .. } => base,
1677 Path::Conic { base, .. } => base,
1678 Path::Bezier { base, .. } => base,
1679 }
1680 }
1681
1682 pub fn get_from(&self) -> [TyF64; 2] {
1684 let p = &self.get_base().from;
1685 let ty: NumericType = self.get_base().units.into();
1686 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1687 }
1688
1689 pub fn get_to(&self) -> [TyF64; 2] {
1691 let p = &self.get_base().to;
1692 let ty: NumericType = self.get_base().units.into();
1693 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1694 }
1695
1696 pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1698 let p = &self.get_base().from;
1699 let ty: NumericType = self.get_base().units.into();
1700 (*p, ty)
1701 }
1702
1703 pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1705 let p = &self.get_base().to;
1706 let ty: NumericType = self.get_base().units.into();
1707 (*p, ty)
1708 }
1709
1710 pub fn length(&self) -> Option<TyF64> {
1713 let n = match self {
1714 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1715 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1716 }
1717 Self::TangentialArc {
1718 base: _,
1719 center,
1720 ccw: _,
1721 }
1722 | Self::TangentialArcTo {
1723 base: _,
1724 center,
1725 ccw: _,
1726 } => {
1727 let radius = linear_distance(&self.get_base().from, center);
1730 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
1731 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1733 }
1734 Self::Circle { radius, .. } => Some(TAU * radius),
1735 Self::CircleThreePoint { .. } => {
1736 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1737 self.get_base().from,
1738 self.get_base().to,
1739 self.get_base().to,
1740 ]);
1741 let radius = linear_distance(
1742 &[circle_center.center[0], circle_center.center[1]],
1743 &self.get_base().from,
1744 );
1745 Some(TAU * radius)
1746 }
1747 Self::Arc { .. } => {
1748 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1750 }
1751 Self::ArcThreePoint { .. } => {
1752 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1754 }
1755 Self::Ellipse { .. } => {
1756 None
1758 }
1759 Self::Conic { .. } => {
1760 None
1762 }
1763 Self::Bezier { .. } => {
1764 None
1766 }
1767 };
1768 n.map(|n| TyF64::new(n, self.get_base().units.into()))
1769 }
1770
1771 pub fn get_base_mut(&mut self) -> &mut BasePath {
1772 match self {
1773 Path::ToPoint { base } => base,
1774 Path::Horizontal { base, .. } => base,
1775 Path::AngledLineTo { base, .. } => base,
1776 Path::Base { base } => base,
1777 Path::TangentialArcTo { base, .. } => base,
1778 Path::TangentialArc { base, .. } => base,
1779 Path::Circle { base, .. } => base,
1780 Path::CircleThreePoint { base, .. } => base,
1781 Path::Arc { base, .. } => base,
1782 Path::ArcThreePoint { base, .. } => base,
1783 Path::Ellipse { base, .. } => base,
1784 Path::Conic { base, .. } => base,
1785 Path::Bezier { base, .. } => base,
1786 }
1787 }
1788
1789 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1790 match self {
1791 Path::TangentialArc { center, ccw, .. }
1792 | Path::TangentialArcTo { center, ccw, .. }
1793 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1794 center: *center,
1795 ccw: *ccw,
1796 },
1797 Path::ArcThreePoint { p1, p2, p3, .. } => {
1798 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1799 GetTangentialInfoFromPathsResult::Arc {
1800 center: circle.center,
1801 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
1802 }
1803 }
1804 Path::Circle {
1805 center, ccw, radius, ..
1806 } => GetTangentialInfoFromPathsResult::Circle {
1807 center: *center,
1808 ccw: *ccw,
1809 radius: *radius,
1810 },
1811 Path::CircleThreePoint { p1, p2, p3, .. } => {
1812 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1813 let center_point = [circle.center[0], circle.center[1]];
1814 GetTangentialInfoFromPathsResult::Circle {
1815 center: center_point,
1816 ccw: true,
1818 radius: circle.radius,
1819 }
1820 }
1821 Path::Ellipse {
1823 center,
1824 major_axis,
1825 minor_radius,
1826 ccw,
1827 ..
1828 } => GetTangentialInfoFromPathsResult::Ellipse {
1829 center: *center,
1830 major_axis: *major_axis,
1831 _minor_radius: *minor_radius,
1832 ccw: *ccw,
1833 },
1834 Path::Conic { .. }
1835 | Path::ToPoint { .. }
1836 | Path::Horizontal { .. }
1837 | Path::AngledLineTo { .. }
1838 | Path::Base { .. }
1839 | Path::Bezier { .. } => {
1840 let base = self.get_base();
1841 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1842 }
1843 }
1844 }
1845
1846 pub(crate) fn is_straight_line(&self) -> bool {
1848 matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
1849 }
1850}
1851
1852#[rustfmt::skip]
1854fn linear_distance(
1855 [x0, y0]: &[f64; 2],
1856 [x1, y1]: &[f64; 2]
1857) -> f64 {
1858 let y_sq = (y1 - y0).powi(2);
1859 let x_sq = (x1 - x0).powi(2);
1860 (y_sq + x_sq).sqrt()
1861}
1862
1863#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1865#[ts(export)]
1866#[serde(tag = "type", rename_all = "camelCase")]
1867pub enum ExtrudeSurface {
1868 ExtrudePlane(ExtrudePlane),
1870 ExtrudeArc(ExtrudeArc),
1871 Chamfer(ChamferSurface),
1872 Fillet(FilletSurface),
1873}
1874
1875#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1877#[ts(export)]
1878#[serde(rename_all = "camelCase")]
1879pub struct ChamferSurface {
1880 pub face_id: uuid::Uuid,
1882 pub tag: Option<Node<TagDeclarator>>,
1884 #[serde(flatten)]
1886 pub geo_meta: GeoMeta,
1887}
1888
1889#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1891#[ts(export)]
1892#[serde(rename_all = "camelCase")]
1893pub struct FilletSurface {
1894 pub face_id: uuid::Uuid,
1896 pub tag: Option<Node<TagDeclarator>>,
1898 #[serde(flatten)]
1900 pub geo_meta: GeoMeta,
1901}
1902
1903#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1905#[ts(export)]
1906#[serde(rename_all = "camelCase")]
1907pub struct ExtrudePlane {
1908 pub face_id: uuid::Uuid,
1910 pub tag: Option<Node<TagDeclarator>>,
1912 #[serde(flatten)]
1914 pub geo_meta: GeoMeta,
1915}
1916
1917#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1919#[ts(export)]
1920#[serde(rename_all = "camelCase")]
1921pub struct ExtrudeArc {
1922 pub face_id: uuid::Uuid,
1924 pub tag: Option<Node<TagDeclarator>>,
1926 #[serde(flatten)]
1928 pub geo_meta: GeoMeta,
1929}
1930
1931impl ExtrudeSurface {
1932 pub fn get_id(&self) -> uuid::Uuid {
1933 match self {
1934 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
1935 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
1936 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
1937 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
1938 }
1939 }
1940
1941 pub fn face_id(&self) -> uuid::Uuid {
1942 match self {
1943 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id,
1944 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id,
1945 ExtrudeSurface::Fillet(f) => f.face_id,
1946 ExtrudeSurface::Chamfer(c) => c.face_id,
1947 }
1948 }
1949
1950 pub fn set_face_id(&mut self, face_id: uuid::Uuid) {
1951 match self {
1952 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id = face_id,
1953 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id = face_id,
1954 ExtrudeSurface::Fillet(f) => f.face_id = face_id,
1955 ExtrudeSurface::Chamfer(c) => c.face_id = face_id,
1956 }
1957 }
1958
1959 pub fn set_surface_tag(&mut self, tag: &TagNode) {
1960 match self {
1961 ExtrudeSurface::ExtrudePlane(extrude_plane) => extrude_plane.tag = Some(tag.clone()),
1962 ExtrudeSurface::ExtrudeArc(extrude_arc) => extrude_arc.tag = Some(tag.clone()),
1963 ExtrudeSurface::Chamfer(chamfer) => chamfer.tag = Some(tag.clone()),
1964 ExtrudeSurface::Fillet(fillet) => fillet.tag = Some(tag.clone()),
1965 }
1966 }
1967
1968 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
1969 match self {
1970 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
1971 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
1972 ExtrudeSurface::Fillet(f) => f.tag.clone(),
1973 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
1974 }
1975 }
1976}
1977
1978#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ts_rs::TS)]
1979pub struct SketchVarId(pub usize);
1980
1981impl SketchVarId {
1982 pub fn to_constraint_id(self, range: SourceRange) -> Result<ezpz::Id, KclError> {
1983 self.0.try_into().map_err(|_| {
1984 KclError::new_type(KclErrorDetails::new(
1985 "Cannot convert to constraint ID since the sketch variable ID is too large".to_owned(),
1986 vec![range],
1987 ))
1988 })
1989 }
1990}
1991
1992#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1993#[ts(export_to = "Geometry.ts")]
1994#[serde(rename_all = "camelCase")]
1995pub struct SketchVar {
1996 pub id: SketchVarId,
1997 pub initial_value: f64,
1998 pub ty: NumericType,
1999 #[serde(skip)]
2000 pub meta: Vec<Metadata>,
2001}
2002
2003impl SketchVar {
2004 pub fn initial_value_to_solver_units(
2005 &self,
2006 exec_state: &mut ExecState,
2007 source_range: SourceRange,
2008 description: &str,
2009 ) -> Result<TyF64, KclError> {
2010 let x_initial_value = KclValue::Number {
2011 value: self.initial_value,
2012 ty: self.ty,
2013 meta: vec![source_range.into()],
2014 };
2015 let normalized_value =
2016 normalize_to_solver_distance_unit(&x_initial_value, source_range, exec_state, description)?;
2017 normalized_value.as_ty_f64().ok_or_else(|| {
2018 let message = format!(
2019 "Expected number after coercion, but found {}",
2020 normalized_value.human_friendly_type()
2021 );
2022 debug_assert!(false, "{}", &message);
2023 KclError::new_internal(KclErrorDetails::new(message, vec![source_range]))
2024 })
2025 }
2026}
2027
2028#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2029#[ts(export_to = "Geometry.ts")]
2030#[serde(tag = "type")]
2031pub enum UnsolvedExpr {
2032 Known(TyF64),
2033 Unknown(SketchVarId),
2034}
2035
2036impl UnsolvedExpr {
2037 pub fn var(&self) -> Option<SketchVarId> {
2038 match self {
2039 UnsolvedExpr::Known(_) => None,
2040 UnsolvedExpr::Unknown(id) => Some(*id),
2041 }
2042 }
2043}
2044
2045pub type UnsolvedPoint2dExpr = [UnsolvedExpr; 2];
2046
2047#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2048#[ts(export_to = "Geometry.ts")]
2049#[serde(rename_all = "camelCase")]
2050pub struct ConstrainablePoint2d {
2051 pub vars: crate::front::Point2d<SketchVarId>,
2052 pub object_id: ObjectId,
2053}
2054
2055#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2056#[ts(export_to = "Geometry.ts")]
2057#[serde(rename_all = "camelCase")]
2058pub struct ConstrainableLine2d {
2059 pub vars: [crate::front::Point2d<SketchVarId>; 2],
2060 pub object_id: ObjectId,
2061}
2062
2063#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2064#[ts(export_to = "Geometry.ts")]
2065#[serde(rename_all = "camelCase")]
2066pub struct UnsolvedSegment {
2067 pub id: Uuid,
2069 pub object_id: ObjectId,
2070 pub kind: UnsolvedSegmentKind,
2071 #[serde(skip_serializing_if = "Option::is_none")]
2072 pub tag: Option<TagIdentifier>,
2073 #[serde(skip)]
2074 pub meta: Vec<Metadata>,
2075}
2076
2077#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2078#[ts(export_to = "Geometry.ts")]
2079#[serde(rename_all = "camelCase")]
2080pub enum UnsolvedSegmentKind {
2081 Point {
2082 position: UnsolvedPoint2dExpr,
2083 ctor: Box<PointCtor>,
2084 },
2085 Line {
2086 start: UnsolvedPoint2dExpr,
2087 end: UnsolvedPoint2dExpr,
2088 ctor: Box<LineCtor>,
2089 start_object_id: ObjectId,
2090 end_object_id: ObjectId,
2091 construction: bool,
2092 },
2093 Arc {
2094 start: UnsolvedPoint2dExpr,
2095 end: UnsolvedPoint2dExpr,
2096 center: UnsolvedPoint2dExpr,
2097 ctor: Box<ArcCtor>,
2098 start_object_id: ObjectId,
2099 end_object_id: ObjectId,
2100 center_object_id: ObjectId,
2101 construction: bool,
2102 },
2103 Circle {
2104 start: UnsolvedPoint2dExpr,
2105 center: UnsolvedPoint2dExpr,
2106 ctor: Box<CircleCtor>,
2107 start_object_id: ObjectId,
2108 center_object_id: ObjectId,
2109 construction: bool,
2110 },
2111}
2112
2113#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2114#[ts(export_to = "Geometry.ts")]
2115#[serde(rename_all = "camelCase")]
2116pub struct Segment {
2117 pub id: Uuid,
2119 pub object_id: ObjectId,
2120 pub kind: SegmentKind,
2121 pub surface: SketchSurface,
2122 pub sketch_id: Uuid,
2124 #[serde(skip)]
2125 pub sketch: Option<Sketch>,
2126 #[serde(skip_serializing_if = "Option::is_none")]
2127 pub tag: Option<TagIdentifier>,
2128 #[serde(skip)]
2129 pub meta: Vec<Metadata>,
2130}
2131
2132impl Segment {
2133 pub fn is_construction(&self) -> bool {
2134 match &self.kind {
2135 SegmentKind::Point { .. } => true,
2136 SegmentKind::Line { construction, .. } => *construction,
2137 SegmentKind::Arc { construction, .. } => *construction,
2138 SegmentKind::Circle { construction, .. } => *construction,
2139 }
2140 }
2141}
2142
2143#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2144#[ts(export_to = "Geometry.ts")]
2145#[serde(rename_all = "camelCase")]
2146pub enum SegmentKind {
2147 Point {
2148 position: [TyF64; 2],
2149 ctor: Box<PointCtor>,
2150 #[serde(skip_serializing_if = "Option::is_none")]
2151 freedom: Option<Freedom>,
2152 },
2153 Line {
2154 start: [TyF64; 2],
2155 end: [TyF64; 2],
2156 ctor: Box<LineCtor>,
2157 start_object_id: ObjectId,
2158 end_object_id: ObjectId,
2159 #[serde(skip_serializing_if = "Option::is_none")]
2160 start_freedom: Option<Freedom>,
2161 #[serde(skip_serializing_if = "Option::is_none")]
2162 end_freedom: Option<Freedom>,
2163 construction: bool,
2164 },
2165 Arc {
2166 start: [TyF64; 2],
2167 end: [TyF64; 2],
2168 center: [TyF64; 2],
2169 ctor: Box<ArcCtor>,
2170 start_object_id: ObjectId,
2171 end_object_id: ObjectId,
2172 center_object_id: ObjectId,
2173 #[serde(skip_serializing_if = "Option::is_none")]
2174 start_freedom: Option<Freedom>,
2175 #[serde(skip_serializing_if = "Option::is_none")]
2176 end_freedom: Option<Freedom>,
2177 #[serde(skip_serializing_if = "Option::is_none")]
2178 center_freedom: Option<Freedom>,
2179 construction: bool,
2180 },
2181 Circle {
2182 start: [TyF64; 2],
2183 center: [TyF64; 2],
2184 ctor: Box<CircleCtor>,
2185 start_object_id: ObjectId,
2186 center_object_id: ObjectId,
2187 #[serde(skip_serializing_if = "Option::is_none")]
2188 start_freedom: Option<Freedom>,
2189 #[serde(skip_serializing_if = "Option::is_none")]
2190 center_freedom: Option<Freedom>,
2191 construction: bool,
2192 },
2193}
2194
2195#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2196#[ts(export_to = "Geometry.ts")]
2197#[serde(rename_all = "camelCase")]
2198pub struct AbstractSegment {
2199 pub repr: SegmentRepr,
2200 #[serde(skip)]
2201 pub meta: Vec<Metadata>,
2202}
2203
2204#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2205pub enum SegmentRepr {
2206 Unsolved { segment: Box<UnsolvedSegment> },
2207 Solved { segment: Box<Segment> },
2208}
2209
2210#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2211#[ts(export_to = "Geometry.ts")]
2212#[serde(rename_all = "camelCase")]
2213pub struct SketchConstraint {
2214 pub kind: SketchConstraintKind,
2215 #[serde(skip)]
2216 pub meta: Vec<Metadata>,
2217}
2218
2219#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2220#[ts(export_to = "Geometry.ts")]
2221#[serde(rename_all = "camelCase")]
2222pub enum SketchConstraintKind {
2223 Angle {
2224 line0: ConstrainableLine2d,
2225 line1: ConstrainableLine2d,
2226 },
2227 Distance {
2228 points: [ConstrainablePoint2d; 2],
2229 },
2230 Radius {
2231 points: [ConstrainablePoint2d; 2],
2232 },
2233 Diameter {
2234 points: [ConstrainablePoint2d; 2],
2235 },
2236 HorizontalDistance {
2237 points: [ConstrainablePoint2d; 2],
2238 },
2239 VerticalDistance {
2240 points: [ConstrainablePoint2d; 2],
2241 },
2242}
2243
2244impl SketchConstraintKind {
2245 pub fn name(&self) -> &'static str {
2246 match self {
2247 SketchConstraintKind::Angle { .. } => "angle",
2248 SketchConstraintKind::Distance { .. } => "distance",
2249 SketchConstraintKind::Radius { .. } => "radius",
2250 SketchConstraintKind::Diameter { .. } => "diameter",
2251 SketchConstraintKind::HorizontalDistance { .. } => "horizontalDistance",
2252 SketchConstraintKind::VerticalDistance { .. } => "verticalDistance",
2253 }
2254 }
2255}