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