1use std::ops::{Add, AddAssign, Mul, Sub, SubAssign};
2
3use anyhow::Result;
4use indexmap::IndexMap;
5use kcl_error::SourceRange;
6use kittycad_modeling_cmds as kcmc;
7use kittycad_modeling_cmds::{
8 ModelingCmd, each_cmd as mcmd, length_unit::LengthUnit, units::UnitLength, websocket::ModelingCmdReq,
9};
10use parse_display::{Display, FromStr};
11use serde::{Deserialize, Serialize};
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::{args::TyF64, sketch::PlaneData},
24};
25
26type Point3D = kcmc::shared::Point3d<f64>;
27
28#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
30#[ts(export)]
31#[serde(tag = "type", rename_all = "camelCase")]
32pub struct GdtAnnotation {
33 pub id: uuid::Uuid,
35 #[serde(skip)]
36 pub meta: Vec<Metadata>,
37}
38
39#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
41#[ts(export)]
42#[serde(tag = "type")]
43#[allow(clippy::large_enum_variant)]
44pub enum Geometry {
45 Sketch(Sketch),
46 Solid(Solid),
47}
48
49impl Geometry {
50 pub fn id(&self) -> uuid::Uuid {
51 match self {
52 Geometry::Sketch(s) => s.id,
53 Geometry::Solid(e) => e.id,
54 }
55 }
56
57 pub fn original_id(&self) -> uuid::Uuid {
61 match self {
62 Geometry::Sketch(s) => s.original_id,
63 Geometry::Solid(e) => e.sketch.original_id,
64 }
65 }
66}
67
68#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
70#[ts(export)]
71#[serde(tag = "type")]
72#[allow(clippy::large_enum_variant)]
73pub enum GeometryWithImportedGeometry {
74 Sketch(Sketch),
75 Solid(Solid),
76 ImportedGeometry(Box<ImportedGeometry>),
77}
78
79impl GeometryWithImportedGeometry {
80 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
81 match self {
82 GeometryWithImportedGeometry::Sketch(s) => Ok(s.id),
83 GeometryWithImportedGeometry::Solid(e) => Ok(e.id),
84 GeometryWithImportedGeometry::ImportedGeometry(i) => {
85 let id = i.id(ctx).await?;
86 Ok(id)
87 }
88 }
89 }
90}
91
92#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
94#[ts(export)]
95#[serde(tag = "type")]
96#[allow(clippy::vec_box)]
97pub enum Geometries {
98 Sketches(Vec<Sketch>),
99 Solids(Vec<Solid>),
100}
101
102impl From<Geometry> for Geometries {
103 fn from(value: Geometry) -> Self {
104 match value {
105 Geometry::Sketch(x) => Self::Sketches(vec![x]),
106 Geometry::Solid(x) => Self::Solids(vec![x]),
107 }
108 }
109}
110
111#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
113#[ts(export)]
114#[serde(rename_all = "camelCase")]
115pub struct ImportedGeometry {
116 pub id: uuid::Uuid,
118 pub value: Vec<String>,
120 #[serde(skip)]
121 pub meta: Vec<Metadata>,
122 #[serde(skip)]
124 completed: bool,
125}
126
127impl ImportedGeometry {
128 pub fn new(id: uuid::Uuid, value: Vec<String>, meta: Vec<Metadata>) -> Self {
129 Self {
130 id,
131 value,
132 meta,
133 completed: false,
134 }
135 }
136
137 async fn wait_for_finish(&mut self, ctx: &ExecutorContext) -> Result<(), KclError> {
138 if self.completed {
139 return Ok(());
140 }
141
142 ctx.engine
143 .ensure_async_command_completed(self.id, self.meta.first().map(|m| m.source_range))
144 .await?;
145
146 self.completed = true;
147
148 Ok(())
149 }
150
151 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
152 if !self.completed {
153 self.wait_for_finish(ctx).await?;
154 }
155
156 Ok(self.id)
157 }
158}
159
160#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
162#[ts(export)]
163#[serde(tag = "type", rename_all = "camelCase")]
164#[allow(clippy::vec_box)]
165pub enum SolidOrSketchOrImportedGeometry {
166 ImportedGeometry(Box<ImportedGeometry>),
167 SolidSet(Vec<Solid>),
168 SketchSet(Vec<Sketch>),
169}
170
171impl From<SolidOrSketchOrImportedGeometry> for crate::execution::KclValue {
172 fn from(value: SolidOrSketchOrImportedGeometry) -> Self {
173 match value {
174 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
175 SolidOrSketchOrImportedGeometry::SolidSet(mut s) => {
176 if s.len() == 1
177 && let Some(s) = s.pop()
178 {
179 crate::execution::KclValue::Solid { value: Box::new(s) }
180 } else {
181 crate::execution::KclValue::HomArray {
182 value: s
183 .into_iter()
184 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
185 .collect(),
186 ty: crate::execution::types::RuntimeType::solid(),
187 }
188 }
189 }
190 SolidOrSketchOrImportedGeometry::SketchSet(mut s) => {
191 if s.len() == 1
192 && let Some(s) = s.pop()
193 {
194 crate::execution::KclValue::Sketch { value: Box::new(s) }
195 } else {
196 crate::execution::KclValue::HomArray {
197 value: s
198 .into_iter()
199 .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) })
200 .collect(),
201 ty: crate::execution::types::RuntimeType::sketch(),
202 }
203 }
204 }
205 }
206 }
207}
208
209impl SolidOrSketchOrImportedGeometry {
210 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
211 match self {
212 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => {
213 let id = s.id(ctx).await?;
214
215 Ok(vec![id])
216 }
217 SolidOrSketchOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
218 SolidOrSketchOrImportedGeometry::SketchSet(s) => Ok(s.iter().map(|s| s.id).collect()),
219 }
220 }
221}
222
223#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
225#[ts(export)]
226#[serde(tag = "type", rename_all = "camelCase")]
227#[allow(clippy::vec_box)]
228pub enum SolidOrImportedGeometry {
229 ImportedGeometry(Box<ImportedGeometry>),
230 SolidSet(Vec<Solid>),
231}
232
233impl From<SolidOrImportedGeometry> for crate::execution::KclValue {
234 fn from(value: SolidOrImportedGeometry) -> Self {
235 match value {
236 SolidOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
237 SolidOrImportedGeometry::SolidSet(mut s) => {
238 if s.len() == 1
239 && let Some(s) = s.pop()
240 {
241 crate::execution::KclValue::Solid { value: Box::new(s) }
242 } else {
243 crate::execution::KclValue::HomArray {
244 value: s
245 .into_iter()
246 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
247 .collect(),
248 ty: crate::execution::types::RuntimeType::solid(),
249 }
250 }
251 }
252 }
253 }
254}
255
256impl SolidOrImportedGeometry {
257 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
258 match self {
259 SolidOrImportedGeometry::ImportedGeometry(s) => {
260 let id = s.id(ctx).await?;
261
262 Ok(vec![id])
263 }
264 SolidOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
265 }
266 }
267}
268
269#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
271#[ts(export)]
272#[serde(rename_all = "camelCase")]
273pub struct Helix {
274 pub value: uuid::Uuid,
276 pub artifact_id: ArtifactId,
278 pub revolutions: f64,
280 pub angle_start: f64,
282 pub ccw: bool,
284 pub cylinder_id: Option<uuid::Uuid>,
286 pub units: UnitLength,
287 #[serde(skip)]
288 pub meta: Vec<Metadata>,
289}
290
291#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
292#[ts(export)]
293#[serde(rename_all = "camelCase")]
294pub struct Plane {
295 pub id: uuid::Uuid,
297 pub artifact_id: ArtifactId,
299 #[serde(skip_serializing_if = "Option::is_none")]
303 pub object_id: Option<ObjectId>,
304 pub value: PlaneType,
306 #[serde(flatten)]
308 pub info: PlaneInfo,
309 #[serde(skip)]
310 pub meta: Vec<Metadata>,
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS)]
314#[ts(export)]
315#[serde(rename_all = "camelCase")]
316pub struct PlaneInfo {
317 pub origin: Point3d,
319 pub x_axis: Point3d,
321 pub y_axis: Point3d,
323 pub z_axis: Point3d,
325}
326
327impl PlaneInfo {
328 pub(crate) fn into_plane_data(self) -> PlaneData {
329 if self.origin.is_zero() {
330 match self {
331 Self {
332 origin:
333 Point3d {
334 x: 0.0,
335 y: 0.0,
336 z: 0.0,
337 units: Some(UnitLength::Millimeters),
338 },
339 x_axis:
340 Point3d {
341 x: 1.0,
342 y: 0.0,
343 z: 0.0,
344 units: _,
345 },
346 y_axis:
347 Point3d {
348 x: 0.0,
349 y: 1.0,
350 z: 0.0,
351 units: _,
352 },
353 z_axis: _,
354 } => return PlaneData::XY,
355 Self {
356 origin:
357 Point3d {
358 x: 0.0,
359 y: 0.0,
360 z: 0.0,
361 units: Some(UnitLength::Millimeters),
362 },
363 x_axis:
364 Point3d {
365 x: -1.0,
366 y: 0.0,
367 z: 0.0,
368 units: _,
369 },
370 y_axis:
371 Point3d {
372 x: 0.0,
373 y: 1.0,
374 z: 0.0,
375 units: _,
376 },
377 z_axis: _,
378 } => return PlaneData::NegXY,
379 Self {
380 origin:
381 Point3d {
382 x: 0.0,
383 y: 0.0,
384 z: 0.0,
385 units: Some(UnitLength::Millimeters),
386 },
387 x_axis:
388 Point3d {
389 x: 1.0,
390 y: 0.0,
391 z: 0.0,
392 units: _,
393 },
394 y_axis:
395 Point3d {
396 x: 0.0,
397 y: 0.0,
398 z: 1.0,
399 units: _,
400 },
401 z_axis: _,
402 } => return PlaneData::XZ,
403 Self {
404 origin:
405 Point3d {
406 x: 0.0,
407 y: 0.0,
408 z: 0.0,
409 units: Some(UnitLength::Millimeters),
410 },
411 x_axis:
412 Point3d {
413 x: -1.0,
414 y: 0.0,
415 z: 0.0,
416 units: _,
417 },
418 y_axis:
419 Point3d {
420 x: 0.0,
421 y: 0.0,
422 z: 1.0,
423 units: _,
424 },
425 z_axis: _,
426 } => return PlaneData::NegXZ,
427 Self {
428 origin:
429 Point3d {
430 x: 0.0,
431 y: 0.0,
432 z: 0.0,
433 units: Some(UnitLength::Millimeters),
434 },
435 x_axis:
436 Point3d {
437 x: 0.0,
438 y: 1.0,
439 z: 0.0,
440 units: _,
441 },
442 y_axis:
443 Point3d {
444 x: 0.0,
445 y: 0.0,
446 z: 1.0,
447 units: _,
448 },
449 z_axis: _,
450 } => return PlaneData::YZ,
451 Self {
452 origin:
453 Point3d {
454 x: 0.0,
455 y: 0.0,
456 z: 0.0,
457 units: Some(UnitLength::Millimeters),
458 },
459 x_axis:
460 Point3d {
461 x: 0.0,
462 y: -1.0,
463 z: 0.0,
464 units: _,
465 },
466 y_axis:
467 Point3d {
468 x: 0.0,
469 y: 0.0,
470 z: 1.0,
471 units: _,
472 },
473 z_axis: _,
474 } => return PlaneData::NegYZ,
475 _ => {}
476 }
477 }
478
479 PlaneData::Plane(Self {
480 origin: self.origin,
481 x_axis: self.x_axis,
482 y_axis: self.y_axis,
483 z_axis: self.z_axis,
484 })
485 }
486
487 pub(crate) fn is_right_handed(&self) -> bool {
488 let lhs = self
491 .x_axis
492 .axes_cross_product(&self.y_axis)
493 .axes_dot_product(&self.z_axis);
494 let rhs_x = self.x_axis.axes_dot_product(&self.x_axis);
495 let rhs_y = self.y_axis.axes_dot_product(&self.y_axis);
496 let rhs_z = self.z_axis.axes_dot_product(&self.z_axis);
497 let rhs = (rhs_x * rhs_y * rhs_z).sqrt();
498 (lhs - rhs).abs() <= 0.0001
500 }
501
502 #[cfg(test)]
503 pub(crate) fn is_left_handed(&self) -> bool {
504 !self.is_right_handed()
505 }
506
507 pub(crate) fn make_right_handed(self) -> Self {
508 if self.is_right_handed() {
509 return self;
510 }
511 Self {
513 origin: self.origin,
514 x_axis: self.x_axis.negated(),
515 y_axis: self.y_axis,
516 z_axis: self.z_axis,
517 }
518 }
519}
520
521impl TryFrom<PlaneData> for PlaneInfo {
522 type Error = KclError;
523
524 fn try_from(value: PlaneData) -> Result<Self, Self::Error> {
525 if let PlaneData::Plane(info) = value {
526 return Ok(info);
527 }
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(_) => {
536 return Err(KclError::new_internal(KclErrorDetails::new(
538 format!("PlaneData {value:?} not found"),
539 Default::default(),
540 )));
541 }
542 };
543
544 let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
545 KclError::new_internal(KclErrorDetails::new(
546 format!("Plane {name} not found"),
547 Default::default(),
548 ))
549 })?;
550
551 Ok(info.clone())
552 }
553}
554
555impl From<PlaneData> for PlaneType {
556 fn from(value: PlaneData) -> Self {
557 match value {
558 PlaneData::XY => PlaneType::XY,
559 PlaneData::NegXY => PlaneType::XY,
560 PlaneData::XZ => PlaneType::XZ,
561 PlaneData::NegXZ => PlaneType::XZ,
562 PlaneData::YZ => PlaneType::YZ,
563 PlaneData::NegYZ => PlaneType::YZ,
564 PlaneData::Plane(_) => PlaneType::Custom,
565 }
566 }
567}
568
569impl Plane {
570 pub(crate) fn from_plane_data(value: PlaneData, exec_state: &mut ExecState) -> Result<Self, KclError> {
571 let id = exec_state.next_uuid();
572 Ok(Plane {
573 id,
574 object_id: None,
575 artifact_id: id.into(),
576 info: PlaneInfo::try_from(value.clone())?,
577 value: value.into(),
578 meta: vec![],
579 })
580 }
581
582 pub fn is_standard(&self) -> bool {
584 !matches!(self.value, PlaneType::Custom | PlaneType::Uninit)
585 }
586
587 pub fn project(&self, point: Point3d) -> Point3d {
590 let v = point - self.info.origin;
591 let dot = v.axes_dot_product(&self.info.z_axis);
592
593 point - self.info.z_axis * dot
594 }
595}
596
597#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
599#[ts(export)]
600#[serde(rename_all = "camelCase")]
601pub struct Face {
602 pub id: uuid::Uuid,
604 pub artifact_id: ArtifactId,
606 pub value: String,
608 pub x_axis: Point3d,
610 pub y_axis: Point3d,
612 pub solid: Box<Solid>,
614 pub units: UnitLength,
615 #[serde(skip)]
616 pub meta: Vec<Metadata>,
617}
618
619#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, FromStr, Display)]
621#[ts(export)]
622#[display(style = "camelCase")]
623pub enum PlaneType {
624 #[serde(rename = "XY", alias = "xy")]
625 #[display("XY")]
626 XY,
627 #[serde(rename = "XZ", alias = "xz")]
628 #[display("XZ")]
629 XZ,
630 #[serde(rename = "YZ", alias = "yz")]
631 #[display("YZ")]
632 YZ,
633 #[display("Custom")]
635 Custom,
636 #[display("Uninit")]
638 Uninit,
639}
640
641#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
642#[ts(export)]
643#[serde(tag = "type", rename_all = "camelCase")]
644pub struct Sketch {
645 pub id: uuid::Uuid,
647 pub paths: Vec<Path>,
651 #[serde(default, skip_serializing_if = "Vec::is_empty")]
653 pub inner_paths: Vec<Path>,
654 pub on: SketchSurface,
656 pub start: BasePath,
658 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
660 pub tags: IndexMap<String, TagIdentifier>,
661 pub artifact_id: ArtifactId,
664 #[ts(skip)]
665 pub original_id: uuid::Uuid,
666 #[serde(skip)]
668 pub mirror: Option<uuid::Uuid>,
669 #[serde(skip)]
671 pub clone: Option<uuid::Uuid>,
672 pub units: UnitLength,
673 #[serde(skip)]
675 pub meta: Vec<Metadata>,
676 #[serde(
679 default = "ProfileClosed::explicitly",
680 skip_serializing_if = "ProfileClosed::is_explicitly"
681 )]
682 pub is_closed: ProfileClosed,
683}
684
685impl ProfileClosed {
686 #[expect(dead_code, reason = "it's not actually dead, it's called by serde")]
687 fn explicitly() -> Self {
688 Self::Explicitly
689 }
690
691 fn is_explicitly(&self) -> bool {
692 matches!(self, ProfileClosed::Explicitly)
693 }
694}
695
696#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd, ts_rs::TS)]
698#[serde(rename_all = "camelCase")]
699pub enum ProfileClosed {
700 No,
702 Maybe,
704 Implicitly,
706 Explicitly,
708}
709
710impl Sketch {
711 pub(crate) fn build_sketch_mode_cmds(
714 &self,
715 exec_state: &mut ExecState,
716 inner_cmd: ModelingCmdReq,
717 ) -> Vec<ModelingCmdReq> {
718 vec![
719 ModelingCmdReq {
722 cmd: ModelingCmd::from(mcmd::EnableSketchMode {
723 animated: false,
724 ortho: false,
725 entity_id: self.on.id(),
726 adjust_camera: false,
727 planar_normal: if let SketchSurface::Plane(plane) = &self.on {
728 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
730 Some(normal.into())
731 } else {
732 None
733 },
734 }),
735 cmd_id: exec_state.next_uuid().into(),
736 },
737 inner_cmd,
738 ModelingCmdReq {
739 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
740 cmd_id: exec_state.next_uuid().into(),
741 },
742 ]
743 }
744}
745
746#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
748#[ts(export)]
749#[serde(tag = "type", rename_all = "camelCase")]
750pub enum SketchSurface {
751 Plane(Box<Plane>),
752 Face(Box<Face>),
753}
754
755impl SketchSurface {
756 pub(crate) fn id(&self) -> uuid::Uuid {
757 match self {
758 SketchSurface::Plane(plane) => plane.id,
759 SketchSurface::Face(face) => face.id,
760 }
761 }
762 pub(crate) fn x_axis(&self) -> Point3d {
763 match self {
764 SketchSurface::Plane(plane) => plane.info.x_axis,
765 SketchSurface::Face(face) => face.x_axis,
766 }
767 }
768 pub(crate) fn y_axis(&self) -> Point3d {
769 match self {
770 SketchSurface::Plane(plane) => plane.info.y_axis,
771 SketchSurface::Face(face) => face.y_axis,
772 }
773 }
774}
775
776#[derive(Debug, Clone)]
777pub(crate) enum GetTangentialInfoFromPathsResult {
778 PreviousPoint([f64; 2]),
779 Arc {
780 center: [f64; 2],
781 ccw: bool,
782 },
783 Circle {
784 center: [f64; 2],
785 ccw: bool,
786 radius: f64,
787 },
788 Ellipse {
789 center: [f64; 2],
790 ccw: bool,
791 major_axis: [f64; 2],
792 _minor_radius: f64,
793 },
794}
795
796impl GetTangentialInfoFromPathsResult {
797 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
798 match self {
799 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
800 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
801 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
802 }
803 GetTangentialInfoFromPathsResult::Circle {
806 center, radius, ccw, ..
807 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
808 GetTangentialInfoFromPathsResult::Ellipse {
809 center,
810 major_axis,
811 ccw,
812 ..
813 } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
814 }
815 }
816}
817
818impl Sketch {
819 pub(crate) fn add_tag(
820 &mut self,
821 tag: NodeRef<'_, TagDeclarator>,
822 current_path: &Path,
823 exec_state: &ExecState,
824 surface: Option<&ExtrudeSurface>,
825 ) {
826 let mut tag_identifier: TagIdentifier = tag.into();
827 let base = current_path.get_base();
828 tag_identifier.info.push((
829 exec_state.stack().current_epoch(),
830 TagEngineInfo {
831 id: base.geo_meta.id,
832 sketch: self.id,
833 path: Some(current_path.clone()),
834 surface: surface.cloned(),
835 },
836 ));
837
838 self.tags.insert(tag.name.to_string(), tag_identifier);
839 }
840
841 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
842 for t in tags {
843 match self.tags.get_mut(&t.value) {
844 Some(id) => {
845 id.merge_info(t);
846 }
847 None => {
848 self.tags.insert(t.value.clone(), t.clone());
849 }
850 }
851 }
852 }
853
854 pub(crate) fn latest_path(&self) -> Option<&Path> {
856 self.paths.last()
857 }
858
859 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
863 let Some(path) = self.latest_path() else {
864 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
865 };
866
867 let to = path.get_base().to;
868 Ok(Point2d::new(to[0], to[1], path.get_base().units))
869 }
870
871 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
872 let Some(path) = self.latest_path() else {
873 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
874 };
875 path.get_tangential_info()
876 }
877}
878
879#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
880#[ts(export)]
881#[serde(tag = "type", rename_all = "camelCase")]
882pub struct Solid {
883 pub id: uuid::Uuid,
885 pub artifact_id: ArtifactId,
887 pub value: Vec<ExtrudeSurface>,
889 pub sketch: Sketch,
891 pub start_cap_id: Option<uuid::Uuid>,
893 pub end_cap_id: Option<uuid::Uuid>,
895 #[serde(default, skip_serializing_if = "Vec::is_empty")]
897 pub edge_cuts: Vec<EdgeCut>,
898 pub units: UnitLength,
900 pub sectional: bool,
902 #[serde(skip)]
904 pub meta: Vec<Metadata>,
905}
906
907impl Solid {
908 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
909 self.edge_cuts.iter().map(|foc| foc.id())
910 }
911}
912
913#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
915#[ts(export)]
916#[serde(tag = "type", rename_all = "camelCase")]
917pub enum EdgeCut {
918 Fillet {
920 id: uuid::Uuid,
922 radius: TyF64,
923 #[serde(rename = "edgeId")]
925 edge_id: uuid::Uuid,
926 tag: Box<Option<TagNode>>,
927 },
928 Chamfer {
930 id: uuid::Uuid,
932 length: TyF64,
933 #[serde(rename = "edgeId")]
935 edge_id: uuid::Uuid,
936 tag: Box<Option<TagNode>>,
937 },
938}
939
940impl EdgeCut {
941 pub fn id(&self) -> uuid::Uuid {
942 match self {
943 EdgeCut::Fillet { id, .. } => *id,
944 EdgeCut::Chamfer { id, .. } => *id,
945 }
946 }
947
948 pub fn set_id(&mut self, id: uuid::Uuid) {
949 match self {
950 EdgeCut::Fillet { id: i, .. } => *i = id,
951 EdgeCut::Chamfer { id: i, .. } => *i = id,
952 }
953 }
954
955 pub fn edge_id(&self) -> uuid::Uuid {
956 match self {
957 EdgeCut::Fillet { edge_id, .. } => *edge_id,
958 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
959 }
960 }
961
962 pub fn set_edge_id(&mut self, id: uuid::Uuid) {
963 match self {
964 EdgeCut::Fillet { edge_id: i, .. } => *i = id,
965 EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
966 }
967 }
968
969 pub fn tag(&self) -> Option<TagNode> {
970 match self {
971 EdgeCut::Fillet { tag, .. } => *tag.clone(),
972 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
973 }
974 }
975}
976
977#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
978#[ts(export)]
979pub struct Point2d {
980 pub x: f64,
981 pub y: f64,
982 pub units: UnitLength,
983}
984
985impl Point2d {
986 pub const ZERO: Self = Self {
987 x: 0.0,
988 y: 0.0,
989 units: UnitLength::Millimeters,
990 };
991
992 pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
993 Self { x, y, units }
994 }
995
996 pub fn into_x(self) -> TyF64 {
997 TyF64::new(self.x, self.units.into())
998 }
999
1000 pub fn into_y(self) -> TyF64 {
1001 TyF64::new(self.y, self.units.into())
1002 }
1003
1004 pub fn ignore_units(self) -> [f64; 2] {
1005 [self.x, self.y]
1006 }
1007}
1008
1009#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
1010#[ts(export)]
1011pub struct Point3d {
1012 pub x: f64,
1013 pub y: f64,
1014 pub z: f64,
1015 pub units: Option<UnitLength>,
1016}
1017
1018impl Point3d {
1019 pub const ZERO: Self = Self {
1020 x: 0.0,
1021 y: 0.0,
1022 z: 0.0,
1023 units: Some(UnitLength::Millimeters),
1024 };
1025
1026 pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
1027 Self { x, y, z, units }
1028 }
1029
1030 pub const fn is_zero(&self) -> bool {
1031 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
1032 }
1033
1034 pub fn axes_cross_product(&self, other: &Self) -> Self {
1039 Self {
1040 x: self.y * other.z - self.z * other.y,
1041 y: self.z * other.x - self.x * other.z,
1042 z: self.x * other.y - self.y * other.x,
1043 units: None,
1044 }
1045 }
1046
1047 pub fn axes_dot_product(&self, other: &Self) -> f64 {
1052 let x = self.x * other.x;
1053 let y = self.y * other.y;
1054 let z = self.z * other.z;
1055 x + y + z
1056 }
1057
1058 pub fn normalize(&self) -> Self {
1059 let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1060 Point3d {
1061 x: self.x / len,
1062 y: self.y / len,
1063 z: self.z / len,
1064 units: None,
1065 }
1066 }
1067
1068 pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1069 let p = [self.x, self.y, self.z];
1070 let u = self.units;
1071 (p, u)
1072 }
1073
1074 pub(crate) fn negated(self) -> Self {
1075 Self {
1076 x: -self.x,
1077 y: -self.y,
1078 z: -self.z,
1079 units: self.units,
1080 }
1081 }
1082}
1083
1084impl From<[TyF64; 3]> for Point3d {
1085 fn from(p: [TyF64; 3]) -> Self {
1086 Self {
1087 x: p[0].n,
1088 y: p[1].n,
1089 z: p[2].n,
1090 units: p[0].ty.as_length(),
1091 }
1092 }
1093}
1094
1095impl From<Point3d> for Point3D {
1096 fn from(p: Point3d) -> Self {
1097 Self { x: p.x, y: p.y, z: p.z }
1098 }
1099}
1100
1101impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1102 fn from(p: Point3d) -> Self {
1103 if let Some(units) = p.units {
1104 Self {
1105 x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1106 y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1107 z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1108 }
1109 } else {
1110 Self {
1111 x: LengthUnit(p.x),
1112 y: LengthUnit(p.y),
1113 z: LengthUnit(p.z),
1114 }
1115 }
1116 }
1117}
1118
1119impl Add for Point3d {
1120 type Output = Point3d;
1121
1122 fn add(self, rhs: Self) -> Self::Output {
1123 Point3d {
1125 x: self.x + rhs.x,
1126 y: self.y + rhs.y,
1127 z: self.z + rhs.z,
1128 units: self.units,
1129 }
1130 }
1131}
1132
1133impl AddAssign for Point3d {
1134 fn add_assign(&mut self, rhs: Self) {
1135 *self = *self + rhs
1136 }
1137}
1138
1139impl Sub for Point3d {
1140 type Output = Point3d;
1141
1142 fn sub(self, rhs: Self) -> Self::Output {
1143 let (x, y, z) = if rhs.units != self.units
1144 && let Some(sunits) = self.units
1145 && let Some(runits) = rhs.units
1146 {
1147 (
1148 adjust_length(runits, rhs.x, sunits).0,
1149 adjust_length(runits, rhs.y, sunits).0,
1150 adjust_length(runits, rhs.z, sunits).0,
1151 )
1152 } else {
1153 (rhs.x, rhs.y, rhs.z)
1154 };
1155 Point3d {
1156 x: self.x - x,
1157 y: self.y - y,
1158 z: self.z - z,
1159 units: self.units,
1160 }
1161 }
1162}
1163
1164impl SubAssign for Point3d {
1165 fn sub_assign(&mut self, rhs: Self) {
1166 *self = *self - rhs
1167 }
1168}
1169
1170impl Mul<f64> for Point3d {
1171 type Output = Point3d;
1172
1173 fn mul(self, rhs: f64) -> Self::Output {
1174 Point3d {
1175 x: self.x * rhs,
1176 y: self.y * rhs,
1177 z: self.z * rhs,
1178 units: self.units,
1179 }
1180 }
1181}
1182
1183#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1185#[ts(export)]
1186#[serde(rename_all = "camelCase")]
1187pub struct BasePath {
1188 #[ts(type = "[number, number]")]
1190 pub from: [f64; 2],
1191 #[ts(type = "[number, number]")]
1193 pub to: [f64; 2],
1194 pub units: UnitLength,
1195 pub tag: Option<TagNode>,
1197 #[serde(rename = "__geoMeta")]
1199 pub geo_meta: GeoMeta,
1200}
1201
1202impl BasePath {
1203 pub fn get_to(&self) -> [TyF64; 2] {
1204 let ty: NumericType = self.units.into();
1205 [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1206 }
1207
1208 pub fn get_from(&self) -> [TyF64; 2] {
1209 let ty: NumericType = self.units.into();
1210 [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1211 }
1212}
1213
1214#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1216#[ts(export)]
1217#[serde(rename_all = "camelCase")]
1218pub struct GeoMeta {
1219 pub id: uuid::Uuid,
1221 #[serde(flatten)]
1223 pub metadata: Metadata,
1224}
1225
1226#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1228#[ts(export)]
1229#[serde(tag = "type")]
1230pub enum Path {
1231 ToPoint {
1233 #[serde(flatten)]
1234 base: BasePath,
1235 },
1236 TangentialArcTo {
1238 #[serde(flatten)]
1239 base: BasePath,
1240 #[ts(type = "[number, number]")]
1242 center: [f64; 2],
1243 ccw: bool,
1245 },
1246 TangentialArc {
1248 #[serde(flatten)]
1249 base: BasePath,
1250 #[ts(type = "[number, number]")]
1252 center: [f64; 2],
1253 ccw: bool,
1255 },
1256 Circle {
1259 #[serde(flatten)]
1260 base: BasePath,
1261 #[ts(type = "[number, number]")]
1263 center: [f64; 2],
1264 radius: f64,
1266 ccw: bool,
1269 },
1270 CircleThreePoint {
1271 #[serde(flatten)]
1272 base: BasePath,
1273 #[ts(type = "[number, number]")]
1275 p1: [f64; 2],
1276 #[ts(type = "[number, number]")]
1278 p2: [f64; 2],
1279 #[ts(type = "[number, number]")]
1281 p3: [f64; 2],
1282 },
1283 ArcThreePoint {
1284 #[serde(flatten)]
1285 base: BasePath,
1286 #[ts(type = "[number, number]")]
1288 p1: [f64; 2],
1289 #[ts(type = "[number, number]")]
1291 p2: [f64; 2],
1292 #[ts(type = "[number, number]")]
1294 p3: [f64; 2],
1295 },
1296 Horizontal {
1298 #[serde(flatten)]
1299 base: BasePath,
1300 x: f64,
1302 },
1303 AngledLineTo {
1305 #[serde(flatten)]
1306 base: BasePath,
1307 x: Option<f64>,
1309 y: Option<f64>,
1311 },
1312 Base {
1314 #[serde(flatten)]
1315 base: BasePath,
1316 },
1317 Arc {
1319 #[serde(flatten)]
1320 base: BasePath,
1321 center: [f64; 2],
1323 radius: f64,
1325 ccw: bool,
1327 },
1328 Ellipse {
1329 #[serde(flatten)]
1330 base: BasePath,
1331 center: [f64; 2],
1332 major_axis: [f64; 2],
1333 minor_radius: f64,
1334 ccw: bool,
1335 },
1336 Conic {
1338 #[serde(flatten)]
1339 base: BasePath,
1340 },
1341}
1342
1343impl Path {
1344 pub fn get_id(&self) -> uuid::Uuid {
1345 match self {
1346 Path::ToPoint { base } => base.geo_meta.id,
1347 Path::Horizontal { base, .. } => base.geo_meta.id,
1348 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1349 Path::Base { base } => base.geo_meta.id,
1350 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1351 Path::TangentialArc { base, .. } => base.geo_meta.id,
1352 Path::Circle { base, .. } => base.geo_meta.id,
1353 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1354 Path::Arc { base, .. } => base.geo_meta.id,
1355 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1356 Path::Ellipse { base, .. } => base.geo_meta.id,
1357 Path::Conic { base, .. } => base.geo_meta.id,
1358 }
1359 }
1360
1361 pub fn set_id(&mut self, id: uuid::Uuid) {
1362 match self {
1363 Path::ToPoint { base } => base.geo_meta.id = id,
1364 Path::Horizontal { base, .. } => base.geo_meta.id = id,
1365 Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1366 Path::Base { base } => base.geo_meta.id = id,
1367 Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1368 Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1369 Path::Circle { base, .. } => base.geo_meta.id = id,
1370 Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1371 Path::Arc { base, .. } => base.geo_meta.id = id,
1372 Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1373 Path::Ellipse { base, .. } => base.geo_meta.id = id,
1374 Path::Conic { base, .. } => base.geo_meta.id = id,
1375 }
1376 }
1377
1378 pub fn get_tag(&self) -> Option<TagNode> {
1379 match self {
1380 Path::ToPoint { base } => base.tag.clone(),
1381 Path::Horizontal { base, .. } => base.tag.clone(),
1382 Path::AngledLineTo { base, .. } => base.tag.clone(),
1383 Path::Base { base } => base.tag.clone(),
1384 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1385 Path::TangentialArc { base, .. } => base.tag.clone(),
1386 Path::Circle { base, .. } => base.tag.clone(),
1387 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1388 Path::Arc { base, .. } => base.tag.clone(),
1389 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1390 Path::Ellipse { base, .. } => base.tag.clone(),
1391 Path::Conic { base, .. } => base.tag.clone(),
1392 }
1393 }
1394
1395 pub fn get_base(&self) -> &BasePath {
1396 match self {
1397 Path::ToPoint { base } => base,
1398 Path::Horizontal { base, .. } => base,
1399 Path::AngledLineTo { base, .. } => base,
1400 Path::Base { base } => base,
1401 Path::TangentialArcTo { base, .. } => base,
1402 Path::TangentialArc { base, .. } => base,
1403 Path::Circle { base, .. } => base,
1404 Path::CircleThreePoint { base, .. } => base,
1405 Path::Arc { base, .. } => base,
1406 Path::ArcThreePoint { base, .. } => base,
1407 Path::Ellipse { base, .. } => base,
1408 Path::Conic { base, .. } => base,
1409 }
1410 }
1411
1412 pub fn get_from(&self) -> [TyF64; 2] {
1414 let p = &self.get_base().from;
1415 let ty: NumericType = self.get_base().units.into();
1416 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1417 }
1418
1419 pub fn get_to(&self) -> [TyF64; 2] {
1421 let p = &self.get_base().to;
1422 let ty: NumericType = self.get_base().units.into();
1423 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1424 }
1425
1426 pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1428 let p = &self.get_base().from;
1429 let ty: NumericType = self.get_base().units.into();
1430 (*p, ty)
1431 }
1432
1433 pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1435 let p = &self.get_base().to;
1436 let ty: NumericType = self.get_base().units.into();
1437 (*p, ty)
1438 }
1439
1440 pub fn length(&self) -> Option<TyF64> {
1443 let n = match self {
1444 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1445 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1446 }
1447 Self::TangentialArc {
1448 base: _,
1449 center,
1450 ccw: _,
1451 }
1452 | Self::TangentialArcTo {
1453 base: _,
1454 center,
1455 ccw: _,
1456 } => {
1457 let radius = linear_distance(&self.get_base().from, center);
1460 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
1461 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1463 }
1464 Self::Circle { radius, .. } => Some(2.0 * std::f64::consts::PI * radius),
1465 Self::CircleThreePoint { .. } => {
1466 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1467 self.get_base().from,
1468 self.get_base().to,
1469 self.get_base().to,
1470 ]);
1471 let radius = linear_distance(
1472 &[circle_center.center[0], circle_center.center[1]],
1473 &self.get_base().from,
1474 );
1475 Some(2.0 * std::f64::consts::PI * radius)
1476 }
1477 Self::Arc { .. } => {
1478 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1480 }
1481 Self::ArcThreePoint { .. } => {
1482 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1484 }
1485 Self::Ellipse { .. } => {
1486 None
1488 }
1489 Self::Conic { .. } => {
1490 None
1492 }
1493 };
1494 n.map(|n| TyF64::new(n, self.get_base().units.into()))
1495 }
1496
1497 pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
1498 match self {
1499 Path::ToPoint { base } => Some(base),
1500 Path::Horizontal { base, .. } => Some(base),
1501 Path::AngledLineTo { base, .. } => Some(base),
1502 Path::Base { base } => Some(base),
1503 Path::TangentialArcTo { base, .. } => Some(base),
1504 Path::TangentialArc { base, .. } => Some(base),
1505 Path::Circle { base, .. } => Some(base),
1506 Path::CircleThreePoint { base, .. } => Some(base),
1507 Path::Arc { base, .. } => Some(base),
1508 Path::ArcThreePoint { base, .. } => Some(base),
1509 Path::Ellipse { base, .. } => Some(base),
1510 Path::Conic { base, .. } => Some(base),
1511 }
1512 }
1513
1514 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1515 match self {
1516 Path::TangentialArc { center, ccw, .. }
1517 | Path::TangentialArcTo { center, ccw, .. }
1518 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1519 center: *center,
1520 ccw: *ccw,
1521 },
1522 Path::ArcThreePoint { p1, p2, p3, .. } => {
1523 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1524 GetTangentialInfoFromPathsResult::Arc {
1525 center: circle.center,
1526 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
1527 }
1528 }
1529 Path::Circle {
1530 center, ccw, radius, ..
1531 } => GetTangentialInfoFromPathsResult::Circle {
1532 center: *center,
1533 ccw: *ccw,
1534 radius: *radius,
1535 },
1536 Path::CircleThreePoint { p1, p2, p3, .. } => {
1537 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1538 let center_point = [circle.center[0], circle.center[1]];
1539 GetTangentialInfoFromPathsResult::Circle {
1540 center: center_point,
1541 ccw: true,
1543 radius: circle.radius,
1544 }
1545 }
1546 Path::Ellipse {
1548 center,
1549 major_axis,
1550 minor_radius,
1551 ccw,
1552 ..
1553 } => GetTangentialInfoFromPathsResult::Ellipse {
1554 center: *center,
1555 major_axis: *major_axis,
1556 _minor_radius: *minor_radius,
1557 ccw: *ccw,
1558 },
1559 Path::Conic { .. }
1560 | Path::ToPoint { .. }
1561 | Path::Horizontal { .. }
1562 | Path::AngledLineTo { .. }
1563 | Path::Base { .. } => {
1564 let base = self.get_base();
1565 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1566 }
1567 }
1568 }
1569
1570 pub(crate) fn is_straight_line(&self) -> bool {
1572 matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
1573 }
1574}
1575
1576#[rustfmt::skip]
1578fn linear_distance(
1579 [x0, y0]: &[f64; 2],
1580 [x1, y1]: &[f64; 2]
1581) -> f64 {
1582 let y_sq = (y1 - y0).powi(2);
1583 let x_sq = (x1 - x0).powi(2);
1584 (y_sq + x_sq).sqrt()
1585}
1586
1587#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1589#[ts(export)]
1590#[serde(tag = "type", rename_all = "camelCase")]
1591pub enum ExtrudeSurface {
1592 ExtrudePlane(ExtrudePlane),
1594 ExtrudeArc(ExtrudeArc),
1595 Chamfer(ChamferSurface),
1596 Fillet(FilletSurface),
1597}
1598
1599#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1601#[ts(export)]
1602#[serde(rename_all = "camelCase")]
1603pub struct ChamferSurface {
1604 pub face_id: uuid::Uuid,
1606 pub tag: Option<Node<TagDeclarator>>,
1608 #[serde(flatten)]
1610 pub geo_meta: GeoMeta,
1611}
1612
1613#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1615#[ts(export)]
1616#[serde(rename_all = "camelCase")]
1617pub struct FilletSurface {
1618 pub face_id: uuid::Uuid,
1620 pub tag: Option<Node<TagDeclarator>>,
1622 #[serde(flatten)]
1624 pub geo_meta: GeoMeta,
1625}
1626
1627#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1629#[ts(export)]
1630#[serde(rename_all = "camelCase")]
1631pub struct ExtrudePlane {
1632 pub face_id: uuid::Uuid,
1634 pub tag: Option<Node<TagDeclarator>>,
1636 #[serde(flatten)]
1638 pub geo_meta: GeoMeta,
1639}
1640
1641#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1643#[ts(export)]
1644#[serde(rename_all = "camelCase")]
1645pub struct ExtrudeArc {
1646 pub face_id: uuid::Uuid,
1648 pub tag: Option<Node<TagDeclarator>>,
1650 #[serde(flatten)]
1652 pub geo_meta: GeoMeta,
1653}
1654
1655impl ExtrudeSurface {
1656 pub fn get_id(&self) -> uuid::Uuid {
1657 match self {
1658 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
1659 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
1660 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
1661 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
1662 }
1663 }
1664
1665 pub fn face_id(&self) -> uuid::Uuid {
1666 match self {
1667 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id,
1668 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id,
1669 ExtrudeSurface::Fillet(f) => f.face_id,
1670 ExtrudeSurface::Chamfer(c) => c.face_id,
1671 }
1672 }
1673
1674 pub fn set_face_id(&mut self, face_id: uuid::Uuid) {
1675 match self {
1676 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id = face_id,
1677 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id = face_id,
1678 ExtrudeSurface::Fillet(f) => f.face_id = face_id,
1679 ExtrudeSurface::Chamfer(c) => c.face_id = face_id,
1680 }
1681 }
1682
1683 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
1684 match self {
1685 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
1686 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
1687 ExtrudeSurface::Fillet(f) => f.tag.clone(),
1688 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
1689 }
1690 }
1691}
1692
1693#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ts_rs::TS)]
1694pub struct SketchVarId(pub usize);
1695
1696impl SketchVarId {
1697 pub fn to_constraint_id(self, range: SourceRange) -> Result<kcl_ezpz::Id, KclError> {
1698 self.0.try_into().map_err(|_| {
1699 KclError::new_type(KclErrorDetails::new(
1700 "Cannot convert to constraint ID since the sketch variable ID is too large".to_owned(),
1701 vec![range],
1702 ))
1703 })
1704 }
1705}
1706
1707#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1708#[ts(export_to = "Geometry.ts")]
1709#[serde(rename_all = "camelCase")]
1710pub struct SketchVar {
1711 pub id: SketchVarId,
1712 pub initial_value: f64,
1713 pub ty: NumericType,
1714 #[serde(skip)]
1715 pub meta: Vec<Metadata>,
1716}
1717
1718impl SketchVar {
1719 pub fn initial_value_to_solver_units(
1720 &self,
1721 exec_state: &mut ExecState,
1722 source_range: SourceRange,
1723 description: &str,
1724 ) -> Result<TyF64, KclError> {
1725 let x_initial_value = KclValue::Number {
1726 value: self.initial_value,
1727 ty: self.ty,
1728 meta: vec![source_range.into()],
1729 };
1730 let normalized_value = normalize_to_solver_unit(&x_initial_value, source_range, exec_state, description)?;
1731 normalized_value.as_ty_f64().ok_or_else(|| {
1732 let message = format!(
1733 "Expected number after coercion, but found {}",
1734 normalized_value.human_friendly_type()
1735 );
1736 debug_assert!(false, "{}", &message);
1737 KclError::new_internal(KclErrorDetails::new(message, vec![source_range]))
1738 })
1739 }
1740}
1741
1742#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1743#[ts(export_to = "Geometry.ts")]
1744#[serde(tag = "type")]
1745pub enum UnsolvedExpr {
1746 Known(TyF64),
1747 Unknown(SketchVarId),
1748}
1749
1750impl UnsolvedExpr {
1751 pub fn var(&self) -> Option<SketchVarId> {
1752 match self {
1753 UnsolvedExpr::Known(_) => None,
1754 UnsolvedExpr::Unknown(id) => Some(*id),
1755 }
1756 }
1757}
1758
1759pub type UnsolvedPoint2dExpr = [UnsolvedExpr; 2];
1760
1761#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1762#[ts(export_to = "Geometry.ts")]
1763#[serde(rename_all = "camelCase")]
1764pub struct ConstrainablePoint2d {
1765 pub vars: crate::front::Point2d<SketchVarId>,
1766 pub object_id: ObjectId,
1767}
1768
1769#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1770#[ts(export_to = "Geometry.ts")]
1771#[serde(rename_all = "camelCase")]
1772pub struct UnsolvedSegment {
1773 pub object_id: ObjectId,
1774 pub kind: UnsolvedSegmentKind,
1775 #[serde(skip)]
1776 pub meta: Vec<Metadata>,
1777}
1778
1779#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1780#[ts(export_to = "Geometry.ts")]
1781#[serde(rename_all = "camelCase")]
1782pub enum UnsolvedSegmentKind {
1783 Point {
1784 position: UnsolvedPoint2dExpr,
1785 ctor: Box<PointCtor>,
1786 },
1787 Line {
1788 start: UnsolvedPoint2dExpr,
1789 end: UnsolvedPoint2dExpr,
1790 ctor: Box<LineCtor>,
1791 start_object_id: ObjectId,
1792 end_object_id: ObjectId,
1793 },
1794 Arc {
1795 start: UnsolvedPoint2dExpr,
1796 end: UnsolvedPoint2dExpr,
1797 center: UnsolvedPoint2dExpr,
1798 ctor: Box<ArcCtor>,
1799 start_object_id: ObjectId,
1800 end_object_id: ObjectId,
1801 center_object_id: ObjectId,
1802 },
1803}
1804
1805#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1806#[ts(export_to = "Geometry.ts")]
1807#[serde(rename_all = "camelCase")]
1808pub struct Segment {
1809 pub object_id: ObjectId,
1810 pub kind: SegmentKind,
1811 #[serde(skip)]
1812 pub meta: Vec<Metadata>,
1813}
1814
1815#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1816#[ts(export_to = "Geometry.ts")]
1817#[serde(rename_all = "camelCase")]
1818pub enum SegmentKind {
1819 Point {
1820 position: [TyF64; 2],
1821 ctor: Box<PointCtor>,
1822 #[serde(skip_serializing_if = "Option::is_none")]
1823 freedom: Option<Freedom>,
1824 },
1825 Line {
1826 start: [TyF64; 2],
1827 end: [TyF64; 2],
1828 ctor: Box<LineCtor>,
1829 start_object_id: ObjectId,
1830 end_object_id: ObjectId,
1831 #[serde(skip_serializing_if = "Option::is_none")]
1832 start_freedom: Option<Freedom>,
1833 #[serde(skip_serializing_if = "Option::is_none")]
1834 end_freedom: Option<Freedom>,
1835 },
1836 Arc {
1837 start: [TyF64; 2],
1838 end: [TyF64; 2],
1839 center: [TyF64; 2],
1840 ctor: Box<ArcCtor>,
1841 start_object_id: ObjectId,
1842 end_object_id: ObjectId,
1843 center_object_id: ObjectId,
1844 #[serde(skip_serializing_if = "Option::is_none")]
1845 start_freedom: Option<Freedom>,
1846 #[serde(skip_serializing_if = "Option::is_none")]
1847 end_freedom: Option<Freedom>,
1848 #[serde(skip_serializing_if = "Option::is_none")]
1849 center_freedom: Option<Freedom>,
1850 },
1851}
1852
1853#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1854#[ts(export_to = "Geometry.ts")]
1855#[serde(rename_all = "camelCase")]
1856pub struct AbstractSegment {
1857 pub repr: SegmentRepr,
1858 #[serde(skip)]
1859 pub meta: Vec<Metadata>,
1860}
1861
1862#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1863pub enum SegmentRepr {
1864 Unsolved { segment: UnsolvedSegment },
1865 Solved { segment: Segment },
1866}
1867
1868#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1869#[ts(export_to = "Geometry.ts")]
1870#[serde(rename_all = "camelCase")]
1871pub struct SketchConstraint {
1872 pub kind: SketchConstraintKind,
1873 #[serde(skip)]
1874 pub meta: Vec<Metadata>,
1875}
1876
1877#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1878#[ts(export_to = "Geometry.ts")]
1879#[serde(rename_all = "camelCase")]
1880pub enum SketchConstraintKind {
1881 Distance { points: [ConstrainablePoint2d; 2] },
1882}