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