1use std::f64::consts::TAU;
2use std::ops::Add;
3use std::ops::AddAssign;
4use std::ops::Mul;
5use std::ops::Sub;
6use std::ops::SubAssign;
7use std::sync::Arc;
8
9use anyhow::Result;
10use indexmap::IndexMap;
11use kcl_api::UnitLength;
12use kcl_error::SourceRange;
13use kittycad_modeling_cmds::ModelingCmd;
14use kittycad_modeling_cmds::each_cmd as mcmd;
15use kittycad_modeling_cmds::length_unit::LengthUnit;
16use kittycad_modeling_cmds::websocket::ModelingCmdReq;
17use kittycad_modeling_cmds::{self as kcmc};
18use parse_display::Display;
19use parse_display::FromStr;
20use serde::Deserialize;
21use serde::Serialize;
22use uuid::Uuid;
23
24use crate::NodePath;
25use crate::engine::DEFAULT_PLANE_INFO;
26use crate::engine::PlaneName;
27use crate::errors::KclError;
28use crate::errors::KclErrorDetails;
29use crate::exec::KclValue;
30use crate::execution::ArtifactId;
31use crate::execution::ExecState;
32use crate::execution::ExecutorContext;
33use crate::execution::Metadata;
34use crate::execution::TagEngineInfo;
35use crate::execution::TagIdentifier;
36use crate::execution::normalize_to_solver_distance_unit;
37use crate::execution::types::NumericType;
38use crate::execution::types::NumericTypeExt;
39use crate::execution::types::adjust_length;
40use crate::front::ArcCtor;
41use crate::front::CircleCtor;
42use crate::front::ControlPointSplineCtor;
43use crate::front::Freedom;
44use crate::front::LineCtor;
45use crate::front::Number;
46use crate::front::ObjectId;
47use crate::front::Point2d as ApiPoint2d;
48use crate::front::PointCtor;
49use crate::parsing::ast::types::Node;
50use crate::parsing::ast::types::NodeRef;
51use crate::parsing::ast::types::TagDeclarator;
52use crate::parsing::ast::types::TagNode;
53use crate::std::Args;
54use crate::std::args::TyF64;
55use crate::std::edge::UnresolvedEdgeSpecifier;
56use crate::std::sketch::FaceTag;
57use crate::std::sketch::PlaneData;
58use crate::util::MathExt;
59
60type Point3D = kcmc::shared::Point3d<f64>;
61
62#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
64#[ts(export)]
65#[serde(tag = "type", rename_all = "camelCase")]
66pub struct GdtAnnotation {
67 pub id: uuid::Uuid,
69 #[serde(skip)]
70 pub meta: Vec<Metadata>,
71}
72
73#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
75#[ts(export)]
76#[serde(tag = "type")]
77#[allow(clippy::large_enum_variant)]
78pub enum Geometry {
79 Sketch(Sketch),
80 Solid(Solid),
81}
82
83impl Geometry {
84 pub fn id(&self) -> uuid::Uuid {
85 match self {
86 Geometry::Sketch(s) => s.id,
87 Geometry::Solid(e) => e.id,
88 }
89 }
90
91 pub fn original_id(&self) -> uuid::Uuid {
95 match self {
96 Geometry::Sketch(s) => s.original_id,
97 Geometry::Solid(e) => e.original_id(),
98 }
99 }
100}
101
102#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
104#[ts(export)]
105#[serde(tag = "type")]
106#[allow(clippy::large_enum_variant)]
107pub enum GeometryWithImportedGeometry {
108 Sketch(Sketch),
109 Solid(Solid),
110 ImportedGeometry(Box<ImportedGeometry>),
111}
112
113impl GeometryWithImportedGeometry {
114 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
115 match self {
116 GeometryWithImportedGeometry::Sketch(s) => Ok(s.id),
117 GeometryWithImportedGeometry::Solid(e) => Ok(e.id),
118 GeometryWithImportedGeometry::ImportedGeometry(i) => {
119 let id = i.id(ctx).await?;
120 Ok(id)
121 }
122 }
123 }
124
125 pub fn into_solid(self) -> Option<Solid> {
126 match self {
127 GeometryWithImportedGeometry::Sketch(_) => None,
128 GeometryWithImportedGeometry::Solid(solid) => Some(solid),
129 GeometryWithImportedGeometry::ImportedGeometry(_) => None,
130 }
131 }
132}
133
134#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
136#[ts(export)]
137#[serde(tag = "type")]
138#[allow(clippy::vec_box)]
139pub enum Geometries {
140 Sketches(Vec<Sketch>),
141 Solids(Vec<Solid>),
142}
143
144impl From<Geometry> for Geometries {
145 fn from(value: Geometry) -> Self {
146 match value {
147 Geometry::Sketch(x) => Self::Sketches(vec![x]),
148 Geometry::Solid(x) => Self::Solids(vec![x]),
149 }
150 }
151}
152
153#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
155#[ts(export)]
156#[serde(rename_all = "camelCase")]
157pub struct ImportedGeometry {
158 pub id: uuid::Uuid,
160 pub value: Vec<String>,
162 #[serde(skip)]
163 pub meta: Vec<Metadata>,
164 #[serde(skip)]
166 completed: bool,
167}
168
169impl ImportedGeometry {
170 pub fn new(id: uuid::Uuid, value: Vec<String>, meta: Vec<Metadata>) -> Self {
171 Self {
172 id,
173 value,
174 meta,
175 completed: false,
176 }
177 }
178
179 async fn wait_for_finish(&mut self, ctx: &ExecutorContext) -> Result<(), KclError> {
180 if self.completed {
181 return Ok(());
182 }
183
184 ctx.engine
185 .ensure_async_command_completed(self.id, self.meta.first().map(|m| m.source_range))
186 .await?;
187
188 self.completed = true;
189
190 Ok(())
191 }
192
193 pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
194 if !self.completed {
195 self.wait_for_finish(ctx).await?;
196 }
197
198 Ok(self.id)
199 }
200}
201
202#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
204#[ts(export)]
205#[serde(tag = "type", rename_all = "camelCase")]
206#[allow(clippy::vec_box)]
207pub enum HideableGeometry {
208 ImportedGeometry(Box<ImportedGeometry>),
209 SolidSet(Vec<Solid>),
210 PlaneSet(Vec<Plane>),
211 SketchSet(Vec<Sketch>),
212 HelixSet(Vec<Helix>),
213 GdtAnnotationSet(Vec<GdtAnnotation>),
214}
215
216impl From<HideableGeometry> for crate::execution::KclValue {
217 fn from(value: HideableGeometry) -> Self {
218 match value {
219 HideableGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
220 HideableGeometry::PlaneSet(mut s) => {
221 if s.len() == 1
222 && let Some(s) = s.pop()
223 {
224 crate::execution::KclValue::Plane { value: Box::new(s) }
225 } else {
226 crate::execution::KclValue::HomArray {
227 value: s
228 .into_iter()
229 .map(|s| crate::execution::KclValue::Plane { value: Box::new(s) })
230 .collect(),
231 ty: crate::execution::types::RuntimeType::plane(),
232 }
233 }
234 }
235 HideableGeometry::SolidSet(mut s) => {
236 if s.len() == 1
237 && let Some(s) = s.pop()
238 {
239 crate::execution::KclValue::Solid { value: Box::new(s) }
240 } else {
241 crate::execution::KclValue::HomArray {
242 value: s
243 .into_iter()
244 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
245 .collect(),
246 ty: crate::execution::types::RuntimeType::solid(),
247 }
248 }
249 }
250 HideableGeometry::GdtAnnotationSet(mut s) => {
251 if s.len() == 1
252 && let Some(s) = s.pop()
253 {
254 crate::execution::KclValue::GdtAnnotation { value: Box::new(s) }
255 } else {
256 crate::execution::KclValue::HomArray {
257 value: s
258 .into_iter()
259 .map(|s| crate::execution::KclValue::GdtAnnotation { value: Box::new(s) })
260 .collect(),
261 ty: crate::execution::types::RuntimeType::gdt(),
262 }
263 }
264 }
265 HideableGeometry::SketchSet(mut s) => {
266 if s.len() == 1
267 && let Some(s) = s.pop()
268 {
269 crate::execution::KclValue::Sketch { value: Box::new(s) }
270 } else {
271 crate::execution::KclValue::HomArray {
272 value: s
273 .into_iter()
274 .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) })
275 .collect(),
276 ty: crate::execution::types::RuntimeType::sketch(),
277 }
278 }
279 }
280 HideableGeometry::HelixSet(mut s) => {
281 if s.len() == 1
282 && let Some(s) = s.pop()
283 {
284 crate::execution::KclValue::Helix { value: Box::new(s) }
285 } else {
286 crate::execution::KclValue::HomArray {
287 value: s
288 .into_iter()
289 .map(|s| crate::execution::KclValue::Helix { value: Box::new(s) })
290 .collect(),
291 ty: crate::execution::types::RuntimeType::helices(),
292 }
293 }
294 }
295 }
296 }
297}
298
299impl HideableGeometry {
300 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
301 match self {
302 HideableGeometry::ImportedGeometry(s) => {
303 let id = s.id(ctx).await?;
304
305 Ok(vec![id])
306 }
307 HideableGeometry::PlaneSet(s) => Ok(s.iter().map(|s| s.id).collect()),
308 HideableGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
309 HideableGeometry::GdtAnnotationSet(s) => Ok(s.iter().map(|s| s.id).collect()),
310 HideableGeometry::SketchSet(s) => Ok(s.iter().map(|s| s.id).collect()),
311 HideableGeometry::HelixSet(s) => Ok(s.iter().map(|s| s.value).collect()),
312 }
313 }
314}
315
316#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
318#[ts(export)]
319#[serde(tag = "type", rename_all = "camelCase")]
320#[allow(clippy::vec_box)]
321pub enum SolidOrSketchOrImportedGeometry {
322 ImportedGeometry(Box<ImportedGeometry>),
323 SolidSet(Vec<Solid>),
324 SketchSet(Vec<Sketch>),
325}
326
327impl From<SolidOrSketchOrImportedGeometry> for crate::execution::KclValue {
328 fn from(value: SolidOrSketchOrImportedGeometry) -> Self {
329 match value {
330 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
331 SolidOrSketchOrImportedGeometry::SolidSet(mut s) => {
332 if s.len() == 1
333 && let Some(s) = s.pop()
334 {
335 crate::execution::KclValue::Solid { value: Box::new(s) }
336 } else {
337 crate::execution::KclValue::HomArray {
338 value: s
339 .into_iter()
340 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
341 .collect(),
342 ty: crate::execution::types::RuntimeType::solid(),
343 }
344 }
345 }
346 SolidOrSketchOrImportedGeometry::SketchSet(mut s) => {
347 if s.len() == 1
348 && let Some(s) = s.pop()
349 {
350 crate::execution::KclValue::Sketch { value: Box::new(s) }
351 } else {
352 crate::execution::KclValue::HomArray {
353 value: s
354 .into_iter()
355 .map(|s| crate::execution::KclValue::Sketch { value: Box::new(s) })
356 .collect(),
357 ty: crate::execution::types::RuntimeType::sketch(),
358 }
359 }
360 }
361 }
362 }
363}
364
365impl SolidOrSketchOrImportedGeometry {
366 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
367 match self {
368 SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => {
369 let id = s.id(ctx).await?;
370
371 Ok(vec![id])
372 }
373 SolidOrSketchOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
374 SolidOrSketchOrImportedGeometry::SketchSet(s) => Ok(s.iter().map(|s| s.id).collect()),
375 }
376 }
377}
378
379#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
381#[ts(export)]
382#[serde(tag = "type", rename_all = "camelCase")]
383#[allow(clippy::vec_box)]
384pub enum SolidOrImportedGeometry {
385 ImportedGeometry(Box<ImportedGeometry>),
386 SolidSet(Vec<Solid>),
387}
388
389impl From<SolidOrImportedGeometry> for crate::execution::KclValue {
390 fn from(value: SolidOrImportedGeometry) -> Self {
391 match value {
392 SolidOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
393 SolidOrImportedGeometry::SolidSet(mut s) => {
394 if s.len() == 1
395 && let Some(s) = s.pop()
396 {
397 crate::execution::KclValue::Solid { value: Box::new(s) }
398 } else {
399 crate::execution::KclValue::HomArray {
400 value: s
401 .into_iter()
402 .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
403 .collect(),
404 ty: crate::execution::types::RuntimeType::solid(),
405 }
406 }
407 }
408 }
409 }
410}
411
412#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
414#[ts(export)]
415#[serde(tag = "type", rename_all = "camelCase")]
416#[allow(clippy::vec_box)]
417pub enum HasAppearance {
418 ImportedGeometry(Box<ImportedGeometry>),
419 SolidSet(Vec<Solid>),
420 Plane(Box<Plane>),
421}
422
423impl From<HasAppearance> for KclValue {
424 fn from(value: HasAppearance) -> Self {
425 match value {
426 HasAppearance::Plane(p) => KclValue::Plane { value: p },
427 HasAppearance::ImportedGeometry(s) => KclValue::ImportedGeometry(*s),
428 HasAppearance::SolidSet(mut s) => {
429 if s.len() == 1
430 && let Some(s) = s.pop()
431 {
432 KclValue::Solid { value: Box::new(s) }
433 } else {
434 KclValue::HomArray {
435 value: s.into_iter().map(|s| KclValue::Solid { value: Box::new(s) }).collect(),
436 ty: crate::execution::types::RuntimeType::solid(),
437 }
438 }
439 }
440 }
441 }
442}
443
444impl HasAppearance {
445 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
446 match self {
447 HasAppearance::Plane(p) => Ok(vec![p.id]),
448 HasAppearance::ImportedGeometry(s) => {
449 let id = s.id(ctx).await?;
450
451 Ok(vec![id])
452 }
453 HasAppearance::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
454 }
455 }
456}
457
458#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
460#[ts(export)]
461#[serde(rename_all = "camelCase")]
462pub struct Helix {
463 pub value: uuid::Uuid,
465 pub artifact_id: ArtifactId,
467 pub revolutions: f64,
469 pub angle_start: f64,
471 pub ccw: bool,
473 pub cylinder_id: Option<uuid::Uuid>,
475 pub units: UnitLength,
476 #[serde(skip)]
477 pub meta: Vec<Metadata>,
478}
479
480#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
481#[ts(export)]
482#[serde(rename_all = "camelCase")]
483pub struct Plane {
484 pub id: uuid::Uuid,
486 pub artifact_id: ArtifactId,
488 #[serde(skip_serializing_if = "Option::is_none")]
491 pub object_id: Option<ObjectId>,
492 pub kind: PlaneKind,
494 #[serde(flatten)]
496 pub info: PlaneInfo,
497 #[serde(skip)]
498 pub meta: Vec<Metadata>,
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS)]
502#[ts(export)]
503#[serde(rename_all = "camelCase")]
504pub struct PlaneInfo {
505 pub origin: Point3d,
507 pub x_axis: Point3d,
509 pub y_axis: Point3d,
511 pub z_axis: Point3d,
513}
514
515impl PlaneInfo {
516 pub(crate) fn into_plane_data(self) -> PlaneData {
517 if self.origin.is_zero() {
518 match self {
519 Self {
520 origin:
521 Point3d {
522 x: 0.0,
523 y: 0.0,
524 z: 0.0,
525 units: Some(UnitLength::Millimeters),
526 },
527 x_axis:
528 Point3d {
529 x: 1.0,
530 y: 0.0,
531 z: 0.0,
532 units: _,
533 },
534 y_axis:
535 Point3d {
536 x: 0.0,
537 y: 1.0,
538 z: 0.0,
539 units: _,
540 },
541 z_axis: _,
542 } => return PlaneData::XY,
543 Self {
544 origin:
545 Point3d {
546 x: 0.0,
547 y: 0.0,
548 z: 0.0,
549 units: Some(UnitLength::Millimeters),
550 },
551 x_axis:
552 Point3d {
553 x: -1.0,
554 y: 0.0,
555 z: 0.0,
556 units: _,
557 },
558 y_axis:
559 Point3d {
560 x: 0.0,
561 y: 1.0,
562 z: 0.0,
563 units: _,
564 },
565 z_axis: _,
566 } => return PlaneData::NegXY,
567 Self {
568 origin:
569 Point3d {
570 x: 0.0,
571 y: 0.0,
572 z: 0.0,
573 units: Some(UnitLength::Millimeters),
574 },
575 x_axis:
576 Point3d {
577 x: 1.0,
578 y: 0.0,
579 z: 0.0,
580 units: _,
581 },
582 y_axis:
583 Point3d {
584 x: 0.0,
585 y: 0.0,
586 z: 1.0,
587 units: _,
588 },
589 z_axis: _,
590 } => return PlaneData::XZ,
591 Self {
592 origin:
593 Point3d {
594 x: 0.0,
595 y: 0.0,
596 z: 0.0,
597 units: Some(UnitLength::Millimeters),
598 },
599 x_axis:
600 Point3d {
601 x: -1.0,
602 y: 0.0,
603 z: 0.0,
604 units: _,
605 },
606 y_axis:
607 Point3d {
608 x: 0.0,
609 y: 0.0,
610 z: 1.0,
611 units: _,
612 },
613 z_axis: _,
614 } => return PlaneData::NegXZ,
615 Self {
616 origin:
617 Point3d {
618 x: 0.0,
619 y: 0.0,
620 z: 0.0,
621 units: Some(UnitLength::Millimeters),
622 },
623 x_axis:
624 Point3d {
625 x: 0.0,
626 y: 1.0,
627 z: 0.0,
628 units: _,
629 },
630 y_axis:
631 Point3d {
632 x: 0.0,
633 y: 0.0,
634 z: 1.0,
635 units: _,
636 },
637 z_axis: _,
638 } => return PlaneData::YZ,
639 Self {
640 origin:
641 Point3d {
642 x: 0.0,
643 y: 0.0,
644 z: 0.0,
645 units: Some(UnitLength::Millimeters),
646 },
647 x_axis:
648 Point3d {
649 x: 0.0,
650 y: -1.0,
651 z: 0.0,
652 units: _,
653 },
654 y_axis:
655 Point3d {
656 x: 0.0,
657 y: 0.0,
658 z: 1.0,
659 units: _,
660 },
661 z_axis: _,
662 } => return PlaneData::NegYZ,
663 _ => {}
664 }
665 }
666
667 PlaneData::Plane(Self {
668 origin: self.origin,
669 x_axis: self.x_axis,
670 y_axis: self.y_axis,
671 z_axis: self.z_axis,
672 })
673 }
674
675 pub(crate) fn is_right_handed(&self) -> bool {
676 let lhs = self
679 .x_axis
680 .axes_cross_product(&self.y_axis)
681 .axes_dot_product(&self.z_axis);
682 let rhs_x = self.x_axis.axes_dot_product(&self.x_axis);
683 let rhs_y = self.y_axis.axes_dot_product(&self.y_axis);
684 let rhs_z = self.z_axis.axes_dot_product(&self.z_axis);
685 let rhs = (rhs_x * rhs_y * rhs_z).sqrt();
686 (lhs - rhs).abs() <= 0.0001
688 }
689
690 #[cfg(test)]
691 pub(crate) fn is_left_handed(&self) -> bool {
692 !self.is_right_handed()
693 }
694
695 pub(crate) fn make_right_handed(self) -> Self {
696 if self.is_right_handed() {
697 return self;
698 }
699 Self {
701 origin: self.origin,
702 x_axis: self.x_axis.negated(),
703 y_axis: self.y_axis,
704 z_axis: self.z_axis,
705 }
706 }
707}
708
709impl TryFrom<PlaneData> for PlaneInfo {
710 type Error = KclError;
711
712 fn try_from(value: PlaneData) -> Result<Self, Self::Error> {
713 let name = match value {
714 PlaneData::XY => PlaneName::Xy,
715 PlaneData::NegXY => PlaneName::NegXy,
716 PlaneData::XZ => PlaneName::Xz,
717 PlaneData::NegXZ => PlaneName::NegXz,
718 PlaneData::YZ => PlaneName::Yz,
719 PlaneData::NegYZ => PlaneName::NegYz,
720 PlaneData::Plane(info) => {
721 return Ok(info);
722 }
723 };
724
725 let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
726 KclError::new_internal(KclErrorDetails::new(
727 format!("Plane {name} not found"),
728 Default::default(),
729 ))
730 })?;
731
732 Ok(info.clone())
733 }
734}
735
736impl From<&PlaneData> for PlaneKind {
737 fn from(value: &PlaneData) -> Self {
738 match value {
739 PlaneData::XY => PlaneKind::XY,
740 PlaneData::NegXY => PlaneKind::XY,
741 PlaneData::XZ => PlaneKind::XZ,
742 PlaneData::NegXZ => PlaneKind::XZ,
743 PlaneData::YZ => PlaneKind::YZ,
744 PlaneData::NegYZ => PlaneKind::YZ,
745 PlaneData::Plane(_) => PlaneKind::Custom,
746 }
747 }
748}
749
750impl From<&PlaneInfo> for PlaneKind {
751 fn from(value: &PlaneInfo) -> Self {
752 let data = PlaneData::Plane(value.clone());
753 PlaneKind::from(&data)
754 }
755}
756
757impl From<PlaneInfo> for PlaneKind {
758 fn from(value: PlaneInfo) -> Self {
759 let data = PlaneData::Plane(value);
760 PlaneKind::from(&data)
761 }
762}
763
764impl Plane {
765 #[cfg(test)]
766 pub(crate) fn from_plane_data_skipping_engine(
767 value: PlaneData,
768 exec_state: &mut ExecState,
769 ) -> Result<Self, KclError> {
770 let id = exec_state.next_uuid();
771 let kind = PlaneKind::from(&value);
772 Ok(Plane {
773 id,
774 artifact_id: id.into(),
775 info: PlaneInfo::try_from(value)?,
776 object_id: None,
777 kind,
778 meta: vec![],
779 })
780 }
781
782 pub fn is_initialized(&self) -> bool {
784 self.object_id.is_some()
785 }
786
787 pub fn is_uninitialized(&self) -> bool {
789 !self.is_initialized()
790 }
791
792 pub fn is_standard(&self) -> bool {
794 match &self.kind {
795 PlaneKind::XY | PlaneKind::YZ | PlaneKind::XZ => true,
796 PlaneKind::Custom => false,
797 }
798 }
799
800 pub fn project(&self, point: Point3d) -> Point3d {
803 let v = point - self.info.origin;
804 let dot = v.axes_dot_product(&self.info.z_axis);
805
806 point - self.info.z_axis * dot
807 }
808}
809
810#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
812#[ts(export)]
813#[serde(rename_all = "camelCase")]
814pub struct Face {
815 pub id: uuid::Uuid,
817 pub artifact_id: ArtifactId,
819 pub object_id: ObjectId,
821 pub value: String,
823 pub x_axis: Point3d,
825 pub y_axis: Point3d,
827 pub parent_solid: FaceParentSolid,
829 pub units: UnitLength,
830 #[serde(skip)]
831 pub meta: Vec<Metadata>,
832}
833
834#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
836#[ts(export)]
837#[serde(rename_all = "camelCase")]
838pub struct FaceParentSolid {
839 pub solid_id: Uuid,
841 pub creator_sketch_id: Option<Uuid>,
843 pub creator_sketch_is_closed: Option<ProfileClosed>,
845 #[serde(default, skip_serializing_if = "Vec::is_empty")]
847 pub edge_cut_ids: Vec<Uuid>,
848}
849
850impl FaceParentSolid {
851 pub(crate) fn sketch_or_solid_id(&self) -> Uuid {
852 self.creator_sketch_id.unwrap_or(self.solid_id)
853 }
854}
855
856#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
859#[ts(export)]
860#[serde(rename_all = "camelCase")]
861pub struct BoundedEdge {
862 pub face_id: uuid::Uuid,
864 #[serde(skip_serializing_if = "Option::is_none")]
866 pub edge_id: Option<uuid::Uuid>,
867 #[serde(skip_serializing_if = "Option::is_none")]
869 pub edge_specifier: Option<UnresolvedEdgeSpecifier>,
870 pub lower_bound: f32,
873 pub upper_bound: f32,
876}
877
878#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS, FromStr, Display)]
880#[ts(export)]
881#[display(style = "camelCase")]
882pub enum PlaneKind {
883 #[serde(rename = "XY", alias = "xy")]
884 #[display("XY")]
885 XY,
886 #[serde(rename = "XZ", alias = "xz")]
887 #[display("XZ")]
888 XZ,
889 #[serde(rename = "YZ", alias = "yz")]
890 #[display("YZ")]
891 YZ,
892 #[display("Custom")]
894 Custom,
895}
896
897#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
898#[ts(export)]
899#[serde(tag = "type", rename_all = "camelCase")]
900pub struct Sketch {
901 pub id: uuid::Uuid,
903 pub paths: Vec<Path>,
907 #[serde(default, skip_serializing_if = "Vec::is_empty")]
909 pub inner_paths: Vec<Path>,
910 pub on: SketchSurface,
912 pub start: BasePath,
914 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
916 pub tags: IndexMap<String, TagIdentifier>,
917 pub artifact_id: ArtifactId,
920 #[ts(skip)]
921 pub original_id: uuid::Uuid,
922 #[serde(skip_serializing_if = "Option::is_none")]
927 #[ts(skip)]
928 pub origin_sketch_id: Option<uuid::Uuid>,
929 #[serde(skip)]
931 pub mirror: Option<uuid::Uuid>,
932 #[serde(skip)]
934 pub clone: Option<uuid::Uuid>,
935 #[serde(skip)]
937 #[ts(skip)]
938 pub synthetic_jump_path_ids: Vec<uuid::Uuid>,
939 pub units: UnitLength,
940 #[serde(skip)]
942 pub meta: Vec<Metadata>,
943 #[serde(
946 default = "ProfileClosed::explicitly",
947 skip_serializing_if = "ProfileClosed::is_explicitly"
948 )]
949 pub is_closed: ProfileClosed,
950}
951
952impl ProfileClosed {
953 #[expect(dead_code, reason = "it's not actually dead, it's called by serde")]
954 fn explicitly() -> Self {
955 Self::Explicitly
956 }
957
958 fn is_explicitly(&self) -> bool {
959 matches!(self, ProfileClosed::Explicitly)
960 }
961}
962
963#[derive(Debug, Serialize, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd, ts_rs::TS)]
965#[serde(rename_all = "camelCase")]
966pub enum ProfileClosed {
967 No,
969 Maybe,
971 Implicitly,
973 Explicitly,
975}
976
977impl Sketch {
978 pub(crate) fn build_sketch_mode_cmds(
981 &self,
982 exec_state: &mut ExecState,
983 inner_cmd: ModelingCmdReq,
984 ) -> Vec<ModelingCmdReq> {
985 vec![
986 ModelingCmdReq {
989 cmd: ModelingCmd::from(
990 mcmd::EnableSketchMode::builder()
991 .animated(false)
992 .ortho(false)
993 .entity_id(self.on.id())
994 .adjust_camera(false)
995 .maybe_planar_normal(if let SketchSurface::Plane(plane) = &self.on {
996 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
998 Some(normal.into())
999 } else {
1000 None
1001 })
1002 .build(),
1003 ),
1004 cmd_id: exec_state.next_uuid().into(),
1005 },
1006 inner_cmd,
1007 ModelingCmdReq {
1008 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::builder().build()),
1009 cmd_id: exec_state.next_uuid().into(),
1010 },
1011 ]
1012 }
1013}
1014
1015#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1017#[ts(export)]
1018#[serde(tag = "type", rename_all = "camelCase")]
1019pub enum SketchSurface {
1020 Plane(Box<Plane>),
1021 Face(Box<Face>),
1022}
1023
1024impl SketchSurface {
1025 pub(crate) fn id(&self) -> uuid::Uuid {
1026 match self {
1027 SketchSurface::Plane(plane) => plane.id,
1028 SketchSurface::Face(face) => face.id,
1029 }
1030 }
1031 pub(crate) fn x_axis(&self) -> Point3d {
1032 match self {
1033 SketchSurface::Plane(plane) => plane.info.x_axis,
1034 SketchSurface::Face(face) => face.x_axis,
1035 }
1036 }
1037 pub(crate) fn y_axis(&self) -> Point3d {
1038 match self {
1039 SketchSurface::Plane(plane) => plane.info.y_axis,
1040 SketchSurface::Face(face) => face.y_axis,
1041 }
1042 }
1043
1044 pub(crate) fn object_id(&self) -> Option<ObjectId> {
1045 match self {
1046 SketchSurface::Plane(plane) => plane.object_id,
1047 SketchSurface::Face(face) => Some(face.object_id),
1048 }
1049 }
1050
1051 pub(crate) fn set_object_id(&mut self, object_id: ObjectId) {
1052 match self {
1053 SketchSurface::Plane(plane) => plane.object_id = Some(object_id),
1054 SketchSurface::Face(face) => face.object_id = object_id,
1055 }
1056 }
1057}
1058
1059#[derive(Debug, Clone, PartialEq)]
1061pub enum Extrudable {
1062 Sketch(Box<Sketch>),
1064 FaceTag(FaceTag),
1066 Face(Box<Face>),
1068 EdgeTag(Box<TagIdentifier>),
1070 Edge(Uuid),
1072}
1073
1074impl Extrudable {
1075 pub async fn id_to_extrude(
1077 &self,
1078 exec_state: &mut ExecState,
1079 args: &Args,
1080 must_be_planar: bool,
1081 ) -> Result<uuid::Uuid, KclError> {
1082 match self {
1083 Extrudable::Sketch(sketch) => Ok(sketch.id),
1084 Extrudable::FaceTag(face_tag) => face_tag.get_face_id_from_tag(exec_state, args, must_be_planar).await,
1085 Extrudable::Face(face) => Ok(face.id),
1086 Extrudable::EdgeTag(edge_tag) => match edge_tag.get_cur_info() {
1087 Some(info) => Ok(info.id),
1088 None => Err(KclError::new_type(KclErrorDetails::new(
1089 "Could not find a valid id to extrude".to_owned(),
1090 vec![args.source_range],
1091 ))),
1092 },
1093 Extrudable::Edge(edge) => Ok(*edge),
1094 }
1095 }
1096
1097 pub fn as_sketch(&self) -> Option<Sketch> {
1098 match self {
1099 Extrudable::Sketch(sketch) => Some((**sketch).clone()),
1100 Extrudable::FaceTag(face) => match face.geometry() {
1101 Some(Geometry::Sketch(sketch)) => Some(sketch),
1102 Some(Geometry::Solid(solid)) => solid.sketch().cloned(),
1103 None => None,
1104 },
1105 Extrudable::Face(_) => None,
1106 Extrudable::EdgeTag(tag_identifier) => match tag_identifier.geometry() {
1107 Some(Geometry::Sketch(sketch)) => Some(sketch),
1108 Some(Geometry::Solid(solid)) => solid.sketch().cloned(),
1109 None => None,
1110 },
1111 Extrudable::Edge(_) => None,
1112 }
1113 }
1114
1115 pub fn is_closed(&self) -> ProfileClosed {
1116 match self {
1117 Extrudable::Sketch(sketch) => sketch.is_closed,
1118 Extrudable::FaceTag(face_tag) => match face_tag.geometry() {
1119 Some(Geometry::Sketch(sketch)) => sketch.is_closed,
1120 Some(Geometry::Solid(solid)) => solid
1121 .sketch()
1122 .map(|sketch| sketch.is_closed)
1123 .unwrap_or(ProfileClosed::Maybe),
1124 _ => ProfileClosed::Maybe,
1125 },
1126 Extrudable::Face(face) => match face.parent_solid.creator_sketch_is_closed {
1127 Some(is_closed) => is_closed,
1128 None => ProfileClosed::Maybe,
1129 },
1130 Extrudable::EdgeTag(edge_tag) => match edge_tag.geometry() {
1131 Some(Geometry::Sketch(sketch)) => sketch.is_closed,
1132 Some(Geometry::Solid(solid)) => solid
1133 .sketch()
1134 .map(|sketch| sketch.is_closed)
1135 .unwrap_or(ProfileClosed::Maybe),
1136 _ => ProfileClosed::Maybe,
1137 },
1138 Extrudable::Edge(_) => ProfileClosed::Maybe,
1139 }
1140 }
1141}
1142
1143impl From<Sketch> for Extrudable {
1144 fn from(value: Sketch) -> Self {
1145 Extrudable::Sketch(Box::new(value))
1146 }
1147}
1148
1149#[derive(Debug, Clone)]
1150pub(crate) enum GetTangentialInfoFromPathsResult {
1151 PreviousPoint([f64; 2]),
1152 Arc {
1153 center: [f64; 2],
1154 ccw: bool,
1155 },
1156 Circle {
1157 center: [f64; 2],
1158 ccw: bool,
1159 radius: f64,
1160 },
1161 Ellipse {
1162 center: [f64; 2],
1163 ccw: bool,
1164 major_axis: [f64; 2],
1165 _minor_radius: f64,
1166 },
1167}
1168
1169impl GetTangentialInfoFromPathsResult {
1170 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
1171 match self {
1172 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
1173 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
1174 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
1175 }
1176 GetTangentialInfoFromPathsResult::Circle {
1179 center, radius, ccw, ..
1180 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
1181 GetTangentialInfoFromPathsResult::Ellipse {
1182 center,
1183 major_axis,
1184 ccw,
1185 ..
1186 } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
1187 }
1188 }
1189}
1190
1191impl Sketch {
1192 pub(crate) fn add_tag(
1193 &mut self,
1194 tag: NodeRef<'_, TagDeclarator>,
1195 current_path: &Path,
1196 exec_state: &ExecState,
1197 surface: Option<&ExtrudeSurface>,
1198 ) {
1199 let mut tag_identifier: TagIdentifier = tag.into();
1200 let base = current_path.get_base();
1201 let mut sketch_copy = self.clone();
1202 sketch_copy.tags.clear();
1203 tag_identifier.info.push((
1204 exec_state.stack().current_epoch(),
1205 TagEngineInfo {
1206 id: base.geo_meta.id,
1207 geometry: Geometry::Sketch(sketch_copy),
1208 path: Some(current_path.clone()),
1209 surface: surface.cloned(),
1210 },
1211 ));
1212
1213 self.tags.insert(tag.name.to_string(), tag_identifier);
1214 }
1215
1216 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
1217 for t in tags {
1218 match self.tags.get_mut(&t.value) {
1219 Some(id) => {
1220 id.merge_info(t);
1221 }
1222 None => {
1223 self.tags.insert(t.value.clone(), t.clone());
1224 }
1225 }
1226 }
1227 }
1228
1229 pub(crate) fn latest_path(&self) -> Option<&Path> {
1231 self.paths.last()
1232 }
1233
1234 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
1238 let Some(path) = self.latest_path() else {
1239 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
1240 };
1241
1242 let to = path.get_base().to;
1243 Ok(Point2d::new(to[0], to[1], path.get_base().units))
1244 }
1245
1246 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
1247 let Some(path) = self.latest_path() else {
1248 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
1249 };
1250 path.get_tangential_info()
1251 }
1252}
1253
1254#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1255#[ts(export)]
1256#[serde(tag = "type", rename_all = "camelCase")]
1257pub struct Solid {
1258 pub id: uuid::Uuid,
1260 #[serde(skip)]
1262 #[ts(skip)]
1263 pub value_id: uuid::Uuid,
1264 pub artifact_id: ArtifactId,
1266 pub value: Vec<ExtrudeSurface>,
1268 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
1271 pub faces: IndexMap<String, TagIdentifier>,
1272 #[serde(rename = "sketch")]
1274 pub creator: SolidCreator,
1275 pub start_cap_id: Option<uuid::Uuid>,
1277 pub end_cap_id: Option<uuid::Uuid>,
1279 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1281 pub edge_cuts: Vec<EdgeCut>,
1282 #[serde(skip)]
1284 #[ts(skip)]
1285 pub pending_edge_cut_ids: Vec<uuid::Uuid>,
1286 pub units: UnitLength,
1288 pub sectional: bool,
1290 #[serde(skip)]
1292 pub meta: Vec<Metadata>,
1293}
1294
1295#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1296#[ts(export)]
1297pub struct CreatorFace {
1298 pub face_id: uuid::Uuid,
1300 pub solid_id: uuid::Uuid,
1302 pub sketch: Sketch,
1304}
1305
1306#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1307#[ts(export)]
1308pub struct CreatorEdge {
1309 pub edge_id: uuid::Uuid,
1311 pub body_id: uuid::Uuid,
1313}
1314
1315#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1317#[ts(export)]
1318#[serde(tag = "creatorType", rename_all = "camelCase")]
1319pub enum SolidCreator {
1320 Sketch(Sketch),
1322 Face(CreatorFace),
1324 Edge(CreatorEdge),
1326 Procedural,
1328}
1329
1330impl Solid {
1331 pub fn sketch(&self) -> Option<&Sketch> {
1332 match &self.creator {
1333 SolidCreator::Sketch(sketch) => Some(sketch),
1334 SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1335 SolidCreator::Edge(_) => None,
1336 SolidCreator::Procedural => None,
1337 }
1338 }
1339
1340 pub fn sketch_mut(&mut self) -> Option<&mut Sketch> {
1341 match &mut self.creator {
1342 SolidCreator::Sketch(sketch) => Some(sketch),
1343 SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1344 SolidCreator::Edge(_) => None,
1345 SolidCreator::Procedural => None,
1346 }
1347 }
1348
1349 pub fn sketch_id(&self) -> Option<uuid::Uuid> {
1350 self.sketch().map(|sketch| sketch.id)
1351 }
1352
1353 pub fn original_id(&self) -> uuid::Uuid {
1354 self.sketch().map(|sketch| sketch.original_id).unwrap_or(self.id)
1355 }
1356
1357 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
1358 self.edge_cuts
1359 .iter()
1360 .map(|foc| foc.id())
1361 .chain(self.pending_edge_cut_ids.iter().copied())
1362 }
1363}
1364
1365impl From<&Solid> for FaceParentSolid {
1366 fn from(solid: &Solid) -> Self {
1367 Self {
1368 solid_id: solid.id,
1369 creator_sketch_id: solid.sketch_id(),
1370 creator_sketch_is_closed: solid.sketch().map(|sketch| sketch.is_closed),
1371 edge_cut_ids: solid.get_all_edge_cut_ids().collect(),
1372 }
1373 }
1374}
1375
1376#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1378#[ts(export)]
1379#[serde(tag = "type", rename_all = "camelCase")]
1380pub enum EdgeCut {
1381 Fillet {
1383 id: uuid::Uuid,
1385 radius: TyF64,
1386 #[serde(rename = "edgeId")]
1388 edge_id: uuid::Uuid,
1389 tag: Box<Option<TagNode>>,
1390 },
1391 Chamfer {
1393 id: uuid::Uuid,
1395 length: TyF64,
1396 #[serde(rename = "edgeId")]
1398 edge_id: uuid::Uuid,
1399 tag: Box<Option<TagNode>>,
1400 },
1401}
1402
1403impl EdgeCut {
1404 pub fn id(&self) -> uuid::Uuid {
1405 match self {
1406 EdgeCut::Fillet { id, .. } => *id,
1407 EdgeCut::Chamfer { id, .. } => *id,
1408 }
1409 }
1410
1411 pub fn set_id(&mut self, id: uuid::Uuid) {
1412 match self {
1413 EdgeCut::Fillet { id: i, .. } => *i = id,
1414 EdgeCut::Chamfer { id: i, .. } => *i = id,
1415 }
1416 }
1417
1418 pub fn edge_id(&self) -> uuid::Uuid {
1419 match self {
1420 EdgeCut::Fillet { edge_id, .. } => *edge_id,
1421 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
1422 }
1423 }
1424
1425 pub fn set_edge_id(&mut self, id: uuid::Uuid) {
1426 match self {
1427 EdgeCut::Fillet { edge_id: i, .. } => *i = id,
1428 EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
1429 }
1430 }
1431
1432 pub fn tag(&self) -> Option<TagNode> {
1433 match self {
1434 EdgeCut::Fillet { tag, .. } => *tag.clone(),
1435 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
1436 }
1437 }
1438}
1439
1440#[derive(Debug, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
1441#[ts(export)]
1442pub struct Point2d {
1443 pub x: f64,
1444 pub y: f64,
1445 pub units: UnitLength,
1446}
1447
1448impl Point2d {
1449 pub const ZERO: Self = Self {
1450 x: 0.0,
1451 y: 0.0,
1452 units: UnitLength::Millimeters,
1453 };
1454
1455 pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
1456 Self { x, y, units }
1457 }
1458
1459 pub fn into_x(self) -> TyF64 {
1460 TyF64::new(self.x, NumericType::length(self.units))
1461 }
1462
1463 pub fn into_y(self) -> TyF64 {
1464 TyF64::new(self.y, NumericType::length(self.units))
1465 }
1466
1467 pub fn ignore_units(self) -> [f64; 2] {
1468 [self.x, self.y]
1469 }
1470}
1471
1472#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
1473#[ts(export)]
1474pub struct Point3d {
1475 pub x: f64,
1476 pub y: f64,
1477 pub z: f64,
1478 pub units: Option<UnitLength>,
1479}
1480
1481impl Point3d {
1482 pub const ZERO: Self = Self {
1483 x: 0.0,
1484 y: 0.0,
1485 z: 0.0,
1486 units: Some(UnitLength::Millimeters),
1487 };
1488
1489 pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
1490 Self { x, y, z, units }
1491 }
1492
1493 pub const fn is_zero(&self) -> bool {
1494 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
1495 }
1496
1497 pub fn axes_cross_product(&self, other: &Self) -> Self {
1502 Self {
1503 x: self.y * other.z - self.z * other.y,
1504 y: self.z * other.x - self.x * other.z,
1505 z: self.x * other.y - self.y * other.x,
1506 units: None,
1507 }
1508 }
1509
1510 pub fn canonicalize_signed_zero(&mut self) {
1512 if self.x == 0.0 {
1513 self.x = 0.0;
1514 }
1515 if self.y == 0.0 {
1516 self.y = 0.0;
1517 }
1518 if self.z == 0.0 {
1519 self.z = 0.0;
1520 }
1521 }
1522
1523 pub fn axes_dot_product(&self, other: &Self) -> f64 {
1528 let x = self.x * other.x;
1529 let y = self.y * other.y;
1530 let z = self.z * other.z;
1531 x + y + z
1532 }
1533
1534 pub fn normalize(&self) -> Self {
1535 let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1536 Point3d {
1537 x: self.x / len,
1538 y: self.y / len,
1539 z: self.z / len,
1540 units: None,
1541 }
1542 }
1543
1544 pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1545 let p = [self.x, self.y, self.z];
1546 let u = self.units;
1547 (p, u)
1548 }
1549
1550 pub(crate) fn negated(self) -> Self {
1551 Self {
1552 x: -self.x,
1553 y: -self.y,
1554 z: -self.z,
1555 units: self.units,
1556 }
1557 }
1558}
1559
1560impl From<[TyF64; 3]> for Point3d {
1561 fn from(p: [TyF64; 3]) -> Self {
1562 Self {
1563 x: p[0].n,
1564 y: p[1].n,
1565 z: p[2].n,
1566 units: p[0].ty.as_length(),
1567 }
1568 }
1569}
1570
1571impl From<Point3d> for Point3D {
1572 fn from(p: Point3d) -> Self {
1573 Self { x: p.x, y: p.y, z: p.z }
1574 }
1575}
1576
1577impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1578 fn from(p: Point3d) -> Self {
1579 if let Some(units) = p.units {
1580 Self {
1581 x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1582 y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1583 z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1584 }
1585 } else {
1586 Self {
1587 x: LengthUnit(p.x),
1588 y: LengthUnit(p.y),
1589 z: LengthUnit(p.z),
1590 }
1591 }
1592 }
1593}
1594
1595impl Add for Point3d {
1596 type Output = Point3d;
1597
1598 fn add(self, rhs: Self) -> Self::Output {
1599 Point3d {
1601 x: self.x + rhs.x,
1602 y: self.y + rhs.y,
1603 z: self.z + rhs.z,
1604 units: self.units,
1605 }
1606 }
1607}
1608
1609impl AddAssign for Point3d {
1610 fn add_assign(&mut self, rhs: Self) {
1611 *self = *self + rhs
1612 }
1613}
1614
1615impl Sub for Point3d {
1616 type Output = Point3d;
1617
1618 fn sub(self, rhs: Self) -> Self::Output {
1619 let (x, y, z) = if rhs.units != self.units
1620 && let Some(sunits) = self.units
1621 && let Some(runits) = rhs.units
1622 {
1623 (
1624 adjust_length(runits, rhs.x, sunits).0,
1625 adjust_length(runits, rhs.y, sunits).0,
1626 adjust_length(runits, rhs.z, sunits).0,
1627 )
1628 } else {
1629 (rhs.x, rhs.y, rhs.z)
1630 };
1631 Point3d {
1632 x: self.x - x,
1633 y: self.y - y,
1634 z: self.z - z,
1635 units: self.units,
1636 }
1637 }
1638}
1639
1640impl SubAssign for Point3d {
1641 fn sub_assign(&mut self, rhs: Self) {
1642 *self = *self - rhs
1643 }
1644}
1645
1646impl Mul<f64> for Point3d {
1647 type Output = Point3d;
1648
1649 fn mul(self, rhs: f64) -> Self::Output {
1650 Point3d {
1651 x: self.x * rhs,
1652 y: self.y * rhs,
1653 z: self.z * rhs,
1654 units: self.units,
1655 }
1656 }
1657}
1658
1659#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1661#[ts(export)]
1662#[serde(rename_all = "camelCase")]
1663pub struct BasePath {
1664 #[ts(type = "[number, number]")]
1666 pub from: [f64; 2],
1667 #[ts(type = "[number, number]")]
1669 pub to: [f64; 2],
1670 pub units: UnitLength,
1671 pub tag: Option<TagNode>,
1673 #[serde(rename = "__geoMeta")]
1675 pub geo_meta: GeoMeta,
1676}
1677
1678impl BasePath {
1679 pub fn get_to(&self) -> [TyF64; 2] {
1680 let ty = NumericType::length(self.units);
1681 [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1682 }
1683
1684 pub fn get_from(&self) -> [TyF64; 2] {
1685 let ty = NumericType::length(self.units);
1686 [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1687 }
1688}
1689
1690#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1692#[ts(export)]
1693#[serde(rename_all = "camelCase")]
1694pub struct GeoMeta {
1695 pub id: uuid::Uuid,
1697 #[serde(flatten)]
1699 pub metadata: Metadata,
1700}
1701
1702#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1704#[ts(export)]
1705#[serde(tag = "type")]
1706pub enum Path {
1707 ToPoint {
1709 #[serde(flatten)]
1710 base: BasePath,
1711 },
1712 TangentialArcTo {
1714 #[serde(flatten)]
1715 base: BasePath,
1716 #[ts(type = "[number, number]")]
1718 center: [f64; 2],
1719 ccw: bool,
1721 },
1722 TangentialArc {
1724 #[serde(flatten)]
1725 base: BasePath,
1726 #[ts(type = "[number, number]")]
1728 center: [f64; 2],
1729 ccw: bool,
1731 },
1732 Circle {
1735 #[serde(flatten)]
1736 base: BasePath,
1737 #[ts(type = "[number, number]")]
1739 center: [f64; 2],
1740 radius: f64,
1742 ccw: bool,
1745 },
1746 CircleThreePoint {
1747 #[serde(flatten)]
1748 base: BasePath,
1749 #[ts(type = "[number, number]")]
1751 p1: [f64; 2],
1752 #[ts(type = "[number, number]")]
1754 p2: [f64; 2],
1755 #[ts(type = "[number, number]")]
1757 p3: [f64; 2],
1758 },
1759 ArcThreePoint {
1760 #[serde(flatten)]
1761 base: BasePath,
1762 #[ts(type = "[number, number]")]
1764 p1: [f64; 2],
1765 #[ts(type = "[number, number]")]
1767 p2: [f64; 2],
1768 #[ts(type = "[number, number]")]
1770 p3: [f64; 2],
1771 },
1772 Horizontal {
1774 #[serde(flatten)]
1775 base: BasePath,
1776 x: f64,
1778 },
1779 AngledLineTo {
1781 #[serde(flatten)]
1782 base: BasePath,
1783 x: Option<f64>,
1785 y: Option<f64>,
1787 },
1788 Base {
1790 #[serde(flatten)]
1791 base: BasePath,
1792 },
1793 Arc {
1795 #[serde(flatten)]
1796 base: BasePath,
1797 center: [f64; 2],
1799 radius: f64,
1801 ccw: bool,
1803 },
1804 Ellipse {
1805 #[serde(flatten)]
1806 base: BasePath,
1807 center: [f64; 2],
1808 major_axis: [f64; 2],
1809 minor_radius: f64,
1810 ccw: bool,
1811 },
1812 Conic {
1814 #[serde(flatten)]
1815 base: BasePath,
1816 },
1817 Bezier {
1819 #[serde(flatten)]
1820 base: BasePath,
1821 #[ts(type = "[number, number]")]
1823 control1: [f64; 2],
1824 #[ts(type = "[number, number]")]
1826 control2: [f64; 2],
1827 },
1828}
1829
1830impl Path {
1831 pub fn get_id(&self) -> uuid::Uuid {
1832 match self {
1833 Path::ToPoint { base } => base.geo_meta.id,
1834 Path::Horizontal { base, .. } => base.geo_meta.id,
1835 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1836 Path::Base { base } => base.geo_meta.id,
1837 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1838 Path::TangentialArc { base, .. } => base.geo_meta.id,
1839 Path::Circle { base, .. } => base.geo_meta.id,
1840 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1841 Path::Arc { base, .. } => base.geo_meta.id,
1842 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1843 Path::Ellipse { base, .. } => base.geo_meta.id,
1844 Path::Conic { base, .. } => base.geo_meta.id,
1845 Path::Bezier { base, .. } => base.geo_meta.id,
1846 }
1847 }
1848
1849 pub fn set_id(&mut self, id: uuid::Uuid) {
1850 match self {
1851 Path::ToPoint { base } => base.geo_meta.id = id,
1852 Path::Horizontal { base, .. } => base.geo_meta.id = id,
1853 Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1854 Path::Base { base } => base.geo_meta.id = id,
1855 Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1856 Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1857 Path::Circle { base, .. } => base.geo_meta.id = id,
1858 Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1859 Path::Arc { base, .. } => base.geo_meta.id = id,
1860 Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1861 Path::Ellipse { base, .. } => base.geo_meta.id = id,
1862 Path::Conic { base, .. } => base.geo_meta.id = id,
1863 Path::Bezier { base, .. } => base.geo_meta.id = id,
1864 }
1865 }
1866
1867 pub fn get_tag(&self) -> Option<TagNode> {
1868 match self {
1869 Path::ToPoint { base } => base.tag.clone(),
1870 Path::Horizontal { base, .. } => base.tag.clone(),
1871 Path::AngledLineTo { base, .. } => base.tag.clone(),
1872 Path::Base { base } => base.tag.clone(),
1873 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1874 Path::TangentialArc { base, .. } => base.tag.clone(),
1875 Path::Circle { base, .. } => base.tag.clone(),
1876 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1877 Path::Arc { base, .. } => base.tag.clone(),
1878 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1879 Path::Ellipse { base, .. } => base.tag.clone(),
1880 Path::Conic { base, .. } => base.tag.clone(),
1881 Path::Bezier { base, .. } => base.tag.clone(),
1882 }
1883 }
1884
1885 pub fn get_base(&self) -> &BasePath {
1886 match self {
1887 Path::ToPoint { base } => base,
1888 Path::Horizontal { base, .. } => base,
1889 Path::AngledLineTo { base, .. } => base,
1890 Path::Base { base } => base,
1891 Path::TangentialArcTo { base, .. } => base,
1892 Path::TangentialArc { base, .. } => base,
1893 Path::Circle { base, .. } => base,
1894 Path::CircleThreePoint { base, .. } => base,
1895 Path::Arc { base, .. } => base,
1896 Path::ArcThreePoint { base, .. } => base,
1897 Path::Ellipse { base, .. } => base,
1898 Path::Conic { base, .. } => base,
1899 Path::Bezier { base, .. } => base,
1900 }
1901 }
1902
1903 pub fn get_from(&self) -> [TyF64; 2] {
1905 let p = &self.get_base().from;
1906 let ty = NumericType::length(self.get_base().units);
1907 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1908 }
1909
1910 pub fn get_to(&self) -> [TyF64; 2] {
1912 let p = &self.get_base().to;
1913 let ty = NumericType::length(self.get_base().units);
1914 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1915 }
1916
1917 pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1919 let p = &self.get_base().from;
1920 let ty = NumericType::length(self.get_base().units);
1921 (*p, ty)
1922 }
1923
1924 pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1926 let p = &self.get_base().to;
1927 let ty = NumericType::length(self.get_base().units);
1928 (*p, ty)
1929 }
1930
1931 pub fn length(&self) -> Option<TyF64> {
1934 let n = match self {
1935 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1936 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1937 }
1938 Self::TangentialArc {
1939 base: _,
1940 center,
1941 ccw: _,
1942 }
1943 | Self::TangentialArcTo {
1944 base: _,
1945 center,
1946 ccw: _,
1947 } => {
1948 let radius = linear_distance(&self.get_base().from, center);
1951 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
1952 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1954 }
1955 Self::Circle { radius, .. } => Some(TAU * radius),
1956 Self::CircleThreePoint { .. } => {
1957 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1958 self.get_base().from,
1959 self.get_base().to,
1960 self.get_base().to,
1961 ]);
1962 let radius = linear_distance(
1963 &[circle_center.center[0], circle_center.center[1]],
1964 &self.get_base().from,
1965 );
1966 Some(TAU * radius)
1967 }
1968 Self::Arc { .. } => {
1969 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1971 }
1972 Self::ArcThreePoint { .. } => {
1973 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1975 }
1976 Self::Ellipse { .. } => {
1977 None
1979 }
1980 Self::Conic { .. } => {
1981 None
1983 }
1984 Self::Bezier { .. } => {
1985 None
1987 }
1988 };
1989 n.map(|n| TyF64::new(n, NumericType::length(self.get_base().units)))
1990 }
1991
1992 pub fn get_base_mut(&mut self) -> &mut BasePath {
1993 match self {
1994 Path::ToPoint { base } => base,
1995 Path::Horizontal { base, .. } => base,
1996 Path::AngledLineTo { base, .. } => base,
1997 Path::Base { base } => base,
1998 Path::TangentialArcTo { base, .. } => base,
1999 Path::TangentialArc { base, .. } => base,
2000 Path::Circle { base, .. } => base,
2001 Path::CircleThreePoint { base, .. } => base,
2002 Path::Arc { base, .. } => base,
2003 Path::ArcThreePoint { base, .. } => base,
2004 Path::Ellipse { base, .. } => base,
2005 Path::Conic { base, .. } => base,
2006 Path::Bezier { base, .. } => base,
2007 }
2008 }
2009
2010 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
2011 match self {
2012 Path::TangentialArc { center, ccw, .. }
2013 | Path::TangentialArcTo { center, ccw, .. }
2014 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
2015 center: *center,
2016 ccw: *ccw,
2017 },
2018 Path::ArcThreePoint { p1, p2, p3, .. } => {
2019 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
2020 GetTangentialInfoFromPathsResult::Arc {
2021 center: circle.center,
2022 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
2023 }
2024 }
2025 Path::Circle {
2026 center, ccw, radius, ..
2027 } => GetTangentialInfoFromPathsResult::Circle {
2028 center: *center,
2029 ccw: *ccw,
2030 radius: *radius,
2031 },
2032 Path::CircleThreePoint { p1, p2, p3, .. } => {
2033 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
2034 let center_point = [circle.center[0], circle.center[1]];
2035 GetTangentialInfoFromPathsResult::Circle {
2036 center: center_point,
2037 ccw: true,
2039 radius: circle.radius,
2040 }
2041 }
2042 Path::Ellipse {
2044 center,
2045 major_axis,
2046 minor_radius,
2047 ccw,
2048 ..
2049 } => GetTangentialInfoFromPathsResult::Ellipse {
2050 center: *center,
2051 major_axis: *major_axis,
2052 _minor_radius: *minor_radius,
2053 ccw: *ccw,
2054 },
2055 Path::Conic { .. }
2056 | Path::ToPoint { .. }
2057 | Path::Horizontal { .. }
2058 | Path::AngledLineTo { .. }
2059 | Path::Base { .. }
2060 | Path::Bezier { .. } => {
2061 let base = self.get_base();
2062 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
2063 }
2064 }
2065 }
2066
2067 pub(crate) fn is_straight_line(&self) -> bool {
2069 matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
2070 }
2071}
2072
2073#[rustfmt::skip]
2075fn linear_distance(
2076 [x0, y0]: &[f64; 2],
2077 [x1, y1]: &[f64; 2]
2078) -> f64 {
2079 let y_sq = (y1 - y0).squared();
2080 let x_sq = (x1 - x0).squared();
2081 (y_sq + x_sq).sqrt()
2082}
2083
2084#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2086#[ts(export)]
2087#[serde(tag = "type", rename_all = "camelCase")]
2088pub enum ExtrudeSurface {
2089 ExtrudePlane(ExtrudePlane),
2091 ExtrudeArc(ExtrudeArc),
2092 Chamfer(ChamferSurface),
2093 Fillet(FilletSurface),
2094}
2095
2096#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2098#[ts(export)]
2099#[serde(rename_all = "camelCase")]
2100pub struct ChamferSurface {
2101 pub face_id: uuid::Uuid,
2103 pub tag: Option<Node<TagDeclarator>>,
2105 #[serde(flatten)]
2107 pub geo_meta: GeoMeta,
2108}
2109
2110#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2112#[ts(export)]
2113#[serde(rename_all = "camelCase")]
2114pub struct FilletSurface {
2115 pub face_id: uuid::Uuid,
2117 pub tag: Option<Node<TagDeclarator>>,
2119 #[serde(flatten)]
2121 pub geo_meta: GeoMeta,
2122}
2123
2124#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2126#[ts(export)]
2127#[serde(rename_all = "camelCase")]
2128pub struct ExtrudePlane {
2129 pub face_id: uuid::Uuid,
2131 pub tag: Option<Node<TagDeclarator>>,
2133 #[serde(flatten)]
2135 pub geo_meta: GeoMeta,
2136}
2137
2138#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2140#[ts(export)]
2141#[serde(rename_all = "camelCase")]
2142pub struct ExtrudeArc {
2143 pub face_id: uuid::Uuid,
2145 pub tag: Option<Node<TagDeclarator>>,
2147 #[serde(flatten)]
2149 pub geo_meta: GeoMeta,
2150}
2151
2152impl ExtrudeSurface {
2153 pub fn get_id(&self) -> uuid::Uuid {
2154 match self {
2155 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
2156 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
2157 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
2158 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
2159 }
2160 }
2161
2162 pub fn face_id(&self) -> uuid::Uuid {
2163 match self {
2164 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id,
2165 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id,
2166 ExtrudeSurface::Fillet(f) => f.face_id,
2167 ExtrudeSurface::Chamfer(c) => c.face_id,
2168 }
2169 }
2170
2171 pub fn set_face_id(&mut self, face_id: uuid::Uuid) {
2172 match self {
2173 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id = face_id,
2174 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id = face_id,
2175 ExtrudeSurface::Fillet(f) => f.face_id = face_id,
2176 ExtrudeSurface::Chamfer(c) => c.face_id = face_id,
2177 }
2178 }
2179
2180 pub fn set_surface_tag(&mut self, tag: &TagNode) {
2181 match self {
2182 ExtrudeSurface::ExtrudePlane(extrude_plane) => extrude_plane.tag = Some(tag.clone()),
2183 ExtrudeSurface::ExtrudeArc(extrude_arc) => extrude_arc.tag = Some(tag.clone()),
2184 ExtrudeSurface::Chamfer(chamfer) => chamfer.tag = Some(tag.clone()),
2185 ExtrudeSurface::Fillet(fillet) => fillet.tag = Some(tag.clone()),
2186 }
2187 }
2188
2189 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
2190 match self {
2191 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
2192 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
2193 ExtrudeSurface::Fillet(f) => f.tag.clone(),
2194 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
2195 }
2196 }
2197}
2198
2199#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ts_rs::TS)]
2200pub struct SketchVarId(pub usize);
2201
2202impl SketchVarId {
2203 pub const INVALID: Self = Self(usize::MAX);
2204
2205 pub fn to_constraint_id(self, range: SourceRange) -> Result<ezpz::Id, KclError> {
2206 self.0.try_into().map_err(|_| {
2207 KclError::new_type(KclErrorDetails::new(
2208 "Cannot convert to constraint ID since the sketch variable ID is too large".to_owned(),
2209 vec![range],
2210 ))
2211 })
2212 }
2213}
2214
2215#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2216#[ts(export_to = "Geometry.ts")]
2217#[serde(rename_all = "camelCase")]
2218pub struct SketchVar {
2219 pub id: SketchVarId,
2220 pub initial_value: f64,
2221 pub ty: NumericType,
2222 pub node_path: Option<NodePath>,
2224 #[serde(skip)]
2225 pub meta: Vec<Metadata>,
2226}
2227
2228impl SketchVar {
2229 pub fn initial_value_to_solver_units(
2230 &self,
2231 exec_state: &mut ExecState,
2232 source_range: SourceRange,
2233 description: &str,
2234 ) -> Result<TyF64, KclError> {
2235 let x_initial_value = KclValue::Number {
2236 value: self.initial_value,
2237 ty: self.ty,
2238 meta: vec![source_range.into()],
2239 };
2240 let normalized_value =
2241 normalize_to_solver_distance_unit(&x_initial_value, source_range, exec_state, description)?;
2242 normalized_value.as_ty_f64().ok_or_else(|| {
2243 let message = format!(
2244 "Expected number after coercion, but found {}",
2245 normalized_value.human_friendly_type()
2246 );
2247 debug_assert!(false, "{}", &message);
2248 KclError::new_internal(KclErrorDetails::new(message, vec![source_range]))
2249 })
2250 }
2251}
2252
2253#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2254#[ts(export_to = "Geometry.ts")]
2255#[serde(tag = "type")]
2256pub enum UnsolvedExpr {
2257 Known(TyF64),
2258 Unknown(SketchVarId),
2259}
2260
2261impl UnsolvedExpr {
2262 pub fn var(&self) -> Option<SketchVarId> {
2263 match self {
2264 UnsolvedExpr::Known(_) => None,
2265 UnsolvedExpr::Unknown(id) => Some(*id),
2266 }
2267 }
2268}
2269
2270pub type UnsolvedPoint2dExpr = [UnsolvedExpr; 2];
2271
2272#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2273#[ts(export_to = "Geometry.ts")]
2274#[serde(rename_all = "camelCase")]
2275pub struct ConstrainablePoint2d {
2276 pub vars: crate::front::Point2d<SketchVarId>,
2277 pub object_id: ObjectId,
2278}
2279
2280#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2281#[ts(export_to = "Geometry.ts")]
2282pub enum ConstrainablePoint2dOrOrigin {
2283 Point(ConstrainablePoint2d),
2284 Origin,
2285}
2286
2287#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2288#[ts(export_to = "Geometry.ts")]
2289#[serde(rename_all = "camelCase")]
2290pub struct ConstrainableLine2d {
2291 pub vars: [crate::front::Point2d<SketchVarId>; 2],
2292 pub object_id: ObjectId,
2293}
2294
2295#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2296#[ts(export_to = "Geometry.ts")]
2297#[serde(rename_all = "camelCase")]
2298pub struct UnsolvedSegment {
2299 pub id: Uuid,
2301 pub object_id: ObjectId,
2302 pub kind: UnsolvedSegmentKind,
2303 #[serde(skip_serializing_if = "Option::is_none")]
2304 pub tag: Option<TagIdentifier>,
2305 #[serde(skip)]
2306 pub node_path: Option<NodePath>,
2307 #[serde(skip)]
2308 pub meta: Vec<Metadata>,
2309}
2310
2311#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2312#[ts(export_to = "Geometry.ts")]
2313#[serde(rename_all = "camelCase")]
2314pub enum UnsolvedSegmentKind {
2315 Point {
2316 position: UnsolvedPoint2dExpr,
2317 ctor: Box<PointCtor>,
2318 },
2319 Line {
2320 start: UnsolvedPoint2dExpr,
2321 end: UnsolvedPoint2dExpr,
2322 ctor: Box<LineCtor>,
2323 start_object_id: ObjectId,
2324 end_object_id: ObjectId,
2325 construction: bool,
2326 },
2327 Arc {
2328 start: UnsolvedPoint2dExpr,
2329 end: UnsolvedPoint2dExpr,
2330 center: UnsolvedPoint2dExpr,
2331 ctor: Box<ArcCtor>,
2332 start_object_id: ObjectId,
2333 end_object_id: ObjectId,
2334 center_object_id: ObjectId,
2335 construction: bool,
2336 },
2337 Circle {
2338 start: UnsolvedPoint2dExpr,
2339 center: UnsolvedPoint2dExpr,
2340 ctor: Box<CircleCtor>,
2341 start_object_id: ObjectId,
2342 center_object_id: ObjectId,
2343 construction: bool,
2344 },
2345 ControlPointSpline {
2346 controls: Vec<UnsolvedPoint2dExpr>,
2347 ctor: Box<ControlPointSplineCtor>,
2348 control_object_ids: Vec<ObjectId>,
2349 control_polygon_edge_object_ids: Vec<ObjectId>,
2350 degree: u32,
2351 construction: bool,
2352 },
2353}
2354
2355impl UnsolvedSegmentKind {
2356 pub fn human_friendly_kind_with_article(&self) -> &'static str {
2359 match self {
2360 Self::Point { .. } => "a Point",
2361 Self::Line { .. } => "a Line",
2362 Self::Arc { .. } => "an Arc",
2363 Self::Circle { .. } => "a Circle",
2364 Self::ControlPointSpline { .. } => "a Control Point Spline",
2365 }
2366 }
2367}
2368
2369#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2370#[ts(export_to = "Geometry.ts")]
2371#[serde(rename_all = "camelCase")]
2372pub struct Segment {
2373 pub id: Uuid,
2375 pub object_id: ObjectId,
2376 pub kind: SegmentKind,
2377 pub surface: SketchSurface,
2378 pub sketch_id: Uuid,
2380 #[serde(skip)]
2381 #[ts(skip)]
2382 pub sketch: Option<Arc<Sketch>>,
2383 #[serde(skip_serializing_if = "Option::is_none")]
2384 pub tag: Option<TagIdentifier>,
2385 #[serde(skip)]
2386 pub node_path: Option<NodePath>,
2387 #[serde(skip)]
2388 pub meta: Vec<Metadata>,
2389}
2390
2391impl Segment {
2392 pub fn is_construction(&self) -> bool {
2393 match &self.kind {
2394 SegmentKind::Point { .. } => true,
2395 SegmentKind::Line { construction, .. } => *construction,
2396 SegmentKind::Arc { construction, .. } => *construction,
2397 SegmentKind::Circle { construction, .. } => *construction,
2398 SegmentKind::ControlPointSpline { construction, .. } => *construction,
2399 }
2400 }
2401}
2402
2403#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2404#[ts(export_to = "Geometry.ts")]
2405#[serde(rename_all = "camelCase")]
2406pub enum SegmentKind {
2407 Point {
2408 position: [TyF64; 2],
2409 ctor: Box<PointCtor>,
2410 #[serde(skip_serializing_if = "Option::is_none")]
2411 freedom: Option<Freedom>,
2412 },
2413 Line {
2414 start: [TyF64; 2],
2415 end: [TyF64; 2],
2416 ctor: Box<LineCtor>,
2417 start_object_id: ObjectId,
2418 end_object_id: ObjectId,
2419 #[serde(skip_serializing_if = "Option::is_none")]
2420 start_freedom: Option<Freedom>,
2421 #[serde(skip_serializing_if = "Option::is_none")]
2422 end_freedom: Option<Freedom>,
2423 construction: bool,
2424 },
2425 Arc {
2426 start: [TyF64; 2],
2427 end: [TyF64; 2],
2428 center: [TyF64; 2],
2429 ctor: Box<ArcCtor>,
2430 start_object_id: ObjectId,
2431 end_object_id: ObjectId,
2432 center_object_id: ObjectId,
2433 #[serde(skip_serializing_if = "Option::is_none")]
2434 start_freedom: Option<Freedom>,
2435 #[serde(skip_serializing_if = "Option::is_none")]
2436 end_freedom: Option<Freedom>,
2437 #[serde(skip_serializing_if = "Option::is_none")]
2438 center_freedom: Option<Freedom>,
2439 construction: bool,
2440 },
2441 Circle {
2442 start: [TyF64; 2],
2443 center: [TyF64; 2],
2444 ctor: Box<CircleCtor>,
2445 start_object_id: ObjectId,
2446 center_object_id: ObjectId,
2447 #[serde(skip_serializing_if = "Option::is_none")]
2448 start_freedom: Option<Freedom>,
2449 #[serde(skip_serializing_if = "Option::is_none")]
2450 center_freedom: Option<Freedom>,
2451 construction: bool,
2452 },
2453 ControlPointSpline {
2454 controls: Vec<[TyF64; 2]>,
2455 ctor: Box<ControlPointSplineCtor>,
2456 control_object_ids: Vec<ObjectId>,
2457 control_polygon_edge_object_ids: Vec<ObjectId>,
2458 #[serde(skip_serializing_if = "Vec::is_empty")]
2459 control_freedoms: Vec<Option<Freedom>>,
2460 degree: u32,
2461 construction: bool,
2462 },
2463}
2464
2465#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2466#[ts(export_to = "Geometry.ts")]
2467#[serde(rename_all = "camelCase")]
2468pub struct AbstractSegment {
2469 pub repr: SegmentRepr,
2470 #[serde(skip)]
2471 pub meta: Vec<Metadata>,
2472}
2473
2474#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2475pub enum SegmentRepr {
2476 Unsolved { segment: Box<UnsolvedSegment> },
2477 Solved { segment: Box<Segment> },
2478}
2479
2480#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2481#[ts(export_to = "Geometry.ts")]
2482#[serde(rename_all = "camelCase")]
2483pub struct SketchConstraint {
2484 pub kind: SketchConstraintKind,
2485 #[serde(skip)]
2486 pub meta: Vec<Metadata>,
2487}
2488
2489#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2490#[ts(export_to = "Geometry.ts")]
2491#[serde(rename_all = "camelCase")]
2492pub enum SketchConstraintKind {
2493 Angle {
2494 line0: ConstrainableLine2d,
2495 line1: ConstrainableLine2d,
2496 },
2497 Distance {
2498 points: [ConstrainablePoint2dOrOrigin; 2],
2499 #[serde(rename = "labelPosition")]
2500 #[serde(skip_serializing_if = "Option::is_none")]
2501 #[ts(rename = "labelPosition")]
2502 #[ts(optional)]
2503 label_position: Option<ApiPoint2d<Number>>,
2504 },
2505 PointLineDistance {
2506 point: ConstrainablePoint2dOrOrigin,
2507 line: ConstrainableLine2d,
2508 input_object_ids: [Option<ObjectId>; 2],
2509 #[serde(rename = "labelPosition")]
2510 #[serde(skip_serializing_if = "Option::is_none")]
2511 #[ts(rename = "labelPosition")]
2512 #[ts(optional)]
2513 label_position: Option<ApiPoint2d<Number>>,
2514 },
2515 LineLineDistance {
2516 line0: ConstrainableLine2d,
2517 line1: ConstrainableLine2d,
2518 input_object_ids: [ObjectId; 2],
2519 #[serde(rename = "labelPosition")]
2520 #[serde(skip_serializing_if = "Option::is_none")]
2521 #[ts(rename = "labelPosition")]
2522 #[ts(optional)]
2523 label_position: Option<ApiPoint2d<Number>>,
2524 },
2525 PointCircularDistance {
2526 point: ConstrainablePoint2dOrOrigin,
2527 center: ConstrainablePoint2d,
2528 start: ConstrainablePoint2d,
2529 end: Option<ConstrainablePoint2d>,
2530 input_object_ids: [Option<ObjectId>; 2],
2531 #[serde(rename = "labelPosition")]
2532 #[serde(skip_serializing_if = "Option::is_none")]
2533 #[ts(rename = "labelPosition")]
2534 #[ts(optional)]
2535 label_position: Option<ApiPoint2d<Number>>,
2536 },
2537 LineCircularDistance {
2538 line: ConstrainableLine2d,
2539 center: ConstrainablePoint2d,
2540 start: ConstrainablePoint2d,
2541 end: Option<ConstrainablePoint2d>,
2542 input_object_ids: [ObjectId; 2],
2543 #[serde(rename = "labelPosition")]
2544 #[serde(skip_serializing_if = "Option::is_none")]
2545 #[ts(rename = "labelPosition")]
2546 #[ts(optional)]
2547 label_position: Option<ApiPoint2d<Number>>,
2548 },
2549 CircularCircularDistance {
2550 center0: ConstrainablePoint2d,
2551 start0: ConstrainablePoint2d,
2552 end0: Option<ConstrainablePoint2d>,
2553 center1: ConstrainablePoint2d,
2554 start1: ConstrainablePoint2d,
2555 end1: Option<ConstrainablePoint2d>,
2556 input_object_ids: [ObjectId; 2],
2557 #[serde(rename = "labelPosition")]
2558 #[serde(skip_serializing_if = "Option::is_none")]
2559 #[ts(rename = "labelPosition")]
2560 #[ts(optional)]
2561 label_position: Option<ApiPoint2d<Number>>,
2562 },
2563 Radius {
2564 points: [ConstrainablePoint2d; 2],
2565 #[serde(rename = "labelPosition")]
2566 #[serde(skip_serializing_if = "Option::is_none")]
2567 #[ts(rename = "labelPosition")]
2568 #[ts(optional)]
2569 label_position: Option<ApiPoint2d<Number>>,
2570 },
2571 Diameter {
2572 points: [ConstrainablePoint2d; 2],
2573 #[serde(rename = "labelPosition")]
2574 #[serde(skip_serializing_if = "Option::is_none")]
2575 #[ts(rename = "labelPosition")]
2576 #[ts(optional)]
2577 label_position: Option<ApiPoint2d<Number>>,
2578 },
2579 HorizontalDistance {
2580 points: [ConstrainablePoint2dOrOrigin; 2],
2581 #[serde(rename = "labelPosition")]
2582 #[serde(skip_serializing_if = "Option::is_none")]
2583 #[ts(rename = "labelPosition")]
2584 #[ts(optional)]
2585 label_position: Option<ApiPoint2d<Number>>,
2586 },
2587 VerticalDistance {
2588 points: [ConstrainablePoint2dOrOrigin; 2],
2589 #[serde(rename = "labelPosition")]
2590 #[serde(skip_serializing_if = "Option::is_none")]
2591 #[ts(rename = "labelPosition")]
2592 #[ts(optional)]
2593 label_position: Option<ApiPoint2d<Number>>,
2594 },
2595}
2596
2597impl SketchConstraintKind {
2598 pub fn name(&self) -> &'static str {
2599 match self {
2600 SketchConstraintKind::Angle { .. } => "angle",
2601 SketchConstraintKind::Distance { .. } => "distance",
2602 SketchConstraintKind::PointLineDistance { .. } => "distance",
2603 SketchConstraintKind::LineLineDistance { .. } => "distance",
2604 SketchConstraintKind::PointCircularDistance { .. } => "distance",
2605 SketchConstraintKind::LineCircularDistance { .. } => "distance",
2606 SketchConstraintKind::CircularCircularDistance { .. } => "distance",
2607 SketchConstraintKind::Radius { .. } => "radius",
2608 SketchConstraintKind::Diameter { .. } => "diameter",
2609 SketchConstraintKind::HorizontalDistance { .. } => "horizontalDistance",
2610 SketchConstraintKind::VerticalDistance { .. } => "verticalDistance",
2611 }
2612 }
2613}