1use std::ops::{Add, AddAssign, Mul, Sub, SubAssign};
2
3use anyhow::Result;
4use indexmap::IndexMap;
5use kcl_error::SourceRange;
6use kittycad_modeling_cmds::{
7 self as kcmc, ModelingCmd, each_cmd as mcmd, length_unit::LengthUnit, units::UnitLength, websocket::ModelingCmdReq,
8};
9use parse_display::{Display, FromStr};
10use serde::{Deserialize, Serialize};
11use uuid::Uuid;
12
13use crate::{
14 engine::{DEFAULT_PLANE_INFO, PlaneName},
15 errors::{KclError, KclErrorDetails},
16 exec::KclValue,
17 execution::{
18 ArtifactId, ExecState, ExecutorContext, Metadata, TagEngineInfo, TagIdentifier, normalize_to_solver_unit,
19 types::{NumericType, adjust_length},
20 },
21 front::{ArcCtor, Freedom, LineCtor, ObjectId, PointCtor},
22 parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
23 std::{
24 Args,
25 args::TyF64,
26 sketch::{FaceTag, PlaneData},
27 },
28};
29
30type Point3D = kcmc::shared::Point3d<f64>;
31
32#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
34#[ts(export)]
35#[serde(tag = "type", rename_all = "camelCase")]
36pub struct GdtAnnotation {
37 pub id: uuid::Uuid,
39 #[serde(skip)]
40 pub meta: Vec<Metadata>,
41}
42
43#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
45#[ts(export)]
46#[serde(tag = "type")]
47#[allow(clippy::large_enum_variant)]
48pub enum Geometry {
49 Sketch(Sketch),
50 Solid(Solid),
51}
52
53impl Geometry {
54 pub fn id(&self) -> uuid::Uuid {
55 match self {
56 Geometry::Sketch(s) => s.id,
57 Geometry::Solid(e) => e.id,
58 }
59 }
60
61 pub fn original_id(&self) -> uuid::Uuid {
65 match self {
66 Geometry::Sketch(s) => s.original_id,
67 Geometry::Solid(e) => e.sketch.original_id,
68 }
69 }
70}
71
72#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
74#[ts(export)]
75#[serde(tag = "type")]
76#[allow(clippy::large_enum_variant)]
77pub enum GeometryWithImportedGeometry {
78 Sketch(Sketch),
79 Solid(Solid),
80 ImportedGeometry(Box<ImportedGeometry>),
81}
82
83impl GeometryWithImportedGeometry {
84 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
85 match self {
86 GeometryWithImportedGeometry::Sketch(s) => Ok(s.id),
87 GeometryWithImportedGeometry::Solid(e) => Ok(e.id),
88 GeometryWithImportedGeometry::ImportedGeometry(i) => {
89 let id = i.id(ctx).await?;
90 Ok(id)
91 }
92 }
93 }
94}
95
96#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
98#[ts(export)]
99#[serde(tag = "type")]
100#[allow(clippy::vec_box)]
101pub enum Geometries {
102 Sketches(Vec<Sketch>),
103 Solids(Vec<Solid>),
104}
105
106impl From<Geometry> for Geometries {
107 fn from(value: Geometry) -> Self {
108 match value {
109 Geometry::Sketch(x) => Self::Sketches(vec![x]),
110 Geometry::Solid(x) => Self::Solids(vec![x]),
111 }
112 }
113}
114
115#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
117#[ts(export)]
118#[serde(rename_all = "camelCase")]
119pub struct ImportedGeometry {
120 pub id: uuid::Uuid,
122 pub value: Vec<String>,
124 #[serde(skip)]
125 pub meta: Vec<Metadata>,
126 #[serde(skip)]
128 completed: bool,
129}
130
131impl ImportedGeometry {
132 pub fn new(id: uuid::Uuid, value: Vec<String>, meta: Vec<Metadata>) -> Self {
133 Self {
134 id,
135 value,
136 meta,
137 completed: false,
138 }
139 }
140
141 async fn wait_for_finish(&mut self, ctx: &ExecutorContext) -> Result<(), KclError> {
142 if self.completed {
143 return Ok(());
144 }
145
146 ctx.engine
147 .ensure_async_command_completed(self.id, self.meta.first().map(|m| m.source_range))
148 .await?;
149
150 self.completed = true;
151
152 Ok(())
153 }
154
155 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
156 if !self.completed {
157 self.wait_for_finish(ctx).await?;
158 }
159
160 Ok(self.id)
161 }
162}
163
164#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
166#[ts(export)]
167#[serde(tag = "type", rename_all = "camelCase")]
168#[allow(clippy::vec_box)]
169pub enum SolidOrSketchOrImportedGeometry {
170 ImportedGeometry(Box<ImportedGeometry>),
171 SolidSet(Vec<Solid>),
172 SketchSet(Vec<Sketch>),
173}
174
175impl From<SolidOrSketchOrImportedGeometry> for crate::execution::KclValue {
176 fn from(value: SolidOrSketchOrImportedGeometry) -> Self {
177 match value {
178 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
179 SolidOrSketchOrImportedGeometry::SolidSet(mut s) => {
180 if s.len() == 1
181 && let Some(s) = s.pop()
182 {
183 crate::execution::KclValue::Solid { value: Box::new(s) }
184 } else {
185 crate::execution::KclValue::HomArray {
186 value: s
187 .into_iter()
188 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
189 .collect(),
190 ty: crate::execution::types::RuntimeType::solid(),
191 }
192 }
193 }
194 SolidOrSketchOrImportedGeometry::SketchSet(mut s) => {
195 if s.len() == 1
196 && let Some(s) = s.pop()
197 {
198 crate::execution::KclValue::Sketch { value: Box::new(s) }
199 } else {
200 crate::execution::KclValue::HomArray {
201 value: s
202 .into_iter()
203 .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) })
204 .collect(),
205 ty: crate::execution::types::RuntimeType::sketch(),
206 }
207 }
208 }
209 }
210 }
211}
212
213impl SolidOrSketchOrImportedGeometry {
214 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
215 match self {
216 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => {
217 let id = s.id(ctx).await?;
218
219 Ok(vec![id])
220 }
221 SolidOrSketchOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
222 SolidOrSketchOrImportedGeometry::SketchSet(s) => Ok(s.iter().map(|s| s.id).collect()),
223 }
224 }
225}
226
227#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
229#[ts(export)]
230#[serde(tag = "type", rename_all = "camelCase")]
231#[allow(clippy::vec_box)]
232pub enum SolidOrImportedGeometry {
233 ImportedGeometry(Box<ImportedGeometry>),
234 SolidSet(Vec<Solid>),
235}
236
237impl From<SolidOrImportedGeometry> for crate::execution::KclValue {
238 fn from(value: SolidOrImportedGeometry) -> Self {
239 match value {
240 SolidOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
241 SolidOrImportedGeometry::SolidSet(mut s) => {
242 if s.len() == 1
243 && let Some(s) = s.pop()
244 {
245 crate::execution::KclValue::Solid { value: Box::new(s) }
246 } else {
247 crate::execution::KclValue::HomArray {
248 value: s
249 .into_iter()
250 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
251 .collect(),
252 ty: crate::execution::types::RuntimeType::solid(),
253 }
254 }
255 }
256 }
257 }
258}
259
260impl SolidOrImportedGeometry {
261 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
262 match self {
263 SolidOrImportedGeometry::ImportedGeometry(s) => {
264 let id = s.id(ctx).await?;
265
266 Ok(vec![id])
267 }
268 SolidOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
269 }
270 }
271}
272
273#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
275#[ts(export)]
276#[serde(rename_all = "camelCase")]
277pub struct Helix {
278 pub value: uuid::Uuid,
280 pub artifact_id: ArtifactId,
282 pub revolutions: f64,
284 pub angle_start: f64,
286 pub ccw: bool,
288 pub cylinder_id: Option<uuid::Uuid>,
290 pub units: UnitLength,
291 #[serde(skip)]
292 pub meta: Vec<Metadata>,
293}
294
295#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
296#[ts(export)]
297#[serde(rename_all = "camelCase")]
298pub struct Plane {
299 pub id: uuid::Uuid,
301 pub artifact_id: ArtifactId,
303 #[serde(skip_serializing_if = "Option::is_none")]
306 pub object_id: Option<ObjectId>,
307 pub kind: PlaneKind,
309 #[serde(flatten)]
311 pub info: PlaneInfo,
312 #[serde(skip)]
313 pub meta: Vec<Metadata>,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS)]
317#[ts(export)]
318#[serde(rename_all = "camelCase")]
319pub struct PlaneInfo {
320 pub origin: Point3d,
322 pub x_axis: Point3d,
324 pub y_axis: Point3d,
326 pub z_axis: Point3d,
328}
329
330impl PlaneInfo {
331 pub(crate) fn into_plane_data(self) -> PlaneData {
332 if self.origin.is_zero() {
333 match self {
334 Self {
335 origin:
336 Point3d {
337 x: 0.0,
338 y: 0.0,
339 z: 0.0,
340 units: Some(UnitLength::Millimeters),
341 },
342 x_axis:
343 Point3d {
344 x: 1.0,
345 y: 0.0,
346 z: 0.0,
347 units: _,
348 },
349 y_axis:
350 Point3d {
351 x: 0.0,
352 y: 1.0,
353 z: 0.0,
354 units: _,
355 },
356 z_axis: _,
357 } => return PlaneData::XY,
358 Self {
359 origin:
360 Point3d {
361 x: 0.0,
362 y: 0.0,
363 z: 0.0,
364 units: Some(UnitLength::Millimeters),
365 },
366 x_axis:
367 Point3d {
368 x: -1.0,
369 y: 0.0,
370 z: 0.0,
371 units: _,
372 },
373 y_axis:
374 Point3d {
375 x: 0.0,
376 y: 1.0,
377 z: 0.0,
378 units: _,
379 },
380 z_axis: _,
381 } => return PlaneData::NegXY,
382 Self {
383 origin:
384 Point3d {
385 x: 0.0,
386 y: 0.0,
387 z: 0.0,
388 units: Some(UnitLength::Millimeters),
389 },
390 x_axis:
391 Point3d {
392 x: 1.0,
393 y: 0.0,
394 z: 0.0,
395 units: _,
396 },
397 y_axis:
398 Point3d {
399 x: 0.0,
400 y: 0.0,
401 z: 1.0,
402 units: _,
403 },
404 z_axis: _,
405 } => return PlaneData::XZ,
406 Self {
407 origin:
408 Point3d {
409 x: 0.0,
410 y: 0.0,
411 z: 0.0,
412 units: Some(UnitLength::Millimeters),
413 },
414 x_axis:
415 Point3d {
416 x: -1.0,
417 y: 0.0,
418 z: 0.0,
419 units: _,
420 },
421 y_axis:
422 Point3d {
423 x: 0.0,
424 y: 0.0,
425 z: 1.0,
426 units: _,
427 },
428 z_axis: _,
429 } => return PlaneData::NegXZ,
430 Self {
431 origin:
432 Point3d {
433 x: 0.0,
434 y: 0.0,
435 z: 0.0,
436 units: Some(UnitLength::Millimeters),
437 },
438 x_axis:
439 Point3d {
440 x: 0.0,
441 y: 1.0,
442 z: 0.0,
443 units: _,
444 },
445 y_axis:
446 Point3d {
447 x: 0.0,
448 y: 0.0,
449 z: 1.0,
450 units: _,
451 },
452 z_axis: _,
453 } => return PlaneData::YZ,
454 Self {
455 origin:
456 Point3d {
457 x: 0.0,
458 y: 0.0,
459 z: 0.0,
460 units: Some(UnitLength::Millimeters),
461 },
462 x_axis:
463 Point3d {
464 x: 0.0,
465 y: -1.0,
466 z: 0.0,
467 units: _,
468 },
469 y_axis:
470 Point3d {
471 x: 0.0,
472 y: 0.0,
473 z: 1.0,
474 units: _,
475 },
476 z_axis: _,
477 } => return PlaneData::NegYZ,
478 _ => {}
479 }
480 }
481
482 PlaneData::Plane(Self {
483 origin: self.origin,
484 x_axis: self.x_axis,
485 y_axis: self.y_axis,
486 z_axis: self.z_axis,
487 })
488 }
489
490 pub(crate) fn is_right_handed(&self) -> bool {
491 let lhs = self
494 .x_axis
495 .axes_cross_product(&self.y_axis)
496 .axes_dot_product(&self.z_axis);
497 let rhs_x = self.x_axis.axes_dot_product(&self.x_axis);
498 let rhs_y = self.y_axis.axes_dot_product(&self.y_axis);
499 let rhs_z = self.z_axis.axes_dot_product(&self.z_axis);
500 let rhs = (rhs_x * rhs_y * rhs_z).sqrt();
501 (lhs - rhs).abs() <= 0.0001
503 }
504
505 #[cfg(test)]
506 pub(crate) fn is_left_handed(&self) -> bool {
507 !self.is_right_handed()
508 }
509
510 pub(crate) fn make_right_handed(self) -> Self {
511 if self.is_right_handed() {
512 return self;
513 }
514 Self {
516 origin: self.origin,
517 x_axis: self.x_axis.negated(),
518 y_axis: self.y_axis,
519 z_axis: self.z_axis,
520 }
521 }
522}
523
524impl TryFrom<PlaneData> for PlaneInfo {
525 type Error = KclError;
526
527 fn try_from(value: PlaneData) -> Result<Self, Self::Error> {
528 let name = match value {
529 PlaneData::XY => PlaneName::Xy,
530 PlaneData::NegXY => PlaneName::NegXy,
531 PlaneData::XZ => PlaneName::Xz,
532 PlaneData::NegXZ => PlaneName::NegXz,
533 PlaneData::YZ => PlaneName::Yz,
534 PlaneData::NegYZ => PlaneName::NegYz,
535 PlaneData::Plane(info) => {
536 return Ok(info);
537 }
538 };
539
540 let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
541 KclError::new_internal(KclErrorDetails::new(
542 format!("Plane {name} not found"),
543 Default::default(),
544 ))
545 })?;
546
547 Ok(info.clone())
548 }
549}
550
551impl From<&PlaneData> for PlaneKind {
552 fn from(value: &PlaneData) -> Self {
553 match value {
554 PlaneData::XY => PlaneKind::XY,
555 PlaneData::NegXY => PlaneKind::XY,
556 PlaneData::XZ => PlaneKind::XZ,
557 PlaneData::NegXZ => PlaneKind::XZ,
558 PlaneData::YZ => PlaneKind::YZ,
559 PlaneData::NegYZ => PlaneKind::YZ,
560 PlaneData::Plane(_) => PlaneKind::Custom,
561 }
562 }
563}
564
565impl From<&PlaneInfo> for PlaneKind {
566 fn from(value: &PlaneInfo) -> Self {
567 let data = PlaneData::Plane(value.clone());
568 PlaneKind::from(&data)
569 }
570}
571
572impl From<PlaneInfo> for PlaneKind {
573 fn from(value: PlaneInfo) -> Self {
574 let data = PlaneData::Plane(value);
575 PlaneKind::from(&data)
576 }
577}
578
579impl Plane {
580 #[cfg(test)]
581 pub(crate) fn from_plane_data_skipping_engine(
582 value: PlaneData,
583 exec_state: &mut ExecState,
584 ) -> Result<Self, KclError> {
585 let id = exec_state.next_uuid();
586 let kind = PlaneKind::from(&value);
587 Ok(Plane {
588 id,
589 artifact_id: id.into(),
590 info: PlaneInfo::try_from(value)?,
591 object_id: None,
592 kind,
593 meta: vec![],
594 })
595 }
596
597 pub fn is_initialized(&self) -> bool {
599 self.object_id.is_some()
600 }
601
602 pub fn is_uninitialized(&self) -> bool {
604 !self.is_initialized()
605 }
606
607 pub fn is_standard(&self) -> bool {
609 match &self.kind {
610 PlaneKind::XY | PlaneKind::YZ | PlaneKind::XZ => true,
611 PlaneKind::Custom => false,
612 }
613 }
614
615 pub fn project(&self, point: Point3d) -> Point3d {
618 let v = point - self.info.origin;
619 let dot = v.axes_dot_product(&self.info.z_axis);
620
621 point - self.info.z_axis * dot
622 }
623}
624
625#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
627#[ts(export)]
628#[serde(rename_all = "camelCase")]
629pub struct Face {
630 pub id: uuid::Uuid,
632 pub artifact_id: ArtifactId,
634 pub object_id: ObjectId,
636 pub value: String,
638 pub x_axis: Point3d,
640 pub y_axis: Point3d,
642 pub solid: Box<Solid>,
644 pub units: UnitLength,
645 #[serde(skip)]
646 pub meta: Vec<Metadata>,
647}
648
649#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS, FromStr, Display)]
651#[ts(export)]
652#[display(style = "camelCase")]
653pub enum PlaneKind {
654 #[serde(rename = "XY", alias = "xy")]
655 #[display("XY")]
656 XY,
657 #[serde(rename = "XZ", alias = "xz")]
658 #[display("XZ")]
659 XZ,
660 #[serde(rename = "YZ", alias = "yz")]
661 #[display("YZ")]
662 YZ,
663 #[display("Custom")]
665 Custom,
666}
667
668#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
669#[ts(export)]
670#[serde(tag = "type", rename_all = "camelCase")]
671pub struct Sketch {
672 pub id: uuid::Uuid,
674 pub paths: Vec<Path>,
678 #[serde(default, skip_serializing_if = "Vec::is_empty")]
680 pub inner_paths: Vec<Path>,
681 pub on: SketchSurface,
683 pub start: BasePath,
685 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
687 pub tags: IndexMap<String, TagIdentifier>,
688 pub artifact_id: ArtifactId,
691 #[ts(skip)]
692 pub original_id: uuid::Uuid,
693 #[serde(skip)]
695 pub mirror: Option<uuid::Uuid>,
696 #[serde(skip)]
698 pub clone: Option<uuid::Uuid>,
699 pub units: UnitLength,
700 #[serde(skip)]
702 pub meta: Vec<Metadata>,
703 #[serde(
706 default = "ProfileClosed::explicitly",
707 skip_serializing_if = "ProfileClosed::is_explicitly"
708 )]
709 pub is_closed: ProfileClosed,
710}
711
712impl ProfileClosed {
713 #[expect(dead_code, reason = "it's not actually dead, it's called by serde")]
714 fn explicitly() -> Self {
715 Self::Explicitly
716 }
717
718 fn is_explicitly(&self) -> bool {
719 matches!(self, ProfileClosed::Explicitly)
720 }
721}
722
723#[derive(Debug, Serialize, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd, ts_rs::TS)]
725#[serde(rename_all = "camelCase")]
726pub enum ProfileClosed {
727 No,
729 Maybe,
731 Implicitly,
733 Explicitly,
735}
736
737impl Sketch {
738 pub(crate) fn build_sketch_mode_cmds(
741 &self,
742 exec_state: &mut ExecState,
743 inner_cmd: ModelingCmdReq,
744 ) -> Vec<ModelingCmdReq> {
745 vec![
746 ModelingCmdReq {
749 cmd: ModelingCmd::from(
750 mcmd::EnableSketchMode::builder()
751 .animated(false)
752 .ortho(false)
753 .entity_id(self.on.id())
754 .adjust_camera(false)
755 .maybe_planar_normal(if let SketchSurface::Plane(plane) = &self.on {
756 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
758 Some(normal.into())
759 } else {
760 None
761 })
762 .build(),
763 ),
764 cmd_id: exec_state.next_uuid().into(),
765 },
766 inner_cmd,
767 ModelingCmdReq {
768 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::builder().build()),
769 cmd_id: exec_state.next_uuid().into(),
770 },
771 ]
772 }
773}
774
775#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
777#[ts(export)]
778#[serde(tag = "type", rename_all = "camelCase")]
779pub enum SketchSurface {
780 Plane(Box<Plane>),
781 Face(Box<Face>),
782}
783
784impl SketchSurface {
785 pub(crate) fn id(&self) -> uuid::Uuid {
786 match self {
787 SketchSurface::Plane(plane) => plane.id,
788 SketchSurface::Face(face) => face.id,
789 }
790 }
791 pub(crate) fn x_axis(&self) -> Point3d {
792 match self {
793 SketchSurface::Plane(plane) => plane.info.x_axis,
794 SketchSurface::Face(face) => face.x_axis,
795 }
796 }
797 pub(crate) fn y_axis(&self) -> Point3d {
798 match self {
799 SketchSurface::Plane(plane) => plane.info.y_axis,
800 SketchSurface::Face(face) => face.y_axis,
801 }
802 }
803
804 pub(crate) fn object_id(&self) -> Option<ObjectId> {
805 match self {
806 SketchSurface::Plane(plane) => plane.object_id,
807 SketchSurface::Face(face) => Some(face.object_id),
808 }
809 }
810
811 pub(crate) fn set_object_id(&mut self, object_id: ObjectId) {
812 match self {
813 SketchSurface::Plane(plane) => plane.object_id = Some(object_id),
814 SketchSurface::Face(face) => face.object_id = object_id,
815 }
816 }
817}
818
819#[derive(Debug, Clone, PartialEq)]
821pub enum Extrudable {
822 Sketch(Box<Sketch>),
824 Face(FaceTag),
826}
827
828impl Extrudable {
829 pub async fn id_to_extrude(
831 &self,
832 exec_state: &mut ExecState,
833 args: &Args,
834 must_be_planar: bool,
835 ) -> Result<uuid::Uuid, KclError> {
836 match self {
837 Extrudable::Sketch(sketch) => Ok(sketch.id),
838 Extrudable::Face(face_tag) => face_tag.get_face_id_from_tag(exec_state, args, must_be_planar).await,
839 }
840 }
841
842 pub fn as_sketch(&self) -> Option<Sketch> {
843 match self {
844 Extrudable::Sketch(sketch) => Some((**sketch).clone()),
845 Extrudable::Face(face_tag) => match face_tag.geometry() {
846 Some(Geometry::Sketch(sketch)) => Some(sketch),
847 Some(Geometry::Solid(solid)) => Some(solid.sketch),
848 None => None,
849 },
850 }
851 }
852
853 pub fn is_closed(&self) -> ProfileClosed {
854 match self {
855 Extrudable::Sketch(sketch) => sketch.is_closed,
856 Extrudable::Face(face_tag) => match face_tag.geometry() {
857 Some(Geometry::Sketch(sketch)) => sketch.is_closed,
858 Some(Geometry::Solid(solid)) => solid.sketch.is_closed,
859 _ => ProfileClosed::Maybe,
860 },
861 }
862 }
863}
864
865impl From<Sketch> for Extrudable {
866 fn from(value: Sketch) -> Self {
867 Extrudable::Sketch(Box::new(value))
868 }
869}
870
871#[derive(Debug, Clone)]
872pub(crate) enum GetTangentialInfoFromPathsResult {
873 PreviousPoint([f64; 2]),
874 Arc {
875 center: [f64; 2],
876 ccw: bool,
877 },
878 Circle {
879 center: [f64; 2],
880 ccw: bool,
881 radius: f64,
882 },
883 Ellipse {
884 center: [f64; 2],
885 ccw: bool,
886 major_axis: [f64; 2],
887 _minor_radius: f64,
888 },
889}
890
891impl GetTangentialInfoFromPathsResult {
892 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
893 match self {
894 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
895 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
896 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
897 }
898 GetTangentialInfoFromPathsResult::Circle {
901 center, radius, ccw, ..
902 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
903 GetTangentialInfoFromPathsResult::Ellipse {
904 center,
905 major_axis,
906 ccw,
907 ..
908 } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
909 }
910 }
911}
912
913impl Sketch {
914 pub(crate) fn add_tag(
915 &mut self,
916 tag: NodeRef<'_, TagDeclarator>,
917 current_path: &Path,
918 exec_state: &ExecState,
919 surface: Option<&ExtrudeSurface>,
920 ) {
921 let mut tag_identifier: TagIdentifier = tag.into();
922 let base = current_path.get_base();
923 let mut sketch_copy = self.clone();
924 sketch_copy.tags.clear();
925 tag_identifier.info.push((
926 exec_state.stack().current_epoch(),
927 TagEngineInfo {
928 id: base.geo_meta.id,
929 geometry: Geometry::Sketch(sketch_copy),
930 path: Some(current_path.clone()),
931 surface: surface.cloned(),
932 },
933 ));
934
935 self.tags.insert(tag.name.to_string(), tag_identifier);
936 }
937
938 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
939 for t in tags {
940 match self.tags.get_mut(&t.value) {
941 Some(id) => {
942 id.merge_info(t);
943 }
944 None => {
945 self.tags.insert(t.value.clone(), t.clone());
946 }
947 }
948 }
949 }
950
951 pub(crate) fn latest_path(&self) -> Option<&Path> {
953 self.paths.last()
954 }
955
956 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
960 let Some(path) = self.latest_path() else {
961 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
962 };
963
964 let to = path.get_base().to;
965 Ok(Point2d::new(to[0], to[1], path.get_base().units))
966 }
967
968 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
969 let Some(path) = self.latest_path() else {
970 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
971 };
972 path.get_tangential_info()
973 }
974}
975
976#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
977#[ts(export)]
978#[serde(tag = "type", rename_all = "camelCase")]
979pub struct Solid {
980 pub id: uuid::Uuid,
982 pub artifact_id: ArtifactId,
984 pub value: Vec<ExtrudeSurface>,
986 pub sketch: Sketch,
988 pub start_cap_id: Option<uuid::Uuid>,
990 pub end_cap_id: Option<uuid::Uuid>,
992 #[serde(default, skip_serializing_if = "Vec::is_empty")]
994 pub edge_cuts: Vec<EdgeCut>,
995 pub units: UnitLength,
997 pub sectional: bool,
999 #[serde(skip)]
1001 pub meta: Vec<Metadata>,
1002}
1003
1004impl Solid {
1005 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
1006 self.edge_cuts.iter().map(|foc| foc.id())
1007 }
1008}
1009
1010#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1012#[ts(export)]
1013#[serde(tag = "type", rename_all = "camelCase")]
1014pub enum EdgeCut {
1015 Fillet {
1017 id: uuid::Uuid,
1019 radius: TyF64,
1020 #[serde(rename = "edgeId")]
1022 edge_id: uuid::Uuid,
1023 tag: Box<Option<TagNode>>,
1024 },
1025 Chamfer {
1027 id: uuid::Uuid,
1029 length: TyF64,
1030 #[serde(rename = "edgeId")]
1032 edge_id: uuid::Uuid,
1033 tag: Box<Option<TagNode>>,
1034 },
1035}
1036
1037impl EdgeCut {
1038 pub fn id(&self) -> uuid::Uuid {
1039 match self {
1040 EdgeCut::Fillet { id, .. } => *id,
1041 EdgeCut::Chamfer { id, .. } => *id,
1042 }
1043 }
1044
1045 pub fn set_id(&mut self, id: uuid::Uuid) {
1046 match self {
1047 EdgeCut::Fillet { id: i, .. } => *i = id,
1048 EdgeCut::Chamfer { id: i, .. } => *i = id,
1049 }
1050 }
1051
1052 pub fn edge_id(&self) -> uuid::Uuid {
1053 match self {
1054 EdgeCut::Fillet { edge_id, .. } => *edge_id,
1055 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
1056 }
1057 }
1058
1059 pub fn set_edge_id(&mut self, id: uuid::Uuid) {
1060 match self {
1061 EdgeCut::Fillet { edge_id: i, .. } => *i = id,
1062 EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
1063 }
1064 }
1065
1066 pub fn tag(&self) -> Option<TagNode> {
1067 match self {
1068 EdgeCut::Fillet { tag, .. } => *tag.clone(),
1069 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
1070 }
1071 }
1072}
1073
1074#[derive(Debug, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
1075#[ts(export)]
1076pub struct Point2d {
1077 pub x: f64,
1078 pub y: f64,
1079 pub units: UnitLength,
1080}
1081
1082impl Point2d {
1083 pub const ZERO: Self = Self {
1084 x: 0.0,
1085 y: 0.0,
1086 units: UnitLength::Millimeters,
1087 };
1088
1089 pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
1090 Self { x, y, units }
1091 }
1092
1093 pub fn into_x(self) -> TyF64 {
1094 TyF64::new(self.x, self.units.into())
1095 }
1096
1097 pub fn into_y(self) -> TyF64 {
1098 TyF64::new(self.y, self.units.into())
1099 }
1100
1101 pub fn ignore_units(self) -> [f64; 2] {
1102 [self.x, self.y]
1103 }
1104}
1105
1106#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
1107#[ts(export)]
1108pub struct Point3d {
1109 pub x: f64,
1110 pub y: f64,
1111 pub z: f64,
1112 pub units: Option<UnitLength>,
1113}
1114
1115impl Point3d {
1116 pub const ZERO: Self = Self {
1117 x: 0.0,
1118 y: 0.0,
1119 z: 0.0,
1120 units: Some(UnitLength::Millimeters),
1121 };
1122
1123 pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
1124 Self { x, y, z, units }
1125 }
1126
1127 pub const fn is_zero(&self) -> bool {
1128 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
1129 }
1130
1131 pub fn axes_cross_product(&self, other: &Self) -> Self {
1136 Self {
1137 x: self.y * other.z - self.z * other.y,
1138 y: self.z * other.x - self.x * other.z,
1139 z: self.x * other.y - self.y * other.x,
1140 units: None,
1141 }
1142 }
1143
1144 pub fn axes_dot_product(&self, other: &Self) -> f64 {
1149 let x = self.x * other.x;
1150 let y = self.y * other.y;
1151 let z = self.z * other.z;
1152 x + y + z
1153 }
1154
1155 pub fn normalize(&self) -> Self {
1156 let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1157 Point3d {
1158 x: self.x / len,
1159 y: self.y / len,
1160 z: self.z / len,
1161 units: None,
1162 }
1163 }
1164
1165 pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1166 let p = [self.x, self.y, self.z];
1167 let u = self.units;
1168 (p, u)
1169 }
1170
1171 pub(crate) fn negated(self) -> Self {
1172 Self {
1173 x: -self.x,
1174 y: -self.y,
1175 z: -self.z,
1176 units: self.units,
1177 }
1178 }
1179}
1180
1181impl From<[TyF64; 3]> for Point3d {
1182 fn from(p: [TyF64; 3]) -> Self {
1183 Self {
1184 x: p[0].n,
1185 y: p[1].n,
1186 z: p[2].n,
1187 units: p[0].ty.as_length(),
1188 }
1189 }
1190}
1191
1192impl From<Point3d> for Point3D {
1193 fn from(p: Point3d) -> Self {
1194 Self { x: p.x, y: p.y, z: p.z }
1195 }
1196}
1197
1198impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1199 fn from(p: Point3d) -> Self {
1200 if let Some(units) = p.units {
1201 Self {
1202 x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1203 y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1204 z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1205 }
1206 } else {
1207 Self {
1208 x: LengthUnit(p.x),
1209 y: LengthUnit(p.y),
1210 z: LengthUnit(p.z),
1211 }
1212 }
1213 }
1214}
1215
1216impl Add for Point3d {
1217 type Output = Point3d;
1218
1219 fn add(self, rhs: Self) -> Self::Output {
1220 Point3d {
1222 x: self.x + rhs.x,
1223 y: self.y + rhs.y,
1224 z: self.z + rhs.z,
1225 units: self.units,
1226 }
1227 }
1228}
1229
1230impl AddAssign for Point3d {
1231 fn add_assign(&mut self, rhs: Self) {
1232 *self = *self + rhs
1233 }
1234}
1235
1236impl Sub for Point3d {
1237 type Output = Point3d;
1238
1239 fn sub(self, rhs: Self) -> Self::Output {
1240 let (x, y, z) = if rhs.units != self.units
1241 && let Some(sunits) = self.units
1242 && let Some(runits) = rhs.units
1243 {
1244 (
1245 adjust_length(runits, rhs.x, sunits).0,
1246 adjust_length(runits, rhs.y, sunits).0,
1247 adjust_length(runits, rhs.z, sunits).0,
1248 )
1249 } else {
1250 (rhs.x, rhs.y, rhs.z)
1251 };
1252 Point3d {
1253 x: self.x - x,
1254 y: self.y - y,
1255 z: self.z - z,
1256 units: self.units,
1257 }
1258 }
1259}
1260
1261impl SubAssign for Point3d {
1262 fn sub_assign(&mut self, rhs: Self) {
1263 *self = *self - rhs
1264 }
1265}
1266
1267impl Mul<f64> for Point3d {
1268 type Output = Point3d;
1269
1270 fn mul(self, rhs: f64) -> Self::Output {
1271 Point3d {
1272 x: self.x * rhs,
1273 y: self.y * rhs,
1274 z: self.z * rhs,
1275 units: self.units,
1276 }
1277 }
1278}
1279
1280#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1282#[ts(export)]
1283#[serde(rename_all = "camelCase")]
1284pub struct BasePath {
1285 #[ts(type = "[number, number]")]
1287 pub from: [f64; 2],
1288 #[ts(type = "[number, number]")]
1290 pub to: [f64; 2],
1291 pub units: UnitLength,
1292 pub tag: Option<TagNode>,
1294 #[serde(rename = "__geoMeta")]
1296 pub geo_meta: GeoMeta,
1297}
1298
1299impl BasePath {
1300 pub fn get_to(&self) -> [TyF64; 2] {
1301 let ty: NumericType = self.units.into();
1302 [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1303 }
1304
1305 pub fn get_from(&self) -> [TyF64; 2] {
1306 let ty: NumericType = self.units.into();
1307 [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1308 }
1309}
1310
1311#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1313#[ts(export)]
1314#[serde(rename_all = "camelCase")]
1315pub struct GeoMeta {
1316 pub id: uuid::Uuid,
1318 #[serde(flatten)]
1320 pub metadata: Metadata,
1321}
1322
1323#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1325#[ts(export)]
1326#[serde(tag = "type")]
1327pub enum Path {
1328 ToPoint {
1330 #[serde(flatten)]
1331 base: BasePath,
1332 },
1333 TangentialArcTo {
1335 #[serde(flatten)]
1336 base: BasePath,
1337 #[ts(type = "[number, number]")]
1339 center: [f64; 2],
1340 ccw: bool,
1342 },
1343 TangentialArc {
1345 #[serde(flatten)]
1346 base: BasePath,
1347 #[ts(type = "[number, number]")]
1349 center: [f64; 2],
1350 ccw: bool,
1352 },
1353 Circle {
1356 #[serde(flatten)]
1357 base: BasePath,
1358 #[ts(type = "[number, number]")]
1360 center: [f64; 2],
1361 radius: f64,
1363 ccw: bool,
1366 },
1367 CircleThreePoint {
1368 #[serde(flatten)]
1369 base: BasePath,
1370 #[ts(type = "[number, number]")]
1372 p1: [f64; 2],
1373 #[ts(type = "[number, number]")]
1375 p2: [f64; 2],
1376 #[ts(type = "[number, number]")]
1378 p3: [f64; 2],
1379 },
1380 ArcThreePoint {
1381 #[serde(flatten)]
1382 base: BasePath,
1383 #[ts(type = "[number, number]")]
1385 p1: [f64; 2],
1386 #[ts(type = "[number, number]")]
1388 p2: [f64; 2],
1389 #[ts(type = "[number, number]")]
1391 p3: [f64; 2],
1392 },
1393 Horizontal {
1395 #[serde(flatten)]
1396 base: BasePath,
1397 x: f64,
1399 },
1400 AngledLineTo {
1402 #[serde(flatten)]
1403 base: BasePath,
1404 x: Option<f64>,
1406 y: Option<f64>,
1408 },
1409 Base {
1411 #[serde(flatten)]
1412 base: BasePath,
1413 },
1414 Arc {
1416 #[serde(flatten)]
1417 base: BasePath,
1418 center: [f64; 2],
1420 radius: f64,
1422 ccw: bool,
1424 },
1425 Ellipse {
1426 #[serde(flatten)]
1427 base: BasePath,
1428 center: [f64; 2],
1429 major_axis: [f64; 2],
1430 minor_radius: f64,
1431 ccw: bool,
1432 },
1433 Conic {
1435 #[serde(flatten)]
1436 base: BasePath,
1437 },
1438 Bezier {
1440 #[serde(flatten)]
1441 base: BasePath,
1442 #[ts(type = "[number, number]")]
1444 control1: [f64; 2],
1445 #[ts(type = "[number, number]")]
1447 control2: [f64; 2],
1448 },
1449}
1450
1451impl Path {
1452 pub fn get_id(&self) -> uuid::Uuid {
1453 match self {
1454 Path::ToPoint { base } => base.geo_meta.id,
1455 Path::Horizontal { base, .. } => base.geo_meta.id,
1456 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1457 Path::Base { base } => base.geo_meta.id,
1458 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1459 Path::TangentialArc { base, .. } => base.geo_meta.id,
1460 Path::Circle { base, .. } => base.geo_meta.id,
1461 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1462 Path::Arc { base, .. } => base.geo_meta.id,
1463 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1464 Path::Ellipse { base, .. } => base.geo_meta.id,
1465 Path::Conic { base, .. } => base.geo_meta.id,
1466 Path::Bezier { base, .. } => base.geo_meta.id,
1467 }
1468 }
1469
1470 pub fn set_id(&mut self, id: uuid::Uuid) {
1471 match self {
1472 Path::ToPoint { base } => base.geo_meta.id = id,
1473 Path::Horizontal { base, .. } => base.geo_meta.id = id,
1474 Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1475 Path::Base { base } => base.geo_meta.id = id,
1476 Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1477 Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1478 Path::Circle { base, .. } => base.geo_meta.id = id,
1479 Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1480 Path::Arc { base, .. } => base.geo_meta.id = id,
1481 Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1482 Path::Ellipse { base, .. } => base.geo_meta.id = id,
1483 Path::Conic { base, .. } => base.geo_meta.id = id,
1484 Path::Bezier { base, .. } => base.geo_meta.id = id,
1485 }
1486 }
1487
1488 pub fn get_tag(&self) -> Option<TagNode> {
1489 match self {
1490 Path::ToPoint { base } => base.tag.clone(),
1491 Path::Horizontal { base, .. } => base.tag.clone(),
1492 Path::AngledLineTo { base, .. } => base.tag.clone(),
1493 Path::Base { base } => base.tag.clone(),
1494 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1495 Path::TangentialArc { base, .. } => base.tag.clone(),
1496 Path::Circle { base, .. } => base.tag.clone(),
1497 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1498 Path::Arc { base, .. } => base.tag.clone(),
1499 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1500 Path::Ellipse { base, .. } => base.tag.clone(),
1501 Path::Conic { base, .. } => base.tag.clone(),
1502 Path::Bezier { base, .. } => base.tag.clone(),
1503 }
1504 }
1505
1506 pub fn get_base(&self) -> &BasePath {
1507 match self {
1508 Path::ToPoint { base } => base,
1509 Path::Horizontal { base, .. } => base,
1510 Path::AngledLineTo { base, .. } => base,
1511 Path::Base { base } => base,
1512 Path::TangentialArcTo { base, .. } => base,
1513 Path::TangentialArc { base, .. } => base,
1514 Path::Circle { base, .. } => base,
1515 Path::CircleThreePoint { base, .. } => base,
1516 Path::Arc { base, .. } => base,
1517 Path::ArcThreePoint { base, .. } => base,
1518 Path::Ellipse { base, .. } => base,
1519 Path::Conic { base, .. } => base,
1520 Path::Bezier { base, .. } => base,
1521 }
1522 }
1523
1524 pub fn get_from(&self) -> [TyF64; 2] {
1526 let p = &self.get_base().from;
1527 let ty: NumericType = self.get_base().units.into();
1528 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1529 }
1530
1531 pub fn get_to(&self) -> [TyF64; 2] {
1533 let p = &self.get_base().to;
1534 let ty: NumericType = self.get_base().units.into();
1535 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1536 }
1537
1538 pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1540 let p = &self.get_base().from;
1541 let ty: NumericType = self.get_base().units.into();
1542 (*p, ty)
1543 }
1544
1545 pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1547 let p = &self.get_base().to;
1548 let ty: NumericType = self.get_base().units.into();
1549 (*p, ty)
1550 }
1551
1552 pub fn length(&self) -> Option<TyF64> {
1555 let n = match self {
1556 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1557 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1558 }
1559 Self::TangentialArc {
1560 base: _,
1561 center,
1562 ccw: _,
1563 }
1564 | Self::TangentialArcTo {
1565 base: _,
1566 center,
1567 ccw: _,
1568 } => {
1569 let radius = linear_distance(&self.get_base().from, center);
1572 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
1573 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1575 }
1576 Self::Circle { radius, .. } => Some(2.0 * std::f64::consts::PI * radius),
1577 Self::CircleThreePoint { .. } => {
1578 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1579 self.get_base().from,
1580 self.get_base().to,
1581 self.get_base().to,
1582 ]);
1583 let radius = linear_distance(
1584 &[circle_center.center[0], circle_center.center[1]],
1585 &self.get_base().from,
1586 );
1587 Some(2.0 * std::f64::consts::PI * radius)
1588 }
1589 Self::Arc { .. } => {
1590 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1592 }
1593 Self::ArcThreePoint { .. } => {
1594 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1596 }
1597 Self::Ellipse { .. } => {
1598 None
1600 }
1601 Self::Conic { .. } => {
1602 None
1604 }
1605 Self::Bezier { .. } => {
1606 None
1608 }
1609 };
1610 n.map(|n| TyF64::new(n, self.get_base().units.into()))
1611 }
1612
1613 pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
1614 match self {
1615 Path::ToPoint { base } => Some(base),
1616 Path::Horizontal { base, .. } => Some(base),
1617 Path::AngledLineTo { base, .. } => Some(base),
1618 Path::Base { base } => Some(base),
1619 Path::TangentialArcTo { base, .. } => Some(base),
1620 Path::TangentialArc { base, .. } => Some(base),
1621 Path::Circle { base, .. } => Some(base),
1622 Path::CircleThreePoint { base, .. } => Some(base),
1623 Path::Arc { base, .. } => Some(base),
1624 Path::ArcThreePoint { base, .. } => Some(base),
1625 Path::Ellipse { base, .. } => Some(base),
1626 Path::Conic { base, .. } => Some(base),
1627 Path::Bezier { base, .. } => Some(base),
1628 }
1629 }
1630
1631 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1632 match self {
1633 Path::TangentialArc { center, ccw, .. }
1634 | Path::TangentialArcTo { center, ccw, .. }
1635 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1636 center: *center,
1637 ccw: *ccw,
1638 },
1639 Path::ArcThreePoint { p1, p2, p3, .. } => {
1640 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1641 GetTangentialInfoFromPathsResult::Arc {
1642 center: circle.center,
1643 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
1644 }
1645 }
1646 Path::Circle {
1647 center, ccw, radius, ..
1648 } => GetTangentialInfoFromPathsResult::Circle {
1649 center: *center,
1650 ccw: *ccw,
1651 radius: *radius,
1652 },
1653 Path::CircleThreePoint { p1, p2, p3, .. } => {
1654 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1655 let center_point = [circle.center[0], circle.center[1]];
1656 GetTangentialInfoFromPathsResult::Circle {
1657 center: center_point,
1658 ccw: true,
1660 radius: circle.radius,
1661 }
1662 }
1663 Path::Ellipse {
1665 center,
1666 major_axis,
1667 minor_radius,
1668 ccw,
1669 ..
1670 } => GetTangentialInfoFromPathsResult::Ellipse {
1671 center: *center,
1672 major_axis: *major_axis,
1673 _minor_radius: *minor_radius,
1674 ccw: *ccw,
1675 },
1676 Path::Conic { .. }
1677 | Path::ToPoint { .. }
1678 | Path::Horizontal { .. }
1679 | Path::AngledLineTo { .. }
1680 | Path::Base { .. }
1681 | Path::Bezier { .. } => {
1682 let base = self.get_base();
1683 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1684 }
1685 }
1686 }
1687
1688 pub(crate) fn is_straight_line(&self) -> bool {
1690 matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
1691 }
1692}
1693
1694#[rustfmt::skip]
1696fn linear_distance(
1697 [x0, y0]: &[f64; 2],
1698 [x1, y1]: &[f64; 2]
1699) -> f64 {
1700 let y_sq = (y1 - y0).powi(2);
1701 let x_sq = (x1 - x0).powi(2);
1702 (y_sq + x_sq).sqrt()
1703}
1704
1705#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1707#[ts(export)]
1708#[serde(tag = "type", rename_all = "camelCase")]
1709pub enum ExtrudeSurface {
1710 ExtrudePlane(ExtrudePlane),
1712 ExtrudeArc(ExtrudeArc),
1713 Chamfer(ChamferSurface),
1714 Fillet(FilletSurface),
1715}
1716
1717#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1719#[ts(export)]
1720#[serde(rename_all = "camelCase")]
1721pub struct ChamferSurface {
1722 pub face_id: uuid::Uuid,
1724 pub tag: Option<Node<TagDeclarator>>,
1726 #[serde(flatten)]
1728 pub geo_meta: GeoMeta,
1729}
1730
1731#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1733#[ts(export)]
1734#[serde(rename_all = "camelCase")]
1735pub struct FilletSurface {
1736 pub face_id: uuid::Uuid,
1738 pub tag: Option<Node<TagDeclarator>>,
1740 #[serde(flatten)]
1742 pub geo_meta: GeoMeta,
1743}
1744
1745#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1747#[ts(export)]
1748#[serde(rename_all = "camelCase")]
1749pub struct ExtrudePlane {
1750 pub face_id: uuid::Uuid,
1752 pub tag: Option<Node<TagDeclarator>>,
1754 #[serde(flatten)]
1756 pub geo_meta: GeoMeta,
1757}
1758
1759#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1761#[ts(export)]
1762#[serde(rename_all = "camelCase")]
1763pub struct ExtrudeArc {
1764 pub face_id: uuid::Uuid,
1766 pub tag: Option<Node<TagDeclarator>>,
1768 #[serde(flatten)]
1770 pub geo_meta: GeoMeta,
1771}
1772
1773impl ExtrudeSurface {
1774 pub fn get_id(&self) -> uuid::Uuid {
1775 match self {
1776 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
1777 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
1778 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
1779 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
1780 }
1781 }
1782
1783 pub fn face_id(&self) -> uuid::Uuid {
1784 match self {
1785 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id,
1786 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id,
1787 ExtrudeSurface::Fillet(f) => f.face_id,
1788 ExtrudeSurface::Chamfer(c) => c.face_id,
1789 }
1790 }
1791
1792 pub fn set_face_id(&mut self, face_id: uuid::Uuid) {
1793 match self {
1794 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id = face_id,
1795 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id = face_id,
1796 ExtrudeSurface::Fillet(f) => f.face_id = face_id,
1797 ExtrudeSurface::Chamfer(c) => c.face_id = face_id,
1798 }
1799 }
1800
1801 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
1802 match self {
1803 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
1804 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
1805 ExtrudeSurface::Fillet(f) => f.tag.clone(),
1806 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
1807 }
1808 }
1809}
1810
1811#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ts_rs::TS)]
1812pub struct SketchVarId(pub usize);
1813
1814impl SketchVarId {
1815 pub fn to_constraint_id(self, range: SourceRange) -> Result<kcl_ezpz::Id, KclError> {
1816 self.0.try_into().map_err(|_| {
1817 KclError::new_type(KclErrorDetails::new(
1818 "Cannot convert to constraint ID since the sketch variable ID is too large".to_owned(),
1819 vec![range],
1820 ))
1821 })
1822 }
1823}
1824
1825#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1826#[ts(export_to = "Geometry.ts")]
1827#[serde(rename_all = "camelCase")]
1828pub struct SketchVar {
1829 pub id: SketchVarId,
1830 pub initial_value: f64,
1831 pub ty: NumericType,
1832 #[serde(skip)]
1833 pub meta: Vec<Metadata>,
1834}
1835
1836impl SketchVar {
1837 pub fn initial_value_to_solver_units(
1838 &self,
1839 exec_state: &mut ExecState,
1840 source_range: SourceRange,
1841 description: &str,
1842 ) -> Result<TyF64, KclError> {
1843 let x_initial_value = KclValue::Number {
1844 value: self.initial_value,
1845 ty: self.ty,
1846 meta: vec![source_range.into()],
1847 };
1848 let normalized_value = normalize_to_solver_unit(&x_initial_value, source_range, exec_state, description)?;
1849 normalized_value.as_ty_f64().ok_or_else(|| {
1850 let message = format!(
1851 "Expected number after coercion, but found {}",
1852 normalized_value.human_friendly_type()
1853 );
1854 debug_assert!(false, "{}", &message);
1855 KclError::new_internal(KclErrorDetails::new(message, vec![source_range]))
1856 })
1857 }
1858}
1859
1860#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1861#[ts(export_to = "Geometry.ts")]
1862#[serde(tag = "type")]
1863pub enum UnsolvedExpr {
1864 Known(TyF64),
1865 Unknown(SketchVarId),
1866}
1867
1868impl UnsolvedExpr {
1869 pub fn var(&self) -> Option<SketchVarId> {
1870 match self {
1871 UnsolvedExpr::Known(_) => None,
1872 UnsolvedExpr::Unknown(id) => Some(*id),
1873 }
1874 }
1875}
1876
1877pub type UnsolvedPoint2dExpr = [UnsolvedExpr; 2];
1878
1879#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1880#[ts(export_to = "Geometry.ts")]
1881#[serde(rename_all = "camelCase")]
1882pub struct ConstrainablePoint2d {
1883 pub vars: crate::front::Point2d<SketchVarId>,
1884 pub object_id: ObjectId,
1885}
1886
1887#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1888#[ts(export_to = "Geometry.ts")]
1889#[serde(rename_all = "camelCase")]
1890pub struct UnsolvedSegment {
1891 pub id: Uuid,
1893 pub object_id: ObjectId,
1894 pub kind: UnsolvedSegmentKind,
1895 #[serde(skip)]
1896 pub meta: Vec<Metadata>,
1897}
1898
1899#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1900#[ts(export_to = "Geometry.ts")]
1901#[serde(rename_all = "camelCase")]
1902pub enum UnsolvedSegmentKind {
1903 Point {
1904 position: UnsolvedPoint2dExpr,
1905 ctor: Box<PointCtor>,
1906 },
1907 Line {
1908 start: UnsolvedPoint2dExpr,
1909 end: UnsolvedPoint2dExpr,
1910 ctor: Box<LineCtor>,
1911 start_object_id: ObjectId,
1912 end_object_id: ObjectId,
1913 construction: bool,
1914 },
1915 Arc {
1916 start: UnsolvedPoint2dExpr,
1917 end: UnsolvedPoint2dExpr,
1918 center: UnsolvedPoint2dExpr,
1919 ctor: Box<ArcCtor>,
1920 start_object_id: ObjectId,
1921 end_object_id: ObjectId,
1922 center_object_id: ObjectId,
1923 construction: bool,
1924 },
1925}
1926
1927#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1928#[ts(export_to = "Geometry.ts")]
1929#[serde(rename_all = "camelCase")]
1930pub struct Segment {
1931 pub id: Uuid,
1933 pub object_id: ObjectId,
1934 pub kind: SegmentKind,
1935 #[serde(skip)]
1936 pub meta: Vec<Metadata>,
1937}
1938
1939impl Segment {
1940 pub fn is_construction(&self) -> bool {
1941 match &self.kind {
1942 SegmentKind::Point { .. } => true,
1943 SegmentKind::Line { construction, .. } => *construction,
1944 SegmentKind::Arc { construction, .. } => *construction,
1945 }
1946 }
1947}
1948
1949#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1950#[ts(export_to = "Geometry.ts")]
1951#[serde(rename_all = "camelCase")]
1952pub enum SegmentKind {
1953 Point {
1954 position: [TyF64; 2],
1955 ctor: Box<PointCtor>,
1956 #[serde(skip_serializing_if = "Option::is_none")]
1957 freedom: Option<Freedom>,
1958 },
1959 Line {
1960 start: [TyF64; 2],
1961 end: [TyF64; 2],
1962 ctor: Box<LineCtor>,
1963 start_object_id: ObjectId,
1964 end_object_id: ObjectId,
1965 #[serde(skip_serializing_if = "Option::is_none")]
1966 start_freedom: Option<Freedom>,
1967 #[serde(skip_serializing_if = "Option::is_none")]
1968 end_freedom: Option<Freedom>,
1969 construction: bool,
1970 },
1971 Arc {
1972 start: [TyF64; 2],
1973 end: [TyF64; 2],
1974 center: [TyF64; 2],
1975 ctor: Box<ArcCtor>,
1976 start_object_id: ObjectId,
1977 end_object_id: ObjectId,
1978 center_object_id: ObjectId,
1979 #[serde(skip_serializing_if = "Option::is_none")]
1980 start_freedom: Option<Freedom>,
1981 #[serde(skip_serializing_if = "Option::is_none")]
1982 end_freedom: Option<Freedom>,
1983 #[serde(skip_serializing_if = "Option::is_none")]
1984 center_freedom: Option<Freedom>,
1985 construction: bool,
1986 },
1987}
1988
1989#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1990#[ts(export_to = "Geometry.ts")]
1991#[serde(rename_all = "camelCase")]
1992pub struct AbstractSegment {
1993 pub repr: SegmentRepr,
1994 #[serde(skip)]
1995 pub meta: Vec<Metadata>,
1996}
1997
1998#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1999pub enum SegmentRepr {
2000 Unsolved { segment: UnsolvedSegment },
2001 Solved { segment: Segment },
2002}
2003
2004#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2005#[ts(export_to = "Geometry.ts")]
2006#[serde(rename_all = "camelCase")]
2007pub struct SketchConstraint {
2008 pub kind: SketchConstraintKind,
2009 #[serde(skip)]
2010 pub meta: Vec<Metadata>,
2011}
2012
2013#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2014#[ts(export_to = "Geometry.ts")]
2015#[serde(rename_all = "camelCase")]
2016pub enum SketchConstraintKind {
2017 Distance { points: [ConstrainablePoint2d; 2] },
2018 Radius { points: [ConstrainablePoint2d; 2] },
2019 Diameter { points: [ConstrainablePoint2d; 2] },
2020 HorizontalDistance { points: [ConstrainablePoint2d; 2] },
2021 VerticalDistance { points: [ConstrainablePoint2d; 2] },
2022}
2023
2024impl SketchConstraintKind {
2025 pub fn name(&self) -> &'static str {
2026 match self {
2027 SketchConstraintKind::Distance { .. } => "distance",
2028 SketchConstraintKind::Radius { .. } => "radius",
2029 SketchConstraintKind::Diameter { .. } => "diameter",
2030 SketchConstraintKind::HorizontalDistance { .. } => "horizontalDistance",
2031 SketchConstraintKind::VerticalDistance { .. } => "verticalDistance",
2032 }
2033 }
2034}