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