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