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