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