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