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 pub creator_sketch_is_closed: Option<ProfileClosed>,
811 #[serde(default, skip_serializing_if = "Vec::is_empty")]
813 pub edge_cut_ids: Vec<Uuid>,
814}
815
816impl FaceParentSolid {
817 pub(crate) fn sketch_or_solid_id(&self) -> Uuid {
818 self.creator_sketch_id.unwrap_or(self.solid_id)
819 }
820}
821
822#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
825#[ts(export)]
826#[serde(rename_all = "camelCase")]
827pub struct BoundedEdge {
828 pub face_id: uuid::Uuid,
830 #[serde(skip_serializing_if = "Option::is_none")]
832 pub edge_id: Option<uuid::Uuid>,
833 #[serde(skip_serializing_if = "Option::is_none")]
835 pub edge_specifier: Option<UnresolvedEdgeSpecifier>,
836 pub lower_bound: f32,
839 pub upper_bound: f32,
842}
843
844#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS, FromStr, Display)]
846#[ts(export)]
847#[display(style = "camelCase")]
848pub enum PlaneKind {
849 #[serde(rename = "XY", alias = "xy")]
850 #[display("XY")]
851 XY,
852 #[serde(rename = "XZ", alias = "xz")]
853 #[display("XZ")]
854 XZ,
855 #[serde(rename = "YZ", alias = "yz")]
856 #[display("YZ")]
857 YZ,
858 #[display("Custom")]
860 Custom,
861}
862
863#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
864#[ts(export)]
865#[serde(tag = "type", rename_all = "camelCase")]
866pub struct Sketch {
867 pub id: uuid::Uuid,
869 pub paths: Vec<Path>,
873 #[serde(default, skip_serializing_if = "Vec::is_empty")]
875 pub inner_paths: Vec<Path>,
876 pub on: SketchSurface,
878 pub start: BasePath,
880 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
882 pub tags: IndexMap<String, TagIdentifier>,
883 pub artifact_id: ArtifactId,
886 #[ts(skip)]
887 pub original_id: uuid::Uuid,
888 #[serde(skip_serializing_if = "Option::is_none")]
893 #[ts(skip)]
894 pub origin_sketch_id: Option<uuid::Uuid>,
895 #[serde(skip)]
897 pub mirror: Option<uuid::Uuid>,
898 #[serde(skip)]
900 pub clone: Option<uuid::Uuid>,
901 #[serde(skip)]
903 #[ts(skip)]
904 pub synthetic_jump_path_ids: Vec<uuid::Uuid>,
905 pub units: UnitLength,
906 #[serde(skip)]
908 pub meta: Vec<Metadata>,
909 #[serde(
912 default = "ProfileClosed::explicitly",
913 skip_serializing_if = "ProfileClosed::is_explicitly"
914 )]
915 pub is_closed: ProfileClosed,
916}
917
918impl ProfileClosed {
919 #[expect(dead_code, reason = "it's not actually dead, it's called by serde")]
920 fn explicitly() -> Self {
921 Self::Explicitly
922 }
923
924 fn is_explicitly(&self) -> bool {
925 matches!(self, ProfileClosed::Explicitly)
926 }
927}
928
929#[derive(Debug, Serialize, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd, ts_rs::TS)]
931#[serde(rename_all = "camelCase")]
932pub enum ProfileClosed {
933 No,
935 Maybe,
937 Implicitly,
939 Explicitly,
941}
942
943impl Sketch {
944 pub(crate) fn build_sketch_mode_cmds(
947 &self,
948 exec_state: &mut ExecState,
949 inner_cmd: ModelingCmdReq,
950 ) -> Vec<ModelingCmdReq> {
951 vec![
952 ModelingCmdReq {
955 cmd: ModelingCmd::from(
956 mcmd::EnableSketchMode::builder()
957 .animated(false)
958 .ortho(false)
959 .entity_id(self.on.id())
960 .adjust_camera(false)
961 .maybe_planar_normal(if let SketchSurface::Plane(plane) = &self.on {
962 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
964 Some(normal.into())
965 } else {
966 None
967 })
968 .build(),
969 ),
970 cmd_id: exec_state.next_uuid().into(),
971 },
972 inner_cmd,
973 ModelingCmdReq {
974 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::builder().build()),
975 cmd_id: exec_state.next_uuid().into(),
976 },
977 ]
978 }
979}
980
981#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
983#[ts(export)]
984#[serde(tag = "type", rename_all = "camelCase")]
985pub enum SketchSurface {
986 Plane(Box<Plane>),
987 Face(Box<Face>),
988}
989
990impl SketchSurface {
991 pub(crate) fn id(&self) -> uuid::Uuid {
992 match self {
993 SketchSurface::Plane(plane) => plane.id,
994 SketchSurface::Face(face) => face.id,
995 }
996 }
997 pub(crate) fn x_axis(&self) -> Point3d {
998 match self {
999 SketchSurface::Plane(plane) => plane.info.x_axis,
1000 SketchSurface::Face(face) => face.x_axis,
1001 }
1002 }
1003 pub(crate) fn y_axis(&self) -> Point3d {
1004 match self {
1005 SketchSurface::Plane(plane) => plane.info.y_axis,
1006 SketchSurface::Face(face) => face.y_axis,
1007 }
1008 }
1009
1010 pub(crate) fn object_id(&self) -> Option<ObjectId> {
1011 match self {
1012 SketchSurface::Plane(plane) => plane.object_id,
1013 SketchSurface::Face(face) => Some(face.object_id),
1014 }
1015 }
1016
1017 pub(crate) fn set_object_id(&mut self, object_id: ObjectId) {
1018 match self {
1019 SketchSurface::Plane(plane) => plane.object_id = Some(object_id),
1020 SketchSurface::Face(face) => face.object_id = object_id,
1021 }
1022 }
1023}
1024
1025#[derive(Debug, Clone, PartialEq)]
1027pub enum Extrudable {
1028 Sketch(Box<Sketch>),
1030 FaceTag(FaceTag),
1032 Face(Box<Face>),
1034}
1035
1036impl Extrudable {
1037 pub async fn id_to_extrude(
1039 &self,
1040 exec_state: &mut ExecState,
1041 args: &Args,
1042 must_be_planar: bool,
1043 ) -> Result<uuid::Uuid, KclError> {
1044 match self {
1045 Extrudable::Sketch(sketch) => Ok(sketch.id),
1046 Extrudable::FaceTag(face_tag) => face_tag.get_face_id_from_tag(exec_state, args, must_be_planar).await,
1047 Extrudable::Face(face) => Ok(face.id),
1048 }
1049 }
1050
1051 pub fn as_sketch(&self) -> Option<Sketch> {
1052 match self {
1053 Extrudable::Sketch(sketch) => Some((**sketch).clone()),
1054 Extrudable::FaceTag(face) => match face.geometry() {
1055 Some(Geometry::Sketch(sketch)) => Some(sketch),
1056 Some(Geometry::Solid(solid)) => solid.sketch().cloned(),
1057 None => None,
1058 },
1059 Extrudable::Face(_) => None,
1060 }
1061 }
1062
1063 pub fn is_closed(&self) -> ProfileClosed {
1064 match self {
1065 Extrudable::Sketch(sketch) => sketch.is_closed,
1066 Extrudable::FaceTag(face_tag) => match face_tag.geometry() {
1067 Some(Geometry::Sketch(sketch)) => sketch.is_closed,
1068 Some(Geometry::Solid(solid)) => solid
1069 .sketch()
1070 .map(|sketch| sketch.is_closed)
1071 .unwrap_or(ProfileClosed::Maybe),
1072 _ => ProfileClosed::Maybe,
1073 },
1074 Extrudable::Face(face) => match face.parent_solid.creator_sketch_is_closed {
1075 Some(is_closed) => is_closed,
1076 None => ProfileClosed::Maybe,
1077 },
1078 }
1079 }
1080}
1081
1082impl From<Sketch> for Extrudable {
1083 fn from(value: Sketch) -> Self {
1084 Extrudable::Sketch(Box::new(value))
1085 }
1086}
1087
1088#[derive(Debug, Clone)]
1089pub(crate) enum GetTangentialInfoFromPathsResult {
1090 PreviousPoint([f64; 2]),
1091 Arc {
1092 center: [f64; 2],
1093 ccw: bool,
1094 },
1095 Circle {
1096 center: [f64; 2],
1097 ccw: bool,
1098 radius: f64,
1099 },
1100 Ellipse {
1101 center: [f64; 2],
1102 ccw: bool,
1103 major_axis: [f64; 2],
1104 _minor_radius: f64,
1105 },
1106}
1107
1108impl GetTangentialInfoFromPathsResult {
1109 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
1110 match self {
1111 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
1112 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
1113 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
1114 }
1115 GetTangentialInfoFromPathsResult::Circle {
1118 center, radius, ccw, ..
1119 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
1120 GetTangentialInfoFromPathsResult::Ellipse {
1121 center,
1122 major_axis,
1123 ccw,
1124 ..
1125 } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
1126 }
1127 }
1128}
1129
1130impl Sketch {
1131 pub(crate) fn add_tag(
1132 &mut self,
1133 tag: NodeRef<'_, TagDeclarator>,
1134 current_path: &Path,
1135 exec_state: &ExecState,
1136 surface: Option<&ExtrudeSurface>,
1137 ) {
1138 let mut tag_identifier: TagIdentifier = tag.into();
1139 let base = current_path.get_base();
1140 let mut sketch_copy = self.clone();
1141 sketch_copy.tags.clear();
1142 tag_identifier.info.push((
1143 exec_state.stack().current_epoch(),
1144 TagEngineInfo {
1145 id: base.geo_meta.id,
1146 geometry: Geometry::Sketch(sketch_copy),
1147 path: Some(current_path.clone()),
1148 surface: surface.cloned(),
1149 },
1150 ));
1151
1152 self.tags.insert(tag.name.to_string(), tag_identifier);
1153 }
1154
1155 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
1156 for t in tags {
1157 match self.tags.get_mut(&t.value) {
1158 Some(id) => {
1159 id.merge_info(t);
1160 }
1161 None => {
1162 self.tags.insert(t.value.clone(), t.clone());
1163 }
1164 }
1165 }
1166 }
1167
1168 pub(crate) fn latest_path(&self) -> Option<&Path> {
1170 self.paths.last()
1171 }
1172
1173 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
1177 let Some(path) = self.latest_path() else {
1178 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
1179 };
1180
1181 let to = path.get_base().to;
1182 Ok(Point2d::new(to[0], to[1], path.get_base().units))
1183 }
1184
1185 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
1186 let Some(path) = self.latest_path() else {
1187 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
1188 };
1189 path.get_tangential_info()
1190 }
1191}
1192
1193#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1194#[ts(export)]
1195#[serde(tag = "type", rename_all = "camelCase")]
1196pub struct Solid {
1197 pub id: uuid::Uuid,
1199 #[serde(skip)]
1201 #[ts(skip)]
1202 pub value_id: uuid::Uuid,
1203 pub artifact_id: ArtifactId,
1205 pub value: Vec<ExtrudeSurface>,
1207 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
1210 pub faces: IndexMap<String, TagIdentifier>,
1211 #[serde(rename = "sketch")]
1213 pub creator: SolidCreator,
1214 pub start_cap_id: Option<uuid::Uuid>,
1216 pub end_cap_id: Option<uuid::Uuid>,
1218 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1220 pub edge_cuts: Vec<EdgeCut>,
1221 #[serde(skip)]
1223 #[ts(skip)]
1224 pub pending_edge_cut_ids: Vec<uuid::Uuid>,
1225 pub units: UnitLength,
1227 pub sectional: bool,
1229 #[serde(skip)]
1231 pub meta: Vec<Metadata>,
1232}
1233
1234#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1235#[ts(export)]
1236pub struct CreatorFace {
1237 pub face_id: uuid::Uuid,
1239 pub solid_id: uuid::Uuid,
1241 pub sketch: Sketch,
1243}
1244
1245#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1247#[ts(export)]
1248#[serde(tag = "creatorType", rename_all = "camelCase")]
1249pub enum SolidCreator {
1250 Sketch(Sketch),
1252 Face(CreatorFace),
1254 Procedural,
1256}
1257
1258impl Solid {
1259 pub fn sketch(&self) -> Option<&Sketch> {
1260 match &self.creator {
1261 SolidCreator::Sketch(sketch) => Some(sketch),
1262 SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1263 SolidCreator::Procedural => None,
1264 }
1265 }
1266
1267 pub fn sketch_mut(&mut self) -> Option<&mut Sketch> {
1268 match &mut self.creator {
1269 SolidCreator::Sketch(sketch) => Some(sketch),
1270 SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1271 SolidCreator::Procedural => None,
1272 }
1273 }
1274
1275 pub fn sketch_id(&self) -> Option<uuid::Uuid> {
1276 self.sketch().map(|sketch| sketch.id)
1277 }
1278
1279 pub fn original_id(&self) -> uuid::Uuid {
1280 self.sketch().map(|sketch| sketch.original_id).unwrap_or(self.id)
1281 }
1282
1283 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
1284 self.edge_cuts
1285 .iter()
1286 .map(|foc| foc.id())
1287 .chain(self.pending_edge_cut_ids.iter().copied())
1288 }
1289}
1290
1291impl From<&Solid> for FaceParentSolid {
1292 fn from(solid: &Solid) -> Self {
1293 Self {
1294 solid_id: solid.id,
1295 creator_sketch_id: solid.sketch_id(),
1296 creator_sketch_is_closed: solid.sketch().map(|sketch| sketch.is_closed),
1297 edge_cut_ids: solid.get_all_edge_cut_ids().collect(),
1298 }
1299 }
1300}
1301
1302#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1304#[ts(export)]
1305#[serde(tag = "type", rename_all = "camelCase")]
1306pub enum EdgeCut {
1307 Fillet {
1309 id: uuid::Uuid,
1311 radius: TyF64,
1312 #[serde(rename = "edgeId")]
1314 edge_id: uuid::Uuid,
1315 tag: Box<Option<TagNode>>,
1316 },
1317 Chamfer {
1319 id: uuid::Uuid,
1321 length: TyF64,
1322 #[serde(rename = "edgeId")]
1324 edge_id: uuid::Uuid,
1325 tag: Box<Option<TagNode>>,
1326 },
1327}
1328
1329impl EdgeCut {
1330 pub fn id(&self) -> uuid::Uuid {
1331 match self {
1332 EdgeCut::Fillet { id, .. } => *id,
1333 EdgeCut::Chamfer { id, .. } => *id,
1334 }
1335 }
1336
1337 pub fn set_id(&mut self, id: uuid::Uuid) {
1338 match self {
1339 EdgeCut::Fillet { id: i, .. } => *i = id,
1340 EdgeCut::Chamfer { id: i, .. } => *i = id,
1341 }
1342 }
1343
1344 pub fn edge_id(&self) -> uuid::Uuid {
1345 match self {
1346 EdgeCut::Fillet { edge_id, .. } => *edge_id,
1347 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
1348 }
1349 }
1350
1351 pub fn set_edge_id(&mut self, id: uuid::Uuid) {
1352 match self {
1353 EdgeCut::Fillet { edge_id: i, .. } => *i = id,
1354 EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
1355 }
1356 }
1357
1358 pub fn tag(&self) -> Option<TagNode> {
1359 match self {
1360 EdgeCut::Fillet { tag, .. } => *tag.clone(),
1361 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
1362 }
1363 }
1364}
1365
1366#[derive(Debug, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
1367#[ts(export)]
1368pub struct Point2d {
1369 pub x: f64,
1370 pub y: f64,
1371 pub units: UnitLength,
1372}
1373
1374impl Point2d {
1375 pub const ZERO: Self = Self {
1376 x: 0.0,
1377 y: 0.0,
1378 units: UnitLength::Millimeters,
1379 };
1380
1381 pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
1382 Self { x, y, units }
1383 }
1384
1385 pub fn into_x(self) -> TyF64 {
1386 TyF64::new(self.x, self.units.into())
1387 }
1388
1389 pub fn into_y(self) -> TyF64 {
1390 TyF64::new(self.y, self.units.into())
1391 }
1392
1393 pub fn ignore_units(self) -> [f64; 2] {
1394 [self.x, self.y]
1395 }
1396}
1397
1398#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
1399#[ts(export)]
1400pub struct Point3d {
1401 pub x: f64,
1402 pub y: f64,
1403 pub z: f64,
1404 pub units: Option<UnitLength>,
1405}
1406
1407impl Point3d {
1408 pub const ZERO: Self = Self {
1409 x: 0.0,
1410 y: 0.0,
1411 z: 0.0,
1412 units: Some(UnitLength::Millimeters),
1413 };
1414
1415 pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
1416 Self { x, y, z, units }
1417 }
1418
1419 pub const fn is_zero(&self) -> bool {
1420 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
1421 }
1422
1423 pub fn axes_cross_product(&self, other: &Self) -> Self {
1428 Self {
1429 x: self.y * other.z - self.z * other.y,
1430 y: self.z * other.x - self.x * other.z,
1431 z: self.x * other.y - self.y * other.x,
1432 units: None,
1433 }
1434 }
1435
1436 pub fn canonicalize_signed_zero(&mut self) {
1438 if self.x == 0.0 {
1439 self.x = 0.0;
1440 }
1441 if self.y == 0.0 {
1442 self.y = 0.0;
1443 }
1444 if self.z == 0.0 {
1445 self.z = 0.0;
1446 }
1447 }
1448
1449 pub fn axes_dot_product(&self, other: &Self) -> f64 {
1454 let x = self.x * other.x;
1455 let y = self.y * other.y;
1456 let z = self.z * other.z;
1457 x + y + z
1458 }
1459
1460 pub fn normalize(&self) -> Self {
1461 let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1462 Point3d {
1463 x: self.x / len,
1464 y: self.y / len,
1465 z: self.z / len,
1466 units: None,
1467 }
1468 }
1469
1470 pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1471 let p = [self.x, self.y, self.z];
1472 let u = self.units;
1473 (p, u)
1474 }
1475
1476 pub(crate) fn negated(self) -> Self {
1477 Self {
1478 x: -self.x,
1479 y: -self.y,
1480 z: -self.z,
1481 units: self.units,
1482 }
1483 }
1484}
1485
1486impl From<[TyF64; 3]> for Point3d {
1487 fn from(p: [TyF64; 3]) -> Self {
1488 Self {
1489 x: p[0].n,
1490 y: p[1].n,
1491 z: p[2].n,
1492 units: p[0].ty.as_length(),
1493 }
1494 }
1495}
1496
1497impl From<Point3d> for Point3D {
1498 fn from(p: Point3d) -> Self {
1499 Self { x: p.x, y: p.y, z: p.z }
1500 }
1501}
1502
1503impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1504 fn from(p: Point3d) -> Self {
1505 if let Some(units) = p.units {
1506 Self {
1507 x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1508 y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1509 z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1510 }
1511 } else {
1512 Self {
1513 x: LengthUnit(p.x),
1514 y: LengthUnit(p.y),
1515 z: LengthUnit(p.z),
1516 }
1517 }
1518 }
1519}
1520
1521impl Add for Point3d {
1522 type Output = Point3d;
1523
1524 fn add(self, rhs: Self) -> Self::Output {
1525 Point3d {
1527 x: self.x + rhs.x,
1528 y: self.y + rhs.y,
1529 z: self.z + rhs.z,
1530 units: self.units,
1531 }
1532 }
1533}
1534
1535impl AddAssign for Point3d {
1536 fn add_assign(&mut self, rhs: Self) {
1537 *self = *self + rhs
1538 }
1539}
1540
1541impl Sub for Point3d {
1542 type Output = Point3d;
1543
1544 fn sub(self, rhs: Self) -> Self::Output {
1545 let (x, y, z) = if rhs.units != self.units
1546 && let Some(sunits) = self.units
1547 && let Some(runits) = rhs.units
1548 {
1549 (
1550 adjust_length(runits, rhs.x, sunits).0,
1551 adjust_length(runits, rhs.y, sunits).0,
1552 adjust_length(runits, rhs.z, sunits).0,
1553 )
1554 } else {
1555 (rhs.x, rhs.y, rhs.z)
1556 };
1557 Point3d {
1558 x: self.x - x,
1559 y: self.y - y,
1560 z: self.z - z,
1561 units: self.units,
1562 }
1563 }
1564}
1565
1566impl SubAssign for Point3d {
1567 fn sub_assign(&mut self, rhs: Self) {
1568 *self = *self - rhs
1569 }
1570}
1571
1572impl Mul<f64> for Point3d {
1573 type Output = Point3d;
1574
1575 fn mul(self, rhs: f64) -> Self::Output {
1576 Point3d {
1577 x: self.x * rhs,
1578 y: self.y * rhs,
1579 z: self.z * rhs,
1580 units: self.units,
1581 }
1582 }
1583}
1584
1585#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1587#[ts(export)]
1588#[serde(rename_all = "camelCase")]
1589pub struct BasePath {
1590 #[ts(type = "[number, number]")]
1592 pub from: [f64; 2],
1593 #[ts(type = "[number, number]")]
1595 pub to: [f64; 2],
1596 pub units: UnitLength,
1597 pub tag: Option<TagNode>,
1599 #[serde(rename = "__geoMeta")]
1601 pub geo_meta: GeoMeta,
1602}
1603
1604impl BasePath {
1605 pub fn get_to(&self) -> [TyF64; 2] {
1606 let ty: NumericType = self.units.into();
1607 [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1608 }
1609
1610 pub fn get_from(&self) -> [TyF64; 2] {
1611 let ty: NumericType = self.units.into();
1612 [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1613 }
1614}
1615
1616#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1618#[ts(export)]
1619#[serde(rename_all = "camelCase")]
1620pub struct GeoMeta {
1621 pub id: uuid::Uuid,
1623 #[serde(flatten)]
1625 pub metadata: Metadata,
1626}
1627
1628#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1630#[ts(export)]
1631#[serde(tag = "type")]
1632pub enum Path {
1633 ToPoint {
1635 #[serde(flatten)]
1636 base: BasePath,
1637 },
1638 TangentialArcTo {
1640 #[serde(flatten)]
1641 base: BasePath,
1642 #[ts(type = "[number, number]")]
1644 center: [f64; 2],
1645 ccw: bool,
1647 },
1648 TangentialArc {
1650 #[serde(flatten)]
1651 base: BasePath,
1652 #[ts(type = "[number, number]")]
1654 center: [f64; 2],
1655 ccw: bool,
1657 },
1658 Circle {
1661 #[serde(flatten)]
1662 base: BasePath,
1663 #[ts(type = "[number, number]")]
1665 center: [f64; 2],
1666 radius: f64,
1668 ccw: bool,
1671 },
1672 CircleThreePoint {
1673 #[serde(flatten)]
1674 base: BasePath,
1675 #[ts(type = "[number, number]")]
1677 p1: [f64; 2],
1678 #[ts(type = "[number, number]")]
1680 p2: [f64; 2],
1681 #[ts(type = "[number, number]")]
1683 p3: [f64; 2],
1684 },
1685 ArcThreePoint {
1686 #[serde(flatten)]
1687 base: BasePath,
1688 #[ts(type = "[number, number]")]
1690 p1: [f64; 2],
1691 #[ts(type = "[number, number]")]
1693 p2: [f64; 2],
1694 #[ts(type = "[number, number]")]
1696 p3: [f64; 2],
1697 },
1698 Horizontal {
1700 #[serde(flatten)]
1701 base: BasePath,
1702 x: f64,
1704 },
1705 AngledLineTo {
1707 #[serde(flatten)]
1708 base: BasePath,
1709 x: Option<f64>,
1711 y: Option<f64>,
1713 },
1714 Base {
1716 #[serde(flatten)]
1717 base: BasePath,
1718 },
1719 Arc {
1721 #[serde(flatten)]
1722 base: BasePath,
1723 center: [f64; 2],
1725 radius: f64,
1727 ccw: bool,
1729 },
1730 Ellipse {
1731 #[serde(flatten)]
1732 base: BasePath,
1733 center: [f64; 2],
1734 major_axis: [f64; 2],
1735 minor_radius: f64,
1736 ccw: bool,
1737 },
1738 Conic {
1740 #[serde(flatten)]
1741 base: BasePath,
1742 },
1743 Bezier {
1745 #[serde(flatten)]
1746 base: BasePath,
1747 #[ts(type = "[number, number]")]
1749 control1: [f64; 2],
1750 #[ts(type = "[number, number]")]
1752 control2: [f64; 2],
1753 },
1754}
1755
1756impl Path {
1757 pub fn get_id(&self) -> uuid::Uuid {
1758 match self {
1759 Path::ToPoint { base } => base.geo_meta.id,
1760 Path::Horizontal { base, .. } => base.geo_meta.id,
1761 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1762 Path::Base { base } => base.geo_meta.id,
1763 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1764 Path::TangentialArc { base, .. } => base.geo_meta.id,
1765 Path::Circle { base, .. } => base.geo_meta.id,
1766 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1767 Path::Arc { base, .. } => base.geo_meta.id,
1768 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1769 Path::Ellipse { base, .. } => base.geo_meta.id,
1770 Path::Conic { base, .. } => base.geo_meta.id,
1771 Path::Bezier { base, .. } => base.geo_meta.id,
1772 }
1773 }
1774
1775 pub fn set_id(&mut self, id: uuid::Uuid) {
1776 match self {
1777 Path::ToPoint { base } => base.geo_meta.id = id,
1778 Path::Horizontal { base, .. } => base.geo_meta.id = id,
1779 Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1780 Path::Base { base } => base.geo_meta.id = id,
1781 Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1782 Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1783 Path::Circle { base, .. } => base.geo_meta.id = id,
1784 Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1785 Path::Arc { base, .. } => base.geo_meta.id = id,
1786 Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1787 Path::Ellipse { base, .. } => base.geo_meta.id = id,
1788 Path::Conic { base, .. } => base.geo_meta.id = id,
1789 Path::Bezier { base, .. } => base.geo_meta.id = id,
1790 }
1791 }
1792
1793 pub fn get_tag(&self) -> Option<TagNode> {
1794 match self {
1795 Path::ToPoint { base } => base.tag.clone(),
1796 Path::Horizontal { base, .. } => base.tag.clone(),
1797 Path::AngledLineTo { base, .. } => base.tag.clone(),
1798 Path::Base { base } => base.tag.clone(),
1799 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1800 Path::TangentialArc { base, .. } => base.tag.clone(),
1801 Path::Circle { base, .. } => base.tag.clone(),
1802 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1803 Path::Arc { base, .. } => base.tag.clone(),
1804 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1805 Path::Ellipse { base, .. } => base.tag.clone(),
1806 Path::Conic { base, .. } => base.tag.clone(),
1807 Path::Bezier { base, .. } => base.tag.clone(),
1808 }
1809 }
1810
1811 pub fn get_base(&self) -> &BasePath {
1812 match self {
1813 Path::ToPoint { base } => base,
1814 Path::Horizontal { base, .. } => base,
1815 Path::AngledLineTo { base, .. } => base,
1816 Path::Base { base } => base,
1817 Path::TangentialArcTo { base, .. } => base,
1818 Path::TangentialArc { base, .. } => base,
1819 Path::Circle { base, .. } => base,
1820 Path::CircleThreePoint { base, .. } => base,
1821 Path::Arc { base, .. } => base,
1822 Path::ArcThreePoint { base, .. } => base,
1823 Path::Ellipse { base, .. } => base,
1824 Path::Conic { base, .. } => base,
1825 Path::Bezier { base, .. } => base,
1826 }
1827 }
1828
1829 pub fn get_from(&self) -> [TyF64; 2] {
1831 let p = &self.get_base().from;
1832 let ty: NumericType = self.get_base().units.into();
1833 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1834 }
1835
1836 pub fn get_to(&self) -> [TyF64; 2] {
1838 let p = &self.get_base().to;
1839 let ty: NumericType = self.get_base().units.into();
1840 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1841 }
1842
1843 pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1845 let p = &self.get_base().from;
1846 let ty: NumericType = self.get_base().units.into();
1847 (*p, ty)
1848 }
1849
1850 pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1852 let p = &self.get_base().to;
1853 let ty: NumericType = self.get_base().units.into();
1854 (*p, ty)
1855 }
1856
1857 pub fn length(&self) -> Option<TyF64> {
1860 let n = match self {
1861 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1862 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1863 }
1864 Self::TangentialArc {
1865 base: _,
1866 center,
1867 ccw: _,
1868 }
1869 | Self::TangentialArcTo {
1870 base: _,
1871 center,
1872 ccw: _,
1873 } => {
1874 let radius = linear_distance(&self.get_base().from, center);
1877 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
1878 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1880 }
1881 Self::Circle { radius, .. } => Some(TAU * radius),
1882 Self::CircleThreePoint { .. } => {
1883 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1884 self.get_base().from,
1885 self.get_base().to,
1886 self.get_base().to,
1887 ]);
1888 let radius = linear_distance(
1889 &[circle_center.center[0], circle_center.center[1]],
1890 &self.get_base().from,
1891 );
1892 Some(TAU * radius)
1893 }
1894 Self::Arc { .. } => {
1895 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1897 }
1898 Self::ArcThreePoint { .. } => {
1899 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1901 }
1902 Self::Ellipse { .. } => {
1903 None
1905 }
1906 Self::Conic { .. } => {
1907 None
1909 }
1910 Self::Bezier { .. } => {
1911 None
1913 }
1914 };
1915 n.map(|n| TyF64::new(n, self.get_base().units.into()))
1916 }
1917
1918 pub fn get_base_mut(&mut self) -> &mut BasePath {
1919 match self {
1920 Path::ToPoint { base } => base,
1921 Path::Horizontal { base, .. } => base,
1922 Path::AngledLineTo { base, .. } => base,
1923 Path::Base { base } => base,
1924 Path::TangentialArcTo { base, .. } => base,
1925 Path::TangentialArc { base, .. } => base,
1926 Path::Circle { base, .. } => base,
1927 Path::CircleThreePoint { base, .. } => base,
1928 Path::Arc { base, .. } => base,
1929 Path::ArcThreePoint { base, .. } => base,
1930 Path::Ellipse { base, .. } => base,
1931 Path::Conic { base, .. } => base,
1932 Path::Bezier { base, .. } => base,
1933 }
1934 }
1935
1936 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1937 match self {
1938 Path::TangentialArc { center, ccw, .. }
1939 | Path::TangentialArcTo { center, ccw, .. }
1940 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1941 center: *center,
1942 ccw: *ccw,
1943 },
1944 Path::ArcThreePoint { p1, p2, p3, .. } => {
1945 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1946 GetTangentialInfoFromPathsResult::Arc {
1947 center: circle.center,
1948 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
1949 }
1950 }
1951 Path::Circle {
1952 center, ccw, radius, ..
1953 } => GetTangentialInfoFromPathsResult::Circle {
1954 center: *center,
1955 ccw: *ccw,
1956 radius: *radius,
1957 },
1958 Path::CircleThreePoint { p1, p2, p3, .. } => {
1959 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1960 let center_point = [circle.center[0], circle.center[1]];
1961 GetTangentialInfoFromPathsResult::Circle {
1962 center: center_point,
1963 ccw: true,
1965 radius: circle.radius,
1966 }
1967 }
1968 Path::Ellipse {
1970 center,
1971 major_axis,
1972 minor_radius,
1973 ccw,
1974 ..
1975 } => GetTangentialInfoFromPathsResult::Ellipse {
1976 center: *center,
1977 major_axis: *major_axis,
1978 _minor_radius: *minor_radius,
1979 ccw: *ccw,
1980 },
1981 Path::Conic { .. }
1982 | Path::ToPoint { .. }
1983 | Path::Horizontal { .. }
1984 | Path::AngledLineTo { .. }
1985 | Path::Base { .. }
1986 | Path::Bezier { .. } => {
1987 let base = self.get_base();
1988 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1989 }
1990 }
1991 }
1992
1993 pub(crate) fn is_straight_line(&self) -> bool {
1995 matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
1996 }
1997}
1998
1999#[rustfmt::skip]
2001fn linear_distance(
2002 [x0, y0]: &[f64; 2],
2003 [x1, y1]: &[f64; 2]
2004) -> f64 {
2005 let y_sq = (y1 - y0).squared();
2006 let x_sq = (x1 - x0).squared();
2007 (y_sq + x_sq).sqrt()
2008}
2009
2010#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2012#[ts(export)]
2013#[serde(tag = "type", rename_all = "camelCase")]
2014pub enum ExtrudeSurface {
2015 ExtrudePlane(ExtrudePlane),
2017 ExtrudeArc(ExtrudeArc),
2018 Chamfer(ChamferSurface),
2019 Fillet(FilletSurface),
2020}
2021
2022#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2024#[ts(export)]
2025#[serde(rename_all = "camelCase")]
2026pub struct ChamferSurface {
2027 pub face_id: uuid::Uuid,
2029 pub tag: Option<Node<TagDeclarator>>,
2031 #[serde(flatten)]
2033 pub geo_meta: GeoMeta,
2034}
2035
2036#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2038#[ts(export)]
2039#[serde(rename_all = "camelCase")]
2040pub struct FilletSurface {
2041 pub face_id: uuid::Uuid,
2043 pub tag: Option<Node<TagDeclarator>>,
2045 #[serde(flatten)]
2047 pub geo_meta: GeoMeta,
2048}
2049
2050#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2052#[ts(export)]
2053#[serde(rename_all = "camelCase")]
2054pub struct ExtrudePlane {
2055 pub face_id: uuid::Uuid,
2057 pub tag: Option<Node<TagDeclarator>>,
2059 #[serde(flatten)]
2061 pub geo_meta: GeoMeta,
2062}
2063
2064#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2066#[ts(export)]
2067#[serde(rename_all = "camelCase")]
2068pub struct ExtrudeArc {
2069 pub face_id: uuid::Uuid,
2071 pub tag: Option<Node<TagDeclarator>>,
2073 #[serde(flatten)]
2075 pub geo_meta: GeoMeta,
2076}
2077
2078impl ExtrudeSurface {
2079 pub fn get_id(&self) -> uuid::Uuid {
2080 match self {
2081 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
2082 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
2083 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
2084 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
2085 }
2086 }
2087
2088 pub fn face_id(&self) -> uuid::Uuid {
2089 match self {
2090 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id,
2091 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id,
2092 ExtrudeSurface::Fillet(f) => f.face_id,
2093 ExtrudeSurface::Chamfer(c) => c.face_id,
2094 }
2095 }
2096
2097 pub fn set_face_id(&mut self, face_id: uuid::Uuid) {
2098 match self {
2099 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id = face_id,
2100 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id = face_id,
2101 ExtrudeSurface::Fillet(f) => f.face_id = face_id,
2102 ExtrudeSurface::Chamfer(c) => c.face_id = face_id,
2103 }
2104 }
2105
2106 pub fn set_surface_tag(&mut self, tag: &TagNode) {
2107 match self {
2108 ExtrudeSurface::ExtrudePlane(extrude_plane) => extrude_plane.tag = Some(tag.clone()),
2109 ExtrudeSurface::ExtrudeArc(extrude_arc) => extrude_arc.tag = Some(tag.clone()),
2110 ExtrudeSurface::Chamfer(chamfer) => chamfer.tag = Some(tag.clone()),
2111 ExtrudeSurface::Fillet(fillet) => fillet.tag = Some(tag.clone()),
2112 }
2113 }
2114
2115 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
2116 match self {
2117 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
2118 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
2119 ExtrudeSurface::Fillet(f) => f.tag.clone(),
2120 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
2121 }
2122 }
2123}
2124
2125#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ts_rs::TS)]
2126pub struct SketchVarId(pub usize);
2127
2128impl SketchVarId {
2129 pub const INVALID: Self = Self(usize::MAX);
2130
2131 pub fn to_constraint_id(self, range: SourceRange) -> Result<ezpz::Id, KclError> {
2132 self.0.try_into().map_err(|_| {
2133 KclError::new_type(KclErrorDetails::new(
2134 "Cannot convert to constraint ID since the sketch variable ID is too large".to_owned(),
2135 vec![range],
2136 ))
2137 })
2138 }
2139}
2140
2141#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2142#[ts(export_to = "Geometry.ts")]
2143#[serde(rename_all = "camelCase")]
2144pub struct SketchVar {
2145 pub id: SketchVarId,
2146 pub initial_value: f64,
2147 pub ty: NumericType,
2148 pub node_path: Option<NodePath>,
2150 #[serde(skip)]
2151 pub meta: Vec<Metadata>,
2152}
2153
2154impl SketchVar {
2155 pub fn initial_value_to_solver_units(
2156 &self,
2157 exec_state: &mut ExecState,
2158 source_range: SourceRange,
2159 description: &str,
2160 ) -> Result<TyF64, KclError> {
2161 let x_initial_value = KclValue::Number {
2162 value: self.initial_value,
2163 ty: self.ty,
2164 meta: vec![source_range.into()],
2165 };
2166 let normalized_value =
2167 normalize_to_solver_distance_unit(&x_initial_value, source_range, exec_state, description)?;
2168 normalized_value.as_ty_f64().ok_or_else(|| {
2169 let message = format!(
2170 "Expected number after coercion, but found {}",
2171 normalized_value.human_friendly_type()
2172 );
2173 debug_assert!(false, "{}", &message);
2174 KclError::new_internal(KclErrorDetails::new(message, vec![source_range]))
2175 })
2176 }
2177}
2178
2179#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2180#[ts(export_to = "Geometry.ts")]
2181#[serde(tag = "type")]
2182pub enum UnsolvedExpr {
2183 Known(TyF64),
2184 Unknown(SketchVarId),
2185}
2186
2187impl UnsolvedExpr {
2188 pub fn var(&self) -> Option<SketchVarId> {
2189 match self {
2190 UnsolvedExpr::Known(_) => None,
2191 UnsolvedExpr::Unknown(id) => Some(*id),
2192 }
2193 }
2194}
2195
2196pub type UnsolvedPoint2dExpr = [UnsolvedExpr; 2];
2197
2198#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2199#[ts(export_to = "Geometry.ts")]
2200#[serde(rename_all = "camelCase")]
2201pub struct ConstrainablePoint2d {
2202 pub vars: crate::front::Point2d<SketchVarId>,
2203 pub object_id: ObjectId,
2204}
2205
2206#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2207#[ts(export_to = "Geometry.ts")]
2208pub enum ConstrainablePoint2dOrOrigin {
2209 Point(ConstrainablePoint2d),
2210 Origin,
2211}
2212
2213#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2214#[ts(export_to = "Geometry.ts")]
2215#[serde(rename_all = "camelCase")]
2216pub struct ConstrainableLine2d {
2217 pub vars: [crate::front::Point2d<SketchVarId>; 2],
2218 pub object_id: ObjectId,
2219}
2220
2221#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2222#[ts(export_to = "Geometry.ts")]
2223#[serde(rename_all = "camelCase")]
2224pub struct UnsolvedSegment {
2225 pub id: Uuid,
2227 pub object_id: ObjectId,
2228 pub kind: UnsolvedSegmentKind,
2229 #[serde(skip_serializing_if = "Option::is_none")]
2230 pub tag: Option<TagIdentifier>,
2231 #[serde(skip)]
2232 pub node_path: Option<NodePath>,
2233 #[serde(skip)]
2234 pub meta: Vec<Metadata>,
2235}
2236
2237#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2238#[ts(export_to = "Geometry.ts")]
2239#[serde(rename_all = "camelCase")]
2240pub enum UnsolvedSegmentKind {
2241 Point {
2242 position: UnsolvedPoint2dExpr,
2243 ctor: Box<PointCtor>,
2244 },
2245 Line {
2246 start: UnsolvedPoint2dExpr,
2247 end: UnsolvedPoint2dExpr,
2248 ctor: Box<LineCtor>,
2249 start_object_id: ObjectId,
2250 end_object_id: ObjectId,
2251 construction: bool,
2252 },
2253 Arc {
2254 start: UnsolvedPoint2dExpr,
2255 end: UnsolvedPoint2dExpr,
2256 center: UnsolvedPoint2dExpr,
2257 ctor: Box<ArcCtor>,
2258 start_object_id: ObjectId,
2259 end_object_id: ObjectId,
2260 center_object_id: ObjectId,
2261 construction: bool,
2262 },
2263 Circle {
2264 start: UnsolvedPoint2dExpr,
2265 center: UnsolvedPoint2dExpr,
2266 ctor: Box<CircleCtor>,
2267 start_object_id: ObjectId,
2268 center_object_id: ObjectId,
2269 construction: bool,
2270 },
2271 ControlPointSpline {
2272 controls: Vec<UnsolvedPoint2dExpr>,
2273 ctor: Box<ControlPointSplineCtor>,
2274 control_object_ids: Vec<ObjectId>,
2275 control_polygon_edge_object_ids: Vec<ObjectId>,
2276 degree: u32,
2277 construction: bool,
2278 },
2279}
2280
2281impl UnsolvedSegmentKind {
2282 pub fn human_friendly_kind_with_article(&self) -> &'static str {
2285 match self {
2286 Self::Point { .. } => "a Point",
2287 Self::Line { .. } => "a Line",
2288 Self::Arc { .. } => "an Arc",
2289 Self::Circle { .. } => "a Circle",
2290 Self::ControlPointSpline { .. } => "a Control Point Spline",
2291 }
2292 }
2293}
2294
2295#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2296#[ts(export_to = "Geometry.ts")]
2297#[serde(rename_all = "camelCase")]
2298pub struct Segment {
2299 pub id: Uuid,
2301 pub object_id: ObjectId,
2302 pub kind: SegmentKind,
2303 pub surface: SketchSurface,
2304 pub sketch_id: Uuid,
2306 #[serde(skip)]
2307 #[ts(skip)]
2308 pub sketch: Option<Arc<Sketch>>,
2309 #[serde(skip_serializing_if = "Option::is_none")]
2310 pub tag: Option<TagIdentifier>,
2311 #[serde(skip)]
2312 pub node_path: Option<NodePath>,
2313 #[serde(skip)]
2314 pub meta: Vec<Metadata>,
2315}
2316
2317impl Segment {
2318 pub fn is_construction(&self) -> bool {
2319 match &self.kind {
2320 SegmentKind::Point { .. } => true,
2321 SegmentKind::Line { construction, .. } => *construction,
2322 SegmentKind::Arc { construction, .. } => *construction,
2323 SegmentKind::Circle { construction, .. } => *construction,
2324 SegmentKind::ControlPointSpline { construction, .. } => *construction,
2325 }
2326 }
2327}
2328
2329#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2330#[ts(export_to = "Geometry.ts")]
2331#[serde(rename_all = "camelCase")]
2332pub enum SegmentKind {
2333 Point {
2334 position: [TyF64; 2],
2335 ctor: Box<PointCtor>,
2336 #[serde(skip_serializing_if = "Option::is_none")]
2337 freedom: Option<Freedom>,
2338 },
2339 Line {
2340 start: [TyF64; 2],
2341 end: [TyF64; 2],
2342 ctor: Box<LineCtor>,
2343 start_object_id: ObjectId,
2344 end_object_id: ObjectId,
2345 #[serde(skip_serializing_if = "Option::is_none")]
2346 start_freedom: Option<Freedom>,
2347 #[serde(skip_serializing_if = "Option::is_none")]
2348 end_freedom: Option<Freedom>,
2349 construction: bool,
2350 },
2351 Arc {
2352 start: [TyF64; 2],
2353 end: [TyF64; 2],
2354 center: [TyF64; 2],
2355 ctor: Box<ArcCtor>,
2356 start_object_id: ObjectId,
2357 end_object_id: ObjectId,
2358 center_object_id: ObjectId,
2359 #[serde(skip_serializing_if = "Option::is_none")]
2360 start_freedom: Option<Freedom>,
2361 #[serde(skip_serializing_if = "Option::is_none")]
2362 end_freedom: Option<Freedom>,
2363 #[serde(skip_serializing_if = "Option::is_none")]
2364 center_freedom: Option<Freedom>,
2365 construction: bool,
2366 },
2367 Circle {
2368 start: [TyF64; 2],
2369 center: [TyF64; 2],
2370 ctor: Box<CircleCtor>,
2371 start_object_id: ObjectId,
2372 center_object_id: ObjectId,
2373 #[serde(skip_serializing_if = "Option::is_none")]
2374 start_freedom: Option<Freedom>,
2375 #[serde(skip_serializing_if = "Option::is_none")]
2376 center_freedom: Option<Freedom>,
2377 construction: bool,
2378 },
2379 ControlPointSpline {
2380 controls: Vec<[TyF64; 2]>,
2381 ctor: Box<ControlPointSplineCtor>,
2382 control_object_ids: Vec<ObjectId>,
2383 control_polygon_edge_object_ids: Vec<ObjectId>,
2384 #[serde(skip_serializing_if = "Vec::is_empty")]
2385 control_freedoms: Vec<Option<Freedom>>,
2386 degree: u32,
2387 construction: bool,
2388 },
2389}
2390
2391#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2392#[ts(export_to = "Geometry.ts")]
2393#[serde(rename_all = "camelCase")]
2394pub struct AbstractSegment {
2395 pub repr: SegmentRepr,
2396 #[serde(skip)]
2397 pub meta: Vec<Metadata>,
2398}
2399
2400#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2401pub enum SegmentRepr {
2402 Unsolved { segment: Box<UnsolvedSegment> },
2403 Solved { segment: Box<Segment> },
2404}
2405
2406#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2407#[ts(export_to = "Geometry.ts")]
2408#[serde(rename_all = "camelCase")]
2409pub struct SketchConstraint {
2410 pub kind: SketchConstraintKind,
2411 #[serde(skip)]
2412 pub meta: Vec<Metadata>,
2413}
2414
2415#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2416#[ts(export_to = "Geometry.ts")]
2417#[serde(rename_all = "camelCase")]
2418pub enum SketchConstraintKind {
2419 Angle {
2420 line0: ConstrainableLine2d,
2421 line1: ConstrainableLine2d,
2422 },
2423 Distance {
2424 points: [ConstrainablePoint2dOrOrigin; 2],
2425 #[serde(rename = "labelPosition")]
2426 #[serde(skip_serializing_if = "Option::is_none")]
2427 #[ts(rename = "labelPosition")]
2428 #[ts(optional)]
2429 label_position: Option<ApiPoint2d<Number>>,
2430 },
2431 PointLineDistance {
2432 point: ConstrainablePoint2dOrOrigin,
2433 line: ConstrainableLine2d,
2434 input_object_ids: [Option<ObjectId>; 2],
2435 #[serde(rename = "labelPosition")]
2436 #[serde(skip_serializing_if = "Option::is_none")]
2437 #[ts(rename = "labelPosition")]
2438 #[ts(optional)]
2439 label_position: Option<ApiPoint2d<Number>>,
2440 },
2441 LineLineDistance {
2442 line0: ConstrainableLine2d,
2443 line1: ConstrainableLine2d,
2444 input_object_ids: [ObjectId; 2],
2445 #[serde(rename = "labelPosition")]
2446 #[serde(skip_serializing_if = "Option::is_none")]
2447 #[ts(rename = "labelPosition")]
2448 #[ts(optional)]
2449 label_position: Option<ApiPoint2d<Number>>,
2450 },
2451 PointCircularDistance {
2452 point: ConstrainablePoint2dOrOrigin,
2453 center: ConstrainablePoint2d,
2454 start: ConstrainablePoint2d,
2455 end: Option<ConstrainablePoint2d>,
2456 input_object_ids: [Option<ObjectId>; 2],
2457 #[serde(rename = "labelPosition")]
2458 #[serde(skip_serializing_if = "Option::is_none")]
2459 #[ts(rename = "labelPosition")]
2460 #[ts(optional)]
2461 label_position: Option<ApiPoint2d<Number>>,
2462 },
2463 LineCircularDistance {
2464 line: ConstrainableLine2d,
2465 center: ConstrainablePoint2d,
2466 start: ConstrainablePoint2d,
2467 end: Option<ConstrainablePoint2d>,
2468 input_object_ids: [ObjectId; 2],
2469 #[serde(rename = "labelPosition")]
2470 #[serde(skip_serializing_if = "Option::is_none")]
2471 #[ts(rename = "labelPosition")]
2472 #[ts(optional)]
2473 label_position: Option<ApiPoint2d<Number>>,
2474 },
2475 CircularCircularDistance {
2476 center0: ConstrainablePoint2d,
2477 start0: ConstrainablePoint2d,
2478 end0: Option<ConstrainablePoint2d>,
2479 center1: ConstrainablePoint2d,
2480 start1: ConstrainablePoint2d,
2481 end1: Option<ConstrainablePoint2d>,
2482 input_object_ids: [ObjectId; 2],
2483 #[serde(rename = "labelPosition")]
2484 #[serde(skip_serializing_if = "Option::is_none")]
2485 #[ts(rename = "labelPosition")]
2486 #[ts(optional)]
2487 label_position: Option<ApiPoint2d<Number>>,
2488 },
2489 Radius {
2490 points: [ConstrainablePoint2d; 2],
2491 #[serde(rename = "labelPosition")]
2492 #[serde(skip_serializing_if = "Option::is_none")]
2493 #[ts(rename = "labelPosition")]
2494 #[ts(optional)]
2495 label_position: Option<ApiPoint2d<Number>>,
2496 },
2497 Diameter {
2498 points: [ConstrainablePoint2d; 2],
2499 #[serde(rename = "labelPosition")]
2500 #[serde(skip_serializing_if = "Option::is_none")]
2501 #[ts(rename = "labelPosition")]
2502 #[ts(optional)]
2503 label_position: Option<ApiPoint2d<Number>>,
2504 },
2505 HorizontalDistance {
2506 points: [ConstrainablePoint2dOrOrigin; 2],
2507 #[serde(rename = "labelPosition")]
2508 #[serde(skip_serializing_if = "Option::is_none")]
2509 #[ts(rename = "labelPosition")]
2510 #[ts(optional)]
2511 label_position: Option<ApiPoint2d<Number>>,
2512 },
2513 VerticalDistance {
2514 points: [ConstrainablePoint2dOrOrigin; 2],
2515 #[serde(rename = "labelPosition")]
2516 #[serde(skip_serializing_if = "Option::is_none")]
2517 #[ts(rename = "labelPosition")]
2518 #[ts(optional)]
2519 label_position: Option<ApiPoint2d<Number>>,
2520 },
2521}
2522
2523impl SketchConstraintKind {
2524 pub fn name(&self) -> &'static str {
2525 match self {
2526 SketchConstraintKind::Angle { .. } => "angle",
2527 SketchConstraintKind::Distance { .. } => "distance",
2528 SketchConstraintKind::PointLineDistance { .. } => "distance",
2529 SketchConstraintKind::LineLineDistance { .. } => "distance",
2530 SketchConstraintKind::PointCircularDistance { .. } => "distance",
2531 SketchConstraintKind::LineCircularDistance { .. } => "distance",
2532 SketchConstraintKind::CircularCircularDistance { .. } => "distance",
2533 SketchConstraintKind::Radius { .. } => "radius",
2534 SketchConstraintKind::Diameter { .. } => "diameter",
2535 SketchConstraintKind::HorizontalDistance { .. } => "horizontalDistance",
2536 SketchConstraintKind::VerticalDistance { .. } => "verticalDistance",
2537 }
2538 }
2539}