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_api::UnitLength;
12use kcl_error::SourceRange;
13use kittycad_modeling_cmds::ModelingCmd;
14use kittycad_modeling_cmds::each_cmd as mcmd;
15use kittycad_modeling_cmds::length_unit::LengthUnit;
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::NumericTypeExt;
39use crate::execution::types::adjust_length;
40use crate::front::ArcCtor;
41use crate::front::ArcDirection;
42use crate::front::CircleCtor;
43use crate::front::ControlPointSplineCtor;
44use crate::front::Freedom;
45use crate::front::LineCtor;
46use crate::front::Number;
47use crate::front::ObjectId;
48use crate::front::Point2d as ApiPoint2d;
49use crate::front::PointCtor;
50use crate::parsing::ast::types::Node;
51use crate::parsing::ast::types::NodeRef;
52use crate::parsing::ast::types::TagDeclarator;
53use crate::parsing::ast::types::TagNode;
54use crate::std::Args;
55use crate::std::args::TyF64;
56use crate::std::edge::UnresolvedEdgeSpecifier;
57use crate::std::sketch::FaceTag;
58use crate::std::sketch::PlaneData;
59use crate::util::MathExt;
60
61type Point3D = kcmc::shared::Point3d<f64>;
62
63#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
65#[ts(export)]
66#[serde(tag = "type", rename_all = "camelCase")]
67pub struct GdtAnnotation {
68 pub id: uuid::Uuid,
70 #[serde(skip)]
71 pub meta: Vec<Metadata>,
72}
73
74#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
76#[ts(export)]
77#[serde(tag = "type")]
78#[allow(clippy::large_enum_variant)]
79pub enum Geometry {
80 Sketch(Sketch),
81 Solid(Solid),
82}
83
84impl Geometry {
85 pub fn id(&self) -> uuid::Uuid {
86 match self {
87 Geometry::Sketch(s) => s.id,
88 Geometry::Solid(e) => e.id,
89 }
90 }
91
92 pub fn pattern_source_id(&self) -> uuid::Uuid {
95 match self {
96 Geometry::Sketch(s) => s.original_id,
97 Geometry::Solid(e) => e.topology_id(),
98 }
99 }
100}
101
102#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
104#[ts(export)]
105#[serde(tag = "type")]
106#[allow(clippy::large_enum_variant)]
107pub enum GeometryWithImportedGeometry {
108 Sketch(Sketch),
109 Solid(Solid),
110 ImportedGeometry(Box<ImportedGeometry>),
111}
112
113impl GeometryWithImportedGeometry {
114 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
115 match self {
116 GeometryWithImportedGeometry::Sketch(s) => Ok(s.id),
117 GeometryWithImportedGeometry::Solid(e) => Ok(e.id),
118 GeometryWithImportedGeometry::ImportedGeometry(i) => {
119 let id = i.id(ctx).await?;
120 Ok(id)
121 }
122 }
123 }
124
125 pub fn into_solid(self) -> Option<Solid> {
126 match self {
127 GeometryWithImportedGeometry::Sketch(_) => None,
128 GeometryWithImportedGeometry::Solid(solid) => Some(solid),
129 GeometryWithImportedGeometry::ImportedGeometry(_) => None,
130 }
131 }
132}
133
134#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
136#[ts(export)]
137#[serde(tag = "type")]
138#[allow(clippy::vec_box)]
139pub enum Geometries {
140 Sketches(Vec<Sketch>),
141 Solids(Vec<Solid>),
142}
143
144impl From<Geometry> for Geometries {
145 fn from(value: Geometry) -> Self {
146 match value {
147 Geometry::Sketch(x) => Self::Sketches(vec![x]),
148 Geometry::Solid(x) => Self::Solids(vec![x]),
149 }
150 }
151}
152
153#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
155#[ts(export)]
156#[serde(rename_all = "camelCase")]
157pub struct ImportedGeometry {
158 pub id: uuid::Uuid,
160 pub value: Vec<String>,
162 #[serde(skip)]
163 pub meta: Vec<Metadata>,
164 #[serde(skip)]
166 completed: bool,
167}
168
169impl ImportedGeometry {
170 pub fn new(id: uuid::Uuid, value: Vec<String>, meta: Vec<Metadata>) -> Self {
171 Self {
172 id,
173 value,
174 meta,
175 completed: false,
176 }
177 }
178
179 async fn wait_for_finish(&mut self, ctx: &ExecutorContext) -> Result<(), KclError> {
180 if self.completed {
181 return Ok(());
182 }
183
184 ctx.engine
185 .ensure_async_command_completed(self.id, self.meta.first().map(|m| m.source_range))
186 .await?;
187
188 self.completed = true;
189
190 Ok(())
191 }
192
193 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
194 if !self.completed {
195 self.wait_for_finish(ctx).await?;
196 }
197
198 Ok(self.id)
199 }
200}
201
202#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
204#[ts(export)]
205#[serde(tag = "type", rename_all = "camelCase")]
206#[allow(clippy::vec_box)]
207pub enum HideableGeometry {
208 ImportedGeometry(Box<ImportedGeometry>),
209 SolidSet(Vec<Solid>),
210 PlaneSet(Vec<Plane>),
211 SketchSet(Vec<Sketch>),
212 HelixSet(Vec<Helix>),
213 GdtAnnotationSet(Vec<GdtAnnotation>),
214}
215
216impl From<HideableGeometry> for crate::execution::KclValue {
217 fn from(value: HideableGeometry) -> Self {
218 match value {
219 HideableGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
220 HideableGeometry::PlaneSet(mut s) => {
221 if s.len() == 1
222 && let Some(s) = s.pop()
223 {
224 crate::execution::KclValue::Plane { value: Box::new(s) }
225 } else {
226 crate::execution::KclValue::HomArray {
227 value: s
228 .into_iter()
229 .map(|s| crate::execution::KclValue::Plane { value: Box::new(s) })
230 .collect(),
231 ty: crate::execution::types::RuntimeType::plane(),
232 }
233 }
234 }
235 HideableGeometry::SolidSet(mut s) => {
236 if s.len() == 1
237 && let Some(s) = s.pop()
238 {
239 crate::execution::KclValue::Solid { value: Box::new(s) }
240 } else {
241 crate::execution::KclValue::HomArray {
242 value: s
243 .into_iter()
244 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
245 .collect(),
246 ty: crate::execution::types::RuntimeType::solid(),
247 }
248 }
249 }
250 HideableGeometry::GdtAnnotationSet(mut s) => {
251 if s.len() == 1
252 && let Some(s) = s.pop()
253 {
254 crate::execution::KclValue::GdtAnnotation { value: Box::new(s) }
255 } else {
256 crate::execution::KclValue::HomArray {
257 value: s
258 .into_iter()
259 .map(|s| crate::execution::KclValue::GdtAnnotation { value: Box::new(s) })
260 .collect(),
261 ty: crate::execution::types::RuntimeType::gdt(),
262 }
263 }
264 }
265 HideableGeometry::SketchSet(mut s) => {
266 if s.len() == 1
267 && let Some(s) = s.pop()
268 {
269 crate::execution::KclValue::Sketch { value: Box::new(s) }
270 } else {
271 crate::execution::KclValue::HomArray {
272 value: s
273 .into_iter()
274 .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) })
275 .collect(),
276 ty: crate::execution::types::RuntimeType::sketch(),
277 }
278 }
279 }
280 HideableGeometry::HelixSet(mut s) => {
281 if s.len() == 1
282 && let Some(s) = s.pop()
283 {
284 crate::execution::KclValue::Helix { value: Box::new(s) }
285 } else {
286 crate::execution::KclValue::HomArray {
287 value: s
288 .into_iter()
289 .map(|s| crate::execution::KclValue::Helix { value: Box::new(s) })
290 .collect(),
291 ty: crate::execution::types::RuntimeType::helices(),
292 }
293 }
294 }
295 }
296 }
297}
298
299impl HideableGeometry {
300 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
301 match self {
302 HideableGeometry::ImportedGeometry(s) => {
303 let id = s.id(ctx).await?;
304
305 Ok(vec![id])
306 }
307 HideableGeometry::PlaneSet(s) => Ok(s.iter().map(|s| s.id).collect()),
308 HideableGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
309 HideableGeometry::GdtAnnotationSet(s) => Ok(s.iter().map(|s| s.id).collect()),
310 HideableGeometry::SketchSet(s) => Ok(s.iter().map(|s| s.id).collect()),
311 HideableGeometry::HelixSet(s) => Ok(s.iter().map(|s| s.value).collect()),
312 }
313 }
314}
315
316#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
318#[ts(export)]
319#[serde(tag = "type", rename_all = "camelCase")]
320#[allow(clippy::vec_box)]
321pub enum SolidOrSketchOrImportedGeometry {
322 ImportedGeometry(Box<ImportedGeometry>),
323 SolidSet(Vec<Solid>),
324 SketchSet(Vec<Sketch>),
325 HelixSet(Vec<Helix>),
326}
327
328impl From<SolidOrSketchOrImportedGeometry> for crate::execution::KclValue {
329 fn from(value: SolidOrSketchOrImportedGeometry) -> Self {
330 match value {
331 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
332 SolidOrSketchOrImportedGeometry::SolidSet(mut s) => {
333 if s.len() == 1
334 && let Some(s) = s.pop()
335 {
336 crate::execution::KclValue::Solid { value: Box::new(s) }
337 } else {
338 crate::execution::KclValue::HomArray {
339 value: s
340 .into_iter()
341 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
342 .collect(),
343 ty: crate::execution::types::RuntimeType::solid(),
344 }
345 }
346 }
347 SolidOrSketchOrImportedGeometry::SketchSet(mut s) => {
348 if s.len() == 1
349 && let Some(s) = s.pop()
350 {
351 crate::execution::KclValue::Sketch { value: Box::new(s) }
352 } else {
353 crate::execution::KclValue::HomArray {
354 value: s
355 .into_iter()
356 .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) })
357 .collect(),
358 ty: crate::execution::types::RuntimeType::sketch(),
359 }
360 }
361 }
362 SolidOrSketchOrImportedGeometry::HelixSet(mut s) => {
363 if s.len() == 1
364 && let Some(s) = s.pop()
365 {
366 crate::execution::KclValue::Helix { value: Box::new(s) }
367 } else {
368 crate::execution::KclValue::HomArray {
369 value: s
370 .into_iter()
371 .map(|s| crate::execution::KclValue::Helix { value: Box::new(s) })
372 .collect(),
373 ty: crate::execution::types::RuntimeType::helices(),
374 }
375 }
376 }
377 }
378 }
379}
380
381impl SolidOrSketchOrImportedGeometry {
382 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
383 match self {
384 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => {
385 let id = s.id(ctx).await?;
386
387 Ok(vec![id])
388 }
389 SolidOrSketchOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
390 SolidOrSketchOrImportedGeometry::SketchSet(s) => Ok(s.iter().map(|s| s.id).collect()),
391 SolidOrSketchOrImportedGeometry::HelixSet(s) => Ok(s.iter().map(|s| s.value).collect()),
392 }
393 }
394}
395
396#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
398#[ts(export)]
399#[serde(tag = "type", rename_all = "camelCase")]
400#[allow(clippy::vec_box)]
401pub enum SolidOrImportedGeometry {
402 ImportedGeometry(Box<ImportedGeometry>),
403 SolidSet(Vec<Solid>),
404}
405
406impl From<SolidOrImportedGeometry> for crate::execution::KclValue {
407 fn from(value: SolidOrImportedGeometry) -> Self {
408 match value {
409 SolidOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
410 SolidOrImportedGeometry::SolidSet(mut s) => {
411 if s.len() == 1
412 && let Some(s) = s.pop()
413 {
414 crate::execution::KclValue::Solid { value: Box::new(s) }
415 } else {
416 crate::execution::KclValue::HomArray {
417 value: s
418 .into_iter()
419 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
420 .collect(),
421 ty: crate::execution::types::RuntimeType::solid(),
422 }
423 }
424 }
425 }
426 }
427}
428
429#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
431#[ts(export)]
432#[serde(tag = "type", rename_all = "camelCase")]
433#[allow(clippy::vec_box)]
434pub enum HasAppearance {
435 ImportedGeometry(Box<ImportedGeometry>),
436 SolidSet(Vec<Solid>),
437 Plane(Box<Plane>),
438}
439
440impl From<HasAppearance> for KclValue {
441 fn from(value: HasAppearance) -> Self {
442 match value {
443 HasAppearance::Plane(p) => KclValue::Plane { value: p },
444 HasAppearance::ImportedGeometry(s) => KclValue::ImportedGeometry(*s),
445 HasAppearance::SolidSet(mut s) => {
446 if s.len() == 1
447 && let Some(s) = s.pop()
448 {
449 KclValue::Solid { value: Box::new(s) }
450 } else {
451 KclValue::HomArray {
452 value: s.into_iter().map(|s| KclValue::Solid { value: Box::new(s) }).collect(),
453 ty: crate::execution::types::RuntimeType::solid(),
454 }
455 }
456 }
457 }
458 }
459}
460
461impl HasAppearance {
462 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
463 match self {
464 HasAppearance::Plane(p) => Ok(vec![p.id]),
465 HasAppearance::ImportedGeometry(s) => {
466 let id = s.id(ctx).await?;
467
468 Ok(vec![id])
469 }
470 HasAppearance::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
471 }
472 }
473}
474
475#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
477#[ts(export)]
478#[serde(rename_all = "camelCase")]
479pub struct Helix {
480 pub value: uuid::Uuid,
482 pub artifact_id: ArtifactId,
484 pub revolutions: f64,
486 pub angle_start: f64,
488 pub ccw: bool,
490 pub cylinder_id: Option<uuid::Uuid>,
492 pub units: UnitLength,
493 #[serde(skip)]
494 pub meta: Vec<Metadata>,
495}
496
497#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
498#[ts(export)]
499#[serde(rename_all = "camelCase")]
500pub struct Plane {
501 pub id: uuid::Uuid,
503 pub artifact_id: ArtifactId,
505 #[serde(skip_serializing_if = "Option::is_none")]
508 pub object_id: Option<ObjectId>,
509 pub kind: PlaneKind,
511 #[serde(flatten)]
513 pub info: PlaneInfo,
514 #[serde(skip)]
515 pub meta: Vec<Metadata>,
516}
517
518#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS)]
519#[ts(export)]
520#[serde(rename_all = "camelCase")]
521pub struct PlaneInfo {
522 pub origin: Point3d,
524 pub x_axis: Point3d,
526 pub y_axis: Point3d,
528 pub z_axis: Point3d,
530}
531
532impl PlaneInfo {
533 pub(crate) fn into_plane_data(self) -> PlaneData {
534 if self.origin.is_zero() {
535 match self {
536 Self {
537 origin:
538 Point3d {
539 x: 0.0,
540 y: 0.0,
541 z: 0.0,
542 units: Some(UnitLength::Millimeters),
543 },
544 x_axis:
545 Point3d {
546 x: 1.0,
547 y: 0.0,
548 z: 0.0,
549 units: _,
550 },
551 y_axis:
552 Point3d {
553 x: 0.0,
554 y: 1.0,
555 z: 0.0,
556 units: _,
557 },
558 z_axis: _,
559 } => return PlaneData::XY,
560 Self {
561 origin:
562 Point3d {
563 x: 0.0,
564 y: 0.0,
565 z: 0.0,
566 units: Some(UnitLength::Millimeters),
567 },
568 x_axis:
569 Point3d {
570 x: -1.0,
571 y: 0.0,
572 z: 0.0,
573 units: _,
574 },
575 y_axis:
576 Point3d {
577 x: 0.0,
578 y: 1.0,
579 z: 0.0,
580 units: _,
581 },
582 z_axis: _,
583 } => return PlaneData::NegXY,
584 Self {
585 origin:
586 Point3d {
587 x: 0.0,
588 y: 0.0,
589 z: 0.0,
590 units: Some(UnitLength::Millimeters),
591 },
592 x_axis:
593 Point3d {
594 x: 1.0,
595 y: 0.0,
596 z: 0.0,
597 units: _,
598 },
599 y_axis:
600 Point3d {
601 x: 0.0,
602 y: 0.0,
603 z: 1.0,
604 units: _,
605 },
606 z_axis: _,
607 } => return PlaneData::XZ,
608 Self {
609 origin:
610 Point3d {
611 x: 0.0,
612 y: 0.0,
613 z: 0.0,
614 units: Some(UnitLength::Millimeters),
615 },
616 x_axis:
617 Point3d {
618 x: -1.0,
619 y: 0.0,
620 z: 0.0,
621 units: _,
622 },
623 y_axis:
624 Point3d {
625 x: 0.0,
626 y: 0.0,
627 z: 1.0,
628 units: _,
629 },
630 z_axis: _,
631 } => return PlaneData::NegXZ,
632 Self {
633 origin:
634 Point3d {
635 x: 0.0,
636 y: 0.0,
637 z: 0.0,
638 units: Some(UnitLength::Millimeters),
639 },
640 x_axis:
641 Point3d {
642 x: 0.0,
643 y: 1.0,
644 z: 0.0,
645 units: _,
646 },
647 y_axis:
648 Point3d {
649 x: 0.0,
650 y: 0.0,
651 z: 1.0,
652 units: _,
653 },
654 z_axis: _,
655 } => return PlaneData::YZ,
656 Self {
657 origin:
658 Point3d {
659 x: 0.0,
660 y: 0.0,
661 z: 0.0,
662 units: Some(UnitLength::Millimeters),
663 },
664 x_axis:
665 Point3d {
666 x: 0.0,
667 y: -1.0,
668 z: 0.0,
669 units: _,
670 },
671 y_axis:
672 Point3d {
673 x: 0.0,
674 y: 0.0,
675 z: 1.0,
676 units: _,
677 },
678 z_axis: _,
679 } => return PlaneData::NegYZ,
680 _ => {}
681 }
682 }
683
684 PlaneData::Plane(Self {
685 origin: self.origin,
686 x_axis: self.x_axis,
687 y_axis: self.y_axis,
688 z_axis: self.z_axis,
689 })
690 }
691
692 pub(crate) fn is_right_handed(&self) -> bool {
693 let lhs = self
696 .x_axis
697 .axes_cross_product(&self.y_axis)
698 .axes_dot_product(&self.z_axis);
699 let rhs_x = self.x_axis.axes_dot_product(&self.x_axis);
700 let rhs_y = self.y_axis.axes_dot_product(&self.y_axis);
701 let rhs_z = self.z_axis.axes_dot_product(&self.z_axis);
702 let rhs = (rhs_x * rhs_y * rhs_z).sqrt();
703 (lhs - rhs).abs() <= 0.0001
705 }
706
707 #[cfg(test)]
708 pub(crate) fn is_left_handed(&self) -> bool {
709 !self.is_right_handed()
710 }
711
712 pub(crate) fn make_right_handed(self) -> Self {
713 if self.is_right_handed() {
714 return self;
715 }
716 Self {
718 origin: self.origin,
719 x_axis: self.x_axis.negated(),
720 y_axis: self.y_axis,
721 z_axis: self.z_axis,
722 }
723 }
724}
725
726impl TryFrom<PlaneData> for PlaneInfo {
727 type Error = KclError;
728
729 fn try_from(value: PlaneData) -> Result<Self, Self::Error> {
730 let name = match value {
731 PlaneData::XY => PlaneName::Xy,
732 PlaneData::NegXY => PlaneName::NegXy,
733 PlaneData::XZ => PlaneName::Xz,
734 PlaneData::NegXZ => PlaneName::NegXz,
735 PlaneData::YZ => PlaneName::Yz,
736 PlaneData::NegYZ => PlaneName::NegYz,
737 PlaneData::Plane(info) => {
738 return Ok(info);
739 }
740 };
741
742 let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
743 KclError::new_internal(KclErrorDetails::new(
744 format!("Plane {name} not found"),
745 Default::default(),
746 ))
747 })?;
748
749 Ok(info.clone())
750 }
751}
752
753impl From<&PlaneData> for PlaneKind {
754 fn from(value: &PlaneData) -> Self {
755 match value {
756 PlaneData::XY => PlaneKind::XY,
757 PlaneData::NegXY => PlaneKind::XY,
758 PlaneData::XZ => PlaneKind::XZ,
759 PlaneData::NegXZ => PlaneKind::XZ,
760 PlaneData::YZ => PlaneKind::YZ,
761 PlaneData::NegYZ => PlaneKind::YZ,
762 PlaneData::Plane(_) => PlaneKind::Custom,
763 }
764 }
765}
766
767impl From<&PlaneInfo> for PlaneKind {
768 fn from(value: &PlaneInfo) -> Self {
769 let data = PlaneData::Plane(value.clone());
770 PlaneKind::from(&data)
771 }
772}
773
774impl From<PlaneInfo> for PlaneKind {
775 fn from(value: PlaneInfo) -> Self {
776 let data = PlaneData::Plane(value);
777 PlaneKind::from(&data)
778 }
779}
780
781impl Plane {
782 #[cfg(test)]
783 pub(crate) fn from_plane_data_skipping_engine(
784 value: PlaneData,
785 exec_state: &mut ExecState,
786 ) -> Result<Self, KclError> {
787 let id = exec_state.next_uuid();
788 let kind = PlaneKind::from(&value);
789 Ok(Plane {
790 id,
791 artifact_id: id.into(),
792 info: PlaneInfo::try_from(value)?,
793 object_id: None,
794 kind,
795 meta: vec![],
796 })
797 }
798
799 pub fn is_initialized(&self) -> bool {
801 self.object_id.is_some()
802 }
803
804 pub fn is_uninitialized(&self) -> bool {
806 !self.is_initialized()
807 }
808
809 pub fn is_standard(&self) -> bool {
811 match &self.kind {
812 PlaneKind::XY | PlaneKind::YZ | PlaneKind::XZ => true,
813 PlaneKind::Custom => false,
814 }
815 }
816
817 pub fn project(&self, point: Point3d) -> Point3d {
820 let v = point - self.info.origin;
821 let dot = v.axes_dot_product(&self.info.z_axis);
822
823 point - self.info.z_axis * dot
824 }
825}
826
827#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
829#[ts(export)]
830#[serde(rename_all = "camelCase")]
831pub struct Face {
832 pub id: uuid::Uuid,
834 pub artifact_id: ArtifactId,
836 pub object_id: ObjectId,
838 pub value: String,
840 pub x_axis: Point3d,
842 pub y_axis: Point3d,
844 pub parent_solid: FaceParentSolid,
846 pub units: UnitLength,
847 #[serde(skip)]
848 pub meta: Vec<Metadata>,
849}
850
851#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
853#[ts(export)]
854#[serde(rename_all = "camelCase")]
855pub struct FaceParentSolid {
856 pub solid_id: Uuid,
858 pub creator_sketch_id: Option<Uuid>,
860 pub creator_sketch_is_closed: Option<ProfileClosed>,
862 #[serde(default, skip_serializing_if = "Vec::is_empty")]
864 pub edge_cut_ids: Vec<Uuid>,
865}
866
867impl FaceParentSolid {
868 pub(crate) fn sketch_or_solid_id(&self) -> Uuid {
869 self.creator_sketch_id.unwrap_or(self.solid_id)
870 }
871}
872
873#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
876#[ts(export)]
877#[serde(rename_all = "camelCase")]
878pub struct BoundedEdge {
879 pub face_id: uuid::Uuid,
881 #[serde(skip_serializing_if = "Option::is_none")]
883 pub edge_id: Option<uuid::Uuid>,
884 #[serde(skip_serializing_if = "Option::is_none")]
886 pub edge_specifier: Option<UnresolvedEdgeSpecifier>,
887 pub lower_bound: f32,
890 pub upper_bound: f32,
893}
894
895#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS, FromStr, Display)]
897#[ts(export)]
898#[display(style = "camelCase")]
899pub enum PlaneKind {
900 #[serde(rename = "XY", alias = "xy")]
901 #[display("XY")]
902 XY,
903 #[serde(rename = "XZ", alias = "xz")]
904 #[display("XZ")]
905 XZ,
906 #[serde(rename = "YZ", alias = "yz")]
907 #[display("YZ")]
908 YZ,
909 #[display("Custom")]
911 Custom,
912}
913
914#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
915#[ts(export)]
916#[serde(tag = "type", rename_all = "camelCase")]
917pub struct Sketch {
918 pub id: uuid::Uuid,
920 pub paths: Vec<Path>,
924 #[serde(default, skip_serializing_if = "Vec::is_empty")]
926 pub inner_paths: Vec<Path>,
927 pub on: SketchSurface,
929 pub start: BasePath,
931 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
933 pub tags: IndexMap<String, TagIdentifier>,
934 pub artifact_id: ArtifactId,
937 #[ts(skip)]
938 pub original_id: uuid::Uuid,
939 #[serde(skip_serializing_if = "Option::is_none")]
944 #[ts(skip)]
945 pub origin_sketch_id: Option<uuid::Uuid>,
946 #[serde(skip)]
948 pub mirror: Option<uuid::Uuid>,
949 #[serde(skip)]
951 pub clone: Option<uuid::Uuid>,
952 #[serde(skip)]
954 #[ts(skip)]
955 pub synthetic_jump_path_ids: Vec<uuid::Uuid>,
956 pub units: UnitLength,
957 #[serde(skip)]
959 pub meta: Vec<Metadata>,
960 #[serde(
963 default = "ProfileClosed::explicitly",
964 skip_serializing_if = "ProfileClosed::is_explicitly"
965 )]
966 pub is_closed: ProfileClosed,
967}
968
969impl ProfileClosed {
970 #[expect(dead_code, reason = "it's not actually dead, it's called by serde")]
971 fn explicitly() -> Self {
972 Self::Explicitly
973 }
974
975 fn is_explicitly(&self) -> bool {
976 matches!(self, ProfileClosed::Explicitly)
977 }
978}
979
980#[derive(Debug, Serialize, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd, ts_rs::TS)]
982#[serde(rename_all = "camelCase")]
983pub enum ProfileClosed {
984 No,
986 Maybe,
988 Implicitly,
990 Explicitly,
992}
993
994impl Sketch {
995 pub(crate) fn build_sketch_mode_cmds(
998 &self,
999 exec_state: &mut ExecState,
1000 inner_cmd: ModelingCmdReq,
1001 ) -> Vec<ModelingCmdReq> {
1002 vec![
1003 ModelingCmdReq {
1006 cmd: ModelingCmd::from(
1007 mcmd::EnableSketchMode::builder()
1008 .animated(false)
1009 .ortho(false)
1010 .entity_id(self.on.id())
1011 .adjust_camera(false)
1012 .maybe_planar_normal(if let SketchSurface::Plane(plane) = &self.on {
1013 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
1015 Some(normal.into())
1016 } else {
1017 None
1018 })
1019 .build(),
1020 ),
1021 cmd_id: exec_state.next_uuid().into(),
1022 },
1023 inner_cmd,
1024 ModelingCmdReq {
1025 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::builder().build()),
1026 cmd_id: exec_state.next_uuid().into(),
1027 },
1028 ]
1029 }
1030}
1031
1032#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1034#[ts(export)]
1035#[serde(tag = "type", rename_all = "camelCase")]
1036pub enum SketchSurface {
1037 Plane(Box<Plane>),
1038 Face(Box<Face>),
1039}
1040
1041impl SketchSurface {
1042 pub(crate) fn id(&self) -> uuid::Uuid {
1043 match self {
1044 SketchSurface::Plane(plane) => plane.id,
1045 SketchSurface::Face(face) => face.id,
1046 }
1047 }
1048 pub(crate) fn x_axis(&self) -> Point3d {
1049 match self {
1050 SketchSurface::Plane(plane) => plane.info.x_axis,
1051 SketchSurface::Face(face) => face.x_axis,
1052 }
1053 }
1054 pub(crate) fn y_axis(&self) -> Point3d {
1055 match self {
1056 SketchSurface::Plane(plane) => plane.info.y_axis,
1057 SketchSurface::Face(face) => face.y_axis,
1058 }
1059 }
1060
1061 pub(crate) fn object_id(&self) -> Option<ObjectId> {
1062 match self {
1063 SketchSurface::Plane(plane) => plane.object_id,
1064 SketchSurface::Face(face) => Some(face.object_id),
1065 }
1066 }
1067
1068 pub(crate) fn set_object_id(&mut self, object_id: ObjectId) {
1069 match self {
1070 SketchSurface::Plane(plane) => plane.object_id = Some(object_id),
1071 SketchSurface::Face(face) => face.object_id = object_id,
1072 }
1073 }
1074}
1075
1076#[derive(Debug, Clone, PartialEq)]
1078pub enum Extrudable {
1079 Sketch(Box<Sketch>),
1081 FaceTag(FaceTag),
1083 Face(Box<Face>),
1085 EdgeTag(Box<TagIdentifier>),
1087 Edge(Uuid),
1089 EdgeSpecifier(UnresolvedEdgeSpecifier),
1091}
1092
1093impl Extrudable {
1094 pub async fn id_to_extrude(
1096 &self,
1097 exec_state: &mut ExecState,
1098 args: &Args,
1099 must_be_planar: bool,
1100 ) -> Result<uuid::Uuid, KclError> {
1101 match self {
1102 Extrudable::Sketch(sketch) => Ok(sketch.id),
1103 Extrudable::FaceTag(face_tag) => face_tag.get_face_id_from_tag(exec_state, args, must_be_planar).await,
1104 Extrudable::Face(face) => Ok(face.id),
1105 Extrudable::EdgeTag(edge_tag) => match edge_tag.get_cur_info() {
1106 Some(info) => Ok(info.id),
1107 None => Err(KclError::new_type(KclErrorDetails::new(
1108 "Could not find a valid id to extrude".to_owned(),
1109 vec![args.source_range],
1110 ))),
1111 },
1112 Extrudable::Edge(edge) => Ok(*edge),
1113 Extrudable::EdgeSpecifier(_) => Err(KclError::new_type(KclErrorDetails::new(
1114 "Could not find a legacy id for edge specifier".to_owned(),
1115 vec![args.source_range],
1116 ))),
1117 }
1118 }
1119
1120 pub fn as_sketch(&self) -> Option<Sketch> {
1121 match self {
1122 Extrudable::Sketch(sketch) => Some((**sketch).clone()),
1123 Extrudable::FaceTag(face) => match face.geometry() {
1124 Some(Geometry::Sketch(sketch)) => Some(sketch),
1125 Some(Geometry::Solid(solid)) => solid.sketch().cloned(),
1126 None => None,
1127 },
1128 Extrudable::Face(_) => None,
1129 Extrudable::EdgeTag(tag_identifier) => match tag_identifier.geometry() {
1130 Some(Geometry::Sketch(sketch)) => Some(sketch),
1131 Some(Geometry::Solid(solid)) => solid.sketch().cloned(),
1132 None => None,
1133 },
1134 Extrudable::Edge(_) => None,
1135 Extrudable::EdgeSpecifier(_) => None,
1136 }
1137 }
1138
1139 pub fn is_closed(&self) -> ProfileClosed {
1140 match self {
1141 Extrudable::Sketch(sketch) => sketch.is_closed,
1142 Extrudable::FaceTag(face_tag) => match face_tag.geometry() {
1143 Some(Geometry::Sketch(sketch)) => sketch.is_closed,
1144 Some(Geometry::Solid(solid)) => solid
1145 .sketch()
1146 .map(|sketch| sketch.is_closed)
1147 .unwrap_or(ProfileClosed::Maybe),
1148 _ => ProfileClosed::Maybe,
1149 },
1150 Extrudable::Face(face) => match face.parent_solid.creator_sketch_is_closed {
1151 Some(is_closed) => is_closed,
1152 None => ProfileClosed::Maybe,
1153 },
1154 Extrudable::EdgeTag(edge_tag) => match edge_tag.geometry() {
1155 Some(Geometry::Sketch(sketch)) => sketch.is_closed,
1156 Some(Geometry::Solid(solid)) => solid
1157 .sketch()
1158 .map(|sketch| sketch.is_closed)
1159 .unwrap_or(ProfileClosed::Maybe),
1160 _ => ProfileClosed::Maybe,
1161 },
1162 Extrudable::Edge(_) => ProfileClosed::Maybe,
1163 Extrudable::EdgeSpecifier(_) => ProfileClosed::Maybe,
1164 }
1165 }
1166}
1167
1168impl From<Sketch> for Extrudable {
1169 fn from(value: Sketch) -> Self {
1170 Extrudable::Sketch(Box::new(value))
1171 }
1172}
1173
1174#[derive(Debug, Clone)]
1175pub(crate) enum GetTangentialInfoFromPathsResult {
1176 PreviousPoint([f64; 2]),
1177 Arc {
1178 center: [f64; 2],
1179 ccw: bool,
1180 },
1181 Circle {
1182 center: [f64; 2],
1183 ccw: bool,
1184 radius: f64,
1185 },
1186 Ellipse {
1187 center: [f64; 2],
1188 ccw: bool,
1189 major_axis: [f64; 2],
1190 _minor_radius: f64,
1191 },
1192}
1193
1194impl GetTangentialInfoFromPathsResult {
1195 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
1196 match self {
1197 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
1198 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
1199 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
1200 }
1201 GetTangentialInfoFromPathsResult::Circle {
1204 center, radius, ccw, ..
1205 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
1206 GetTangentialInfoFromPathsResult::Ellipse {
1207 center,
1208 major_axis,
1209 ccw,
1210 ..
1211 } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
1212 }
1213 }
1214}
1215
1216impl Sketch {
1217 pub(crate) fn add_tag(
1218 &mut self,
1219 tag: NodeRef<'_, TagDeclarator>,
1220 current_path: &Path,
1221 exec_state: &ExecState,
1222 surface: Option<&ExtrudeSurface>,
1223 ) {
1224 let mut tag_identifier: TagIdentifier = tag.into();
1225 let base = current_path.get_base();
1226 let mut sketch_copy = self.clone();
1227 sketch_copy.tags.clear();
1228 tag_identifier.info.push((
1229 exec_state.stack().current_epoch(),
1230 TagEngineInfo {
1231 id: base.geo_meta.id,
1232 geometry: Geometry::Sketch(sketch_copy),
1233 path: Some(current_path.clone()),
1234 surface: surface.cloned(),
1235 },
1236 ));
1237
1238 self.tags.insert(tag.name.to_string(), tag_identifier);
1239 }
1240
1241 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
1242 for t in tags {
1243 match self.tags.get_mut(&t.value) {
1244 Some(id) => {
1245 id.merge_info(t);
1246 }
1247 None => {
1248 self.tags.insert(t.value.clone(), t.clone());
1249 }
1250 }
1251 }
1252 }
1253
1254 pub(crate) fn latest_path(&self) -> Option<&Path> {
1256 self.paths.last()
1257 }
1258
1259 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
1263 let Some(path) = self.latest_path() else {
1264 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
1265 };
1266
1267 let to = path.get_base().to;
1268 Ok(Point2d::new(to[0], to[1], path.get_base().units))
1269 }
1270
1271 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
1272 let Some(path) = self.latest_path() else {
1273 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
1274 };
1275 path.get_tangential_info()
1276 }
1277}
1278
1279#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1280#[ts(export)]
1281#[serde(tag = "type", rename_all = "camelCase")]
1282pub struct Solid {
1283 pub id: uuid::Uuid,
1285 #[serde(skip)]
1287 #[ts(skip)]
1288 pub value_id: uuid::Uuid,
1289 #[serde(skip)]
1293 #[ts(skip)]
1294 pub(crate) topology_id: uuid::Uuid,
1295 #[serde(skip)]
1299 #[ts(skip)]
1300 pub(crate) pattern_source_artifact_id: Option<ArtifactId>,
1301 #[serde(skip)]
1307 #[ts(skip)]
1308 pub(crate) best_guess_body_type: Option<kcmc::shared::BodyType>,
1309 pub artifact_id: ArtifactId,
1311 pub value: Vec<ExtrudeSurface>,
1313 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
1316 pub faces: IndexMap<String, TagIdentifier>,
1317 #[serde(rename = "sketch")]
1319 pub creator: SolidCreator,
1320 pub start_cap_id: Option<uuid::Uuid>,
1322 pub end_cap_id: Option<uuid::Uuid>,
1324 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1326 pub edge_cuts: Vec<EdgeCut>,
1327 #[serde(skip)]
1329 #[ts(skip)]
1330 pub pending_edge_cut_ids: Vec<uuid::Uuid>,
1331 pub units: UnitLength,
1333 pub sectional: bool,
1335 #[serde(skip)]
1337 pub meta: Vec<Metadata>,
1338}
1339
1340#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1341#[ts(export)]
1342pub struct CreatorFace {
1343 pub face_id: uuid::Uuid,
1345 pub solid_id: uuid::Uuid,
1347 pub sketch: Sketch,
1349}
1350
1351#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1352#[ts(export)]
1353pub struct CreatorEdge {
1354 pub edge_id: uuid::Uuid,
1356 pub body_id: uuid::Uuid,
1358}
1359
1360#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1362#[ts(export)]
1363#[serde(tag = "creatorType", rename_all = "camelCase")]
1364pub enum SolidCreator {
1365 Sketch(Sketch),
1367 Face(CreatorFace),
1369 Edge(CreatorEdge),
1371 Procedural,
1373}
1374
1375impl Solid {
1376 pub fn sketch(&self) -> Option<&Sketch> {
1377 match &self.creator {
1378 SolidCreator::Sketch(sketch) => Some(sketch),
1379 SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1380 SolidCreator::Edge(_) => None,
1381 SolidCreator::Procedural => None,
1382 }
1383 }
1384
1385 pub fn sketch_mut(&mut self) -> Option<&mut Sketch> {
1386 match &mut self.creator {
1387 SolidCreator::Sketch(sketch) => Some(sketch),
1388 SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1389 SolidCreator::Edge(_) => None,
1390 SolidCreator::Procedural => None,
1391 }
1392 }
1393
1394 pub fn sketch_id(&self) -> Option<uuid::Uuid> {
1395 self.sketch().map(|sketch| sketch.id)
1396 }
1397
1398 pub fn original_id(&self) -> uuid::Uuid {
1399 self.sketch().map(|sketch| sketch.original_id).unwrap_or(self.id)
1400 }
1401
1402 pub(crate) fn topology_id(&self) -> uuid::Uuid {
1403 self.topology_id
1404 }
1405
1406 pub(crate) fn become_new_body(&mut self, engine_id: uuid::Uuid, artifact_id: ArtifactId) {
1410 self.topology_id = engine_id;
1411 self.pattern_source_artifact_id = None;
1412 self.artifact_id = artifact_id;
1413 }
1414
1415 pub(crate) fn become_pattern_copy(&mut self, copy_engine_id: uuid::Uuid) {
1419 self.pattern_source_artifact_id.get_or_insert(self.artifact_id);
1420 self.artifact_id = ArtifactId::new(copy_engine_id);
1421 }
1422
1423 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
1424 self.edge_cuts
1425 .iter()
1426 .map(|foc| foc.id())
1427 .chain(self.pending_edge_cut_ids.iter().copied())
1428 }
1429}
1430
1431impl From<&Solid> for FaceParentSolid {
1432 fn from(solid: &Solid) -> Self {
1433 Self {
1434 solid_id: solid.id,
1435 creator_sketch_id: solid.sketch_id(),
1436 creator_sketch_is_closed: solid.sketch().map(|sketch| sketch.is_closed),
1437 edge_cut_ids: solid.get_all_edge_cut_ids().collect(),
1438 }
1439 }
1440}
1441
1442#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1444#[ts(export)]
1445#[serde(tag = "type", rename_all = "camelCase")]
1446pub enum EdgeCut {
1447 Fillet {
1449 id: uuid::Uuid,
1451 radius: TyF64,
1452 #[serde(rename = "edgeId")]
1454 edge_id: uuid::Uuid,
1455 tag: Box<Option<TagNode>>,
1456 },
1457 Chamfer {
1459 id: uuid::Uuid,
1461 length: TyF64,
1462 #[serde(rename = "edgeId")]
1464 edge_id: uuid::Uuid,
1465 tag: Box<Option<TagNode>>,
1466 },
1467}
1468
1469impl EdgeCut {
1470 pub fn id(&self) -> uuid::Uuid {
1471 match self {
1472 EdgeCut::Fillet { id, .. } => *id,
1473 EdgeCut::Chamfer { id, .. } => *id,
1474 }
1475 }
1476
1477 pub fn set_id(&mut self, id: uuid::Uuid) {
1478 match self {
1479 EdgeCut::Fillet { id: i, .. } => *i = id,
1480 EdgeCut::Chamfer { id: i, .. } => *i = id,
1481 }
1482 }
1483
1484 pub fn edge_id(&self) -> uuid::Uuid {
1485 match self {
1486 EdgeCut::Fillet { edge_id, .. } => *edge_id,
1487 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
1488 }
1489 }
1490
1491 pub fn set_edge_id(&mut self, id: uuid::Uuid) {
1492 match self {
1493 EdgeCut::Fillet { edge_id: i, .. } => *i = id,
1494 EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
1495 }
1496 }
1497
1498 pub fn tag(&self) -> Option<TagNode> {
1499 match self {
1500 EdgeCut::Fillet { tag, .. } => *tag.clone(),
1501 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
1502 }
1503 }
1504}
1505
1506#[derive(Debug, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
1507#[ts(export)]
1508pub struct Point2d {
1509 pub x: f64,
1510 pub y: f64,
1511 pub units: UnitLength,
1512}
1513
1514impl Point2d {
1515 pub const ZERO: Self = Self {
1516 x: 0.0,
1517 y: 0.0,
1518 units: UnitLength::Millimeters,
1519 };
1520
1521 pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
1522 Self { x, y, units }
1523 }
1524
1525 pub fn into_x(self) -> TyF64 {
1526 TyF64::new(self.x, NumericType::length(self.units))
1527 }
1528
1529 pub fn into_y(self) -> TyF64 {
1530 TyF64::new(self.y, NumericType::length(self.units))
1531 }
1532
1533 pub fn ignore_units(self) -> [f64; 2] {
1534 [self.x, self.y]
1535 }
1536}
1537
1538#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
1539#[ts(export)]
1540pub struct Point3d {
1541 pub x: f64,
1542 pub y: f64,
1543 pub z: f64,
1544 pub units: Option<UnitLength>,
1545}
1546
1547impl Point3d {
1548 pub const ZERO: Self = Self {
1549 x: 0.0,
1550 y: 0.0,
1551 z: 0.0,
1552 units: Some(UnitLength::Millimeters),
1553 };
1554
1555 pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
1556 Self { x, y, z, units }
1557 }
1558
1559 pub const fn is_zero(&self) -> bool {
1560 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
1561 }
1562
1563 pub fn axes_cross_product(&self, other: &Self) -> Self {
1568 Self {
1569 x: self.y * other.z - self.z * other.y,
1570 y: self.z * other.x - self.x * other.z,
1571 z: self.x * other.y - self.y * other.x,
1572 units: None,
1573 }
1574 }
1575
1576 pub fn canonicalize_signed_zero(&mut self) {
1578 if self.x == 0.0 {
1579 self.x = 0.0;
1580 }
1581 if self.y == 0.0 {
1582 self.y = 0.0;
1583 }
1584 if self.z == 0.0 {
1585 self.z = 0.0;
1586 }
1587 }
1588
1589 pub fn axes_dot_product(&self, other: &Self) -> f64 {
1594 let x = self.x * other.x;
1595 let y = self.y * other.y;
1596 let z = self.z * other.z;
1597 x + y + z
1598 }
1599
1600 pub fn normalize(&self) -> Self {
1601 let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1602 Point3d {
1603 x: self.x / len,
1604 y: self.y / len,
1605 z: self.z / len,
1606 units: None,
1607 }
1608 }
1609
1610 pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1611 let p = [self.x, self.y, self.z];
1612 let u = self.units;
1613 (p, u)
1614 }
1615
1616 pub(crate) fn negated(self) -> Self {
1617 Self {
1618 x: -self.x,
1619 y: -self.y,
1620 z: -self.z,
1621 units: self.units,
1622 }
1623 }
1624}
1625
1626impl From<[TyF64; 3]> for Point3d {
1627 fn from(p: [TyF64; 3]) -> Self {
1628 Self {
1629 x: p[0].n,
1630 y: p[1].n,
1631 z: p[2].n,
1632 units: p[0].ty.as_length(),
1633 }
1634 }
1635}
1636
1637impl From<Point3d> for Point3D {
1638 fn from(p: Point3d) -> Self {
1639 Self { x: p.x, y: p.y, z: p.z }
1640 }
1641}
1642
1643impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1644 fn from(p: Point3d) -> Self {
1645 if let Some(units) = p.units {
1646 Self {
1647 x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1648 y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1649 z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1650 }
1651 } else {
1652 Self {
1653 x: LengthUnit(p.x),
1654 y: LengthUnit(p.y),
1655 z: LengthUnit(p.z),
1656 }
1657 }
1658 }
1659}
1660
1661impl Add for Point3d {
1662 type Output = Point3d;
1663
1664 fn add(self, rhs: Self) -> Self::Output {
1665 Point3d {
1667 x: self.x + rhs.x,
1668 y: self.y + rhs.y,
1669 z: self.z + rhs.z,
1670 units: self.units,
1671 }
1672 }
1673}
1674
1675impl AddAssign for Point3d {
1676 fn add_assign(&mut self, rhs: Self) {
1677 *self = *self + rhs
1678 }
1679}
1680
1681impl Sub for Point3d {
1682 type Output = Point3d;
1683
1684 fn sub(self, rhs: Self) -> Self::Output {
1685 let (x, y, z) = if rhs.units != self.units
1686 && let Some(sunits) = self.units
1687 && let Some(runits) = rhs.units
1688 {
1689 (
1690 adjust_length(runits, rhs.x, sunits).0,
1691 adjust_length(runits, rhs.y, sunits).0,
1692 adjust_length(runits, rhs.z, sunits).0,
1693 )
1694 } else {
1695 (rhs.x, rhs.y, rhs.z)
1696 };
1697 Point3d {
1698 x: self.x - x,
1699 y: self.y - y,
1700 z: self.z - z,
1701 units: self.units,
1702 }
1703 }
1704}
1705
1706impl SubAssign for Point3d {
1707 fn sub_assign(&mut self, rhs: Self) {
1708 *self = *self - rhs
1709 }
1710}
1711
1712impl Mul<f64> for Point3d {
1713 type Output = Point3d;
1714
1715 fn mul(self, rhs: f64) -> Self::Output {
1716 Point3d {
1717 x: self.x * rhs,
1718 y: self.y * rhs,
1719 z: self.z * rhs,
1720 units: self.units,
1721 }
1722 }
1723}
1724
1725#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1727#[ts(export)]
1728#[serde(rename_all = "camelCase")]
1729pub struct BasePath {
1730 #[ts(type = "[number, number]")]
1732 pub from: [f64; 2],
1733 #[ts(type = "[number, number]")]
1735 pub to: [f64; 2],
1736 pub units: UnitLength,
1737 pub tag: Option<TagNode>,
1739 #[serde(rename = "__geoMeta")]
1741 pub geo_meta: GeoMeta,
1742}
1743
1744impl BasePath {
1745 pub fn get_to(&self) -> [TyF64; 2] {
1746 let ty = NumericType::length(self.units);
1747 [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1748 }
1749
1750 pub fn get_from(&self) -> [TyF64; 2] {
1751 let ty = NumericType::length(self.units);
1752 [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1753 }
1754}
1755
1756#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1758#[ts(export)]
1759#[serde(rename_all = "camelCase")]
1760pub struct GeoMeta {
1761 pub id: uuid::Uuid,
1763 #[serde(flatten)]
1765 pub metadata: Metadata,
1766}
1767
1768#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1770#[ts(export)]
1771#[serde(tag = "type")]
1772pub enum Path {
1773 ToPoint {
1775 #[serde(flatten)]
1776 base: BasePath,
1777 },
1778 TangentialArcTo {
1780 #[serde(flatten)]
1781 base: BasePath,
1782 #[ts(type = "[number, number]")]
1784 center: [f64; 2],
1785 ccw: bool,
1787 },
1788 TangentialArc {
1790 #[serde(flatten)]
1791 base: BasePath,
1792 #[ts(type = "[number, number]")]
1794 center: [f64; 2],
1795 ccw: bool,
1797 },
1798 Circle {
1801 #[serde(flatten)]
1802 base: BasePath,
1803 #[ts(type = "[number, number]")]
1805 center: [f64; 2],
1806 radius: f64,
1808 ccw: bool,
1811 },
1812 CircleThreePoint {
1813 #[serde(flatten)]
1814 base: BasePath,
1815 #[ts(type = "[number, number]")]
1817 p1: [f64; 2],
1818 #[ts(type = "[number, number]")]
1820 p2: [f64; 2],
1821 #[ts(type = "[number, number]")]
1823 p3: [f64; 2],
1824 },
1825 ArcThreePoint {
1826 #[serde(flatten)]
1827 base: BasePath,
1828 #[ts(type = "[number, number]")]
1830 p1: [f64; 2],
1831 #[ts(type = "[number, number]")]
1833 p2: [f64; 2],
1834 #[ts(type = "[number, number]")]
1836 p3: [f64; 2],
1837 },
1838 Horizontal {
1840 #[serde(flatten)]
1841 base: BasePath,
1842 x: f64,
1844 },
1845 AngledLineTo {
1847 #[serde(flatten)]
1848 base: BasePath,
1849 x: Option<f64>,
1851 y: Option<f64>,
1853 },
1854 Base {
1856 #[serde(flatten)]
1857 base: BasePath,
1858 },
1859 Arc {
1861 #[serde(flatten)]
1862 base: BasePath,
1863 center: [f64; 2],
1865 radius: f64,
1867 ccw: bool,
1869 },
1870 Ellipse {
1871 #[serde(flatten)]
1872 base: BasePath,
1873 center: [f64; 2],
1874 major_axis: [f64; 2],
1875 minor_radius: f64,
1876 ccw: bool,
1877 },
1878 Conic {
1880 #[serde(flatten)]
1881 base: BasePath,
1882 },
1883 Bezier {
1885 #[serde(flatten)]
1886 base: BasePath,
1887 #[ts(type = "[number, number]")]
1889 control1: [f64; 2],
1890 #[ts(type = "[number, number]")]
1892 control2: [f64; 2],
1893 },
1894}
1895
1896impl Path {
1897 pub fn get_id(&self) -> uuid::Uuid {
1898 match self {
1899 Path::ToPoint { base } => base.geo_meta.id,
1900 Path::Horizontal { base, .. } => base.geo_meta.id,
1901 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1902 Path::Base { base } => base.geo_meta.id,
1903 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1904 Path::TangentialArc { base, .. } => base.geo_meta.id,
1905 Path::Circle { base, .. } => base.geo_meta.id,
1906 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1907 Path::Arc { base, .. } => base.geo_meta.id,
1908 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1909 Path::Ellipse { base, .. } => base.geo_meta.id,
1910 Path::Conic { base, .. } => base.geo_meta.id,
1911 Path::Bezier { base, .. } => base.geo_meta.id,
1912 }
1913 }
1914
1915 pub fn set_id(&mut self, id: uuid::Uuid) {
1916 match self {
1917 Path::ToPoint { base } => base.geo_meta.id = id,
1918 Path::Horizontal { base, .. } => base.geo_meta.id = id,
1919 Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1920 Path::Base { base } => base.geo_meta.id = id,
1921 Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1922 Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1923 Path::Circle { base, .. } => base.geo_meta.id = id,
1924 Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1925 Path::Arc { base, .. } => base.geo_meta.id = id,
1926 Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1927 Path::Ellipse { base, .. } => base.geo_meta.id = id,
1928 Path::Conic { base, .. } => base.geo_meta.id = id,
1929 Path::Bezier { base, .. } => base.geo_meta.id = id,
1930 }
1931 }
1932
1933 pub fn get_tag(&self) -> Option<TagNode> {
1934 match self {
1935 Path::ToPoint { base } => base.tag.clone(),
1936 Path::Horizontal { base, .. } => base.tag.clone(),
1937 Path::AngledLineTo { base, .. } => base.tag.clone(),
1938 Path::Base { base } => base.tag.clone(),
1939 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1940 Path::TangentialArc { base, .. } => base.tag.clone(),
1941 Path::Circle { base, .. } => base.tag.clone(),
1942 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1943 Path::Arc { base, .. } => base.tag.clone(),
1944 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1945 Path::Ellipse { base, .. } => base.tag.clone(),
1946 Path::Conic { base, .. } => base.tag.clone(),
1947 Path::Bezier { base, .. } => base.tag.clone(),
1948 }
1949 }
1950
1951 pub fn get_base(&self) -> &BasePath {
1952 match self {
1953 Path::ToPoint { base } => base,
1954 Path::Horizontal { base, .. } => base,
1955 Path::AngledLineTo { base, .. } => base,
1956 Path::Base { base } => base,
1957 Path::TangentialArcTo { base, .. } => base,
1958 Path::TangentialArc { base, .. } => base,
1959 Path::Circle { base, .. } => base,
1960 Path::CircleThreePoint { base, .. } => base,
1961 Path::Arc { base, .. } => base,
1962 Path::ArcThreePoint { base, .. } => base,
1963 Path::Ellipse { base, .. } => base,
1964 Path::Conic { base, .. } => base,
1965 Path::Bezier { base, .. } => base,
1966 }
1967 }
1968
1969 pub fn get_from(&self) -> [TyF64; 2] {
1971 let p = &self.get_base().from;
1972 let ty = NumericType::length(self.get_base().units);
1973 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1974 }
1975
1976 pub fn get_to(&self) -> [TyF64; 2] {
1978 let p = &self.get_base().to;
1979 let ty = NumericType::length(self.get_base().units);
1980 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1981 }
1982
1983 pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1985 let p = &self.get_base().from;
1986 let ty = NumericType::length(self.get_base().units);
1987 (*p, ty)
1988 }
1989
1990 pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1992 let p = &self.get_base().to;
1993 let ty = NumericType::length(self.get_base().units);
1994 (*p, ty)
1995 }
1996
1997 pub fn length(&self) -> Option<TyF64> {
2000 let n = match self {
2001 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
2002 Some(linear_distance(&self.get_base().from, &self.get_base().to))
2003 }
2004 Self::TangentialArc {
2005 base: _,
2006 center,
2007 ccw: _,
2008 }
2009 | Self::TangentialArcTo {
2010 base: _,
2011 center,
2012 ccw: _,
2013 } => {
2014 let radius = linear_distance(&self.get_base().from, center);
2017 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
2018 Some(linear_distance(&self.get_base().from, &self.get_base().to))
2020 }
2021 Self::Circle { radius, .. } => Some(TAU * radius),
2022 Self::CircleThreePoint { .. } => {
2023 let circle_center = crate::std::utils::calculate_circle_from_3_points([
2024 self.get_base().from,
2025 self.get_base().to,
2026 self.get_base().to,
2027 ]);
2028 let radius = linear_distance(
2029 &[circle_center.center[0], circle_center.center[1]],
2030 &self.get_base().from,
2031 );
2032 Some(TAU * radius)
2033 }
2034 Self::Arc { .. } => {
2035 Some(linear_distance(&self.get_base().from, &self.get_base().to))
2037 }
2038 Self::ArcThreePoint { .. } => {
2039 Some(linear_distance(&self.get_base().from, &self.get_base().to))
2041 }
2042 Self::Ellipse { .. } => {
2043 None
2045 }
2046 Self::Conic { .. } => {
2047 None
2049 }
2050 Self::Bezier { .. } => {
2051 None
2053 }
2054 };
2055 n.map(|n| TyF64::new(n, NumericType::length(self.get_base().units)))
2056 }
2057
2058 pub fn get_base_mut(&mut self) -> &mut BasePath {
2059 match self {
2060 Path::ToPoint { base } => base,
2061 Path::Horizontal { base, .. } => base,
2062 Path::AngledLineTo { base, .. } => base,
2063 Path::Base { base } => base,
2064 Path::TangentialArcTo { base, .. } => base,
2065 Path::TangentialArc { base, .. } => base,
2066 Path::Circle { base, .. } => base,
2067 Path::CircleThreePoint { base, .. } => base,
2068 Path::Arc { base, .. } => base,
2069 Path::ArcThreePoint { base, .. } => base,
2070 Path::Ellipse { base, .. } => base,
2071 Path::Conic { base, .. } => base,
2072 Path::Bezier { base, .. } => base,
2073 }
2074 }
2075
2076 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
2077 match self {
2078 Path::TangentialArc { center, ccw, .. }
2079 | Path::TangentialArcTo { center, ccw, .. }
2080 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
2081 center: *center,
2082 ccw: *ccw,
2083 },
2084 Path::ArcThreePoint { p1, p2, p3, .. } => {
2085 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
2086 GetTangentialInfoFromPathsResult::Arc {
2087 center: circle.center,
2088 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
2089 }
2090 }
2091 Path::Circle {
2092 center, ccw, radius, ..
2093 } => GetTangentialInfoFromPathsResult::Circle {
2094 center: *center,
2095 ccw: *ccw,
2096 radius: *radius,
2097 },
2098 Path::CircleThreePoint { p1, p2, p3, .. } => {
2099 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
2100 let center_point = [circle.center[0], circle.center[1]];
2101 GetTangentialInfoFromPathsResult::Circle {
2102 center: center_point,
2103 ccw: true,
2105 radius: circle.radius,
2106 }
2107 }
2108 Path::Ellipse {
2110 center,
2111 major_axis,
2112 minor_radius,
2113 ccw,
2114 ..
2115 } => GetTangentialInfoFromPathsResult::Ellipse {
2116 center: *center,
2117 major_axis: *major_axis,
2118 _minor_radius: *minor_radius,
2119 ccw: *ccw,
2120 },
2121 Path::Conic { .. }
2122 | Path::ToPoint { .. }
2123 | Path::Horizontal { .. }
2124 | Path::AngledLineTo { .. }
2125 | Path::Base { .. }
2126 | Path::Bezier { .. } => {
2127 let base = self.get_base();
2128 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
2129 }
2130 }
2131 }
2132
2133 pub(crate) fn is_straight_line(&self) -> bool {
2135 matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
2136 }
2137}
2138
2139#[rustfmt::skip]
2141fn linear_distance(
2142 [x0, y0]: &[f64; 2],
2143 [x1, y1]: &[f64; 2]
2144) -> f64 {
2145 let y_sq = (y1 - y0).squared();
2146 let x_sq = (x1 - x0).squared();
2147 (y_sq + x_sq).sqrt()
2148}
2149
2150#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2152#[ts(export)]
2153#[serde(tag = "type", rename_all = "camelCase")]
2154pub enum ExtrudeSurface {
2155 ExtrudePlane(ExtrudePlane),
2157 ExtrudeArc(ExtrudeArc),
2158 Chamfer(ChamferSurface),
2159 Fillet(FilletSurface),
2160}
2161
2162#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2164#[ts(export)]
2165#[serde(rename_all = "camelCase")]
2166pub struct ChamferSurface {
2167 pub face_id: uuid::Uuid,
2169 pub tag: Option<Node<TagDeclarator>>,
2171 #[serde(flatten)]
2173 pub geo_meta: GeoMeta,
2174}
2175
2176#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2178#[ts(export)]
2179#[serde(rename_all = "camelCase")]
2180pub struct FilletSurface {
2181 pub face_id: uuid::Uuid,
2183 pub tag: Option<Node<TagDeclarator>>,
2185 #[serde(flatten)]
2187 pub geo_meta: GeoMeta,
2188}
2189
2190#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2192#[ts(export)]
2193#[serde(rename_all = "camelCase")]
2194pub struct ExtrudePlane {
2195 pub face_id: uuid::Uuid,
2197 pub tag: Option<Node<TagDeclarator>>,
2199 #[serde(flatten)]
2201 pub geo_meta: GeoMeta,
2202}
2203
2204#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2206#[ts(export)]
2207#[serde(rename_all = "camelCase")]
2208pub struct ExtrudeArc {
2209 pub face_id: uuid::Uuid,
2211 pub tag: Option<Node<TagDeclarator>>,
2213 #[serde(flatten)]
2215 pub geo_meta: GeoMeta,
2216}
2217
2218impl ExtrudeSurface {
2219 pub fn get_id(&self) -> uuid::Uuid {
2220 match self {
2221 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
2222 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
2223 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
2224 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
2225 }
2226 }
2227
2228 pub fn face_id(&self) -> uuid::Uuid {
2229 match self {
2230 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id,
2231 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id,
2232 ExtrudeSurface::Fillet(f) => f.face_id,
2233 ExtrudeSurface::Chamfer(c) => c.face_id,
2234 }
2235 }
2236
2237 pub fn set_face_id(&mut self, face_id: uuid::Uuid) {
2238 match self {
2239 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id = face_id,
2240 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id = face_id,
2241 ExtrudeSurface::Fillet(f) => f.face_id = face_id,
2242 ExtrudeSurface::Chamfer(c) => c.face_id = face_id,
2243 }
2244 }
2245
2246 pub fn set_surface_tag(&mut self, tag: &TagNode) {
2247 match self {
2248 ExtrudeSurface::ExtrudePlane(extrude_plane) => extrude_plane.tag = Some(tag.clone()),
2249 ExtrudeSurface::ExtrudeArc(extrude_arc) => extrude_arc.tag = Some(tag.clone()),
2250 ExtrudeSurface::Chamfer(chamfer) => chamfer.tag = Some(tag.clone()),
2251 ExtrudeSurface::Fillet(fillet) => fillet.tag = Some(tag.clone()),
2252 }
2253 }
2254
2255 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
2256 match self {
2257 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
2258 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
2259 ExtrudeSurface::Fillet(f) => f.tag.clone(),
2260 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
2261 }
2262 }
2263}
2264
2265#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ts_rs::TS)]
2266pub struct SketchVarId(pub usize);
2267
2268impl SketchVarId {
2269 pub const INVALID: Self = Self(usize::MAX);
2270
2271 pub fn to_constraint_id(self, range: SourceRange) -> Result<ezpz::Id, KclError> {
2272 self.0.try_into().map_err(|_| {
2273 KclError::new_type(KclErrorDetails::new(
2274 "Cannot convert to constraint ID since the sketch variable ID is too large".to_owned(),
2275 vec![range],
2276 ))
2277 })
2278 }
2279}
2280
2281#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2282#[ts(export_to = "Geometry.ts")]
2283#[serde(rename_all = "camelCase")]
2284pub struct SketchVar {
2285 pub id: SketchVarId,
2286 pub initial_value: f64,
2287 pub ty: NumericType,
2288 pub node_path: Option<NodePath>,
2290 #[serde(skip)]
2291 pub meta: Vec<Metadata>,
2292}
2293
2294impl SketchVar {
2295 pub fn initial_value_to_solver_units(
2296 &self,
2297 exec_state: &mut ExecState,
2298 source_range: SourceRange,
2299 description: &str,
2300 ) -> Result<TyF64, KclError> {
2301 let x_initial_value = KclValue::Number {
2302 value: self.initial_value,
2303 ty: self.ty,
2304 meta: vec![source_range.into()],
2305 };
2306 let normalized_value =
2307 normalize_to_solver_distance_unit(&x_initial_value, source_range, exec_state, description)?;
2308 normalized_value.as_ty_f64().ok_or_else(|| {
2309 let message = format!(
2310 "Expected number after coercion, but found {}",
2311 normalized_value.human_friendly_type()
2312 );
2313 debug_assert!(false, "{}", &message);
2314 KclError::new_internal(KclErrorDetails::new(message, vec![source_range]))
2315 })
2316 }
2317}
2318
2319#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2320#[ts(export_to = "Geometry.ts")]
2321#[serde(tag = "type")]
2322pub enum UnsolvedExpr {
2323 Known(TyF64),
2324 Unknown(SketchVarId),
2325}
2326
2327impl UnsolvedExpr {
2328 pub fn var(&self) -> Option<SketchVarId> {
2329 match self {
2330 UnsolvedExpr::Known(_) => None,
2331 UnsolvedExpr::Unknown(id) => Some(*id),
2332 }
2333 }
2334}
2335
2336pub type UnsolvedPoint2dExpr = [UnsolvedExpr; 2];
2337
2338#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2339#[ts(export_to = "Geometry.ts")]
2340#[serde(rename_all = "camelCase")]
2341pub struct ConstrainablePoint2d {
2342 pub vars: crate::front::Point2d<SketchVarId>,
2343 pub object_id: ObjectId,
2344}
2345
2346#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2347#[ts(export_to = "Geometry.ts")]
2348pub enum ConstrainablePoint2dOrOrigin {
2349 Point(ConstrainablePoint2d),
2350 Origin,
2351}
2352
2353#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2354#[ts(export_to = "Geometry.ts")]
2355#[serde(rename_all = "camelCase")]
2356pub struct ConstrainableLine2d {
2357 pub vars: [crate::front::Point2d<SketchVarId>; 2],
2358 pub object_id: ObjectId,
2359}
2360
2361#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2362#[ts(export_to = "Geometry.ts")]
2363#[serde(rename_all = "camelCase")]
2364pub struct UnsolvedSegment {
2365 pub id: Uuid,
2367 pub object_id: ObjectId,
2368 pub kind: UnsolvedSegmentKind,
2369 #[serde(skip_serializing_if = "Option::is_none")]
2370 pub tag: Option<TagIdentifier>,
2371 #[serde(skip)]
2372 pub node_path: Option<NodePath>,
2373 #[serde(skip)]
2374 pub meta: Vec<Metadata>,
2375}
2376
2377#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2378#[ts(export_to = "Geometry.ts")]
2379#[serde(rename_all = "camelCase")]
2380pub enum UnsolvedSegmentKind {
2381 Point {
2382 position: UnsolvedPoint2dExpr,
2383 ctor: Box<PointCtor>,
2384 },
2385 Line {
2386 start: UnsolvedPoint2dExpr,
2387 end: UnsolvedPoint2dExpr,
2388 ctor: Box<LineCtor>,
2389 start_object_id: ObjectId,
2390 end_object_id: ObjectId,
2391 construction: bool,
2392 },
2393 Arc {
2394 start: UnsolvedPoint2dExpr,
2395 end: UnsolvedPoint2dExpr,
2396 center: UnsolvedPoint2dExpr,
2397 ctor: Box<ArcCtor>,
2398 start_object_id: ObjectId,
2399 end_object_id: ObjectId,
2400 center_object_id: ObjectId,
2401 #[serde(default, skip_serializing_if = "ArcDirection::is_ccw")]
2407 #[ts(as = "Option<ArcDirection>")]
2408 #[ts(optional)]
2409 direction: ArcDirection,
2410 construction: bool,
2411 },
2412 Circle {
2413 start: UnsolvedPoint2dExpr,
2414 center: UnsolvedPoint2dExpr,
2415 ctor: Box<CircleCtor>,
2416 start_object_id: ObjectId,
2417 center_object_id: ObjectId,
2418 construction: bool,
2419 },
2420 ControlPointSpline {
2421 controls: Vec<UnsolvedPoint2dExpr>,
2422 ctor: Box<ControlPointSplineCtor>,
2423 control_object_ids: Vec<ObjectId>,
2424 control_polygon_edge_object_ids: Vec<ObjectId>,
2425 degree: u32,
2426 construction: bool,
2427 },
2428}
2429
2430impl UnsolvedSegmentKind {
2431 pub fn human_friendly_kind_with_article(&self) -> &'static str {
2434 match self {
2435 Self::Point { .. } => "a Point",
2436 Self::Line { .. } => "a Line",
2437 Self::Arc { .. } => "an Arc",
2438 Self::Circle { .. } => "a Circle",
2439 Self::ControlPointSpline { .. } => "a Control Point Spline",
2440 }
2441 }
2442}
2443
2444#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2445#[ts(export_to = "Geometry.ts")]
2446#[serde(rename_all = "camelCase")]
2447pub struct Segment {
2448 pub id: Uuid,
2450 pub object_id: ObjectId,
2451 pub kind: SegmentKind,
2452 pub surface: SketchSurface,
2453 pub sketch_id: Uuid,
2455 #[serde(skip)]
2456 #[ts(skip)]
2457 pub sketch: Option<Arc<Sketch>>,
2458 #[serde(skip_serializing_if = "Option::is_none")]
2459 pub tag: Option<TagIdentifier>,
2460 #[serde(skip)]
2461 pub node_path: Option<NodePath>,
2462 #[serde(skip)]
2463 pub meta: Vec<Metadata>,
2464}
2465
2466impl Segment {
2467 pub fn is_construction(&self) -> bool {
2468 match &self.kind {
2469 SegmentKind::Point { .. } => true,
2470 SegmentKind::Line { construction, .. } => *construction,
2471 SegmentKind::Arc { construction, .. } => *construction,
2472 SegmentKind::Circle { construction, .. } => *construction,
2473 SegmentKind::ControlPointSpline { construction, .. } => *construction,
2474 }
2475 }
2476}
2477
2478#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2479#[ts(export_to = "Geometry.ts")]
2480#[serde(rename_all = "camelCase")]
2481pub enum SegmentKind {
2482 Point {
2483 position: [TyF64; 2],
2484 ctor: Box<PointCtor>,
2485 #[serde(skip_serializing_if = "Option::is_none")]
2486 freedom: Option<Freedom>,
2487 },
2488 Line {
2489 start: [TyF64; 2],
2490 end: [TyF64; 2],
2491 ctor: Box<LineCtor>,
2492 start_object_id: ObjectId,
2493 end_object_id: ObjectId,
2494 #[serde(skip_serializing_if = "Option::is_none")]
2495 start_freedom: Option<Freedom>,
2496 #[serde(skip_serializing_if = "Option::is_none")]
2497 end_freedom: Option<Freedom>,
2498 construction: bool,
2499 },
2500 Arc {
2501 start: [TyF64; 2],
2502 end: [TyF64; 2],
2503 center: [TyF64; 2],
2504 ctor: Box<ArcCtor>,
2505 start_object_id: ObjectId,
2506 end_object_id: ObjectId,
2507 center_object_id: ObjectId,
2508 #[serde(skip_serializing_if = "Option::is_none")]
2509 start_freedom: Option<Freedom>,
2510 #[serde(skip_serializing_if = "Option::is_none")]
2511 end_freedom: Option<Freedom>,
2512 #[serde(skip_serializing_if = "Option::is_none")]
2513 center_freedom: Option<Freedom>,
2514 #[serde(default, skip_serializing_if = "ArcDirection::is_ccw")]
2517 #[ts(as = "Option<ArcDirection>")]
2518 #[ts(optional)]
2519 direction: ArcDirection,
2520 construction: bool,
2521 },
2522 Circle {
2523 start: [TyF64; 2],
2524 center: [TyF64; 2],
2525 ctor: Box<CircleCtor>,
2526 start_object_id: ObjectId,
2527 center_object_id: ObjectId,
2528 #[serde(skip_serializing_if = "Option::is_none")]
2529 start_freedom: Option<Freedom>,
2530 #[serde(skip_serializing_if = "Option::is_none")]
2531 center_freedom: Option<Freedom>,
2532 construction: bool,
2533 },
2534 ControlPointSpline {
2535 controls: Vec<[TyF64; 2]>,
2536 ctor: Box<ControlPointSplineCtor>,
2537 control_object_ids: Vec<ObjectId>,
2538 control_polygon_edge_object_ids: Vec<ObjectId>,
2539 #[serde(skip_serializing_if = "Vec::is_empty")]
2540 control_freedoms: Vec<Option<Freedom>>,
2541 degree: u32,
2542 construction: bool,
2543 },
2544}
2545
2546#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2547#[ts(export_to = "Geometry.ts")]
2548#[serde(rename_all = "camelCase")]
2549pub struct AbstractSegment {
2550 pub repr: SegmentRepr,
2551 #[serde(skip)]
2552 pub meta: Vec<Metadata>,
2553}
2554
2555#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2556pub enum SegmentRepr {
2557 Unsolved { segment: Box<UnsolvedSegment> },
2558 Solved { segment: Box<Segment> },
2559}
2560
2561#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2562#[ts(export_to = "Geometry.ts")]
2563#[serde(rename_all = "camelCase")]
2564pub struct SketchConstraint {
2565 pub kind: SketchConstraintKind,
2566 #[serde(skip)]
2567 pub meta: Vec<Metadata>,
2568}
2569
2570#[derive(Debug, Clone, Copy, PartialEq)]
2571pub enum AngleRayDirection {
2572 Forward,
2573 Reverse,
2574}
2575
2576#[derive(Debug, Clone, Copy, PartialEq)]
2577pub enum AngleSector {
2578 One,
2579 Two,
2580 Three,
2581 Four,
2582}
2583
2584#[derive(Debug, Clone, Copy, PartialEq)]
2585pub enum AngleConstraintMode {
2586 LinesAtAngle,
2587 PointsAtAngle { sector: AngleSector, inverse: bool },
2588}
2589
2590#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2591#[ts(export_to = "Geometry.ts")]
2592#[serde(rename_all = "camelCase")]
2593pub enum SketchConstraintKind {
2594 Angle {
2595 line0: ConstrainableLine2d,
2596 line1: ConstrainableLine2d,
2597 #[serde(skip)]
2598 #[ts(skip)]
2599 mode: AngleConstraintMode,
2600 #[serde(rename = "labelPosition")]
2601 #[serde(skip_serializing_if = "Option::is_none")]
2602 #[ts(rename = "labelPosition")]
2603 #[ts(optional)]
2604 label_position: Option<ApiPoint2d<Number>>,
2605 },
2606 Distance {
2607 points: [ConstrainablePoint2dOrOrigin; 2],
2608 #[serde(rename = "labelPosition")]
2609 #[serde(skip_serializing_if = "Option::is_none")]
2610 #[ts(rename = "labelPosition")]
2611 #[ts(optional)]
2612 label_position: Option<ApiPoint2d<Number>>,
2613 },
2614 PointLineDistance {
2615 point: ConstrainablePoint2dOrOrigin,
2616 line: ConstrainableLine2d,
2617 input_object_ids: [Option<ObjectId>; 2],
2618 #[serde(rename = "labelPosition")]
2619 #[serde(skip_serializing_if = "Option::is_none")]
2620 #[ts(rename = "labelPosition")]
2621 #[ts(optional)]
2622 label_position: Option<ApiPoint2d<Number>>,
2623 },
2624 LineLineDistance {
2625 line0: ConstrainableLine2d,
2626 line1: ConstrainableLine2d,
2627 input_object_ids: [ObjectId; 2],
2628 #[serde(rename = "labelPosition")]
2629 #[serde(skip_serializing_if = "Option::is_none")]
2630 #[ts(rename = "labelPosition")]
2631 #[ts(optional)]
2632 label_position: Option<ApiPoint2d<Number>>,
2633 },
2634 PointCircularDistance {
2635 point: ConstrainablePoint2dOrOrigin,
2636 center: ConstrainablePoint2d,
2637 start: ConstrainablePoint2d,
2638 end: Option<ConstrainablePoint2d>,
2639 input_object_ids: [Option<ObjectId>; 2],
2640 #[serde(rename = "labelPosition")]
2641 #[serde(skip_serializing_if = "Option::is_none")]
2642 #[ts(rename = "labelPosition")]
2643 #[ts(optional)]
2644 label_position: Option<ApiPoint2d<Number>>,
2645 },
2646 LineCircularDistance {
2647 line: ConstrainableLine2d,
2648 center: ConstrainablePoint2d,
2649 start: ConstrainablePoint2d,
2650 end: Option<ConstrainablePoint2d>,
2651 input_object_ids: [ObjectId; 2],
2652 #[serde(rename = "labelPosition")]
2653 #[serde(skip_serializing_if = "Option::is_none")]
2654 #[ts(rename = "labelPosition")]
2655 #[ts(optional)]
2656 label_position: Option<ApiPoint2d<Number>>,
2657 },
2658 CircularCircularDistance {
2659 center0: ConstrainablePoint2d,
2660 start0: ConstrainablePoint2d,
2661 end0: Option<ConstrainablePoint2d>,
2662 center1: ConstrainablePoint2d,
2663 start1: ConstrainablePoint2d,
2664 end1: Option<ConstrainablePoint2d>,
2665 input_object_ids: [ObjectId; 2],
2666 #[serde(rename = "labelPosition")]
2667 #[serde(skip_serializing_if = "Option::is_none")]
2668 #[ts(rename = "labelPosition")]
2669 #[ts(optional)]
2670 label_position: Option<ApiPoint2d<Number>>,
2671 },
2672 Radius {
2673 points: [ConstrainablePoint2d; 2],
2674 #[serde(rename = "labelPosition")]
2675 #[serde(skip_serializing_if = "Option::is_none")]
2676 #[ts(rename = "labelPosition")]
2677 #[ts(optional)]
2678 label_position: Option<ApiPoint2d<Number>>,
2679 },
2680 Diameter {
2681 points: [ConstrainablePoint2d; 2],
2682 #[serde(rename = "labelPosition")]
2683 #[serde(skip_serializing_if = "Option::is_none")]
2684 #[ts(rename = "labelPosition")]
2685 #[ts(optional)]
2686 label_position: Option<ApiPoint2d<Number>>,
2687 },
2688 HorizontalDistance {
2689 points: [ConstrainablePoint2dOrOrigin; 2],
2690 #[serde(rename = "labelPosition")]
2691 #[serde(skip_serializing_if = "Option::is_none")]
2692 #[ts(rename = "labelPosition")]
2693 #[ts(optional)]
2694 label_position: Option<ApiPoint2d<Number>>,
2695 },
2696 VerticalDistance {
2697 points: [ConstrainablePoint2dOrOrigin; 2],
2698 #[serde(rename = "labelPosition")]
2699 #[serde(skip_serializing_if = "Option::is_none")]
2700 #[ts(rename = "labelPosition")]
2701 #[ts(optional)]
2702 label_position: Option<ApiPoint2d<Number>>,
2703 },
2704}
2705
2706impl SketchConstraintKind {
2707 pub fn name(&self) -> &'static str {
2708 match self {
2709 SketchConstraintKind::Angle { .. } => "angle",
2710 SketchConstraintKind::Distance { .. } => "distance",
2711 SketchConstraintKind::PointLineDistance { .. } => "distance",
2712 SketchConstraintKind::LineLineDistance { .. } => "distance",
2713 SketchConstraintKind::PointCircularDistance { .. } => "distance",
2714 SketchConstraintKind::LineCircularDistance { .. } => "distance",
2715 SketchConstraintKind::CircularCircularDistance { .. } => "distance",
2716 SketchConstraintKind::Radius { .. } => "radius",
2717 SketchConstraintKind::Diameter { .. } => "diameter",
2718 SketchConstraintKind::HorizontalDistance { .. } => "horizontalDistance",
2719 SketchConstraintKind::VerticalDistance { .. } => "verticalDistance",
2720 }
2721 }
2722}