1use std::ops::{Add, AddAssign, Mul, Sub, SubAssign};
2
3use anyhow::Result;
4use indexmap::IndexMap;
5use kittycad_modeling_cmds as kcmc;
6use kittycad_modeling_cmds::{
7 ModelingCmd, each_cmd as mcmd, length_unit::LengthUnit, units::UnitLength, websocket::ModelingCmdReq,
8};
9use parse_display::{Display, FromStr};
10use serde::{Deserialize, Serialize};
11
12use crate::{
13 engine::{DEFAULT_PLANE_INFO, PlaneName},
14 errors::{KclError, KclErrorDetails},
15 execution::{
16 ArtifactId, ExecState, ExecutorContext, Metadata, TagEngineInfo, TagIdentifier,
17 types::{NumericType, adjust_length},
18 },
19 parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
20 std::{args::TyF64, sketch::PlaneData},
21};
22
23type Point3D = kcmc::shared::Point3d<f64>;
24
25#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
27#[ts(export)]
28#[serde(tag = "type")]
29#[allow(clippy::large_enum_variant)]
30pub enum Geometry {
31 Sketch(Sketch),
32 Solid(Solid),
33}
34
35impl Geometry {
36 pub fn id(&self) -> uuid::Uuid {
37 match self {
38 Geometry::Sketch(s) => s.id,
39 Geometry::Solid(e) => e.id,
40 }
41 }
42
43 pub fn original_id(&self) -> uuid::Uuid {
47 match self {
48 Geometry::Sketch(s) => s.original_id,
49 Geometry::Solid(e) => e.sketch.original_id,
50 }
51 }
52}
53
54#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
56#[ts(export)]
57#[serde(tag = "type")]
58#[allow(clippy::large_enum_variant)]
59pub enum GeometryWithImportedGeometry {
60 Sketch(Sketch),
61 Solid(Solid),
62 ImportedGeometry(Box<ImportedGeometry>),
63}
64
65impl GeometryWithImportedGeometry {
66 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
67 match self {
68 GeometryWithImportedGeometry::Sketch(s) => Ok(s.id),
69 GeometryWithImportedGeometry::Solid(e) => Ok(e.id),
70 GeometryWithImportedGeometry::ImportedGeometry(i) => {
71 let id = i.id(ctx).await?;
72 Ok(id)
73 }
74 }
75 }
76}
77
78#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
80#[ts(export)]
81#[serde(tag = "type")]
82#[allow(clippy::vec_box)]
83pub enum Geometries {
84 Sketches(Vec<Sketch>),
85 Solids(Vec<Solid>),
86}
87
88impl From<Geometry> for Geometries {
89 fn from(value: Geometry) -> Self {
90 match value {
91 Geometry::Sketch(x) => Self::Sketches(vec![x]),
92 Geometry::Solid(x) => Self::Solids(vec![x]),
93 }
94 }
95}
96
97#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
99#[ts(export)]
100#[serde(rename_all = "camelCase")]
101pub struct ImportedGeometry {
102 pub id: uuid::Uuid,
104 pub value: Vec<String>,
106 #[serde(skip)]
107 pub meta: Vec<Metadata>,
108 #[serde(skip)]
110 completed: bool,
111}
112
113impl ImportedGeometry {
114 pub fn new(id: uuid::Uuid, value: Vec<String>, meta: Vec<Metadata>) -> Self {
115 Self {
116 id,
117 value,
118 meta,
119 completed: false,
120 }
121 }
122
123 async fn wait_for_finish(&mut self, ctx: &ExecutorContext) -> Result<(), KclError> {
124 if self.completed {
125 return Ok(());
126 }
127
128 ctx.engine
129 .ensure_async_command_completed(self.id, self.meta.first().map(|m| m.source_range))
130 .await?;
131
132 self.completed = true;
133
134 Ok(())
135 }
136
137 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
138 if !self.completed {
139 self.wait_for_finish(ctx).await?;
140 }
141
142 Ok(self.id)
143 }
144}
145
146#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
148#[ts(export)]
149#[serde(tag = "type", rename_all = "camelCase")]
150#[allow(clippy::vec_box)]
151pub enum SolidOrSketchOrImportedGeometry {
152 ImportedGeometry(Box<ImportedGeometry>),
153 SolidSet(Vec<Solid>),
154 SketchSet(Vec<Sketch>),
155}
156
157impl From<SolidOrSketchOrImportedGeometry> for crate::execution::KclValue {
158 fn from(value: SolidOrSketchOrImportedGeometry) -> Self {
159 match value {
160 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
161 SolidOrSketchOrImportedGeometry::SolidSet(mut s) => {
162 if s.len() == 1 {
163 crate::execution::KclValue::Solid {
164 value: Box::new(s.pop().unwrap()),
165 }
166 } else {
167 crate::execution::KclValue::HomArray {
168 value: s
169 .into_iter()
170 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
171 .collect(),
172 ty: crate::execution::types::RuntimeType::solid(),
173 }
174 }
175 }
176 SolidOrSketchOrImportedGeometry::SketchSet(mut s) => {
177 if s.len() == 1 {
178 crate::execution::KclValue::Sketch {
179 value: Box::new(s.pop().unwrap()),
180 }
181 } else {
182 crate::execution::KclValue::HomArray {
183 value: s
184 .into_iter()
185 .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) })
186 .collect(),
187 ty: crate::execution::types::RuntimeType::sketch(),
188 }
189 }
190 }
191 }
192 }
193}
194
195impl SolidOrSketchOrImportedGeometry {
196 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
197 match self {
198 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => {
199 let id = s.id(ctx).await?;
200
201 Ok(vec![id])
202 }
203 SolidOrSketchOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
204 SolidOrSketchOrImportedGeometry::SketchSet(s) => Ok(s.iter().map(|s| s.id).collect()),
205 }
206 }
207}
208
209#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
211#[ts(export)]
212#[serde(tag = "type", rename_all = "camelCase")]
213#[allow(clippy::vec_box)]
214pub enum SolidOrImportedGeometry {
215 ImportedGeometry(Box<ImportedGeometry>),
216 SolidSet(Vec<Solid>),
217}
218
219impl From<SolidOrImportedGeometry> for crate::execution::KclValue {
220 fn from(value: SolidOrImportedGeometry) -> Self {
221 match value {
222 SolidOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
223 SolidOrImportedGeometry::SolidSet(mut s) => {
224 if s.len() == 1 {
225 crate::execution::KclValue::Solid {
226 value: Box::new(s.pop().unwrap()),
227 }
228 } else {
229 crate::execution::KclValue::HomArray {
230 value: s
231 .into_iter()
232 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
233 .collect(),
234 ty: crate::execution::types::RuntimeType::solid(),
235 }
236 }
237 }
238 }
239 }
240}
241
242impl SolidOrImportedGeometry {
243 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
244 match self {
245 SolidOrImportedGeometry::ImportedGeometry(s) => {
246 let id = s.id(ctx).await?;
247
248 Ok(vec![id])
249 }
250 SolidOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
251 }
252 }
253}
254
255#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
257#[ts(export)]
258#[serde(rename_all = "camelCase")]
259pub struct Helix {
260 pub value: uuid::Uuid,
262 pub artifact_id: ArtifactId,
264 pub revolutions: f64,
266 pub angle_start: f64,
268 pub ccw: bool,
270 pub cylinder_id: Option<uuid::Uuid>,
272 pub units: UnitLength,
273 #[serde(skip)]
274 pub meta: Vec<Metadata>,
275}
276
277#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
278#[ts(export)]
279#[serde(rename_all = "camelCase")]
280pub struct Plane {
281 pub id: uuid::Uuid,
283 pub artifact_id: ArtifactId,
285 pub value: PlaneType,
287 #[serde(flatten)]
289 pub info: PlaneInfo,
290 #[serde(skip)]
291 pub meta: Vec<Metadata>,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS)]
295#[ts(export)]
296#[serde(rename_all = "camelCase")]
297pub struct PlaneInfo {
298 pub origin: Point3d,
300 pub x_axis: Point3d,
302 pub y_axis: Point3d,
304 pub z_axis: Point3d,
306}
307
308impl PlaneInfo {
309 pub(crate) fn into_plane_data(self) -> PlaneData {
310 if self.origin.is_zero() {
311 match self {
312 Self {
313 origin:
314 Point3d {
315 x: 0.0,
316 y: 0.0,
317 z: 0.0,
318 units: Some(UnitLength::Millimeters),
319 },
320 x_axis:
321 Point3d {
322 x: 1.0,
323 y: 0.0,
324 z: 0.0,
325 units: _,
326 },
327 y_axis:
328 Point3d {
329 x: 0.0,
330 y: 1.0,
331 z: 0.0,
332 units: _,
333 },
334 z_axis: _,
335 } => return PlaneData::XY,
336 Self {
337 origin:
338 Point3d {
339 x: 0.0,
340 y: 0.0,
341 z: 0.0,
342 units: Some(UnitLength::Millimeters),
343 },
344 x_axis:
345 Point3d {
346 x: -1.0,
347 y: 0.0,
348 z: 0.0,
349 units: _,
350 },
351 y_axis:
352 Point3d {
353 x: 0.0,
354 y: 1.0,
355 z: 0.0,
356 units: _,
357 },
358 z_axis: _,
359 } => return PlaneData::NegXY,
360 Self {
361 origin:
362 Point3d {
363 x: 0.0,
364 y: 0.0,
365 z: 0.0,
366 units: Some(UnitLength::Millimeters),
367 },
368 x_axis:
369 Point3d {
370 x: 1.0,
371 y: 0.0,
372 z: 0.0,
373 units: _,
374 },
375 y_axis:
376 Point3d {
377 x: 0.0,
378 y: 0.0,
379 z: 1.0,
380 units: _,
381 },
382 z_axis: _,
383 } => return PlaneData::XZ,
384 Self {
385 origin:
386 Point3d {
387 x: 0.0,
388 y: 0.0,
389 z: 0.0,
390 units: Some(UnitLength::Millimeters),
391 },
392 x_axis:
393 Point3d {
394 x: -1.0,
395 y: 0.0,
396 z: 0.0,
397 units: _,
398 },
399 y_axis:
400 Point3d {
401 x: 0.0,
402 y: 0.0,
403 z: 1.0,
404 units: _,
405 },
406 z_axis: _,
407 } => return PlaneData::NegXZ,
408 Self {
409 origin:
410 Point3d {
411 x: 0.0,
412 y: 0.0,
413 z: 0.0,
414 units: Some(UnitLength::Millimeters),
415 },
416 x_axis:
417 Point3d {
418 x: 0.0,
419 y: 1.0,
420 z: 0.0,
421 units: _,
422 },
423 y_axis:
424 Point3d {
425 x: 0.0,
426 y: 0.0,
427 z: 1.0,
428 units: _,
429 },
430 z_axis: _,
431 } => return PlaneData::YZ,
432 Self {
433 origin:
434 Point3d {
435 x: 0.0,
436 y: 0.0,
437 z: 0.0,
438 units: Some(UnitLength::Millimeters),
439 },
440 x_axis:
441 Point3d {
442 x: 0.0,
443 y: -1.0,
444 z: 0.0,
445 units: _,
446 },
447 y_axis:
448 Point3d {
449 x: 0.0,
450 y: 0.0,
451 z: 1.0,
452 units: _,
453 },
454 z_axis: _,
455 } => return PlaneData::NegYZ,
456 _ => {}
457 }
458 }
459
460 PlaneData::Plane(Self {
461 origin: self.origin,
462 x_axis: self.x_axis,
463 y_axis: self.y_axis,
464 z_axis: self.z_axis,
465 })
466 }
467
468 pub(crate) fn is_right_handed(&self) -> bool {
469 let lhs = self
472 .x_axis
473 .axes_cross_product(&self.y_axis)
474 .axes_dot_product(&self.z_axis);
475 let rhs_x = self.x_axis.axes_dot_product(&self.x_axis);
476 let rhs_y = self.y_axis.axes_dot_product(&self.y_axis);
477 let rhs_z = self.z_axis.axes_dot_product(&self.z_axis);
478 let rhs = (rhs_x * rhs_y * rhs_z).sqrt();
479 (lhs - rhs).abs() <= 0.0001
481 }
482
483 #[cfg(test)]
484 pub(crate) fn is_left_handed(&self) -> bool {
485 !self.is_right_handed()
486 }
487
488 pub(crate) fn make_right_handed(self) -> Self {
489 if self.is_right_handed() {
490 return self;
491 }
492 Self {
494 origin: self.origin,
495 x_axis: self.x_axis.negated(),
496 y_axis: self.y_axis,
497 z_axis: self.z_axis,
498 }
499 }
500}
501
502impl TryFrom<PlaneData> for PlaneInfo {
503 type Error = KclError;
504
505 fn try_from(value: PlaneData) -> Result<Self, Self::Error> {
506 if let PlaneData::Plane(info) = value {
507 return Ok(info);
508 }
509 let name = match value {
510 PlaneData::XY => PlaneName::Xy,
511 PlaneData::NegXY => PlaneName::NegXy,
512 PlaneData::XZ => PlaneName::Xz,
513 PlaneData::NegXZ => PlaneName::NegXz,
514 PlaneData::YZ => PlaneName::Yz,
515 PlaneData::NegYZ => PlaneName::NegYz,
516 PlaneData::Plane(_) => {
517 return Err(KclError::new_internal(KclErrorDetails::new(
519 format!("PlaneData {value:?} not found"),
520 Default::default(),
521 )));
522 }
523 };
524
525 let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
526 KclError::new_internal(KclErrorDetails::new(
527 format!("Plane {name} not found"),
528 Default::default(),
529 ))
530 })?;
531
532 Ok(info.clone())
533 }
534}
535
536impl From<PlaneData> for PlaneType {
537 fn from(value: PlaneData) -> Self {
538 match value {
539 PlaneData::XY => PlaneType::XY,
540 PlaneData::NegXY => PlaneType::XY,
541 PlaneData::XZ => PlaneType::XZ,
542 PlaneData::NegXZ => PlaneType::XZ,
543 PlaneData::YZ => PlaneType::YZ,
544 PlaneData::NegYZ => PlaneType::YZ,
545 PlaneData::Plane(_) => PlaneType::Custom,
546 }
547 }
548}
549
550impl Plane {
551 pub(crate) fn from_plane_data(value: PlaneData, exec_state: &mut ExecState) -> Result<Self, KclError> {
552 let id = exec_state.next_uuid();
553 Ok(Plane {
554 id,
555 artifact_id: id.into(),
556 info: PlaneInfo::try_from(value.clone())?,
557 value: value.into(),
558 meta: vec![],
559 })
560 }
561
562 pub fn is_standard(&self) -> bool {
564 !matches!(self.value, PlaneType::Custom | PlaneType::Uninit)
565 }
566
567 pub fn project(&self, point: Point3d) -> Point3d {
570 let v = point - self.info.origin;
571 let dot = v.axes_dot_product(&self.info.z_axis);
572
573 point - self.info.z_axis * dot
574 }
575}
576
577#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
579#[ts(export)]
580#[serde(rename_all = "camelCase")]
581pub struct Face {
582 pub id: uuid::Uuid,
584 pub artifact_id: ArtifactId,
586 pub value: String,
588 pub x_axis: Point3d,
590 pub y_axis: Point3d,
592 pub solid: Box<Solid>,
594 pub units: UnitLength,
595 #[serde(skip)]
596 pub meta: Vec<Metadata>,
597}
598
599#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, FromStr, Display)]
601#[ts(export)]
602#[display(style = "camelCase")]
603pub enum PlaneType {
604 #[serde(rename = "XY", alias = "xy")]
605 #[display("XY")]
606 XY,
607 #[serde(rename = "XZ", alias = "xz")]
608 #[display("XZ")]
609 XZ,
610 #[serde(rename = "YZ", alias = "yz")]
611 #[display("YZ")]
612 YZ,
613 #[display("Custom")]
615 Custom,
616 #[display("Uninit")]
618 Uninit,
619}
620
621#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
622#[ts(export)]
623#[serde(tag = "type", rename_all = "camelCase")]
624pub struct Sketch {
625 pub id: uuid::Uuid,
627 pub paths: Vec<Path>,
631 #[serde(default, skip_serializing_if = "Vec::is_empty")]
633 pub inner_paths: Vec<Path>,
634 pub on: SketchSurface,
636 pub start: BasePath,
638 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
640 pub tags: IndexMap<String, TagIdentifier>,
641 pub artifact_id: ArtifactId,
644 #[ts(skip)]
645 pub original_id: uuid::Uuid,
646 #[serde(skip)]
648 pub mirror: Option<uuid::Uuid>,
649 pub units: UnitLength,
650 #[serde(skip)]
652 pub meta: Vec<Metadata>,
653 #[serde(default = "very_true", skip_serializing_if = "is_true")]
655 pub is_closed: bool,
656}
657
658fn is_true(b: &bool) -> bool {
659 *b
660}
661
662impl Sketch {
663 pub(crate) fn build_sketch_mode_cmds(
666 &self,
667 exec_state: &mut ExecState,
668 inner_cmd: ModelingCmdReq,
669 ) -> Vec<ModelingCmdReq> {
670 vec![
671 ModelingCmdReq {
674 cmd: ModelingCmd::from(mcmd::EnableSketchMode {
675 animated: false,
676 ortho: false,
677 entity_id: self.on.id(),
678 adjust_camera: false,
679 planar_normal: if let SketchSurface::Plane(plane) = &self.on {
680 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
682 Some(normal.into())
683 } else {
684 None
685 },
686 }),
687 cmd_id: exec_state.next_uuid().into(),
688 },
689 inner_cmd,
690 ModelingCmdReq {
691 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
692 cmd_id: exec_state.next_uuid().into(),
693 },
694 ]
695 }
696}
697
698#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
700#[ts(export)]
701#[serde(tag = "type", rename_all = "camelCase")]
702pub enum SketchSurface {
703 Plane(Box<Plane>),
704 Face(Box<Face>),
705}
706
707impl SketchSurface {
708 pub(crate) fn id(&self) -> uuid::Uuid {
709 match self {
710 SketchSurface::Plane(plane) => plane.id,
711 SketchSurface::Face(face) => face.id,
712 }
713 }
714 pub(crate) fn x_axis(&self) -> Point3d {
715 match self {
716 SketchSurface::Plane(plane) => plane.info.x_axis,
717 SketchSurface::Face(face) => face.x_axis,
718 }
719 }
720 pub(crate) fn y_axis(&self) -> Point3d {
721 match self {
722 SketchSurface::Plane(plane) => plane.info.y_axis,
723 SketchSurface::Face(face) => face.y_axis,
724 }
725 }
726}
727
728#[derive(Debug, Clone)]
729pub(crate) enum GetTangentialInfoFromPathsResult {
730 PreviousPoint([f64; 2]),
731 Arc {
732 center: [f64; 2],
733 ccw: bool,
734 },
735 Circle {
736 center: [f64; 2],
737 ccw: bool,
738 radius: f64,
739 },
740 Ellipse {
741 center: [f64; 2],
742 ccw: bool,
743 major_axis: [f64; 2],
744 _minor_radius: f64,
745 },
746}
747
748impl GetTangentialInfoFromPathsResult {
749 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
750 match self {
751 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
752 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
753 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
754 }
755 GetTangentialInfoFromPathsResult::Circle {
758 center, radius, ccw, ..
759 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
760 GetTangentialInfoFromPathsResult::Ellipse {
761 center,
762 major_axis,
763 ccw,
764 ..
765 } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
766 }
767 }
768}
769
770impl Sketch {
771 pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path, exec_state: &ExecState) {
772 let mut tag_identifier: TagIdentifier = tag.into();
773 let base = current_path.get_base();
774 tag_identifier.info.push((
775 exec_state.stack().current_epoch(),
776 TagEngineInfo {
777 id: base.geo_meta.id,
778 sketch: self.id,
779 path: Some(current_path.clone()),
780 surface: None,
781 },
782 ));
783
784 self.tags.insert(tag.name.to_string(), tag_identifier);
785 }
786
787 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
788 for t in tags {
789 match self.tags.get_mut(&t.value) {
790 Some(id) => {
791 id.merge_info(t);
792 }
793 None => {
794 self.tags.insert(t.value.clone(), t.clone());
795 }
796 }
797 }
798 }
799
800 pub(crate) fn latest_path(&self) -> Option<&Path> {
802 self.paths.last()
803 }
804
805 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
809 let Some(path) = self.latest_path() else {
810 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
811 };
812
813 let to = path.get_base().to;
814 Ok(Point2d::new(to[0], to[1], path.get_base().units))
815 }
816
817 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
818 let Some(path) = self.latest_path() else {
819 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
820 };
821 path.get_tangential_info()
822 }
823}
824
825#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
826#[ts(export)]
827#[serde(tag = "type", rename_all = "camelCase")]
828pub struct Solid {
829 pub id: uuid::Uuid,
831 pub artifact_id: ArtifactId,
833 pub value: Vec<ExtrudeSurface>,
835 pub sketch: Sketch,
837 pub start_cap_id: Option<uuid::Uuid>,
839 pub end_cap_id: Option<uuid::Uuid>,
841 #[serde(default, skip_serializing_if = "Vec::is_empty")]
843 pub edge_cuts: Vec<EdgeCut>,
844 pub units: UnitLength,
846 pub sectional: bool,
848 #[serde(skip)]
850 pub meta: Vec<Metadata>,
851}
852
853impl Solid {
854 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
855 self.edge_cuts.iter().map(|foc| foc.id())
856 }
857}
858
859#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
861#[ts(export)]
862#[serde(tag = "type", rename_all = "camelCase")]
863pub enum EdgeCut {
864 Fillet {
866 id: uuid::Uuid,
868 radius: TyF64,
869 #[serde(rename = "edgeId")]
871 edge_id: uuid::Uuid,
872 tag: Box<Option<TagNode>>,
873 },
874 Chamfer {
876 id: uuid::Uuid,
878 length: TyF64,
879 #[serde(rename = "edgeId")]
881 edge_id: uuid::Uuid,
882 tag: Box<Option<TagNode>>,
883 },
884}
885
886impl EdgeCut {
887 pub fn id(&self) -> uuid::Uuid {
888 match self {
889 EdgeCut::Fillet { id, .. } => *id,
890 EdgeCut::Chamfer { id, .. } => *id,
891 }
892 }
893
894 pub fn set_id(&mut self, id: uuid::Uuid) {
895 match self {
896 EdgeCut::Fillet { id: i, .. } => *i = id,
897 EdgeCut::Chamfer { id: i, .. } => *i = id,
898 }
899 }
900
901 pub fn edge_id(&self) -> uuid::Uuid {
902 match self {
903 EdgeCut::Fillet { edge_id, .. } => *edge_id,
904 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
905 }
906 }
907
908 pub fn set_edge_id(&mut self, id: uuid::Uuid) {
909 match self {
910 EdgeCut::Fillet { edge_id: i, .. } => *i = id,
911 EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
912 }
913 }
914
915 pub fn tag(&self) -> Option<TagNode> {
916 match self {
917 EdgeCut::Fillet { tag, .. } => *tag.clone(),
918 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
919 }
920 }
921}
922
923#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
924#[ts(export)]
925pub struct Point2d {
926 pub x: f64,
927 pub y: f64,
928 pub units: UnitLength,
929}
930
931impl Point2d {
932 pub const ZERO: Self = Self {
933 x: 0.0,
934 y: 0.0,
935 units: UnitLength::Millimeters,
936 };
937
938 pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
939 Self { x, y, units }
940 }
941
942 pub fn into_x(self) -> TyF64 {
943 TyF64::new(self.x, self.units.into())
944 }
945
946 pub fn into_y(self) -> TyF64 {
947 TyF64::new(self.y, self.units.into())
948 }
949
950 pub fn ignore_units(self) -> [f64; 2] {
951 [self.x, self.y]
952 }
953}
954
955#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
956#[ts(export)]
957pub struct Point3d {
958 pub x: f64,
959 pub y: f64,
960 pub z: f64,
961 pub units: Option<UnitLength>,
962}
963
964impl Point3d {
965 pub const ZERO: Self = Self {
966 x: 0.0,
967 y: 0.0,
968 z: 0.0,
969 units: Some(UnitLength::Millimeters),
970 };
971
972 pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
973 Self { x, y, z, units }
974 }
975
976 pub const fn is_zero(&self) -> bool {
977 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
978 }
979
980 pub fn axes_cross_product(&self, other: &Self) -> Self {
985 Self {
986 x: self.y * other.z - self.z * other.y,
987 y: self.z * other.x - self.x * other.z,
988 z: self.x * other.y - self.y * other.x,
989 units: None,
990 }
991 }
992
993 pub fn axes_dot_product(&self, other: &Self) -> f64 {
998 let x = self.x * other.x;
999 let y = self.y * other.y;
1000 let z = self.z * other.z;
1001 x + y + z
1002 }
1003
1004 pub fn normalize(&self) -> Self {
1005 let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1006 Point3d {
1007 x: self.x / len,
1008 y: self.y / len,
1009 z: self.z / len,
1010 units: None,
1011 }
1012 }
1013
1014 pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1015 let p = [self.x, self.y, self.z];
1016 let u = self.units;
1017 (p, u)
1018 }
1019
1020 pub(crate) fn negated(self) -> Self {
1021 Self {
1022 x: -self.x,
1023 y: -self.y,
1024 z: -self.z,
1025 units: self.units,
1026 }
1027 }
1028}
1029
1030impl From<[TyF64; 3]> for Point3d {
1031 fn from(p: [TyF64; 3]) -> Self {
1032 Self {
1033 x: p[0].n,
1034 y: p[1].n,
1035 z: p[2].n,
1036 units: p[0].ty.as_length(),
1037 }
1038 }
1039}
1040
1041impl From<Point3d> for Point3D {
1042 fn from(p: Point3d) -> Self {
1043 Self { x: p.x, y: p.y, z: p.z }
1044 }
1045}
1046
1047impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1048 fn from(p: Point3d) -> Self {
1049 if let Some(units) = p.units {
1050 Self {
1051 x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1052 y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1053 z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1054 }
1055 } else {
1056 Self {
1057 x: LengthUnit(p.x),
1058 y: LengthUnit(p.y),
1059 z: LengthUnit(p.z),
1060 }
1061 }
1062 }
1063}
1064
1065impl Add for Point3d {
1066 type Output = Point3d;
1067
1068 fn add(self, rhs: Self) -> Self::Output {
1069 Point3d {
1071 x: self.x + rhs.x,
1072 y: self.y + rhs.y,
1073 z: self.z + rhs.z,
1074 units: self.units,
1075 }
1076 }
1077}
1078
1079impl AddAssign for Point3d {
1080 fn add_assign(&mut self, rhs: Self) {
1081 *self = *self + rhs
1082 }
1083}
1084
1085impl Sub for Point3d {
1086 type Output = Point3d;
1087
1088 fn sub(self, rhs: Self) -> Self::Output {
1089 let (x, y, z) = if rhs.units != self.units
1090 && let Some(sunits) = self.units
1091 && let Some(runits) = rhs.units
1092 {
1093 (
1094 adjust_length(runits, rhs.x, sunits).0,
1095 adjust_length(runits, rhs.y, sunits).0,
1096 adjust_length(runits, rhs.z, sunits).0,
1097 )
1098 } else {
1099 (rhs.x, rhs.y, rhs.z)
1100 };
1101 Point3d {
1102 x: self.x - x,
1103 y: self.y - y,
1104 z: self.z - z,
1105 units: self.units,
1106 }
1107 }
1108}
1109
1110impl SubAssign for Point3d {
1111 fn sub_assign(&mut self, rhs: Self) {
1112 *self = *self - rhs
1113 }
1114}
1115
1116impl Mul<f64> for Point3d {
1117 type Output = Point3d;
1118
1119 fn mul(self, rhs: f64) -> Self::Output {
1120 Point3d {
1121 x: self.x * rhs,
1122 y: self.y * rhs,
1123 z: self.z * rhs,
1124 units: self.units,
1125 }
1126 }
1127}
1128
1129#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1131#[ts(export)]
1132#[serde(rename_all = "camelCase")]
1133pub struct BasePath {
1134 #[ts(type = "[number, number]")]
1136 pub from: [f64; 2],
1137 #[ts(type = "[number, number]")]
1139 pub to: [f64; 2],
1140 pub units: UnitLength,
1141 pub tag: Option<TagNode>,
1143 #[serde(rename = "__geoMeta")]
1145 pub geo_meta: GeoMeta,
1146}
1147
1148impl BasePath {
1149 pub fn get_to(&self) -> [TyF64; 2] {
1150 let ty: NumericType = self.units.into();
1151 [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1152 }
1153
1154 pub fn get_from(&self) -> [TyF64; 2] {
1155 let ty: NumericType = self.units.into();
1156 [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1157 }
1158}
1159
1160#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1162#[ts(export)]
1163#[serde(rename_all = "camelCase")]
1164pub struct GeoMeta {
1165 pub id: uuid::Uuid,
1167 #[serde(flatten)]
1169 pub metadata: Metadata,
1170}
1171
1172#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1174#[ts(export)]
1175#[serde(tag = "type")]
1176pub enum Path {
1177 ToPoint {
1179 #[serde(flatten)]
1180 base: BasePath,
1181 },
1182 TangentialArcTo {
1184 #[serde(flatten)]
1185 base: BasePath,
1186 #[ts(type = "[number, number]")]
1188 center: [f64; 2],
1189 ccw: bool,
1191 },
1192 TangentialArc {
1194 #[serde(flatten)]
1195 base: BasePath,
1196 #[ts(type = "[number, number]")]
1198 center: [f64; 2],
1199 ccw: bool,
1201 },
1202 Circle {
1205 #[serde(flatten)]
1206 base: BasePath,
1207 #[ts(type = "[number, number]")]
1209 center: [f64; 2],
1210 radius: f64,
1212 ccw: bool,
1215 },
1216 CircleThreePoint {
1217 #[serde(flatten)]
1218 base: BasePath,
1219 #[ts(type = "[number, number]")]
1221 p1: [f64; 2],
1222 #[ts(type = "[number, number]")]
1224 p2: [f64; 2],
1225 #[ts(type = "[number, number]")]
1227 p3: [f64; 2],
1228 },
1229 ArcThreePoint {
1230 #[serde(flatten)]
1231 base: BasePath,
1232 #[ts(type = "[number, number]")]
1234 p1: [f64; 2],
1235 #[ts(type = "[number, number]")]
1237 p2: [f64; 2],
1238 #[ts(type = "[number, number]")]
1240 p3: [f64; 2],
1241 },
1242 Horizontal {
1244 #[serde(flatten)]
1245 base: BasePath,
1246 x: f64,
1248 },
1249 AngledLineTo {
1251 #[serde(flatten)]
1252 base: BasePath,
1253 x: Option<f64>,
1255 y: Option<f64>,
1257 },
1258 Base {
1260 #[serde(flatten)]
1261 base: BasePath,
1262 },
1263 Arc {
1265 #[serde(flatten)]
1266 base: BasePath,
1267 center: [f64; 2],
1269 radius: f64,
1271 ccw: bool,
1273 },
1274 Ellipse {
1275 #[serde(flatten)]
1276 base: BasePath,
1277 center: [f64; 2],
1278 major_axis: [f64; 2],
1279 minor_radius: f64,
1280 ccw: bool,
1281 },
1282 Conic {
1284 #[serde(flatten)]
1285 base: BasePath,
1286 },
1287}
1288
1289impl Path {
1290 pub fn get_id(&self) -> uuid::Uuid {
1291 match self {
1292 Path::ToPoint { base } => base.geo_meta.id,
1293 Path::Horizontal { base, .. } => base.geo_meta.id,
1294 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1295 Path::Base { base } => base.geo_meta.id,
1296 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1297 Path::TangentialArc { base, .. } => base.geo_meta.id,
1298 Path::Circle { base, .. } => base.geo_meta.id,
1299 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1300 Path::Arc { base, .. } => base.geo_meta.id,
1301 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1302 Path::Ellipse { base, .. } => base.geo_meta.id,
1303 Path::Conic { base, .. } => base.geo_meta.id,
1304 }
1305 }
1306
1307 pub fn set_id(&mut self, id: uuid::Uuid) {
1308 match self {
1309 Path::ToPoint { base } => base.geo_meta.id = id,
1310 Path::Horizontal { base, .. } => base.geo_meta.id = id,
1311 Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1312 Path::Base { base } => base.geo_meta.id = id,
1313 Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1314 Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1315 Path::Circle { base, .. } => base.geo_meta.id = id,
1316 Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1317 Path::Arc { base, .. } => base.geo_meta.id = id,
1318 Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1319 Path::Ellipse { base, .. } => base.geo_meta.id = id,
1320 Path::Conic { base, .. } => base.geo_meta.id = id,
1321 }
1322 }
1323
1324 pub fn get_tag(&self) -> Option<TagNode> {
1325 match self {
1326 Path::ToPoint { base } => base.tag.clone(),
1327 Path::Horizontal { base, .. } => base.tag.clone(),
1328 Path::AngledLineTo { base, .. } => base.tag.clone(),
1329 Path::Base { base } => base.tag.clone(),
1330 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1331 Path::TangentialArc { base, .. } => base.tag.clone(),
1332 Path::Circle { base, .. } => base.tag.clone(),
1333 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1334 Path::Arc { base, .. } => base.tag.clone(),
1335 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1336 Path::Ellipse { base, .. } => base.tag.clone(),
1337 Path::Conic { base, .. } => base.tag.clone(),
1338 }
1339 }
1340
1341 pub fn get_base(&self) -> &BasePath {
1342 match self {
1343 Path::ToPoint { base } => base,
1344 Path::Horizontal { base, .. } => base,
1345 Path::AngledLineTo { base, .. } => base,
1346 Path::Base { base } => base,
1347 Path::TangentialArcTo { base, .. } => base,
1348 Path::TangentialArc { base, .. } => base,
1349 Path::Circle { base, .. } => base,
1350 Path::CircleThreePoint { base, .. } => base,
1351 Path::Arc { base, .. } => base,
1352 Path::ArcThreePoint { base, .. } => base,
1353 Path::Ellipse { base, .. } => base,
1354 Path::Conic { base, .. } => base,
1355 }
1356 }
1357
1358 pub fn get_from(&self) -> [TyF64; 2] {
1360 let p = &self.get_base().from;
1361 let ty: NumericType = self.get_base().units.into();
1362 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1363 }
1364
1365 pub fn get_to(&self) -> [TyF64; 2] {
1367 let p = &self.get_base().to;
1368 let ty: NumericType = self.get_base().units.into();
1369 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1370 }
1371
1372 pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1374 let p = &self.get_base().from;
1375 let ty: NumericType = self.get_base().units.into();
1376 (*p, ty)
1377 }
1378
1379 pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1381 let p = &self.get_base().to;
1382 let ty: NumericType = self.get_base().units.into();
1383 (*p, ty)
1384 }
1385
1386 pub fn length(&self) -> Option<TyF64> {
1389 let n = match self {
1390 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1391 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1392 }
1393 Self::TangentialArc {
1394 base: _,
1395 center,
1396 ccw: _,
1397 }
1398 | Self::TangentialArcTo {
1399 base: _,
1400 center,
1401 ccw: _,
1402 } => {
1403 let radius = linear_distance(&self.get_base().from, center);
1406 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
1407 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1409 }
1410 Self::Circle { radius, .. } => Some(2.0 * std::f64::consts::PI * radius),
1411 Self::CircleThreePoint { .. } => {
1412 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1413 self.get_base().from,
1414 self.get_base().to,
1415 self.get_base().to,
1416 ]);
1417 let radius = linear_distance(
1418 &[circle_center.center[0], circle_center.center[1]],
1419 &self.get_base().from,
1420 );
1421 Some(2.0 * std::f64::consts::PI * radius)
1422 }
1423 Self::Arc { .. } => {
1424 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1426 }
1427 Self::ArcThreePoint { .. } => {
1428 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1430 }
1431 Self::Ellipse { .. } => {
1432 None
1434 }
1435 Self::Conic { .. } => {
1436 None
1438 }
1439 };
1440 n.map(|n| TyF64::new(n, self.get_base().units.into()))
1441 }
1442
1443 pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
1444 match self {
1445 Path::ToPoint { base } => Some(base),
1446 Path::Horizontal { base, .. } => Some(base),
1447 Path::AngledLineTo { base, .. } => Some(base),
1448 Path::Base { base } => Some(base),
1449 Path::TangentialArcTo { base, .. } => Some(base),
1450 Path::TangentialArc { base, .. } => Some(base),
1451 Path::Circle { base, .. } => Some(base),
1452 Path::CircleThreePoint { base, .. } => Some(base),
1453 Path::Arc { base, .. } => Some(base),
1454 Path::ArcThreePoint { base, .. } => Some(base),
1455 Path::Ellipse { base, .. } => Some(base),
1456 Path::Conic { base, .. } => Some(base),
1457 }
1458 }
1459
1460 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1461 match self {
1462 Path::TangentialArc { center, ccw, .. }
1463 | Path::TangentialArcTo { center, ccw, .. }
1464 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1465 center: *center,
1466 ccw: *ccw,
1467 },
1468 Path::ArcThreePoint { p1, p2, p3, .. } => {
1469 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1470 GetTangentialInfoFromPathsResult::Arc {
1471 center: circle.center,
1472 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
1473 }
1474 }
1475 Path::Circle {
1476 center, ccw, radius, ..
1477 } => GetTangentialInfoFromPathsResult::Circle {
1478 center: *center,
1479 ccw: *ccw,
1480 radius: *radius,
1481 },
1482 Path::CircleThreePoint { p1, p2, p3, .. } => {
1483 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1484 let center_point = [circle.center[0], circle.center[1]];
1485 GetTangentialInfoFromPathsResult::Circle {
1486 center: center_point,
1487 ccw: true,
1489 radius: circle.radius,
1490 }
1491 }
1492 Path::Ellipse {
1494 center,
1495 major_axis,
1496 minor_radius,
1497 ccw,
1498 ..
1499 } => GetTangentialInfoFromPathsResult::Ellipse {
1500 center: *center,
1501 major_axis: *major_axis,
1502 _minor_radius: *minor_radius,
1503 ccw: *ccw,
1504 },
1505 Path::Conic { .. }
1506 | Path::ToPoint { .. }
1507 | Path::Horizontal { .. }
1508 | Path::AngledLineTo { .. }
1509 | Path::Base { .. } => {
1510 let base = self.get_base();
1511 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
1512 }
1513 }
1514 }
1515
1516 pub(crate) fn is_straight_line(&self) -> bool {
1518 matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
1519 }
1520}
1521
1522#[rustfmt::skip]
1524fn linear_distance(
1525 [x0, y0]: &[f64; 2],
1526 [x1, y1]: &[f64; 2]
1527) -> f64 {
1528 let y_sq = (y1 - y0).powi(2);
1529 let x_sq = (x1 - x0).powi(2);
1530 (y_sq + x_sq).sqrt()
1531}
1532
1533#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1535#[ts(export)]
1536#[serde(tag = "type", rename_all = "camelCase")]
1537pub enum ExtrudeSurface {
1538 ExtrudePlane(ExtrudePlane),
1540 ExtrudeArc(ExtrudeArc),
1541 Chamfer(ChamferSurface),
1542 Fillet(FilletSurface),
1543}
1544
1545#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1547#[ts(export)]
1548#[serde(rename_all = "camelCase")]
1549pub struct ChamferSurface {
1550 pub face_id: uuid::Uuid,
1552 pub tag: Option<Node<TagDeclarator>>,
1554 #[serde(flatten)]
1556 pub geo_meta: GeoMeta,
1557}
1558
1559#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1561#[ts(export)]
1562#[serde(rename_all = "camelCase")]
1563pub struct FilletSurface {
1564 pub face_id: uuid::Uuid,
1566 pub tag: Option<Node<TagDeclarator>>,
1568 #[serde(flatten)]
1570 pub geo_meta: GeoMeta,
1571}
1572
1573#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1575#[ts(export)]
1576#[serde(rename_all = "camelCase")]
1577pub struct ExtrudePlane {
1578 pub face_id: uuid::Uuid,
1580 pub tag: Option<Node<TagDeclarator>>,
1582 #[serde(flatten)]
1584 pub geo_meta: GeoMeta,
1585}
1586
1587#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1589#[ts(export)]
1590#[serde(rename_all = "camelCase")]
1591pub struct ExtrudeArc {
1592 pub face_id: uuid::Uuid,
1594 pub tag: Option<Node<TagDeclarator>>,
1596 #[serde(flatten)]
1598 pub geo_meta: GeoMeta,
1599}
1600
1601impl ExtrudeSurface {
1602 pub fn get_id(&self) -> uuid::Uuid {
1603 match self {
1604 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
1605 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
1606 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
1607 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
1608 }
1609 }
1610
1611 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
1612 match self {
1613 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
1614 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
1615 ExtrudeSurface::Fillet(f) => f.tag.clone(),
1616 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
1617 }
1618 }
1619}