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::ArcCtor;
12use kcl_api::ArcDirection;
13use kcl_api::CircleCtor;
14use kcl_api::ControlPointSplineCtor;
15use kcl_api::Freedom;
16use kcl_api::LineCtor;
17use kcl_api::Number;
18use kcl_api::ObjectId;
19use kcl_api::Point2d as ApiPoint2d;
20use kcl_api::PointCtor;
21use kcl_api::UnitLength;
22use kcl_error::SourceRange;
23use kittycad_modeling_cmds::ModelingCmd;
24use kittycad_modeling_cmds::each_cmd as mcmd;
25use kittycad_modeling_cmds::length_unit::LengthUnit;
26use kittycad_modeling_cmds::websocket::ModelingCmdReq;
27use kittycad_modeling_cmds::{self as kcmc};
28use parse_display::Display;
29use parse_display::FromStr;
30use serde::Deserialize;
31use serde::Serialize;
32use uuid::Uuid;
33
34use crate::NodePath;
35use crate::engine::DEFAULT_PLANE_INFO;
36use crate::engine::PlaneName;
37use crate::errors::KclError;
38use crate::errors::KclErrorDetails;
39use crate::exec::KclValue;
40use crate::execution::ArtifactId;
41use crate::execution::ExecState;
42use crate::execution::ExecutorContext;
43use crate::execution::Metadata;
44use crate::execution::TagEngineInfo;
45use crate::execution::TagIdentifier;
46use crate::execution::normalize_to_solver_distance_unit;
47use crate::execution::types::NumericType;
48use crate::execution::types::NumericTypeExt;
49use crate::execution::types::adjust_length;
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 #[ts(as = "Vec<Path>")]
920 pub paths: imbl::Vector<Path>,
921 #[serde(default, skip_serializing_if = "Vec::is_empty")]
923 pub inner_paths: Vec<Path>,
924 pub on: SketchSurface,
926 pub start: BasePath,
928 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
930 pub tags: IndexMap<String, TagIdentifier>,
931 pub artifact_id: ArtifactId,
934 #[ts(skip)]
935 pub original_id: uuid::Uuid,
936 #[serde(skip_serializing_if = "Option::is_none")]
941 #[ts(skip)]
942 pub origin_sketch_id: Option<uuid::Uuid>,
943 #[serde(skip)]
945 pub mirror: Option<uuid::Uuid>,
946 #[serde(skip)]
948 pub clone: Option<uuid::Uuid>,
949 #[serde(skip)]
951 #[ts(skip)]
952 pub synthetic_jump_path_ids: Vec<uuid::Uuid>,
953 pub units: UnitLength,
954 #[serde(skip)]
956 pub meta: Vec<Metadata>,
957 #[serde(
960 default = "ProfileClosed::explicitly",
961 skip_serializing_if = "ProfileClosed::is_explicitly"
962 )]
963 pub is_closed: ProfileClosed,
964}
965
966impl ProfileClosed {
967 #[expect(dead_code, reason = "it's not actually dead, it's called by serde")]
968 fn explicitly() -> Self {
969 Self::Explicitly
970 }
971
972 fn is_explicitly(&self) -> bool {
973 matches!(self, ProfileClosed::Explicitly)
974 }
975}
976
977#[derive(Debug, Serialize, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd, ts_rs::TS)]
979#[serde(rename_all = "camelCase")]
980pub enum ProfileClosed {
981 No,
983 Maybe,
985 Implicitly,
987 Explicitly,
989}
990
991impl Sketch {
992 pub(crate) fn build_sketch_mode_cmds(
995 &self,
996 exec_state: &mut ExecState,
997 inner_cmd: ModelingCmdReq,
998 ) -> Vec<ModelingCmdReq> {
999 vec![
1000 ModelingCmdReq {
1003 cmd: ModelingCmd::from(
1004 mcmd::EnableSketchMode::builder()
1005 .animated(false)
1006 .ortho(false)
1007 .entity_id(self.on.id())
1008 .adjust_camera(false)
1009 .maybe_planar_normal(if let SketchSurface::Plane(plane) = &self.on {
1010 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
1012 Some(normal.into())
1013 } else {
1014 None
1015 })
1016 .build(),
1017 ),
1018 cmd_id: exec_state.next_uuid().into(),
1019 },
1020 inner_cmd,
1021 ModelingCmdReq {
1022 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::builder().build()),
1023 cmd_id: exec_state.next_uuid().into(),
1024 },
1025 ]
1026 }
1027}
1028
1029#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1031#[ts(export)]
1032#[serde(tag = "type", rename_all = "camelCase")]
1033pub enum SketchSurface {
1034 Plane(Box<Plane>),
1035 Face(Box<Face>),
1036}
1037
1038impl SketchSurface {
1039 pub(crate) fn id(&self) -> uuid::Uuid {
1040 match self {
1041 SketchSurface::Plane(plane) => plane.id,
1042 SketchSurface::Face(face) => face.id,
1043 }
1044 }
1045 pub(crate) fn x_axis(&self) -> Point3d {
1046 match self {
1047 SketchSurface::Plane(plane) => plane.info.x_axis,
1048 SketchSurface::Face(face) => face.x_axis,
1049 }
1050 }
1051 pub(crate) fn y_axis(&self) -> Point3d {
1052 match self {
1053 SketchSurface::Plane(plane) => plane.info.y_axis,
1054 SketchSurface::Face(face) => face.y_axis,
1055 }
1056 }
1057
1058 pub(crate) fn object_id(&self) -> Option<ObjectId> {
1059 match self {
1060 SketchSurface::Plane(plane) => plane.object_id,
1061 SketchSurface::Face(face) => Some(face.object_id),
1062 }
1063 }
1064
1065 pub(crate) fn set_object_id(&mut self, object_id: ObjectId) {
1066 match self {
1067 SketchSurface::Plane(plane) => plane.object_id = Some(object_id),
1068 SketchSurface::Face(face) => face.object_id = object_id,
1069 }
1070 }
1071}
1072
1073#[derive(Debug, Clone, PartialEq)]
1075pub enum Extrudable {
1076 Sketch(Box<Sketch>),
1078 FaceTag(FaceTag),
1080 Face(Box<Face>),
1082 EdgeTag(Box<TagIdentifier>),
1084 Edge(Uuid),
1086 EdgeSpecifier(UnresolvedEdgeSpecifier),
1088}
1089
1090impl Extrudable {
1091 pub async fn id_to_extrude(
1093 &self,
1094 exec_state: &mut ExecState,
1095 args: &Args,
1096 must_be_planar: bool,
1097 ) -> Result<uuid::Uuid, KclError> {
1098 match self {
1099 Extrudable::Sketch(sketch) => Ok(sketch.id),
1100 Extrudable::FaceTag(face_tag) => face_tag.get_face_id_from_tag(exec_state, args, must_be_planar).await,
1101 Extrudable::Face(face) => Ok(face.id),
1102 Extrudable::EdgeTag(edge_tag) => match edge_tag.get_cur_info() {
1103 Some(info) => Ok(info.id),
1104 None => Err(KclError::new_type(KclErrorDetails::new(
1105 "Could not find a valid id to extrude".to_owned(),
1106 vec![args.source_range],
1107 ))),
1108 },
1109 Extrudable::Edge(edge) => Ok(*edge),
1110 Extrudable::EdgeSpecifier(_) => Err(KclError::new_type(KclErrorDetails::new(
1111 "Could not find a legacy id for edge specifier".to_owned(),
1112 vec![args.source_range],
1113 ))),
1114 }
1115 }
1116
1117 pub fn as_sketch(&self) -> Option<Sketch> {
1118 match self {
1119 Extrudable::Sketch(sketch) => Some((**sketch).clone()),
1120 Extrudable::FaceTag(face) => match face.geometry() {
1121 Some(Geometry::Sketch(sketch)) => Some(sketch),
1122 Some(Geometry::Solid(solid)) => solid.sketch().cloned(),
1123 None => None,
1124 },
1125 Extrudable::Face(_) => None,
1126 Extrudable::EdgeTag(tag_identifier) => match tag_identifier.geometry() {
1127 Some(Geometry::Sketch(sketch)) => Some(sketch),
1128 Some(Geometry::Solid(solid)) => solid.sketch().cloned(),
1129 None => None,
1130 },
1131 Extrudable::Edge(_) => None,
1132 Extrudable::EdgeSpecifier(_) => None,
1133 }
1134 }
1135
1136 pub fn is_closed(&self) -> ProfileClosed {
1137 match self {
1138 Extrudable::Sketch(sketch) => sketch.is_closed,
1139 Extrudable::FaceTag(face_tag) => match face_tag.geometry() {
1140 Some(Geometry::Sketch(sketch)) => sketch.is_closed,
1141 Some(Geometry::Solid(solid)) => solid
1142 .sketch()
1143 .map(|sketch| sketch.is_closed)
1144 .unwrap_or(ProfileClosed::Maybe),
1145 _ => ProfileClosed::Maybe,
1146 },
1147 Extrudable::Face(face) => match face.parent_solid.creator_sketch_is_closed {
1148 Some(is_closed) => is_closed,
1149 None => ProfileClosed::Maybe,
1150 },
1151 Extrudable::EdgeTag(edge_tag) => match edge_tag.geometry() {
1152 Some(Geometry::Sketch(sketch)) => sketch.is_closed,
1153 Some(Geometry::Solid(solid)) => solid
1154 .sketch()
1155 .map(|sketch| sketch.is_closed)
1156 .unwrap_or(ProfileClosed::Maybe),
1157 _ => ProfileClosed::Maybe,
1158 },
1159 Extrudable::Edge(_) => ProfileClosed::Maybe,
1160 Extrudable::EdgeSpecifier(_) => ProfileClosed::Maybe,
1161 }
1162 }
1163}
1164
1165impl From<Sketch> for Extrudable {
1166 fn from(value: Sketch) -> Self {
1167 Extrudable::Sketch(Box::new(value))
1168 }
1169}
1170
1171#[derive(Debug, Clone)]
1172pub(crate) enum GetTangentialInfoFromPathsResult {
1173 PreviousPoint([f64; 2]),
1174 Arc {
1175 center: [f64; 2],
1176 ccw: bool,
1177 },
1178 Circle {
1179 center: [f64; 2],
1180 ccw: bool,
1181 radius: f64,
1182 },
1183 Ellipse {
1184 center: [f64; 2],
1185 ccw: bool,
1186 major_axis: [f64; 2],
1187 _minor_radius: f64,
1188 },
1189}
1190
1191impl GetTangentialInfoFromPathsResult {
1192 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
1193 match self {
1194 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
1195 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
1196 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
1197 }
1198 GetTangentialInfoFromPathsResult::Circle {
1201 center, radius, ccw, ..
1202 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
1203 GetTangentialInfoFromPathsResult::Ellipse {
1204 center,
1205 major_axis,
1206 ccw,
1207 ..
1208 } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
1209 }
1210 }
1211}
1212
1213impl Sketch {
1214 pub(crate) fn add_tag(
1215 &mut self,
1216 tag: NodeRef<'_, TagDeclarator>,
1217 current_path: &Path,
1218 exec_state: &ExecState,
1219 surface: Option<&ExtrudeSurface>,
1220 ) {
1221 let mut tag_identifier: TagIdentifier = tag.into();
1222 let base = current_path.get_base();
1223 let mut sketch_copy = self.clone();
1224 sketch_copy.tags.clear();
1225 tag_identifier.info.push((
1226 exec_state.stack().current_epoch(),
1227 TagEngineInfo {
1228 id: base.geo_meta.id,
1229 geometry: Geometry::Sketch(sketch_copy),
1230 path: Some(current_path.clone()),
1231 surface: surface.cloned(),
1232 },
1233 ));
1234
1235 self.tags.insert(tag.name.to_string(), tag_identifier);
1236 }
1237
1238 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
1239 for t in tags {
1240 match self.tags.get_mut(&t.value) {
1241 Some(id) => {
1242 id.merge_info(t);
1243 }
1244 None => {
1245 self.tags.insert(t.value.clone(), t.clone());
1246 }
1247 }
1248 }
1249 }
1250
1251 pub(crate) fn latest_path(&self) -> Option<&Path> {
1253 self.paths.last()
1254 }
1255
1256 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
1260 let Some(path) = self.latest_path() else {
1261 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
1262 };
1263
1264 let to = path.get_base().to;
1265 Ok(Point2d::new(to[0], to[1], path.get_base().units))
1266 }
1267
1268 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
1269 let Some(path) = self.latest_path() else {
1270 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
1271 };
1272 path.get_tangential_info()
1273 }
1274}
1275
1276#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1277#[ts(export)]
1278#[serde(tag = "type", rename_all = "camelCase")]
1279pub struct Solid {
1280 pub id: uuid::Uuid,
1282 #[serde(skip)]
1284 #[ts(skip)]
1285 pub value_id: uuid::Uuid,
1286 #[serde(skip)]
1290 #[ts(skip)]
1291 pub(crate) topology_id: uuid::Uuid,
1292 #[serde(skip)]
1297 #[ts(skip)]
1298 pub(crate) tag_update_source_artifact_id: Option<ArtifactId>,
1299 #[serde(skip)]
1303 #[ts(skip)]
1304 pub(crate) pattern_source_artifact_id: Option<ArtifactId>,
1305 #[serde(skip)]
1311 #[ts(skip)]
1312 pub(crate) best_guess_body_type: Option<kcmc::shared::BodyType>,
1313 pub artifact_id: ArtifactId,
1315 pub value: Vec<ExtrudeSurface>,
1317 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
1320 pub faces: IndexMap<String, TagIdentifier>,
1321 #[serde(rename = "sketch")]
1323 pub creator: SolidCreator,
1324 pub start_cap_id: Option<uuid::Uuid>,
1326 pub end_cap_id: Option<uuid::Uuid>,
1328 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1330 pub edge_cuts: Vec<EdgeCut>,
1331 #[serde(skip)]
1333 #[ts(skip)]
1334 pub pending_edge_cut_ids: Vec<uuid::Uuid>,
1335 pub units: UnitLength,
1337 pub sectional: bool,
1339 #[serde(skip)]
1341 pub meta: Vec<Metadata>,
1342}
1343
1344#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1345#[ts(export)]
1346pub struct CreatorFace {
1347 pub face_id: uuid::Uuid,
1349 pub solid_id: uuid::Uuid,
1351 pub sketch: Sketch,
1353}
1354
1355#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1356#[ts(export)]
1357pub struct CreatorEdge {
1358 pub edge_id: uuid::Uuid,
1360 pub body_id: uuid::Uuid,
1362}
1363
1364#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1366#[ts(export)]
1367#[serde(tag = "creatorType", rename_all = "camelCase")]
1368pub enum SolidCreator {
1369 Sketch(Sketch),
1371 Face(CreatorFace),
1373 Edge(CreatorEdge),
1375 Procedural,
1377}
1378
1379#[derive(Debug, Clone, Copy)]
1381pub(crate) enum NewBodyKind {
1382 Replacement,
1384 IndependentCopy,
1387}
1388
1389impl Solid {
1390 pub fn sketch(&self) -> Option<&Sketch> {
1391 match &self.creator {
1392 SolidCreator::Sketch(sketch) => Some(sketch),
1393 SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1394 SolidCreator::Edge(_) => None,
1395 SolidCreator::Procedural => None,
1396 }
1397 }
1398
1399 pub fn sketch_mut(&mut self) -> Option<&mut Sketch> {
1400 match &mut self.creator {
1401 SolidCreator::Sketch(sketch) => Some(sketch),
1402 SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1403 SolidCreator::Edge(_) => None,
1404 SolidCreator::Procedural => None,
1405 }
1406 }
1407
1408 pub fn sketch_id(&self) -> Option<uuid::Uuid> {
1409 self.sketch().map(|sketch| sketch.id)
1410 }
1411
1412 pub fn original_id(&self) -> uuid::Uuid {
1413 self.sketch().map(|sketch| sketch.original_id).unwrap_or(self.id)
1414 }
1415
1416 pub(crate) fn topology_id(&self) -> uuid::Uuid {
1417 self.topology_id
1418 }
1419
1420 pub(crate) fn become_new_body(&mut self, engine_id: uuid::Uuid, artifact_id: ArtifactId, kind: NewBodyKind) {
1425 self.topology_id = engine_id;
1426 self.pattern_source_artifact_id = None;
1427 self.artifact_id = artifact_id;
1428 match kind {
1429 NewBodyKind::Replacement => {}
1430 NewBodyKind::IndependentCopy => self.tag_update_source_artifact_id = None,
1431 }
1432 }
1433
1434 pub(crate) fn become_pattern_copy(&mut self, copy_engine_id: uuid::Uuid) {
1438 self.pattern_source_artifact_id.get_or_insert(self.artifact_id);
1439 self.artifact_id = ArtifactId::new(copy_engine_id);
1440 self.tag_update_source_artifact_id = None;
1441 }
1442
1443 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
1444 self.edge_cuts
1445 .iter()
1446 .map(|foc| foc.id())
1447 .chain(self.pending_edge_cut_ids.iter().copied())
1448 }
1449}
1450
1451impl From<&Solid> for FaceParentSolid {
1452 fn from(solid: &Solid) -> Self {
1453 Self {
1454 solid_id: solid.id,
1455 creator_sketch_id: solid.sketch_id(),
1456 creator_sketch_is_closed: solid.sketch().map(|sketch| sketch.is_closed),
1457 edge_cut_ids: solid.get_all_edge_cut_ids().collect(),
1458 }
1459 }
1460}
1461
1462#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1464#[ts(export)]
1465#[serde(tag = "type", rename_all = "camelCase")]
1466pub enum EdgeCut {
1467 Fillet {
1469 id: uuid::Uuid,
1471 radius: TyF64,
1472 #[serde(rename = "edgeId")]
1474 edge_id: uuid::Uuid,
1475 tag: Box<Option<TagNode>>,
1476 },
1477 Chamfer {
1479 id: uuid::Uuid,
1481 length: TyF64,
1482 #[serde(rename = "edgeId")]
1484 edge_id: uuid::Uuid,
1485 tag: Box<Option<TagNode>>,
1486 },
1487}
1488
1489impl EdgeCut {
1490 pub fn id(&self) -> uuid::Uuid {
1491 match self {
1492 EdgeCut::Fillet { id, .. } => *id,
1493 EdgeCut::Chamfer { id, .. } => *id,
1494 }
1495 }
1496
1497 pub fn set_id(&mut self, id: uuid::Uuid) {
1498 match self {
1499 EdgeCut::Fillet { id: i, .. } => *i = id,
1500 EdgeCut::Chamfer { id: i, .. } => *i = id,
1501 }
1502 }
1503
1504 pub fn edge_id(&self) -> uuid::Uuid {
1505 match self {
1506 EdgeCut::Fillet { edge_id, .. } => *edge_id,
1507 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
1508 }
1509 }
1510
1511 pub fn set_edge_id(&mut self, id: uuid::Uuid) {
1512 match self {
1513 EdgeCut::Fillet { edge_id: i, .. } => *i = id,
1514 EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
1515 }
1516 }
1517
1518 pub fn tag(&self) -> Option<TagNode> {
1519 match self {
1520 EdgeCut::Fillet { tag, .. } => *tag.clone(),
1521 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
1522 }
1523 }
1524}
1525
1526#[derive(Debug, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
1527#[ts(export)]
1528pub struct Point2d {
1529 pub x: f64,
1530 pub y: f64,
1531 pub units: UnitLength,
1532}
1533
1534impl Point2d {
1535 pub const ZERO: Self = Self {
1536 x: 0.0,
1537 y: 0.0,
1538 units: UnitLength::Millimeters,
1539 };
1540
1541 pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
1542 Self { x, y, units }
1543 }
1544
1545 pub fn into_x(self) -> TyF64 {
1546 TyF64::new(self.x, NumericType::length(self.units))
1547 }
1548
1549 pub fn into_y(self) -> TyF64 {
1550 TyF64::new(self.y, NumericType::length(self.units))
1551 }
1552
1553 pub fn ignore_units(self) -> [f64; 2] {
1554 [self.x, self.y]
1555 }
1556}
1557
1558#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
1559#[ts(export)]
1560pub struct Point3d {
1561 pub x: f64,
1562 pub y: f64,
1563 pub z: f64,
1564 pub units: Option<UnitLength>,
1565}
1566
1567impl Point3d {
1568 pub const ZERO: Self = Self {
1569 x: 0.0,
1570 y: 0.0,
1571 z: 0.0,
1572 units: Some(UnitLength::Millimeters),
1573 };
1574
1575 pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
1576 Self { x, y, z, units }
1577 }
1578
1579 pub const fn is_zero(&self) -> bool {
1580 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
1581 }
1582
1583 pub fn axes_cross_product(&self, other: &Self) -> Self {
1588 Self {
1589 x: self.y * other.z - self.z * other.y,
1590 y: self.z * other.x - self.x * other.z,
1591 z: self.x * other.y - self.y * other.x,
1592 units: None,
1593 }
1594 }
1595
1596 pub fn canonicalize_signed_zero(&mut self) {
1598 if self.x == 0.0 {
1599 self.x = 0.0;
1600 }
1601 if self.y == 0.0 {
1602 self.y = 0.0;
1603 }
1604 if self.z == 0.0 {
1605 self.z = 0.0;
1606 }
1607 }
1608
1609 pub fn axes_dot_product(&self, other: &Self) -> f64 {
1614 let x = self.x * other.x;
1615 let y = self.y * other.y;
1616 let z = self.z * other.z;
1617 x + y + z
1618 }
1619
1620 pub fn normalize(&self) -> Self {
1621 let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1622 Point3d {
1623 x: self.x / len,
1624 y: self.y / len,
1625 z: self.z / len,
1626 units: None,
1627 }
1628 }
1629
1630 pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1631 let p = [self.x, self.y, self.z];
1632 let u = self.units;
1633 (p, u)
1634 }
1635
1636 pub(crate) fn negated(self) -> Self {
1637 Self {
1638 x: -self.x,
1639 y: -self.y,
1640 z: -self.z,
1641 units: self.units,
1642 }
1643 }
1644}
1645
1646impl From<[TyF64; 3]> for Point3d {
1647 fn from(p: [TyF64; 3]) -> Self {
1648 Self {
1649 x: p[0].n,
1650 y: p[1].n,
1651 z: p[2].n,
1652 units: p[0].ty.as_length(),
1653 }
1654 }
1655}
1656
1657impl From<Point3d> for Point3D {
1658 fn from(p: Point3d) -> Self {
1659 Self { x: p.x, y: p.y, z: p.z }
1660 }
1661}
1662
1663impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1664 fn from(p: Point3d) -> Self {
1665 if let Some(units) = p.units {
1666 Self {
1667 x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1668 y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1669 z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1670 }
1671 } else {
1672 Self {
1673 x: LengthUnit(p.x),
1674 y: LengthUnit(p.y),
1675 z: LengthUnit(p.z),
1676 }
1677 }
1678 }
1679}
1680
1681impl Add for Point3d {
1682 type Output = Point3d;
1683
1684 fn add(self, rhs: Self) -> Self::Output {
1685 Point3d {
1687 x: self.x + rhs.x,
1688 y: self.y + rhs.y,
1689 z: self.z + rhs.z,
1690 units: self.units,
1691 }
1692 }
1693}
1694
1695impl AddAssign for Point3d {
1696 fn add_assign(&mut self, rhs: Self) {
1697 *self = *self + rhs
1698 }
1699}
1700
1701impl Sub for Point3d {
1702 type Output = Point3d;
1703
1704 fn sub(self, rhs: Self) -> Self::Output {
1705 let (x, y, z) = if rhs.units != self.units
1706 && let Some(sunits) = self.units
1707 && let Some(runits) = rhs.units
1708 {
1709 (
1710 adjust_length(runits, rhs.x, sunits).0,
1711 adjust_length(runits, rhs.y, sunits).0,
1712 adjust_length(runits, rhs.z, sunits).0,
1713 )
1714 } else {
1715 (rhs.x, rhs.y, rhs.z)
1716 };
1717 Point3d {
1718 x: self.x - x,
1719 y: self.y - y,
1720 z: self.z - z,
1721 units: self.units,
1722 }
1723 }
1724}
1725
1726impl SubAssign for Point3d {
1727 fn sub_assign(&mut self, rhs: Self) {
1728 *self = *self - rhs
1729 }
1730}
1731
1732impl Mul<f64> for Point3d {
1733 type Output = Point3d;
1734
1735 fn mul(self, rhs: f64) -> Self::Output {
1736 Point3d {
1737 x: self.x * rhs,
1738 y: self.y * rhs,
1739 z: self.z * rhs,
1740 units: self.units,
1741 }
1742 }
1743}
1744
1745#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1747#[ts(export)]
1748#[serde(rename_all = "camelCase")]
1749pub struct BasePath {
1750 #[ts(type = "[number, number]")]
1752 pub from: [f64; 2],
1753 #[ts(type = "[number, number]")]
1755 pub to: [f64; 2],
1756 pub units: UnitLength,
1757 pub tag: Option<TagNode>,
1759 #[serde(rename = "__geoMeta")]
1761 pub geo_meta: GeoMeta,
1762}
1763
1764impl BasePath {
1765 pub fn get_to(&self) -> [TyF64; 2] {
1766 let ty = NumericType::length(self.units);
1767 [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1768 }
1769
1770 pub fn get_from(&self) -> [TyF64; 2] {
1771 let ty = NumericType::length(self.units);
1772 [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1773 }
1774}
1775
1776#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1778#[ts(export)]
1779#[serde(rename_all = "camelCase")]
1780pub struct GeoMeta {
1781 pub id: uuid::Uuid,
1783 #[serde(flatten)]
1785 pub metadata: Metadata,
1786}
1787
1788#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1790#[ts(export)]
1791#[serde(tag = "type")]
1792pub enum Path {
1793 ToPoint {
1795 #[serde(flatten)]
1796 base: BasePath,
1797 },
1798 TangentialArcTo {
1800 #[serde(flatten)]
1801 base: BasePath,
1802 #[ts(type = "[number, number]")]
1804 center: [f64; 2],
1805 ccw: bool,
1807 },
1808 TangentialArc {
1810 #[serde(flatten)]
1811 base: BasePath,
1812 #[ts(type = "[number, number]")]
1814 center: [f64; 2],
1815 ccw: bool,
1817 },
1818 Circle {
1821 #[serde(flatten)]
1822 base: BasePath,
1823 #[ts(type = "[number, number]")]
1825 center: [f64; 2],
1826 radius: f64,
1828 ccw: bool,
1831 },
1832 CircleThreePoint {
1833 #[serde(flatten)]
1834 base: BasePath,
1835 #[ts(type = "[number, number]")]
1837 p1: [f64; 2],
1838 #[ts(type = "[number, number]")]
1840 p2: [f64; 2],
1841 #[ts(type = "[number, number]")]
1843 p3: [f64; 2],
1844 },
1845 ArcThreePoint {
1846 #[serde(flatten)]
1847 base: BasePath,
1848 #[ts(type = "[number, number]")]
1850 p1: [f64; 2],
1851 #[ts(type = "[number, number]")]
1853 p2: [f64; 2],
1854 #[ts(type = "[number, number]")]
1856 p3: [f64; 2],
1857 },
1858 Horizontal {
1860 #[serde(flatten)]
1861 base: BasePath,
1862 x: f64,
1864 },
1865 AngledLineTo {
1867 #[serde(flatten)]
1868 base: BasePath,
1869 x: Option<f64>,
1871 y: Option<f64>,
1873 },
1874 Base {
1876 #[serde(flatten)]
1877 base: BasePath,
1878 },
1879 Arc {
1881 #[serde(flatten)]
1882 base: BasePath,
1883 center: [f64; 2],
1885 radius: f64,
1887 ccw: bool,
1889 },
1890 Ellipse {
1891 #[serde(flatten)]
1892 base: BasePath,
1893 center: [f64; 2],
1894 major_axis: [f64; 2],
1895 minor_radius: f64,
1896 ccw: bool,
1897 },
1898 Conic {
1900 #[serde(flatten)]
1901 base: BasePath,
1902 },
1903 Bezier {
1905 #[serde(flatten)]
1906 base: BasePath,
1907 #[ts(type = "[number, number]")]
1909 control1: [f64; 2],
1910 #[ts(type = "[number, number]")]
1912 control2: [f64; 2],
1913 },
1914}
1915
1916impl Path {
1917 pub fn get_id(&self) -> uuid::Uuid {
1918 match self {
1919 Path::ToPoint { base } => base.geo_meta.id,
1920 Path::Horizontal { base, .. } => base.geo_meta.id,
1921 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1922 Path::Base { base } => base.geo_meta.id,
1923 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1924 Path::TangentialArc { base, .. } => base.geo_meta.id,
1925 Path::Circle { base, .. } => base.geo_meta.id,
1926 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1927 Path::Arc { base, .. } => base.geo_meta.id,
1928 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1929 Path::Ellipse { base, .. } => base.geo_meta.id,
1930 Path::Conic { base, .. } => base.geo_meta.id,
1931 Path::Bezier { base, .. } => base.geo_meta.id,
1932 }
1933 }
1934
1935 pub fn set_id(&mut self, id: uuid::Uuid) {
1936 match self {
1937 Path::ToPoint { base } => base.geo_meta.id = id,
1938 Path::Horizontal { base, .. } => base.geo_meta.id = id,
1939 Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1940 Path::Base { base } => base.geo_meta.id = id,
1941 Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1942 Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1943 Path::Circle { base, .. } => base.geo_meta.id = id,
1944 Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1945 Path::Arc { base, .. } => base.geo_meta.id = id,
1946 Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1947 Path::Ellipse { base, .. } => base.geo_meta.id = id,
1948 Path::Conic { base, .. } => base.geo_meta.id = id,
1949 Path::Bezier { base, .. } => base.geo_meta.id = id,
1950 }
1951 }
1952
1953 pub fn get_tag(&self) -> Option<TagNode> {
1954 match self {
1955 Path::ToPoint { base } => base.tag.clone(),
1956 Path::Horizontal { base, .. } => base.tag.clone(),
1957 Path::AngledLineTo { base, .. } => base.tag.clone(),
1958 Path::Base { base } => base.tag.clone(),
1959 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1960 Path::TangentialArc { base, .. } => base.tag.clone(),
1961 Path::Circle { base, .. } => base.tag.clone(),
1962 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1963 Path::Arc { base, .. } => base.tag.clone(),
1964 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1965 Path::Ellipse { base, .. } => base.tag.clone(),
1966 Path::Conic { base, .. } => base.tag.clone(),
1967 Path::Bezier { base, .. } => base.tag.clone(),
1968 }
1969 }
1970
1971 pub fn get_base(&self) -> &BasePath {
1972 match self {
1973 Path::ToPoint { base } => base,
1974 Path::Horizontal { base, .. } => base,
1975 Path::AngledLineTo { base, .. } => base,
1976 Path::Base { base } => base,
1977 Path::TangentialArcTo { base, .. } => base,
1978 Path::TangentialArc { base, .. } => base,
1979 Path::Circle { base, .. } => base,
1980 Path::CircleThreePoint { base, .. } => base,
1981 Path::Arc { base, .. } => base,
1982 Path::ArcThreePoint { base, .. } => base,
1983 Path::Ellipse { base, .. } => base,
1984 Path::Conic { base, .. } => base,
1985 Path::Bezier { base, .. } => base,
1986 }
1987 }
1988
1989 pub fn get_from(&self) -> [TyF64; 2] {
1991 let p = &self.get_base().from;
1992 let ty = NumericType::length(self.get_base().units);
1993 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1994 }
1995
1996 pub fn get_to(&self) -> [TyF64; 2] {
1998 let p = &self.get_base().to;
1999 let ty = NumericType::length(self.get_base().units);
2000 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
2001 }
2002
2003 pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
2005 let p = &self.get_base().from;
2006 let ty = NumericType::length(self.get_base().units);
2007 (*p, ty)
2008 }
2009
2010 pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
2012 let p = &self.get_base().to;
2013 let ty = NumericType::length(self.get_base().units);
2014 (*p, ty)
2015 }
2016
2017 pub fn length(&self) -> Option<TyF64> {
2020 let n = match self {
2021 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
2022 Some(linear_distance(&self.get_base().from, &self.get_base().to))
2023 }
2024 Self::TangentialArc {
2025 base: _,
2026 center,
2027 ccw: _,
2028 }
2029 | Self::TangentialArcTo {
2030 base: _,
2031 center,
2032 ccw: _,
2033 } => {
2034 let radius = linear_distance(&self.get_base().from, center);
2037 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
2038 Some(linear_distance(&self.get_base().from, &self.get_base().to))
2040 }
2041 Self::Circle { radius, .. } => Some(TAU * radius),
2042 Self::CircleThreePoint { .. } => {
2043 let circle_center = crate::std::utils::calculate_circle_from_3_points([
2044 self.get_base().from,
2045 self.get_base().to,
2046 self.get_base().to,
2047 ]);
2048 let radius = linear_distance(
2049 &[circle_center.center[0], circle_center.center[1]],
2050 &self.get_base().from,
2051 );
2052 Some(TAU * radius)
2053 }
2054 Self::Arc { .. } => {
2055 Some(linear_distance(&self.get_base().from, &self.get_base().to))
2057 }
2058 Self::ArcThreePoint { .. } => {
2059 Some(linear_distance(&self.get_base().from, &self.get_base().to))
2061 }
2062 Self::Ellipse { .. } => {
2063 None
2065 }
2066 Self::Conic { .. } => {
2067 None
2069 }
2070 Self::Bezier { .. } => {
2071 None
2073 }
2074 };
2075 n.map(|n| TyF64::new(n, NumericType::length(self.get_base().units)))
2076 }
2077
2078 pub fn get_base_mut(&mut self) -> &mut BasePath {
2079 match self {
2080 Path::ToPoint { base } => base,
2081 Path::Horizontal { base, .. } => base,
2082 Path::AngledLineTo { base, .. } => base,
2083 Path::Base { base } => base,
2084 Path::TangentialArcTo { base, .. } => base,
2085 Path::TangentialArc { base, .. } => base,
2086 Path::Circle { base, .. } => base,
2087 Path::CircleThreePoint { base, .. } => base,
2088 Path::Arc { base, .. } => base,
2089 Path::ArcThreePoint { base, .. } => base,
2090 Path::Ellipse { base, .. } => base,
2091 Path::Conic { base, .. } => base,
2092 Path::Bezier { base, .. } => base,
2093 }
2094 }
2095
2096 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
2097 match self {
2098 Path::TangentialArc { center, ccw, .. }
2099 | Path::TangentialArcTo { center, ccw, .. }
2100 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
2101 center: *center,
2102 ccw: *ccw,
2103 },
2104 Path::ArcThreePoint { p1, p2, p3, .. } => {
2105 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
2106 GetTangentialInfoFromPathsResult::Arc {
2107 center: circle.center,
2108 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
2109 }
2110 }
2111 Path::Circle {
2112 center, ccw, radius, ..
2113 } => GetTangentialInfoFromPathsResult::Circle {
2114 center: *center,
2115 ccw: *ccw,
2116 radius: *radius,
2117 },
2118 Path::CircleThreePoint { p1, p2, p3, .. } => {
2119 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
2120 let center_point = [circle.center[0], circle.center[1]];
2121 GetTangentialInfoFromPathsResult::Circle {
2122 center: center_point,
2123 ccw: true,
2125 radius: circle.radius,
2126 }
2127 }
2128 Path::Ellipse {
2130 center,
2131 major_axis,
2132 minor_radius,
2133 ccw,
2134 ..
2135 } => GetTangentialInfoFromPathsResult::Ellipse {
2136 center: *center,
2137 major_axis: *major_axis,
2138 _minor_radius: *minor_radius,
2139 ccw: *ccw,
2140 },
2141 Path::Conic { .. }
2142 | Path::ToPoint { .. }
2143 | Path::Horizontal { .. }
2144 | Path::AngledLineTo { .. }
2145 | Path::Base { .. }
2146 | Path::Bezier { .. } => {
2147 let base = self.get_base();
2148 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
2149 }
2150 }
2151 }
2152
2153 pub(crate) fn is_straight_line(&self) -> bool {
2155 matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
2156 }
2157}
2158
2159#[rustfmt::skip]
2161fn linear_distance(
2162 [x0, y0]: &[f64; 2],
2163 [x1, y1]: &[f64; 2]
2164) -> f64 {
2165 let y_sq = (y1 - y0).squared();
2166 let x_sq = (x1 - x0).squared();
2167 (y_sq + x_sq).sqrt()
2168}
2169
2170#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2172#[ts(export)]
2173#[serde(tag = "type", rename_all = "camelCase")]
2174pub enum ExtrudeSurface {
2175 ExtrudePlane(ExtrudePlane),
2177 ExtrudeArc(ExtrudeArc),
2178 Chamfer(ChamferSurface),
2179 Fillet(FilletSurface),
2180}
2181
2182#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2184#[ts(export)]
2185#[serde(rename_all = "camelCase")]
2186pub struct ChamferSurface {
2187 pub face_id: uuid::Uuid,
2189 pub tag: Option<Node<TagDeclarator>>,
2191 #[serde(flatten)]
2193 pub geo_meta: GeoMeta,
2194}
2195
2196#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2198#[ts(export)]
2199#[serde(rename_all = "camelCase")]
2200pub struct FilletSurface {
2201 pub face_id: uuid::Uuid,
2203 pub tag: Option<Node<TagDeclarator>>,
2205 #[serde(flatten)]
2207 pub geo_meta: GeoMeta,
2208}
2209
2210#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2212#[ts(export)]
2213#[serde(rename_all = "camelCase")]
2214pub struct ExtrudePlane {
2215 pub face_id: uuid::Uuid,
2217 pub tag: Option<Node<TagDeclarator>>,
2219 #[serde(flatten)]
2221 pub geo_meta: GeoMeta,
2222}
2223
2224#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2226#[ts(export)]
2227#[serde(rename_all = "camelCase")]
2228pub struct ExtrudeArc {
2229 pub face_id: uuid::Uuid,
2231 pub tag: Option<Node<TagDeclarator>>,
2233 #[serde(flatten)]
2235 pub geo_meta: GeoMeta,
2236}
2237
2238impl ExtrudeSurface {
2239 pub fn get_id(&self) -> uuid::Uuid {
2240 match self {
2241 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
2242 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
2243 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
2244 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
2245 }
2246 }
2247
2248 pub fn set_id(&mut self, id: uuid::Uuid) {
2249 match self {
2250 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id = id,
2251 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id = id,
2252 ExtrudeSurface::Fillet(f) => f.geo_meta.id = id,
2253 ExtrudeSurface::Chamfer(c) => c.geo_meta.id = id,
2254 }
2255 }
2256
2257 pub fn face_id(&self) -> uuid::Uuid {
2258 match self {
2259 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id,
2260 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id,
2261 ExtrudeSurface::Fillet(f) => f.face_id,
2262 ExtrudeSurface::Chamfer(c) => c.face_id,
2263 }
2264 }
2265
2266 pub fn set_face_id(&mut self, face_id: uuid::Uuid) {
2267 match self {
2268 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id = face_id,
2269 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id = face_id,
2270 ExtrudeSurface::Fillet(f) => f.face_id = face_id,
2271 ExtrudeSurface::Chamfer(c) => c.face_id = face_id,
2272 }
2273 }
2274
2275 pub fn set_surface_tag(&mut self, tag: &TagNode) {
2276 match self {
2277 ExtrudeSurface::ExtrudePlane(extrude_plane) => extrude_plane.tag = Some(tag.clone()),
2278 ExtrudeSurface::ExtrudeArc(extrude_arc) => extrude_arc.tag = Some(tag.clone()),
2279 ExtrudeSurface::Chamfer(chamfer) => chamfer.tag = Some(tag.clone()),
2280 ExtrudeSurface::Fillet(fillet) => fillet.tag = Some(tag.clone()),
2281 }
2282 }
2283
2284 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
2285 match self {
2286 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
2287 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
2288 ExtrudeSurface::Fillet(f) => f.tag.clone(),
2289 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
2290 }
2291 }
2292}
2293
2294#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ts_rs::TS)]
2295pub struct SketchVarId(pub usize);
2296
2297impl SketchVarId {
2298 pub const INVALID: Self = Self(usize::MAX);
2299
2300 pub fn to_constraint_id(self, range: SourceRange) -> Result<ezpz::Id, KclError> {
2301 self.0.try_into().map_err(|_| {
2302 KclError::new_type(KclErrorDetails::new(
2303 "Cannot convert to constraint ID since the sketch variable ID is too large".to_owned(),
2304 vec![range],
2305 ))
2306 })
2307 }
2308}
2309
2310#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2311#[ts(export_to = "Geometry.ts")]
2312#[serde(rename_all = "camelCase")]
2313pub struct SketchVar {
2314 pub id: SketchVarId,
2315 pub initial_value: f64,
2316 pub ty: NumericType,
2317 pub node_path: Option<NodePath>,
2319 #[serde(skip)]
2320 pub meta: Vec<Metadata>,
2321}
2322
2323impl SketchVar {
2324 pub fn initial_value_to_solver_units(
2325 &self,
2326 exec_state: &mut ExecState,
2327 source_range: SourceRange,
2328 description: &str,
2329 ) -> Result<TyF64, KclError> {
2330 let x_initial_value = KclValue::Number {
2331 value: self.initial_value,
2332 ty: self.ty,
2333 meta: vec![source_range.into()],
2334 };
2335 let normalized_value =
2336 normalize_to_solver_distance_unit(&x_initial_value, source_range, exec_state, description)?;
2337 normalized_value.as_ty_f64().ok_or_else(|| {
2338 let message = format!(
2339 "Expected number after coercion, but found {}",
2340 normalized_value.human_friendly_type()
2341 );
2342 debug_assert!(false, "{}", &message);
2343 KclError::new_internal(KclErrorDetails::new(message, vec![source_range]))
2344 })
2345 }
2346}
2347
2348#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2349#[ts(export_to = "Geometry.ts")]
2350#[serde(tag = "type")]
2351pub enum UnsolvedExpr {
2352 Known(TyF64),
2353 Unknown(SketchVarId),
2354}
2355
2356impl UnsolvedExpr {
2357 pub fn var(&self) -> Option<SketchVarId> {
2358 match self {
2359 UnsolvedExpr::Known(_) => None,
2360 UnsolvedExpr::Unknown(id) => Some(*id),
2361 }
2362 }
2363}
2364
2365pub type UnsolvedPoint2dExpr = [UnsolvedExpr; 2];
2366
2367#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2368#[ts(export_to = "Geometry.ts")]
2369#[serde(rename_all = "camelCase")]
2370pub struct ConstrainablePoint2d {
2371 pub vars: kcl_api::Point2d<SketchVarId>,
2372 pub object_id: ObjectId,
2373}
2374
2375#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2376#[ts(export_to = "Geometry.ts")]
2377pub enum ConstrainablePoint2dOrOrigin {
2378 Point(ConstrainablePoint2d),
2379 Origin,
2380}
2381
2382#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2383#[ts(export_to = "Geometry.ts")]
2384#[serde(rename_all = "camelCase")]
2385pub struct ConstrainableLine2d {
2386 pub vars: [kcl_api::Point2d<SketchVarId>; 2],
2387 pub object_id: ObjectId,
2388}
2389
2390#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2391#[ts(export_to = "Geometry.ts")]
2392#[serde(rename_all = "camelCase")]
2393pub struct UnsolvedSegment {
2394 pub id: Uuid,
2396 pub object_id: ObjectId,
2397 pub kind: UnsolvedSegmentKind,
2398 #[serde(skip_serializing_if = "Option::is_none")]
2399 pub tag: Option<TagIdentifier>,
2400 #[serde(skip)]
2401 pub node_path: Option<NodePath>,
2402 #[serde(skip)]
2403 pub meta: Vec<Metadata>,
2404}
2405
2406#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2407#[ts(export_to = "Geometry.ts")]
2408#[serde(rename_all = "camelCase")]
2409pub enum UnsolvedSegmentKind {
2410 Point {
2411 position: UnsolvedPoint2dExpr,
2412 ctor: Box<PointCtor>,
2413 },
2414 Line {
2415 start: UnsolvedPoint2dExpr,
2416 end: UnsolvedPoint2dExpr,
2417 ctor: Box<LineCtor>,
2418 start_object_id: ObjectId,
2419 end_object_id: ObjectId,
2420 construction: bool,
2421 },
2422 Arc {
2423 start: UnsolvedPoint2dExpr,
2424 end: UnsolvedPoint2dExpr,
2425 center: UnsolvedPoint2dExpr,
2426 ctor: Box<ArcCtor>,
2427 start_object_id: ObjectId,
2428 end_object_id: ObjectId,
2429 center_object_id: ObjectId,
2430 #[serde(default, skip_serializing_if = "ArcDirection::is_ccw")]
2436 #[ts(as = "Option<ArcDirection>")]
2437 #[ts(optional)]
2438 direction: ArcDirection,
2439 construction: bool,
2440 },
2441 Circle {
2442 start: UnsolvedPoint2dExpr,
2443 center: UnsolvedPoint2dExpr,
2444 ctor: Box<CircleCtor>,
2445 start_object_id: ObjectId,
2446 center_object_id: ObjectId,
2447 construction: bool,
2448 },
2449 ControlPointSpline {
2450 controls: Vec<UnsolvedPoint2dExpr>,
2451 ctor: Box<ControlPointSplineCtor>,
2452 control_object_ids: Vec<ObjectId>,
2453 control_polygon_edge_object_ids: Vec<ObjectId>,
2454 degree: u32,
2455 construction: bool,
2456 },
2457}
2458
2459impl UnsolvedSegmentKind {
2460 pub fn human_friendly_kind_with_article(&self) -> &'static str {
2463 match self {
2464 Self::Point { .. } => "a Point",
2465 Self::Line { .. } => "a Line",
2466 Self::Arc { .. } => "an Arc",
2467 Self::Circle { .. } => "a Circle",
2468 Self::ControlPointSpline { .. } => "a Control Point Spline",
2469 }
2470 }
2471}
2472
2473#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2474#[ts(export_to = "Geometry.ts")]
2475#[serde(rename_all = "camelCase")]
2476pub struct Segment {
2477 pub id: Uuid,
2479 pub object_id: ObjectId,
2480 pub kind: SegmentKind,
2481 pub surface: SketchSurface,
2482 pub sketch_id: Uuid,
2484 #[serde(skip)]
2485 #[ts(skip)]
2486 pub sketch: Option<Arc<Sketch>>,
2487 #[serde(skip_serializing_if = "Option::is_none")]
2488 pub tag: Option<TagIdentifier>,
2489 #[serde(skip)]
2490 pub node_path: Option<NodePath>,
2491 #[serde(skip)]
2492 pub meta: Vec<Metadata>,
2493}
2494
2495impl Segment {
2496 pub fn is_construction(&self) -> bool {
2497 match &self.kind {
2498 SegmentKind::Point { .. } => true,
2499 SegmentKind::Line { construction, .. } => *construction,
2500 SegmentKind::Arc { construction, .. } => *construction,
2501 SegmentKind::Circle { construction, .. } => *construction,
2502 SegmentKind::ControlPointSpline { construction, .. } => *construction,
2503 }
2504 }
2505}
2506
2507#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2508#[ts(export_to = "Geometry.ts")]
2509#[serde(rename_all = "camelCase")]
2510pub enum SegmentKind {
2511 Point {
2512 position: [TyF64; 2],
2513 ctor: Box<PointCtor>,
2514 #[serde(skip_serializing_if = "Option::is_none")]
2515 freedom: Option<Freedom>,
2516 },
2517 Line {
2518 start: [TyF64; 2],
2519 end: [TyF64; 2],
2520 ctor: Box<LineCtor>,
2521 start_object_id: ObjectId,
2522 end_object_id: ObjectId,
2523 #[serde(skip_serializing_if = "Option::is_none")]
2524 start_freedom: Option<Freedom>,
2525 #[serde(skip_serializing_if = "Option::is_none")]
2526 end_freedom: Option<Freedom>,
2527 construction: bool,
2528 },
2529 Arc {
2530 start: [TyF64; 2],
2531 end: [TyF64; 2],
2532 center: [TyF64; 2],
2533 ctor: Box<ArcCtor>,
2534 start_object_id: ObjectId,
2535 end_object_id: ObjectId,
2536 center_object_id: ObjectId,
2537 #[serde(skip_serializing_if = "Option::is_none")]
2538 start_freedom: Option<Freedom>,
2539 #[serde(skip_serializing_if = "Option::is_none")]
2540 end_freedom: Option<Freedom>,
2541 #[serde(skip_serializing_if = "Option::is_none")]
2542 center_freedom: Option<Freedom>,
2543 #[serde(default, skip_serializing_if = "ArcDirection::is_ccw")]
2546 #[ts(as = "Option<ArcDirection>")]
2547 #[ts(optional)]
2548 direction: ArcDirection,
2549 construction: bool,
2550 },
2551 Circle {
2552 start: [TyF64; 2],
2553 center: [TyF64; 2],
2554 ctor: Box<CircleCtor>,
2555 start_object_id: ObjectId,
2556 center_object_id: ObjectId,
2557 #[serde(skip_serializing_if = "Option::is_none")]
2558 start_freedom: Option<Freedom>,
2559 #[serde(skip_serializing_if = "Option::is_none")]
2560 center_freedom: Option<Freedom>,
2561 construction: bool,
2562 },
2563 ControlPointSpline {
2564 controls: Vec<[TyF64; 2]>,
2565 ctor: Box<ControlPointSplineCtor>,
2566 control_object_ids: Vec<ObjectId>,
2567 control_polygon_edge_object_ids: Vec<ObjectId>,
2568 #[serde(skip_serializing_if = "Vec::is_empty")]
2569 control_freedoms: Vec<Option<Freedom>>,
2570 degree: u32,
2571 construction: bool,
2572 },
2573}
2574
2575#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2576#[ts(export_to = "Geometry.ts")]
2577#[serde(rename_all = "camelCase")]
2578pub struct AbstractSegment {
2579 pub repr: SegmentRepr,
2580 #[serde(skip)]
2581 pub meta: Vec<Metadata>,
2582}
2583
2584#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2585pub enum SegmentRepr {
2586 Unsolved { segment: Box<UnsolvedSegment> },
2587 Solved { segment: Box<Segment> },
2588}
2589
2590#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2591#[ts(export_to = "Geometry.ts")]
2592#[serde(rename_all = "camelCase")]
2593pub struct SketchConstraint {
2594 pub kind: SketchConstraintKind,
2595 #[serde(skip)]
2596 pub meta: Vec<Metadata>,
2597}
2598
2599#[derive(Debug, Clone, Copy, PartialEq)]
2600pub enum AngleRayDirection {
2601 Forward,
2602 Reverse,
2603}
2604
2605#[derive(Debug, Clone, Copy, PartialEq)]
2606pub enum AngleSector {
2607 One,
2608 Two,
2609 Three,
2610 Four,
2611}
2612
2613#[derive(Debug, Clone, Copy, PartialEq)]
2614pub enum AngleConstraintMode {
2615 LinesAtAngle,
2616 PointsAtAngle { sector: AngleSector, inverse: bool },
2617}
2618
2619#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2620#[ts(export_to = "Geometry.ts")]
2621#[serde(rename_all = "camelCase")]
2622pub enum SketchConstraintKind {
2623 Angle {
2624 line0: ConstrainableLine2d,
2625 line1: ConstrainableLine2d,
2626 #[serde(skip)]
2627 #[ts(skip)]
2628 mode: AngleConstraintMode,
2629 #[serde(rename = "labelPosition")]
2630 #[serde(skip_serializing_if = "Option::is_none")]
2631 #[ts(rename = "labelPosition")]
2632 #[ts(optional)]
2633 label_position: Option<ApiPoint2d<Number>>,
2634 },
2635 Distance {
2636 points: [ConstrainablePoint2dOrOrigin; 2],
2637 #[serde(rename = "labelPosition")]
2638 #[serde(skip_serializing_if = "Option::is_none")]
2639 #[ts(rename = "labelPosition")]
2640 #[ts(optional)]
2641 label_position: Option<ApiPoint2d<Number>>,
2642 },
2643 PointLineDistance {
2644 point: ConstrainablePoint2dOrOrigin,
2645 line: ConstrainableLine2d,
2646 input_object_ids: [Option<ObjectId>; 2],
2647 #[serde(rename = "labelPosition")]
2648 #[serde(skip_serializing_if = "Option::is_none")]
2649 #[ts(rename = "labelPosition")]
2650 #[ts(optional)]
2651 label_position: Option<ApiPoint2d<Number>>,
2652 },
2653 LineLineDistance {
2654 line0: ConstrainableLine2d,
2655 line1: ConstrainableLine2d,
2656 input_object_ids: [ObjectId; 2],
2657 #[serde(rename = "labelPosition")]
2658 #[serde(skip_serializing_if = "Option::is_none")]
2659 #[ts(rename = "labelPosition")]
2660 #[ts(optional)]
2661 label_position: Option<ApiPoint2d<Number>>,
2662 },
2663 PointCircularDistance {
2664 point: ConstrainablePoint2dOrOrigin,
2665 center: ConstrainablePoint2d,
2666 start: ConstrainablePoint2d,
2667 end: Option<ConstrainablePoint2d>,
2668 input_object_ids: [Option<ObjectId>; 2],
2669 #[serde(rename = "labelPosition")]
2670 #[serde(skip_serializing_if = "Option::is_none")]
2671 #[ts(rename = "labelPosition")]
2672 #[ts(optional)]
2673 label_position: Option<ApiPoint2d<Number>>,
2674 },
2675 LineCircularDistance {
2676 line: ConstrainableLine2d,
2677 center: ConstrainablePoint2d,
2678 start: ConstrainablePoint2d,
2679 end: Option<ConstrainablePoint2d>,
2680 input_object_ids: [ObjectId; 2],
2681 #[serde(rename = "labelPosition")]
2682 #[serde(skip_serializing_if = "Option::is_none")]
2683 #[ts(rename = "labelPosition")]
2684 #[ts(optional)]
2685 label_position: Option<ApiPoint2d<Number>>,
2686 },
2687 CircularCircularDistance {
2688 center0: ConstrainablePoint2d,
2689 start0: ConstrainablePoint2d,
2690 end0: Option<ConstrainablePoint2d>,
2691 center1: ConstrainablePoint2d,
2692 start1: ConstrainablePoint2d,
2693 end1: Option<ConstrainablePoint2d>,
2694 input_object_ids: [ObjectId; 2],
2695 #[serde(rename = "labelPosition")]
2696 #[serde(skip_serializing_if = "Option::is_none")]
2697 #[ts(rename = "labelPosition")]
2698 #[ts(optional)]
2699 label_position: Option<ApiPoint2d<Number>>,
2700 },
2701 Radius {
2702 points: [ConstrainablePoint2d; 2],
2703 #[serde(rename = "labelPosition")]
2704 #[serde(skip_serializing_if = "Option::is_none")]
2705 #[ts(rename = "labelPosition")]
2706 #[ts(optional)]
2707 label_position: Option<ApiPoint2d<Number>>,
2708 },
2709 Diameter {
2710 points: [ConstrainablePoint2d; 2],
2711 #[serde(rename = "labelPosition")]
2712 #[serde(skip_serializing_if = "Option::is_none")]
2713 #[ts(rename = "labelPosition")]
2714 #[ts(optional)]
2715 label_position: Option<ApiPoint2d<Number>>,
2716 },
2717 HorizontalDistance {
2718 points: [ConstrainablePoint2dOrOrigin; 2],
2719 #[serde(rename = "labelPosition")]
2720 #[serde(skip_serializing_if = "Option::is_none")]
2721 #[ts(rename = "labelPosition")]
2722 #[ts(optional)]
2723 label_position: Option<ApiPoint2d<Number>>,
2724 },
2725 VerticalDistance {
2726 points: [ConstrainablePoint2dOrOrigin; 2],
2727 #[serde(rename = "labelPosition")]
2728 #[serde(skip_serializing_if = "Option::is_none")]
2729 #[ts(rename = "labelPosition")]
2730 #[ts(optional)]
2731 label_position: Option<ApiPoint2d<Number>>,
2732 },
2733}
2734
2735impl SketchConstraintKind {
2736 pub fn name(&self) -> &'static str {
2737 match self {
2738 SketchConstraintKind::Angle { .. } => "angle",
2739 SketchConstraintKind::Distance { .. } => "distance",
2740 SketchConstraintKind::PointLineDistance { .. } => "distance",
2741 SketchConstraintKind::LineLineDistance { .. } => "distance",
2742 SketchConstraintKind::PointCircularDistance { .. } => "distance",
2743 SketchConstraintKind::LineCircularDistance { .. } => "distance",
2744 SketchConstraintKind::CircularCircularDistance { .. } => "distance",
2745 SketchConstraintKind::Radius { .. } => "radius",
2746 SketchConstraintKind::Diameter { .. } => "diameter",
2747 SketchConstraintKind::HorizontalDistance { .. } => "horizontalDistance",
2748 SketchConstraintKind::VerticalDistance { .. } => "verticalDistance",
2749 }
2750 }
2751}