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 | PlaneData::NegXY => PlaneKind::XY,
757 PlaneData::XZ | PlaneData::NegXZ => PlaneKind::XZ,
758 PlaneData::YZ | PlaneData::NegYZ => PlaneKind::YZ,
759 PlaneData::Plane(_) => PlaneKind::Custom,
760 }
761 }
762}
763
764impl From<&PlaneInfo> for PlaneKind {
765 fn from(value: &PlaneInfo) -> Self {
766 PlaneKind::from(&PlaneData::Plane(value.clone()))
767 }
768}
769
770impl From<PlaneInfo> for PlaneKind {
771 fn from(value: PlaneInfo) -> Self {
772 PlaneKind::from(&PlaneData::Plane(value))
773 }
774}
775
776impl Plane {
777 #[cfg(test)]
778 pub(crate) fn from_plane_data_skipping_engine(
779 value: PlaneData,
780 exec_state: &mut ExecState,
781 ) -> Result<Self, KclError> {
782 let id = exec_state.next_uuid();
783 let kind = PlaneKind::from(&value);
784 Ok(Plane {
785 id,
786 artifact_id: id.into(),
787 info: PlaneInfo::try_from(value)?,
788 object_id: None,
789 kind,
790 meta: vec![],
791 })
792 }
793
794 pub fn is_initialized(&self) -> bool {
796 self.object_id.is_some()
797 }
798
799 pub fn is_uninitialized(&self) -> bool {
801 !self.is_initialized()
802 }
803
804 pub fn is_standard(&self) -> bool {
806 match &self.kind {
807 PlaneKind::XY | PlaneKind::YZ | PlaneKind::XZ => true,
808 PlaneKind::Custom => false,
809 }
810 }
811
812 pub fn project(&self, point: Point3d) -> Point3d {
815 let v = point - self.info.origin;
816 let dot = v.axes_dot_product(&self.info.z_axis);
817
818 point - self.info.z_axis * dot
819 }
820}
821
822#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
824#[ts(export)]
825#[serde(rename_all = "camelCase")]
826pub struct Face {
827 pub id: uuid::Uuid,
829 pub artifact_id: ArtifactId,
831 pub object_id: ObjectId,
833 pub value: String,
835 pub x_axis: Point3d,
837 pub y_axis: Point3d,
839 pub parent_solid: FaceParentSolid,
841 pub units: UnitLength,
842 #[serde(skip)]
843 pub meta: Vec<Metadata>,
844}
845
846#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
848#[ts(export)]
849#[serde(rename_all = "camelCase")]
850pub struct FaceParentSolid {
851 pub solid_id: Uuid,
853 pub creator_sketch_id: Option<Uuid>,
855 pub creator_sketch_is_closed: Option<ProfileClosed>,
857 #[serde(default, skip_serializing_if = "Vec::is_empty")]
859 pub edge_cut_ids: Vec<Uuid>,
860}
861
862impl FaceParentSolid {
863 pub(crate) fn sketch_or_solid_id(&self) -> Uuid {
864 self.creator_sketch_id.unwrap_or(self.solid_id)
865 }
866}
867
868#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
871#[ts(export)]
872#[serde(rename_all = "camelCase")]
873pub struct BoundedEdge {
874 pub face_id: uuid::Uuid,
876 #[serde(skip_serializing_if = "Option::is_none")]
878 pub edge_id: Option<uuid::Uuid>,
879 #[serde(skip_serializing_if = "Option::is_none")]
881 pub edge_specifier: Option<UnresolvedEdgeSpecifier>,
882 pub lower_bound: f32,
885 pub upper_bound: f32,
888}
889
890#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS, FromStr, Display)]
892#[ts(export)]
893#[display(style = "camelCase")]
894pub enum PlaneKind {
895 #[serde(rename = "XY", alias = "xy")]
896 #[display("XY")]
897 XY,
898 #[serde(rename = "XZ", alias = "xz")]
899 #[display("XZ")]
900 XZ,
901 #[serde(rename = "YZ", alias = "yz")]
902 #[display("YZ")]
903 YZ,
904 #[display("Custom")]
906 Custom,
907}
908
909#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
910#[ts(export)]
911#[serde(tag = "type", rename_all = "camelCase")]
912pub struct Sketch {
913 pub id: uuid::Uuid,
915 pub paths: Vec<Path>,
919 #[serde(default, skip_serializing_if = "Vec::is_empty")]
921 pub inner_paths: Vec<Path>,
922 pub on: SketchSurface,
924 pub start: BasePath,
926 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
928 pub tags: IndexMap<String, TagIdentifier>,
929 pub artifact_id: ArtifactId,
932 #[ts(skip)]
933 pub original_id: uuid::Uuid,
934 #[serde(skip_serializing_if = "Option::is_none")]
939 #[ts(skip)]
940 pub origin_sketch_id: Option<uuid::Uuid>,
941 #[serde(skip)]
943 pub mirror: Option<uuid::Uuid>,
944 #[serde(skip)]
946 pub clone: Option<uuid::Uuid>,
947 #[serde(skip)]
949 #[ts(skip)]
950 pub synthetic_jump_path_ids: Vec<uuid::Uuid>,
951 pub units: UnitLength,
952 #[serde(skip)]
954 pub meta: Vec<Metadata>,
955 #[serde(
958 default = "ProfileClosed::explicitly",
959 skip_serializing_if = "ProfileClosed::is_explicitly"
960 )]
961 pub is_closed: ProfileClosed,
962}
963
964impl ProfileClosed {
965 #[expect(dead_code, reason = "it's not actually dead, it's called by serde")]
966 fn explicitly() -> Self {
967 Self::Explicitly
968 }
969
970 fn is_explicitly(&self) -> bool {
971 matches!(self, ProfileClosed::Explicitly)
972 }
973}
974
975#[derive(Debug, Serialize, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd, ts_rs::TS)]
977#[serde(rename_all = "camelCase")]
978pub enum ProfileClosed {
979 No,
981 Maybe,
983 Implicitly,
985 Explicitly,
987}
988
989impl Sketch {
990 pub(crate) fn build_sketch_mode_cmds(
993 &self,
994 exec_state: &mut ExecState,
995 inner_cmd: ModelingCmdReq,
996 ) -> Vec<ModelingCmdReq> {
997 vec![
998 ModelingCmdReq {
1001 cmd: ModelingCmd::from(
1002 mcmd::EnableSketchMode::builder()
1003 .animated(false)
1004 .ortho(false)
1005 .entity_id(self.on.id())
1006 .adjust_camera(false)
1007 .maybe_planar_normal(if let SketchSurface::Plane(plane) = &self.on {
1008 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
1010 Some(normal.into())
1011 } else {
1012 None
1013 })
1014 .build(),
1015 ),
1016 cmd_id: exec_state.next_uuid().into(),
1017 },
1018 inner_cmd,
1019 ModelingCmdReq {
1020 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::builder().build()),
1021 cmd_id: exec_state.next_uuid().into(),
1022 },
1023 ]
1024 }
1025}
1026
1027#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1029#[ts(export)]
1030#[serde(tag = "type", rename_all = "camelCase")]
1031pub enum SketchSurface {
1032 Plane(Box<Plane>),
1033 Face(Box<Face>),
1034}
1035
1036impl SketchSurface {
1037 pub(crate) fn id(&self) -> uuid::Uuid {
1038 match self {
1039 SketchSurface::Plane(plane) => plane.id,
1040 SketchSurface::Face(face) => face.id,
1041 }
1042 }
1043 pub(crate) fn x_axis(&self) -> Point3d {
1044 match self {
1045 SketchSurface::Plane(plane) => plane.info.x_axis,
1046 SketchSurface::Face(face) => face.x_axis,
1047 }
1048 }
1049 pub(crate) fn y_axis(&self) -> Point3d {
1050 match self {
1051 SketchSurface::Plane(plane) => plane.info.y_axis,
1052 SketchSurface::Face(face) => face.y_axis,
1053 }
1054 }
1055
1056 pub(crate) fn object_id(&self) -> Option<ObjectId> {
1057 match self {
1058 SketchSurface::Plane(plane) => plane.object_id,
1059 SketchSurface::Face(face) => Some(face.object_id),
1060 }
1061 }
1062
1063 pub(crate) fn set_object_id(&mut self, object_id: ObjectId) {
1064 match self {
1065 SketchSurface::Plane(plane) => plane.object_id = Some(object_id),
1066 SketchSurface::Face(face) => face.object_id = object_id,
1067 }
1068 }
1069}
1070
1071#[derive(Debug, Clone, PartialEq)]
1073pub enum Extrudable {
1074 Sketch(Box<Sketch>),
1076 FaceTag(FaceTag),
1078 Face(Box<Face>),
1080 EdgeTag(Box<TagIdentifier>),
1082 Edge(Uuid),
1084 EdgeSpecifier(UnresolvedEdgeSpecifier),
1086}
1087
1088impl Extrudable {
1089 pub async fn id_to_extrude(
1091 &self,
1092 exec_state: &mut ExecState,
1093 args: &Args,
1094 must_be_planar: bool,
1095 ) -> Result<uuid::Uuid, KclError> {
1096 match self {
1097 Extrudable::Sketch(sketch) => Ok(sketch.id),
1098 Extrudable::FaceTag(face_tag) => face_tag.get_face_id_from_tag(exec_state, args, must_be_planar).await,
1099 Extrudable::Face(face) => Ok(face.id),
1100 Extrudable::EdgeTag(edge_tag) => match edge_tag.get_cur_info() {
1101 Some(info) => Ok(info.id),
1102 None => Err(KclError::new_type(KclErrorDetails::new(
1103 "Could not find a valid id to extrude".to_owned(),
1104 vec![args.source_range],
1105 ))),
1106 },
1107 Extrudable::Edge(edge) => Ok(*edge),
1108 Extrudable::EdgeSpecifier(_) => Err(KclError::new_type(KclErrorDetails::new(
1109 "Could not find a legacy id for edge specifier".to_owned(),
1110 vec![args.source_range],
1111 ))),
1112 }
1113 }
1114
1115 pub fn as_sketch(&self) -> Option<Sketch> {
1116 match self {
1117 Extrudable::Sketch(sketch) => Some((**sketch).clone()),
1118 Extrudable::FaceTag(face) => match face.geometry() {
1119 Some(Geometry::Sketch(sketch)) => Some(sketch),
1120 Some(Geometry::Solid(solid)) => solid.sketch().cloned(),
1121 None => None,
1122 },
1123 Extrudable::Face(_) => None,
1124 Extrudable::EdgeTag(tag_identifier) => match tag_identifier.geometry() {
1125 Some(Geometry::Sketch(sketch)) => Some(sketch),
1126 Some(Geometry::Solid(solid)) => solid.sketch().cloned(),
1127 None => None,
1128 },
1129 Extrudable::Edge(_) => None,
1130 Extrudable::EdgeSpecifier(_) => None,
1131 }
1132 }
1133
1134 pub fn is_closed(&self) -> ProfileClosed {
1135 match self {
1136 Extrudable::Sketch(sketch) => sketch.is_closed,
1137 Extrudable::FaceTag(face_tag) => match face_tag.geometry() {
1138 Some(Geometry::Sketch(sketch)) => sketch.is_closed,
1139 Some(Geometry::Solid(solid)) => solid
1140 .sketch()
1141 .map(|sketch| sketch.is_closed)
1142 .unwrap_or(ProfileClosed::Maybe),
1143 _ => ProfileClosed::Maybe,
1144 },
1145 Extrudable::Face(face) => match face.parent_solid.creator_sketch_is_closed {
1146 Some(is_closed) => is_closed,
1147 None => ProfileClosed::Maybe,
1148 },
1149 Extrudable::EdgeTag(edge_tag) => match edge_tag.geometry() {
1150 Some(Geometry::Sketch(sketch)) => sketch.is_closed,
1151 Some(Geometry::Solid(solid)) => solid
1152 .sketch()
1153 .map(|sketch| sketch.is_closed)
1154 .unwrap_or(ProfileClosed::Maybe),
1155 _ => ProfileClosed::Maybe,
1156 },
1157 Extrudable::Edge(_) => ProfileClosed::Maybe,
1158 Extrudable::EdgeSpecifier(_) => ProfileClosed::Maybe,
1159 }
1160 }
1161}
1162
1163impl From<Sketch> for Extrudable {
1164 fn from(value: Sketch) -> Self {
1165 Extrudable::Sketch(Box::new(value))
1166 }
1167}
1168
1169#[derive(Debug, Clone)]
1170pub(crate) enum GetTangentialInfoFromPathsResult {
1171 PreviousPoint([f64; 2]),
1172 Arc {
1173 center: [f64; 2],
1174 ccw: bool,
1175 },
1176 Circle {
1177 center: [f64; 2],
1178 ccw: bool,
1179 radius: f64,
1180 },
1181 Ellipse {
1182 center: [f64; 2],
1183 ccw: bool,
1184 major_axis: [f64; 2],
1185 _minor_radius: f64,
1186 },
1187}
1188
1189impl GetTangentialInfoFromPathsResult {
1190 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
1191 match self {
1192 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
1193 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
1194 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
1195 }
1196 GetTangentialInfoFromPathsResult::Circle {
1199 center, radius, ccw, ..
1200 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
1201 GetTangentialInfoFromPathsResult::Ellipse {
1202 center,
1203 major_axis,
1204 ccw,
1205 ..
1206 } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
1207 }
1208 }
1209}
1210
1211impl Sketch {
1212 pub(crate) fn add_tag(
1213 &mut self,
1214 tag: NodeRef<'_, TagDeclarator>,
1215 current_path: &Path,
1216 exec_state: &ExecState,
1217 surface: Option<&ExtrudeSurface>,
1218 ) {
1219 let mut tag_identifier: TagIdentifier = tag.into();
1220 let base = current_path.get_base();
1221 let mut sketch_copy = self.clone();
1222 sketch_copy.tags.clear();
1223 tag_identifier.info.push((
1224 exec_state.stack().current_epoch(),
1225 TagEngineInfo {
1226 id: base.geo_meta.id,
1227 geometry: Geometry::Sketch(sketch_copy),
1228 path: Some(current_path.clone()),
1229 surface: surface.cloned(),
1230 },
1231 ));
1232
1233 self.tags.insert(tag.name.to_string(), tag_identifier);
1234 }
1235
1236 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
1237 for t in tags {
1238 match self.tags.get_mut(&t.value) {
1239 Some(id) => {
1240 id.merge_info(t);
1241 }
1242 None => {
1243 self.tags.insert(t.value.clone(), t.clone());
1244 }
1245 }
1246 }
1247 }
1248
1249 pub(crate) fn latest_path(&self) -> Option<&Path> {
1251 self.paths.last()
1252 }
1253
1254 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
1258 let Some(path) = self.latest_path() else {
1259 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
1260 };
1261
1262 let to = path.get_base().to;
1263 Ok(Point2d::new(to[0], to[1], path.get_base().units))
1264 }
1265
1266 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
1267 let Some(path) = self.latest_path() else {
1268 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
1269 };
1270 path.get_tangential_info()
1271 }
1272}
1273
1274#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1275#[ts(export)]
1276#[serde(tag = "type", rename_all = "camelCase")]
1277pub struct Solid {
1278 pub id: uuid::Uuid,
1280 #[serde(skip)]
1282 #[ts(skip)]
1283 pub value_id: uuid::Uuid,
1284 #[serde(skip)]
1288 #[ts(skip)]
1289 pub(crate) topology_id: uuid::Uuid,
1290 #[serde(skip)]
1294 #[ts(skip)]
1295 pub(crate) pattern_source_artifact_id: Option<ArtifactId>,
1296 #[serde(skip)]
1302 #[ts(skip)]
1303 pub(crate) best_guess_body_type: Option<kcmc::shared::BodyType>,
1304 pub artifact_id: ArtifactId,
1306 pub value: Vec<ExtrudeSurface>,
1308 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
1311 pub faces: IndexMap<String, TagIdentifier>,
1312 #[serde(rename = "sketch")]
1314 pub creator: SolidCreator,
1315 pub start_cap_id: Option<uuid::Uuid>,
1317 pub end_cap_id: Option<uuid::Uuid>,
1319 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1321 pub edge_cuts: Vec<EdgeCut>,
1322 #[serde(skip)]
1324 #[ts(skip)]
1325 pub pending_edge_cut_ids: Vec<uuid::Uuid>,
1326 pub units: UnitLength,
1328 pub sectional: bool,
1330 #[serde(skip)]
1332 pub meta: Vec<Metadata>,
1333}
1334
1335#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1336#[ts(export)]
1337pub struct CreatorFace {
1338 pub face_id: uuid::Uuid,
1340 pub solid_id: uuid::Uuid,
1342 pub sketch: Sketch,
1344}
1345
1346#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1347#[ts(export)]
1348pub struct CreatorEdge {
1349 pub edge_id: uuid::Uuid,
1351 pub body_id: uuid::Uuid,
1353}
1354
1355#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1357#[ts(export)]
1358#[serde(tag = "creatorType", rename_all = "camelCase")]
1359pub enum SolidCreator {
1360 Sketch(Sketch),
1362 Face(CreatorFace),
1364 Edge(CreatorEdge),
1366 Procedural,
1368}
1369
1370impl Solid {
1371 pub fn sketch(&self) -> Option<&Sketch> {
1372 match &self.creator {
1373 SolidCreator::Sketch(sketch) => Some(sketch),
1374 SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1375 SolidCreator::Edge(_) => None,
1376 SolidCreator::Procedural => None,
1377 }
1378 }
1379
1380 pub fn sketch_mut(&mut self) -> Option<&mut Sketch> {
1381 match &mut self.creator {
1382 SolidCreator::Sketch(sketch) => Some(sketch),
1383 SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1384 SolidCreator::Edge(_) => None,
1385 SolidCreator::Procedural => None,
1386 }
1387 }
1388
1389 pub fn sketch_id(&self) -> Option<uuid::Uuid> {
1390 self.sketch().map(|sketch| sketch.id)
1391 }
1392
1393 pub fn original_id(&self) -> uuid::Uuid {
1394 self.sketch().map(|sketch| sketch.original_id).unwrap_or(self.id)
1395 }
1396
1397 pub(crate) fn topology_id(&self) -> uuid::Uuid {
1398 self.topology_id
1399 }
1400
1401 pub(crate) fn become_new_body(&mut self, engine_id: uuid::Uuid, artifact_id: ArtifactId) {
1405 self.topology_id = engine_id;
1406 self.pattern_source_artifact_id = None;
1407 self.artifact_id = artifact_id;
1408 }
1409
1410 pub(crate) fn become_pattern_copy(&mut self, copy_engine_id: uuid::Uuid) {
1414 self.pattern_source_artifact_id.get_or_insert(self.artifact_id);
1415 self.artifact_id = ArtifactId::new(copy_engine_id);
1416 }
1417
1418 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
1419 self.edge_cuts
1420 .iter()
1421 .map(|foc| foc.id())
1422 .chain(self.pending_edge_cut_ids.iter().copied())
1423 }
1424}
1425
1426impl From<&Solid> for FaceParentSolid {
1427 fn from(solid: &Solid) -> Self {
1428 Self {
1429 solid_id: solid.id,
1430 creator_sketch_id: solid.sketch_id(),
1431 creator_sketch_is_closed: solid.sketch().map(|sketch| sketch.is_closed),
1432 edge_cut_ids: solid.get_all_edge_cut_ids().collect(),
1433 }
1434 }
1435}
1436
1437#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1439#[ts(export)]
1440#[serde(tag = "type", rename_all = "camelCase")]
1441pub enum EdgeCut {
1442 Fillet {
1444 id: uuid::Uuid,
1446 radius: TyF64,
1447 #[serde(rename = "edgeId")]
1449 edge_id: uuid::Uuid,
1450 tag: Box<Option<TagNode>>,
1451 },
1452 Chamfer {
1454 id: uuid::Uuid,
1456 length: TyF64,
1457 #[serde(rename = "edgeId")]
1459 edge_id: uuid::Uuid,
1460 tag: Box<Option<TagNode>>,
1461 },
1462}
1463
1464impl EdgeCut {
1465 pub fn id(&self) -> uuid::Uuid {
1466 match self {
1467 EdgeCut::Fillet { id, .. } => *id,
1468 EdgeCut::Chamfer { id, .. } => *id,
1469 }
1470 }
1471
1472 pub fn set_id(&mut self, id: uuid::Uuid) {
1473 match self {
1474 EdgeCut::Fillet { id: i, .. } => *i = id,
1475 EdgeCut::Chamfer { id: i, .. } => *i = id,
1476 }
1477 }
1478
1479 pub fn edge_id(&self) -> uuid::Uuid {
1480 match self {
1481 EdgeCut::Fillet { edge_id, .. } => *edge_id,
1482 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
1483 }
1484 }
1485
1486 pub fn set_edge_id(&mut self, id: uuid::Uuid) {
1487 match self {
1488 EdgeCut::Fillet { edge_id: i, .. } => *i = id,
1489 EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
1490 }
1491 }
1492
1493 pub fn tag(&self) -> Option<TagNode> {
1494 match self {
1495 EdgeCut::Fillet { tag, .. } => *tag.clone(),
1496 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
1497 }
1498 }
1499}
1500
1501#[derive(Debug, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
1502#[ts(export)]
1503pub struct Point2d {
1504 pub x: f64,
1505 pub y: f64,
1506 pub units: UnitLength,
1507}
1508
1509impl Point2d {
1510 pub const ZERO: Self = Self {
1511 x: 0.0,
1512 y: 0.0,
1513 units: UnitLength::Millimeters,
1514 };
1515
1516 pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
1517 Self { x, y, units }
1518 }
1519
1520 pub fn into_x(self) -> TyF64 {
1521 TyF64::new(self.x, NumericType::length(self.units))
1522 }
1523
1524 pub fn into_y(self) -> TyF64 {
1525 TyF64::new(self.y, NumericType::length(self.units))
1526 }
1527
1528 pub fn ignore_units(self) -> [f64; 2] {
1529 [self.x, self.y]
1530 }
1531}
1532
1533#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
1534#[ts(export)]
1535pub struct Point3d {
1536 pub x: f64,
1537 pub y: f64,
1538 pub z: f64,
1539 pub units: Option<UnitLength>,
1540}
1541
1542impl Point3d {
1543 pub const ZERO: Self = Self {
1544 x: 0.0,
1545 y: 0.0,
1546 z: 0.0,
1547 units: Some(UnitLength::Millimeters),
1548 };
1549
1550 pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
1551 Self { x, y, z, units }
1552 }
1553
1554 pub const fn is_zero(&self) -> bool {
1555 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
1556 }
1557
1558 pub fn axes_cross_product(&self, other: &Self) -> Self {
1563 Self {
1564 x: self.y * other.z - self.z * other.y,
1565 y: self.z * other.x - self.x * other.z,
1566 z: self.x * other.y - self.y * other.x,
1567 units: None,
1568 }
1569 }
1570
1571 pub fn canonicalize_signed_zero(&mut self) {
1573 if self.x == 0.0 {
1574 self.x = 0.0;
1575 }
1576 if self.y == 0.0 {
1577 self.y = 0.0;
1578 }
1579 if self.z == 0.0 {
1580 self.z = 0.0;
1581 }
1582 }
1583
1584 pub fn axes_dot_product(&self, other: &Self) -> f64 {
1589 let x = self.x * other.x;
1590 let y = self.y * other.y;
1591 let z = self.z * other.z;
1592 x + y + z
1593 }
1594
1595 pub fn normalize(&self) -> Self {
1596 let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1597 Point3d {
1598 x: self.x / len,
1599 y: self.y / len,
1600 z: self.z / len,
1601 units: None,
1602 }
1603 }
1604
1605 pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1606 let p = [self.x, self.y, self.z];
1607 let u = self.units;
1608 (p, u)
1609 }
1610
1611 pub(crate) fn negated(self) -> Self {
1612 Self {
1613 x: -self.x,
1614 y: -self.y,
1615 z: -self.z,
1616 units: self.units,
1617 }
1618 }
1619}
1620
1621impl From<[TyF64; 3]> for Point3d {
1622 fn from(p: [TyF64; 3]) -> Self {
1623 Self {
1624 x: p[0].n,
1625 y: p[1].n,
1626 z: p[2].n,
1627 units: p[0].ty.as_length(),
1628 }
1629 }
1630}
1631
1632impl From<Point3d> for Point3D {
1633 fn from(p: Point3d) -> Self {
1634 Self { x: p.x, y: p.y, z: p.z }
1635 }
1636}
1637
1638impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1639 fn from(p: Point3d) -> Self {
1640 if let Some(units) = p.units {
1641 Self {
1642 x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1643 y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1644 z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1645 }
1646 } else {
1647 Self {
1648 x: LengthUnit(p.x),
1649 y: LengthUnit(p.y),
1650 z: LengthUnit(p.z),
1651 }
1652 }
1653 }
1654}
1655
1656impl Add for Point3d {
1657 type Output = Point3d;
1658
1659 fn add(self, rhs: Self) -> Self::Output {
1660 Point3d {
1662 x: self.x + rhs.x,
1663 y: self.y + rhs.y,
1664 z: self.z + rhs.z,
1665 units: self.units,
1666 }
1667 }
1668}
1669
1670impl AddAssign for Point3d {
1671 fn add_assign(&mut self, rhs: Self) {
1672 *self = *self + rhs
1673 }
1674}
1675
1676impl Sub for Point3d {
1677 type Output = Point3d;
1678
1679 fn sub(self, rhs: Self) -> Self::Output {
1680 let (x, y, z) = if rhs.units != self.units
1681 && let Some(sunits) = self.units
1682 && let Some(runits) = rhs.units
1683 {
1684 (
1685 adjust_length(runits, rhs.x, sunits).0,
1686 adjust_length(runits, rhs.y, sunits).0,
1687 adjust_length(runits, rhs.z, sunits).0,
1688 )
1689 } else {
1690 (rhs.x, rhs.y, rhs.z)
1691 };
1692 Point3d {
1693 x: self.x - x,
1694 y: self.y - y,
1695 z: self.z - z,
1696 units: self.units,
1697 }
1698 }
1699}
1700
1701impl SubAssign for Point3d {
1702 fn sub_assign(&mut self, rhs: Self) {
1703 *self = *self - rhs
1704 }
1705}
1706
1707impl Mul<f64> for Point3d {
1708 type Output = Point3d;
1709
1710 fn mul(self, rhs: f64) -> Self::Output {
1711 Point3d {
1712 x: self.x * rhs,
1713 y: self.y * rhs,
1714 z: self.z * rhs,
1715 units: self.units,
1716 }
1717 }
1718}
1719
1720#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1722#[ts(export)]
1723#[serde(rename_all = "camelCase")]
1724pub struct BasePath {
1725 #[ts(type = "[number, number]")]
1727 pub from: [f64; 2],
1728 #[ts(type = "[number, number]")]
1730 pub to: [f64; 2],
1731 pub units: UnitLength,
1732 pub tag: Option<TagNode>,
1734 #[serde(rename = "__geoMeta")]
1736 pub geo_meta: GeoMeta,
1737}
1738
1739impl BasePath {
1740 pub fn get_to(&self) -> [TyF64; 2] {
1741 let ty = NumericType::length(self.units);
1742 [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1743 }
1744
1745 pub fn get_from(&self) -> [TyF64; 2] {
1746 let ty = NumericType::length(self.units);
1747 [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1748 }
1749}
1750
1751#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1753#[ts(export)]
1754#[serde(rename_all = "camelCase")]
1755pub struct GeoMeta {
1756 pub id: uuid::Uuid,
1758 #[serde(flatten)]
1760 pub metadata: Metadata,
1761}
1762
1763#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1765#[ts(export)]
1766#[serde(tag = "type")]
1767pub enum Path {
1768 ToPoint {
1770 #[serde(flatten)]
1771 base: BasePath,
1772 },
1773 TangentialArcTo {
1775 #[serde(flatten)]
1776 base: BasePath,
1777 #[ts(type = "[number, number]")]
1779 center: [f64; 2],
1780 ccw: bool,
1782 },
1783 TangentialArc {
1785 #[serde(flatten)]
1786 base: BasePath,
1787 #[ts(type = "[number, number]")]
1789 center: [f64; 2],
1790 ccw: bool,
1792 },
1793 Circle {
1796 #[serde(flatten)]
1797 base: BasePath,
1798 #[ts(type = "[number, number]")]
1800 center: [f64; 2],
1801 radius: f64,
1803 ccw: bool,
1806 },
1807 CircleThreePoint {
1808 #[serde(flatten)]
1809 base: BasePath,
1810 #[ts(type = "[number, number]")]
1812 p1: [f64; 2],
1813 #[ts(type = "[number, number]")]
1815 p2: [f64; 2],
1816 #[ts(type = "[number, number]")]
1818 p3: [f64; 2],
1819 },
1820 ArcThreePoint {
1821 #[serde(flatten)]
1822 base: BasePath,
1823 #[ts(type = "[number, number]")]
1825 p1: [f64; 2],
1826 #[ts(type = "[number, number]")]
1828 p2: [f64; 2],
1829 #[ts(type = "[number, number]")]
1831 p3: [f64; 2],
1832 },
1833 Horizontal {
1835 #[serde(flatten)]
1836 base: BasePath,
1837 x: f64,
1839 },
1840 AngledLineTo {
1842 #[serde(flatten)]
1843 base: BasePath,
1844 x: Option<f64>,
1846 y: Option<f64>,
1848 },
1849 Base {
1851 #[serde(flatten)]
1852 base: BasePath,
1853 },
1854 Arc {
1856 #[serde(flatten)]
1857 base: BasePath,
1858 center: [f64; 2],
1860 radius: f64,
1862 ccw: bool,
1864 },
1865 Ellipse {
1866 #[serde(flatten)]
1867 base: BasePath,
1868 center: [f64; 2],
1869 major_axis: [f64; 2],
1870 minor_radius: f64,
1871 ccw: bool,
1872 },
1873 Conic {
1875 #[serde(flatten)]
1876 base: BasePath,
1877 },
1878 Bezier {
1880 #[serde(flatten)]
1881 base: BasePath,
1882 #[ts(type = "[number, number]")]
1884 control1: [f64; 2],
1885 #[ts(type = "[number, number]")]
1887 control2: [f64; 2],
1888 },
1889}
1890
1891impl Path {
1892 pub fn get_id(&self) -> uuid::Uuid {
1893 match self {
1894 Path::ToPoint { base } => base.geo_meta.id,
1895 Path::Horizontal { base, .. } => base.geo_meta.id,
1896 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1897 Path::Base { base } => base.geo_meta.id,
1898 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1899 Path::TangentialArc { base, .. } => base.geo_meta.id,
1900 Path::Circle { base, .. } => base.geo_meta.id,
1901 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1902 Path::Arc { base, .. } => base.geo_meta.id,
1903 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1904 Path::Ellipse { base, .. } => base.geo_meta.id,
1905 Path::Conic { base, .. } => base.geo_meta.id,
1906 Path::Bezier { base, .. } => base.geo_meta.id,
1907 }
1908 }
1909
1910 pub fn set_id(&mut self, id: uuid::Uuid) {
1911 match self {
1912 Path::ToPoint { base } => base.geo_meta.id = id,
1913 Path::Horizontal { base, .. } => base.geo_meta.id = id,
1914 Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1915 Path::Base { base } => base.geo_meta.id = id,
1916 Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1917 Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1918 Path::Circle { base, .. } => base.geo_meta.id = id,
1919 Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1920 Path::Arc { base, .. } => base.geo_meta.id = id,
1921 Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1922 Path::Ellipse { base, .. } => base.geo_meta.id = id,
1923 Path::Conic { base, .. } => base.geo_meta.id = id,
1924 Path::Bezier { base, .. } => base.geo_meta.id = id,
1925 }
1926 }
1927
1928 pub fn get_tag(&self) -> Option<TagNode> {
1929 match self {
1930 Path::ToPoint { base } => base.tag.clone(),
1931 Path::Horizontal { base, .. } => base.tag.clone(),
1932 Path::AngledLineTo { base, .. } => base.tag.clone(),
1933 Path::Base { base } => base.tag.clone(),
1934 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1935 Path::TangentialArc { base, .. } => base.tag.clone(),
1936 Path::Circle { base, .. } => base.tag.clone(),
1937 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1938 Path::Arc { base, .. } => base.tag.clone(),
1939 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1940 Path::Ellipse { base, .. } => base.tag.clone(),
1941 Path::Conic { base, .. } => base.tag.clone(),
1942 Path::Bezier { base, .. } => base.tag.clone(),
1943 }
1944 }
1945
1946 pub fn get_base(&self) -> &BasePath {
1947 match self {
1948 Path::ToPoint { base } => base,
1949 Path::Horizontal { base, .. } => base,
1950 Path::AngledLineTo { base, .. } => base,
1951 Path::Base { base } => base,
1952 Path::TangentialArcTo { base, .. } => base,
1953 Path::TangentialArc { base, .. } => base,
1954 Path::Circle { base, .. } => base,
1955 Path::CircleThreePoint { base, .. } => base,
1956 Path::Arc { base, .. } => base,
1957 Path::ArcThreePoint { base, .. } => base,
1958 Path::Ellipse { base, .. } => base,
1959 Path::Conic { base, .. } => base,
1960 Path::Bezier { base, .. } => base,
1961 }
1962 }
1963
1964 pub fn get_from(&self) -> [TyF64; 2] {
1966 let p = &self.get_base().from;
1967 let ty = NumericType::length(self.get_base().units);
1968 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1969 }
1970
1971 pub fn get_to(&self) -> [TyF64; 2] {
1973 let p = &self.get_base().to;
1974 let ty = NumericType::length(self.get_base().units);
1975 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1976 }
1977
1978 pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1980 let p = &self.get_base().from;
1981 let ty = NumericType::length(self.get_base().units);
1982 (*p, ty)
1983 }
1984
1985 pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1987 let p = &self.get_base().to;
1988 let ty = NumericType::length(self.get_base().units);
1989 (*p, ty)
1990 }
1991
1992 pub fn length(&self) -> Option<TyF64> {
1995 let n = match self {
1996 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1997 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1998 }
1999 Self::TangentialArc {
2000 base: _,
2001 center,
2002 ccw: _,
2003 }
2004 | Self::TangentialArcTo {
2005 base: _,
2006 center,
2007 ccw: _,
2008 } => {
2009 let radius = linear_distance(&self.get_base().from, center);
2012 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
2013 Some(linear_distance(&self.get_base().from, &self.get_base().to))
2015 }
2016 Self::Circle { radius, .. } => Some(TAU * radius),
2017 Self::CircleThreePoint { .. } => {
2018 let circle_center = crate::std::utils::calculate_circle_from_3_points([
2019 self.get_base().from,
2020 self.get_base().to,
2021 self.get_base().to,
2022 ]);
2023 let radius = linear_distance(
2024 &[circle_center.center[0], circle_center.center[1]],
2025 &self.get_base().from,
2026 );
2027 Some(TAU * radius)
2028 }
2029 Self::Arc { .. } => {
2030 Some(linear_distance(&self.get_base().from, &self.get_base().to))
2032 }
2033 Self::ArcThreePoint { .. } => {
2034 Some(linear_distance(&self.get_base().from, &self.get_base().to))
2036 }
2037 Self::Ellipse { .. } => {
2038 None
2040 }
2041 Self::Conic { .. } => {
2042 None
2044 }
2045 Self::Bezier { .. } => {
2046 None
2048 }
2049 };
2050 n.map(|n| TyF64::new(n, NumericType::length(self.get_base().units)))
2051 }
2052
2053 pub fn get_base_mut(&mut self) -> &mut BasePath {
2054 match self {
2055 Path::ToPoint { base } => base,
2056 Path::Horizontal { base, .. } => base,
2057 Path::AngledLineTo { base, .. } => base,
2058 Path::Base { base } => base,
2059 Path::TangentialArcTo { base, .. } => base,
2060 Path::TangentialArc { base, .. } => base,
2061 Path::Circle { base, .. } => base,
2062 Path::CircleThreePoint { base, .. } => base,
2063 Path::Arc { base, .. } => base,
2064 Path::ArcThreePoint { base, .. } => base,
2065 Path::Ellipse { base, .. } => base,
2066 Path::Conic { base, .. } => base,
2067 Path::Bezier { base, .. } => base,
2068 }
2069 }
2070
2071 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
2072 match self {
2073 Path::TangentialArc { center, ccw, .. }
2074 | Path::TangentialArcTo { center, ccw, .. }
2075 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
2076 center: *center,
2077 ccw: *ccw,
2078 },
2079 Path::ArcThreePoint { p1, p2, p3, .. } => {
2080 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
2081 GetTangentialInfoFromPathsResult::Arc {
2082 center: circle.center,
2083 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
2084 }
2085 }
2086 Path::Circle {
2087 center, ccw, radius, ..
2088 } => GetTangentialInfoFromPathsResult::Circle {
2089 center: *center,
2090 ccw: *ccw,
2091 radius: *radius,
2092 },
2093 Path::CircleThreePoint { p1, p2, p3, .. } => {
2094 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
2095 let center_point = [circle.center[0], circle.center[1]];
2096 GetTangentialInfoFromPathsResult::Circle {
2097 center: center_point,
2098 ccw: true,
2100 radius: circle.radius,
2101 }
2102 }
2103 Path::Ellipse {
2105 center,
2106 major_axis,
2107 minor_radius,
2108 ccw,
2109 ..
2110 } => GetTangentialInfoFromPathsResult::Ellipse {
2111 center: *center,
2112 major_axis: *major_axis,
2113 _minor_radius: *minor_radius,
2114 ccw: *ccw,
2115 },
2116 Path::Conic { .. }
2117 | Path::ToPoint { .. }
2118 | Path::Horizontal { .. }
2119 | Path::AngledLineTo { .. }
2120 | Path::Base { .. }
2121 | Path::Bezier { .. } => {
2122 let base = self.get_base();
2123 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
2124 }
2125 }
2126 }
2127
2128 pub(crate) fn is_straight_line(&self) -> bool {
2130 matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
2131 }
2132}
2133
2134#[rustfmt::skip]
2136fn linear_distance(
2137 [x0, y0]: &[f64; 2],
2138 [x1, y1]: &[f64; 2]
2139) -> f64 {
2140 let y_sq = (y1 - y0).squared();
2141 let x_sq = (x1 - x0).squared();
2142 (y_sq + x_sq).sqrt()
2143}
2144
2145#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2147#[ts(export)]
2148#[serde(tag = "type", rename_all = "camelCase")]
2149pub enum ExtrudeSurface {
2150 ExtrudePlane(ExtrudePlane),
2152 ExtrudeArc(ExtrudeArc),
2153 Chamfer(ChamferSurface),
2154 Fillet(FilletSurface),
2155}
2156
2157#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2159#[ts(export)]
2160#[serde(rename_all = "camelCase")]
2161pub struct ChamferSurface {
2162 pub face_id: uuid::Uuid,
2164 pub tag: Option<Node<TagDeclarator>>,
2166 #[serde(flatten)]
2168 pub geo_meta: GeoMeta,
2169}
2170
2171#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2173#[ts(export)]
2174#[serde(rename_all = "camelCase")]
2175pub struct FilletSurface {
2176 pub face_id: uuid::Uuid,
2178 pub tag: Option<Node<TagDeclarator>>,
2180 #[serde(flatten)]
2182 pub geo_meta: GeoMeta,
2183}
2184
2185#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2187#[ts(export)]
2188#[serde(rename_all = "camelCase")]
2189pub struct ExtrudePlane {
2190 pub face_id: uuid::Uuid,
2192 pub tag: Option<Node<TagDeclarator>>,
2194 #[serde(flatten)]
2196 pub geo_meta: GeoMeta,
2197}
2198
2199#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2201#[ts(export)]
2202#[serde(rename_all = "camelCase")]
2203pub struct ExtrudeArc {
2204 pub face_id: uuid::Uuid,
2206 pub tag: Option<Node<TagDeclarator>>,
2208 #[serde(flatten)]
2210 pub geo_meta: GeoMeta,
2211}
2212
2213impl ExtrudeSurface {
2214 pub fn get_id(&self) -> uuid::Uuid {
2215 match self {
2216 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
2217 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
2218 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
2219 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
2220 }
2221 }
2222
2223 pub fn set_id(&mut self, id: uuid::Uuid) {
2224 match self {
2225 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id = id,
2226 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id = id,
2227 ExtrudeSurface::Fillet(f) => f.geo_meta.id = id,
2228 ExtrudeSurface::Chamfer(c) => c.geo_meta.id = id,
2229 }
2230 }
2231
2232 pub fn face_id(&self) -> uuid::Uuid {
2233 match self {
2234 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id,
2235 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id,
2236 ExtrudeSurface::Fillet(f) => f.face_id,
2237 ExtrudeSurface::Chamfer(c) => c.face_id,
2238 }
2239 }
2240
2241 pub fn set_face_id(&mut self, face_id: uuid::Uuid) {
2242 match self {
2243 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id = face_id,
2244 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id = face_id,
2245 ExtrudeSurface::Fillet(f) => f.face_id = face_id,
2246 ExtrudeSurface::Chamfer(c) => c.face_id = face_id,
2247 }
2248 }
2249
2250 pub fn set_surface_tag(&mut self, tag: &TagNode) {
2251 match self {
2252 ExtrudeSurface::ExtrudePlane(extrude_plane) => extrude_plane.tag = Some(tag.clone()),
2253 ExtrudeSurface::ExtrudeArc(extrude_arc) => extrude_arc.tag = Some(tag.clone()),
2254 ExtrudeSurface::Chamfer(chamfer) => chamfer.tag = Some(tag.clone()),
2255 ExtrudeSurface::Fillet(fillet) => fillet.tag = Some(tag.clone()),
2256 }
2257 }
2258
2259 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
2260 match self {
2261 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
2262 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
2263 ExtrudeSurface::Fillet(f) => f.tag.clone(),
2264 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
2265 }
2266 }
2267}
2268
2269#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ts_rs::TS)]
2270pub struct SketchVarId(pub usize);
2271
2272impl SketchVarId {
2273 pub const INVALID: Self = Self(usize::MAX);
2274
2275 pub fn to_constraint_id(self, range: SourceRange) -> Result<ezpz::Id, KclError> {
2276 self.0.try_into().map_err(|_| {
2277 KclError::new_type(KclErrorDetails::new(
2278 "Cannot convert to constraint ID since the sketch variable ID is too large".to_owned(),
2279 vec![range],
2280 ))
2281 })
2282 }
2283}
2284
2285#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2286#[ts(export_to = "Geometry.ts")]
2287#[serde(rename_all = "camelCase")]
2288pub struct SketchVar {
2289 pub id: SketchVarId,
2290 pub initial_value: f64,
2291 pub ty: NumericType,
2292 pub node_path: Option<NodePath>,
2294 #[serde(skip)]
2295 pub meta: Vec<Metadata>,
2296}
2297
2298impl SketchVar {
2299 pub fn initial_value_to_solver_units(
2300 &self,
2301 exec_state: &mut ExecState,
2302 source_range: SourceRange,
2303 description: &str,
2304 ) -> Result<TyF64, KclError> {
2305 let x_initial_value = KclValue::Number {
2306 value: self.initial_value,
2307 ty: self.ty,
2308 meta: vec![source_range.into()],
2309 };
2310 let normalized_value =
2311 normalize_to_solver_distance_unit(&x_initial_value, source_range, exec_state, description)?;
2312 normalized_value.as_ty_f64().ok_or_else(|| {
2313 let message = format!(
2314 "Expected number after coercion, but found {}",
2315 normalized_value.human_friendly_type()
2316 );
2317 debug_assert!(false, "{}", &message);
2318 KclError::new_internal(KclErrorDetails::new(message, vec![source_range]))
2319 })
2320 }
2321}
2322
2323#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2324#[ts(export_to = "Geometry.ts")]
2325#[serde(tag = "type")]
2326pub enum UnsolvedExpr {
2327 Known(TyF64),
2328 Unknown(SketchVarId),
2329}
2330
2331impl UnsolvedExpr {
2332 pub fn var(&self) -> Option<SketchVarId> {
2333 match self {
2334 UnsolvedExpr::Known(_) => None,
2335 UnsolvedExpr::Unknown(id) => Some(*id),
2336 }
2337 }
2338}
2339
2340pub type UnsolvedPoint2dExpr = [UnsolvedExpr; 2];
2341
2342#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2343#[ts(export_to = "Geometry.ts")]
2344#[serde(rename_all = "camelCase")]
2345pub struct ConstrainablePoint2d {
2346 pub vars: crate::front::Point2d<SketchVarId>,
2347 pub object_id: ObjectId,
2348}
2349
2350#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2351#[ts(export_to = "Geometry.ts")]
2352pub enum ConstrainablePoint2dOrOrigin {
2353 Point(ConstrainablePoint2d),
2354 Origin,
2355}
2356
2357#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2358#[ts(export_to = "Geometry.ts")]
2359#[serde(rename_all = "camelCase")]
2360pub struct ConstrainableLine2d {
2361 pub vars: [crate::front::Point2d<SketchVarId>; 2],
2362 pub object_id: ObjectId,
2363}
2364
2365#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2366#[ts(export_to = "Geometry.ts")]
2367#[serde(rename_all = "camelCase")]
2368pub struct UnsolvedSegment {
2369 pub id: Uuid,
2371 pub object_id: ObjectId,
2372 pub kind: UnsolvedSegmentKind,
2373 #[serde(skip_serializing_if = "Option::is_none")]
2374 pub tag: Option<TagIdentifier>,
2375 #[serde(skip)]
2376 pub node_path: Option<NodePath>,
2377 #[serde(skip)]
2378 pub meta: Vec<Metadata>,
2379}
2380
2381#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2382#[ts(export_to = "Geometry.ts")]
2383#[serde(rename_all = "camelCase")]
2384pub enum UnsolvedSegmentKind {
2385 Point {
2386 position: UnsolvedPoint2dExpr,
2387 ctor: Box<PointCtor>,
2388 },
2389 Line {
2390 start: UnsolvedPoint2dExpr,
2391 end: UnsolvedPoint2dExpr,
2392 ctor: Box<LineCtor>,
2393 start_object_id: ObjectId,
2394 end_object_id: ObjectId,
2395 construction: bool,
2396 },
2397 Arc {
2398 start: UnsolvedPoint2dExpr,
2399 end: UnsolvedPoint2dExpr,
2400 center: UnsolvedPoint2dExpr,
2401 ctor: Box<ArcCtor>,
2402 start_object_id: ObjectId,
2403 end_object_id: ObjectId,
2404 center_object_id: ObjectId,
2405 #[serde(default, skip_serializing_if = "ArcDirection::is_ccw")]
2411 #[ts(as = "Option<ArcDirection>")]
2412 #[ts(optional)]
2413 direction: ArcDirection,
2414 construction: bool,
2415 },
2416 Circle {
2417 start: UnsolvedPoint2dExpr,
2418 center: UnsolvedPoint2dExpr,
2419 ctor: Box<CircleCtor>,
2420 start_object_id: ObjectId,
2421 center_object_id: ObjectId,
2422 construction: bool,
2423 },
2424 ControlPointSpline {
2425 controls: Vec<UnsolvedPoint2dExpr>,
2426 ctor: Box<ControlPointSplineCtor>,
2427 control_object_ids: Vec<ObjectId>,
2428 control_polygon_edge_object_ids: Vec<ObjectId>,
2429 degree: u32,
2430 construction: bool,
2431 },
2432}
2433
2434impl UnsolvedSegmentKind {
2435 pub fn human_friendly_kind_with_article(&self) -> &'static str {
2438 match self {
2439 Self::Point { .. } => "a Point",
2440 Self::Line { .. } => "a Line",
2441 Self::Arc { .. } => "an Arc",
2442 Self::Circle { .. } => "a Circle",
2443 Self::ControlPointSpline { .. } => "a Control Point Spline",
2444 }
2445 }
2446}
2447
2448#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2449#[ts(export_to = "Geometry.ts")]
2450#[serde(rename_all = "camelCase")]
2451pub struct Segment {
2452 pub id: Uuid,
2454 pub object_id: ObjectId,
2455 pub kind: SegmentKind,
2456 pub surface: SketchSurface,
2457 pub sketch_id: Uuid,
2459 #[serde(skip)]
2460 #[ts(skip)]
2461 pub sketch: Option<Arc<Sketch>>,
2462 #[serde(skip_serializing_if = "Option::is_none")]
2463 pub tag: Option<TagIdentifier>,
2464 #[serde(skip)]
2465 pub node_path: Option<NodePath>,
2466 #[serde(skip)]
2467 pub meta: Vec<Metadata>,
2468}
2469
2470impl Segment {
2471 pub fn is_construction(&self) -> bool {
2472 match &self.kind {
2473 SegmentKind::Point { .. } => true,
2474 SegmentKind::Line { construction, .. } => *construction,
2475 SegmentKind::Arc { construction, .. } => *construction,
2476 SegmentKind::Circle { construction, .. } => *construction,
2477 SegmentKind::ControlPointSpline { construction, .. } => *construction,
2478 }
2479 }
2480}
2481
2482#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2483#[ts(export_to = "Geometry.ts")]
2484#[serde(rename_all = "camelCase")]
2485pub enum SegmentKind {
2486 Point {
2487 position: [TyF64; 2],
2488 ctor: Box<PointCtor>,
2489 #[serde(skip_serializing_if = "Option::is_none")]
2490 freedom: Option<Freedom>,
2491 },
2492 Line {
2493 start: [TyF64; 2],
2494 end: [TyF64; 2],
2495 ctor: Box<LineCtor>,
2496 start_object_id: ObjectId,
2497 end_object_id: ObjectId,
2498 #[serde(skip_serializing_if = "Option::is_none")]
2499 start_freedom: Option<Freedom>,
2500 #[serde(skip_serializing_if = "Option::is_none")]
2501 end_freedom: Option<Freedom>,
2502 construction: bool,
2503 },
2504 Arc {
2505 start: [TyF64; 2],
2506 end: [TyF64; 2],
2507 center: [TyF64; 2],
2508 ctor: Box<ArcCtor>,
2509 start_object_id: ObjectId,
2510 end_object_id: ObjectId,
2511 center_object_id: ObjectId,
2512 #[serde(skip_serializing_if = "Option::is_none")]
2513 start_freedom: Option<Freedom>,
2514 #[serde(skip_serializing_if = "Option::is_none")]
2515 end_freedom: Option<Freedom>,
2516 #[serde(skip_serializing_if = "Option::is_none")]
2517 center_freedom: Option<Freedom>,
2518 #[serde(default, skip_serializing_if = "ArcDirection::is_ccw")]
2521 #[ts(as = "Option<ArcDirection>")]
2522 #[ts(optional)]
2523 direction: ArcDirection,
2524 construction: bool,
2525 },
2526 Circle {
2527 start: [TyF64; 2],
2528 center: [TyF64; 2],
2529 ctor: Box<CircleCtor>,
2530 start_object_id: ObjectId,
2531 center_object_id: ObjectId,
2532 #[serde(skip_serializing_if = "Option::is_none")]
2533 start_freedom: Option<Freedom>,
2534 #[serde(skip_serializing_if = "Option::is_none")]
2535 center_freedom: Option<Freedom>,
2536 construction: bool,
2537 },
2538 ControlPointSpline {
2539 controls: Vec<[TyF64; 2]>,
2540 ctor: Box<ControlPointSplineCtor>,
2541 control_object_ids: Vec<ObjectId>,
2542 control_polygon_edge_object_ids: Vec<ObjectId>,
2543 #[serde(skip_serializing_if = "Vec::is_empty")]
2544 control_freedoms: Vec<Option<Freedom>>,
2545 degree: u32,
2546 construction: bool,
2547 },
2548}
2549
2550#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2551#[ts(export_to = "Geometry.ts")]
2552#[serde(rename_all = "camelCase")]
2553pub struct AbstractSegment {
2554 pub repr: SegmentRepr,
2555 #[serde(skip)]
2556 pub meta: Vec<Metadata>,
2557}
2558
2559#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2560pub enum SegmentRepr {
2561 Unsolved { segment: Box<UnsolvedSegment> },
2562 Solved { segment: Box<Segment> },
2563}
2564
2565#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2566#[ts(export_to = "Geometry.ts")]
2567#[serde(rename_all = "camelCase")]
2568pub struct SketchConstraint {
2569 pub kind: SketchConstraintKind,
2570 #[serde(skip)]
2571 pub meta: Vec<Metadata>,
2572}
2573
2574#[derive(Debug, Clone, Copy, PartialEq)]
2575pub enum AngleRayDirection {
2576 Forward,
2577 Reverse,
2578}
2579
2580#[derive(Debug, Clone, Copy, PartialEq)]
2581pub enum AngleSector {
2582 One,
2583 Two,
2584 Three,
2585 Four,
2586}
2587
2588#[derive(Debug, Clone, Copy, PartialEq)]
2589pub enum AngleConstraintMode {
2590 LinesAtAngle,
2591 PointsAtAngle { sector: AngleSector, inverse: bool },
2592}
2593
2594#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2595#[ts(export_to = "Geometry.ts")]
2596#[serde(rename_all = "camelCase")]
2597pub enum SketchConstraintKind {
2598 Angle {
2599 line0: ConstrainableLine2d,
2600 line1: ConstrainableLine2d,
2601 #[serde(skip)]
2602 #[ts(skip)]
2603 mode: AngleConstraintMode,
2604 #[serde(rename = "labelPosition")]
2605 #[serde(skip_serializing_if = "Option::is_none")]
2606 #[ts(rename = "labelPosition")]
2607 #[ts(optional)]
2608 label_position: Option<ApiPoint2d<Number>>,
2609 },
2610 Distance {
2611 points: [ConstrainablePoint2dOrOrigin; 2],
2612 #[serde(rename = "labelPosition")]
2613 #[serde(skip_serializing_if = "Option::is_none")]
2614 #[ts(rename = "labelPosition")]
2615 #[ts(optional)]
2616 label_position: Option<ApiPoint2d<Number>>,
2617 },
2618 PointLineDistance {
2619 point: ConstrainablePoint2dOrOrigin,
2620 line: ConstrainableLine2d,
2621 input_object_ids: [Option<ObjectId>; 2],
2622 #[serde(rename = "labelPosition")]
2623 #[serde(skip_serializing_if = "Option::is_none")]
2624 #[ts(rename = "labelPosition")]
2625 #[ts(optional)]
2626 label_position: Option<ApiPoint2d<Number>>,
2627 },
2628 LineLineDistance {
2629 line0: ConstrainableLine2d,
2630 line1: ConstrainableLine2d,
2631 input_object_ids: [ObjectId; 2],
2632 #[serde(rename = "labelPosition")]
2633 #[serde(skip_serializing_if = "Option::is_none")]
2634 #[ts(rename = "labelPosition")]
2635 #[ts(optional)]
2636 label_position: Option<ApiPoint2d<Number>>,
2637 },
2638 PointCircularDistance {
2639 point: ConstrainablePoint2dOrOrigin,
2640 center: ConstrainablePoint2d,
2641 start: ConstrainablePoint2d,
2642 end: Option<ConstrainablePoint2d>,
2643 input_object_ids: [Option<ObjectId>; 2],
2644 #[serde(rename = "labelPosition")]
2645 #[serde(skip_serializing_if = "Option::is_none")]
2646 #[ts(rename = "labelPosition")]
2647 #[ts(optional)]
2648 label_position: Option<ApiPoint2d<Number>>,
2649 },
2650 LineCircularDistance {
2651 line: ConstrainableLine2d,
2652 center: ConstrainablePoint2d,
2653 start: ConstrainablePoint2d,
2654 end: Option<ConstrainablePoint2d>,
2655 input_object_ids: [ObjectId; 2],
2656 #[serde(rename = "labelPosition")]
2657 #[serde(skip_serializing_if = "Option::is_none")]
2658 #[ts(rename = "labelPosition")]
2659 #[ts(optional)]
2660 label_position: Option<ApiPoint2d<Number>>,
2661 },
2662 CircularCircularDistance {
2663 center0: ConstrainablePoint2d,
2664 start0: ConstrainablePoint2d,
2665 end0: Option<ConstrainablePoint2d>,
2666 center1: ConstrainablePoint2d,
2667 start1: ConstrainablePoint2d,
2668 end1: Option<ConstrainablePoint2d>,
2669 input_object_ids: [ObjectId; 2],
2670 #[serde(rename = "labelPosition")]
2671 #[serde(skip_serializing_if = "Option::is_none")]
2672 #[ts(rename = "labelPosition")]
2673 #[ts(optional)]
2674 label_position: Option<ApiPoint2d<Number>>,
2675 },
2676 Radius {
2677 points: [ConstrainablePoint2d; 2],
2678 #[serde(rename = "labelPosition")]
2679 #[serde(skip_serializing_if = "Option::is_none")]
2680 #[ts(rename = "labelPosition")]
2681 #[ts(optional)]
2682 label_position: Option<ApiPoint2d<Number>>,
2683 },
2684 Diameter {
2685 points: [ConstrainablePoint2d; 2],
2686 #[serde(rename = "labelPosition")]
2687 #[serde(skip_serializing_if = "Option::is_none")]
2688 #[ts(rename = "labelPosition")]
2689 #[ts(optional)]
2690 label_position: Option<ApiPoint2d<Number>>,
2691 },
2692 HorizontalDistance {
2693 points: [ConstrainablePoint2dOrOrigin; 2],
2694 #[serde(rename = "labelPosition")]
2695 #[serde(skip_serializing_if = "Option::is_none")]
2696 #[ts(rename = "labelPosition")]
2697 #[ts(optional)]
2698 label_position: Option<ApiPoint2d<Number>>,
2699 },
2700 VerticalDistance {
2701 points: [ConstrainablePoint2dOrOrigin; 2],
2702 #[serde(rename = "labelPosition")]
2703 #[serde(skip_serializing_if = "Option::is_none")]
2704 #[ts(rename = "labelPosition")]
2705 #[ts(optional)]
2706 label_position: Option<ApiPoint2d<Number>>,
2707 },
2708}
2709
2710impl SketchConstraintKind {
2711 pub fn name(&self) -> &'static str {
2712 match self {
2713 SketchConstraintKind::Angle { .. } => "angle",
2714 SketchConstraintKind::Distance { .. } => "distance",
2715 SketchConstraintKind::PointLineDistance { .. } => "distance",
2716 SketchConstraintKind::LineLineDistance { .. } => "distance",
2717 SketchConstraintKind::PointCircularDistance { .. } => "distance",
2718 SketchConstraintKind::LineCircularDistance { .. } => "distance",
2719 SketchConstraintKind::CircularCircularDistance { .. } => "distance",
2720 SketchConstraintKind::Radius { .. } => "radius",
2721 SketchConstraintKind::Diameter { .. } => "diameter",
2722 SketchConstraintKind::HorizontalDistance { .. } => "horizontalDistance",
2723 SketchConstraintKind::VerticalDistance { .. } => "verticalDistance",
2724 }
2725 }
2726}