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
412impl SolidOrImportedGeometry {
413 pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
414 match self {
415 SolidOrImportedGeometry::ImportedGeometry(s) => {
416 let id = s.id(ctx).await?;
417
418 Ok(vec![id])
419 }
420 SolidOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
421 }
422 }
423}
424
425#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
427#[ts(export)]
428#[serde(rename_all = "camelCase")]
429pub struct Helix {
430 pub value: uuid::Uuid,
432 pub artifact_id: ArtifactId,
434 pub revolutions: f64,
436 pub angle_start: f64,
438 pub ccw: bool,
440 pub cylinder_id: Option<uuid::Uuid>,
442 pub units: UnitLength,
443 #[serde(skip)]
444 pub meta: Vec<Metadata>,
445}
446
447#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
448#[ts(export)]
449#[serde(rename_all = "camelCase")]
450pub struct Plane {
451 pub id: uuid::Uuid,
453 pub artifact_id: ArtifactId,
455 #[serde(skip_serializing_if = "Option::is_none")]
458 pub object_id: Option<ObjectId>,
459 pub kind: PlaneKind,
461 #[serde(flatten)]
463 pub info: PlaneInfo,
464 #[serde(skip)]
465 pub meta: Vec<Metadata>,
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS)]
469#[ts(export)]
470#[serde(rename_all = "camelCase")]
471pub struct PlaneInfo {
472 pub origin: Point3d,
474 pub x_axis: Point3d,
476 pub y_axis: Point3d,
478 pub z_axis: Point3d,
480}
481
482impl PlaneInfo {
483 pub(crate) fn into_plane_data(self) -> PlaneData {
484 if self.origin.is_zero() {
485 match self {
486 Self {
487 origin:
488 Point3d {
489 x: 0.0,
490 y: 0.0,
491 z: 0.0,
492 units: Some(UnitLength::Millimeters),
493 },
494 x_axis:
495 Point3d {
496 x: 1.0,
497 y: 0.0,
498 z: 0.0,
499 units: _,
500 },
501 y_axis:
502 Point3d {
503 x: 0.0,
504 y: 1.0,
505 z: 0.0,
506 units: _,
507 },
508 z_axis: _,
509 } => return PlaneData::XY,
510 Self {
511 origin:
512 Point3d {
513 x: 0.0,
514 y: 0.0,
515 z: 0.0,
516 units: Some(UnitLength::Millimeters),
517 },
518 x_axis:
519 Point3d {
520 x: -1.0,
521 y: 0.0,
522 z: 0.0,
523 units: _,
524 },
525 y_axis:
526 Point3d {
527 x: 0.0,
528 y: 1.0,
529 z: 0.0,
530 units: _,
531 },
532 z_axis: _,
533 } => return PlaneData::NegXY,
534 Self {
535 origin:
536 Point3d {
537 x: 0.0,
538 y: 0.0,
539 z: 0.0,
540 units: Some(UnitLength::Millimeters),
541 },
542 x_axis:
543 Point3d {
544 x: 1.0,
545 y: 0.0,
546 z: 0.0,
547 units: _,
548 },
549 y_axis:
550 Point3d {
551 x: 0.0,
552 y: 0.0,
553 z: 1.0,
554 units: _,
555 },
556 z_axis: _,
557 } => return PlaneData::XZ,
558 Self {
559 origin:
560 Point3d {
561 x: 0.0,
562 y: 0.0,
563 z: 0.0,
564 units: Some(UnitLength::Millimeters),
565 },
566 x_axis:
567 Point3d {
568 x: -1.0,
569 y: 0.0,
570 z: 0.0,
571 units: _,
572 },
573 y_axis:
574 Point3d {
575 x: 0.0,
576 y: 0.0,
577 z: 1.0,
578 units: _,
579 },
580 z_axis: _,
581 } => return PlaneData::NegXZ,
582 Self {
583 origin:
584 Point3d {
585 x: 0.0,
586 y: 0.0,
587 z: 0.0,
588 units: Some(UnitLength::Millimeters),
589 },
590 x_axis:
591 Point3d {
592 x: 0.0,
593 y: 1.0,
594 z: 0.0,
595 units: _,
596 },
597 y_axis:
598 Point3d {
599 x: 0.0,
600 y: 0.0,
601 z: 1.0,
602 units: _,
603 },
604 z_axis: _,
605 } => return PlaneData::YZ,
606 Self {
607 origin:
608 Point3d {
609 x: 0.0,
610 y: 0.0,
611 z: 0.0,
612 units: Some(UnitLength::Millimeters),
613 },
614 x_axis:
615 Point3d {
616 x: 0.0,
617 y: -1.0,
618 z: 0.0,
619 units: _,
620 },
621 y_axis:
622 Point3d {
623 x: 0.0,
624 y: 0.0,
625 z: 1.0,
626 units: _,
627 },
628 z_axis: _,
629 } => return PlaneData::NegYZ,
630 _ => {}
631 }
632 }
633
634 PlaneData::Plane(Self {
635 origin: self.origin,
636 x_axis: self.x_axis,
637 y_axis: self.y_axis,
638 z_axis: self.z_axis,
639 })
640 }
641
642 pub(crate) fn is_right_handed(&self) -> bool {
643 let lhs = self
646 .x_axis
647 .axes_cross_product(&self.y_axis)
648 .axes_dot_product(&self.z_axis);
649 let rhs_x = self.x_axis.axes_dot_product(&self.x_axis);
650 let rhs_y = self.y_axis.axes_dot_product(&self.y_axis);
651 let rhs_z = self.z_axis.axes_dot_product(&self.z_axis);
652 let rhs = (rhs_x * rhs_y * rhs_z).sqrt();
653 (lhs - rhs).abs() <= 0.0001
655 }
656
657 #[cfg(test)]
658 pub(crate) fn is_left_handed(&self) -> bool {
659 !self.is_right_handed()
660 }
661
662 pub(crate) fn make_right_handed(self) -> Self {
663 if self.is_right_handed() {
664 return self;
665 }
666 Self {
668 origin: self.origin,
669 x_axis: self.x_axis.negated(),
670 y_axis: self.y_axis,
671 z_axis: self.z_axis,
672 }
673 }
674}
675
676impl TryFrom<PlaneData> for PlaneInfo {
677 type Error = KclError;
678
679 fn try_from(value: PlaneData) -> Result<Self, Self::Error> {
680 let name = match value {
681 PlaneData::XY => PlaneName::Xy,
682 PlaneData::NegXY => PlaneName::NegXy,
683 PlaneData::XZ => PlaneName::Xz,
684 PlaneData::NegXZ => PlaneName::NegXz,
685 PlaneData::YZ => PlaneName::Yz,
686 PlaneData::NegYZ => PlaneName::NegYz,
687 PlaneData::Plane(info) => {
688 return Ok(info);
689 }
690 };
691
692 let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
693 KclError::new_internal(KclErrorDetails::new(
694 format!("Plane {name} not found"),
695 Default::default(),
696 ))
697 })?;
698
699 Ok(info.clone())
700 }
701}
702
703impl From<&PlaneData> for PlaneKind {
704 fn from(value: &PlaneData) -> Self {
705 match value {
706 PlaneData::XY => PlaneKind::XY,
707 PlaneData::NegXY => PlaneKind::XY,
708 PlaneData::XZ => PlaneKind::XZ,
709 PlaneData::NegXZ => PlaneKind::XZ,
710 PlaneData::YZ => PlaneKind::YZ,
711 PlaneData::NegYZ => PlaneKind::YZ,
712 PlaneData::Plane(_) => PlaneKind::Custom,
713 }
714 }
715}
716
717impl From<&PlaneInfo> for PlaneKind {
718 fn from(value: &PlaneInfo) -> Self {
719 let data = PlaneData::Plane(value.clone());
720 PlaneKind::from(&data)
721 }
722}
723
724impl From<PlaneInfo> for PlaneKind {
725 fn from(value: PlaneInfo) -> Self {
726 let data = PlaneData::Plane(value);
727 PlaneKind::from(&data)
728 }
729}
730
731impl Plane {
732 #[cfg(test)]
733 pub(crate) fn from_plane_data_skipping_engine(
734 value: PlaneData,
735 exec_state: &mut ExecState,
736 ) -> Result<Self, KclError> {
737 let id = exec_state.next_uuid();
738 let kind = PlaneKind::from(&value);
739 Ok(Plane {
740 id,
741 artifact_id: id.into(),
742 info: PlaneInfo::try_from(value)?,
743 object_id: None,
744 kind,
745 meta: vec![],
746 })
747 }
748
749 pub fn is_initialized(&self) -> bool {
751 self.object_id.is_some()
752 }
753
754 pub fn is_uninitialized(&self) -> bool {
756 !self.is_initialized()
757 }
758
759 pub fn is_standard(&self) -> bool {
761 match &self.kind {
762 PlaneKind::XY | PlaneKind::YZ | PlaneKind::XZ => true,
763 PlaneKind::Custom => false,
764 }
765 }
766
767 pub fn project(&self, point: Point3d) -> Point3d {
770 let v = point - self.info.origin;
771 let dot = v.axes_dot_product(&self.info.z_axis);
772
773 point - self.info.z_axis * dot
774 }
775}
776
777#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
779#[ts(export)]
780#[serde(rename_all = "camelCase")]
781pub struct Face {
782 pub id: uuid::Uuid,
784 pub artifact_id: ArtifactId,
786 pub object_id: ObjectId,
788 pub value: String,
790 pub x_axis: Point3d,
792 pub y_axis: Point3d,
794 pub parent_solid: FaceParentSolid,
796 pub units: UnitLength,
797 #[serde(skip)]
798 pub meta: Vec<Metadata>,
799}
800
801#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
803#[ts(export)]
804#[serde(rename_all = "camelCase")]
805pub struct FaceParentSolid {
806 pub solid_id: Uuid,
808 pub creator_sketch_id: Option<Uuid>,
810 pub creator_sketch_is_closed: Option<ProfileClosed>,
812 #[serde(default, skip_serializing_if = "Vec::is_empty")]
814 pub edge_cut_ids: Vec<Uuid>,
815}
816
817impl FaceParentSolid {
818 pub(crate) fn sketch_or_solid_id(&self) -> Uuid {
819 self.creator_sketch_id.unwrap_or(self.solid_id)
820 }
821}
822
823#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
826#[ts(export)]
827#[serde(rename_all = "camelCase")]
828pub struct BoundedEdge {
829 pub face_id: uuid::Uuid,
831 #[serde(skip_serializing_if = "Option::is_none")]
833 pub edge_id: Option<uuid::Uuid>,
834 #[serde(skip_serializing_if = "Option::is_none")]
836 pub edge_specifier: Option<UnresolvedEdgeSpecifier>,
837 pub lower_bound: f32,
840 pub upper_bound: f32,
843}
844
845#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS, FromStr, Display)]
847#[ts(export)]
848#[display(style = "camelCase")]
849pub enum PlaneKind {
850 #[serde(rename = "XY", alias = "xy")]
851 #[display("XY")]
852 XY,
853 #[serde(rename = "XZ", alias = "xz")]
854 #[display("XZ")]
855 XZ,
856 #[serde(rename = "YZ", alias = "yz")]
857 #[display("YZ")]
858 YZ,
859 #[display("Custom")]
861 Custom,
862}
863
864#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
865#[ts(export)]
866#[serde(tag = "type", rename_all = "camelCase")]
867pub struct Sketch {
868 pub id: uuid::Uuid,
870 pub paths: Vec<Path>,
874 #[serde(default, skip_serializing_if = "Vec::is_empty")]
876 pub inner_paths: Vec<Path>,
877 pub on: SketchSurface,
879 pub start: BasePath,
881 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
883 pub tags: IndexMap<String, TagIdentifier>,
884 pub artifact_id: ArtifactId,
887 #[ts(skip)]
888 pub original_id: uuid::Uuid,
889 #[serde(skip_serializing_if = "Option::is_none")]
894 #[ts(skip)]
895 pub origin_sketch_id: Option<uuid::Uuid>,
896 #[serde(skip)]
898 pub mirror: Option<uuid::Uuid>,
899 #[serde(skip)]
901 pub clone: Option<uuid::Uuid>,
902 #[serde(skip)]
904 #[ts(skip)]
905 pub synthetic_jump_path_ids: Vec<uuid::Uuid>,
906 pub units: UnitLength,
907 #[serde(skip)]
909 pub meta: Vec<Metadata>,
910 #[serde(
913 default = "ProfileClosed::explicitly",
914 skip_serializing_if = "ProfileClosed::is_explicitly"
915 )]
916 pub is_closed: ProfileClosed,
917}
918
919impl ProfileClosed {
920 #[expect(dead_code, reason = "it's not actually dead, it's called by serde")]
921 fn explicitly() -> Self {
922 Self::Explicitly
923 }
924
925 fn is_explicitly(&self) -> bool {
926 matches!(self, ProfileClosed::Explicitly)
927 }
928}
929
930#[derive(Debug, Serialize, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd, ts_rs::TS)]
932#[serde(rename_all = "camelCase")]
933pub enum ProfileClosed {
934 No,
936 Maybe,
938 Implicitly,
940 Explicitly,
942}
943
944impl Sketch {
945 pub(crate) fn build_sketch_mode_cmds(
948 &self,
949 exec_state: &mut ExecState,
950 inner_cmd: ModelingCmdReq,
951 ) -> Vec<ModelingCmdReq> {
952 vec![
953 ModelingCmdReq {
956 cmd: ModelingCmd::from(
957 mcmd::EnableSketchMode::builder()
958 .animated(false)
959 .ortho(false)
960 .entity_id(self.on.id())
961 .adjust_camera(false)
962 .maybe_planar_normal(if let SketchSurface::Plane(plane) = &self.on {
963 let normal = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
965 Some(normal.into())
966 } else {
967 None
968 })
969 .build(),
970 ),
971 cmd_id: exec_state.next_uuid().into(),
972 },
973 inner_cmd,
974 ModelingCmdReq {
975 cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::builder().build()),
976 cmd_id: exec_state.next_uuid().into(),
977 },
978 ]
979 }
980}
981
982#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
984#[ts(export)]
985#[serde(tag = "type", rename_all = "camelCase")]
986pub enum SketchSurface {
987 Plane(Box<Plane>),
988 Face(Box<Face>),
989}
990
991impl SketchSurface {
992 pub(crate) fn id(&self) -> uuid::Uuid {
993 match self {
994 SketchSurface::Plane(plane) => plane.id,
995 SketchSurface::Face(face) => face.id,
996 }
997 }
998 pub(crate) fn x_axis(&self) -> Point3d {
999 match self {
1000 SketchSurface::Plane(plane) => plane.info.x_axis,
1001 SketchSurface::Face(face) => face.x_axis,
1002 }
1003 }
1004 pub(crate) fn y_axis(&self) -> Point3d {
1005 match self {
1006 SketchSurface::Plane(plane) => plane.info.y_axis,
1007 SketchSurface::Face(face) => face.y_axis,
1008 }
1009 }
1010
1011 pub(crate) fn object_id(&self) -> Option<ObjectId> {
1012 match self {
1013 SketchSurface::Plane(plane) => plane.object_id,
1014 SketchSurface::Face(face) => Some(face.object_id),
1015 }
1016 }
1017
1018 pub(crate) fn set_object_id(&mut self, object_id: ObjectId) {
1019 match self {
1020 SketchSurface::Plane(plane) => plane.object_id = Some(object_id),
1021 SketchSurface::Face(face) => face.object_id = object_id,
1022 }
1023 }
1024}
1025
1026#[derive(Debug, Clone, PartialEq)]
1028pub enum Extrudable {
1029 Sketch(Box<Sketch>),
1031 FaceTag(FaceTag),
1033 Face(Box<Face>),
1035 EdgeTag(Box<TagIdentifier>),
1037 Edge(Uuid),
1039}
1040
1041impl Extrudable {
1042 pub async fn id_to_extrude(
1044 &self,
1045 exec_state: &mut ExecState,
1046 args: &Args,
1047 must_be_planar: bool,
1048 ) -> Result<uuid::Uuid, KclError> {
1049 match self {
1050 Extrudable::Sketch(sketch) => Ok(sketch.id),
1051 Extrudable::FaceTag(face_tag) => face_tag.get_face_id_from_tag(exec_state, args, must_be_planar).await,
1052 Extrudable::Face(face) => Ok(face.id),
1053 Extrudable::EdgeTag(edge_tag) => match edge_tag.get_cur_info() {
1054 Some(info) => Ok(info.id),
1055 None => Err(KclError::new_type(KclErrorDetails::new(
1056 "Could not find a valid id to extrude".to_owned(),
1057 vec![args.source_range],
1058 ))),
1059 },
1060 Extrudable::Edge(edge) => Ok(*edge),
1061 }
1062 }
1063
1064 pub fn as_sketch(&self) -> Option<Sketch> {
1065 match self {
1066 Extrudable::Sketch(sketch) => Some((**sketch).clone()),
1067 Extrudable::FaceTag(face) => match face.geometry() {
1068 Some(Geometry::Sketch(sketch)) => Some(sketch),
1069 Some(Geometry::Solid(solid)) => solid.sketch().cloned(),
1070 None => None,
1071 },
1072 Extrudable::Face(_) => None,
1073 Extrudable::EdgeTag(tag_identifier) => match tag_identifier.geometry() {
1074 Some(Geometry::Sketch(sketch)) => Some(sketch),
1075 Some(Geometry::Solid(solid)) => solid.sketch().cloned(),
1076 None => None,
1077 },
1078 Extrudable::Edge(_) => None,
1079 }
1080 }
1081
1082 pub fn is_closed(&self) -> ProfileClosed {
1083 match self {
1084 Extrudable::Sketch(sketch) => sketch.is_closed,
1085 Extrudable::FaceTag(face_tag) => match face_tag.geometry() {
1086 Some(Geometry::Sketch(sketch)) => sketch.is_closed,
1087 Some(Geometry::Solid(solid)) => solid
1088 .sketch()
1089 .map(|sketch| sketch.is_closed)
1090 .unwrap_or(ProfileClosed::Maybe),
1091 _ => ProfileClosed::Maybe,
1092 },
1093 Extrudable::Face(face) => match face.parent_solid.creator_sketch_is_closed {
1094 Some(is_closed) => is_closed,
1095 None => ProfileClosed::Maybe,
1096 },
1097 Extrudable::EdgeTag(edge_tag) => match edge_tag.geometry() {
1098 Some(Geometry::Sketch(sketch)) => sketch.is_closed,
1099 Some(Geometry::Solid(solid)) => solid
1100 .sketch()
1101 .map(|sketch| sketch.is_closed)
1102 .unwrap_or(ProfileClosed::Maybe),
1103 _ => ProfileClosed::Maybe,
1104 },
1105 Extrudable::Edge(_) => ProfileClosed::Maybe,
1106 }
1107 }
1108}
1109
1110impl From<Sketch> for Extrudable {
1111 fn from(value: Sketch) -> Self {
1112 Extrudable::Sketch(Box::new(value))
1113 }
1114}
1115
1116#[derive(Debug, Clone)]
1117pub(crate) enum GetTangentialInfoFromPathsResult {
1118 PreviousPoint([f64; 2]),
1119 Arc {
1120 center: [f64; 2],
1121 ccw: bool,
1122 },
1123 Circle {
1124 center: [f64; 2],
1125 ccw: bool,
1126 radius: f64,
1127 },
1128 Ellipse {
1129 center: [f64; 2],
1130 ccw: bool,
1131 major_axis: [f64; 2],
1132 _minor_radius: f64,
1133 },
1134}
1135
1136impl GetTangentialInfoFromPathsResult {
1137 pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
1138 match self {
1139 GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
1140 GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
1141 crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
1142 }
1143 GetTangentialInfoFromPathsResult::Circle {
1146 center, radius, ccw, ..
1147 } => [center[0] + radius, center[1] + if *ccw { -1.0 } else { 1.0 }],
1148 GetTangentialInfoFromPathsResult::Ellipse {
1149 center,
1150 major_axis,
1151 ccw,
1152 ..
1153 } => [center[0] + major_axis[0], center[1] + if *ccw { -1.0 } else { 1.0 }],
1154 }
1155 }
1156}
1157
1158impl Sketch {
1159 pub(crate) fn add_tag(
1160 &mut self,
1161 tag: NodeRef<'_, TagDeclarator>,
1162 current_path: &Path,
1163 exec_state: &ExecState,
1164 surface: Option<&ExtrudeSurface>,
1165 ) {
1166 let mut tag_identifier: TagIdentifier = tag.into();
1167 let base = current_path.get_base();
1168 let mut sketch_copy = self.clone();
1169 sketch_copy.tags.clear();
1170 tag_identifier.info.push((
1171 exec_state.stack().current_epoch(),
1172 TagEngineInfo {
1173 id: base.geo_meta.id,
1174 geometry: Geometry::Sketch(sketch_copy),
1175 path: Some(current_path.clone()),
1176 surface: surface.cloned(),
1177 },
1178 ));
1179
1180 self.tags.insert(tag.name.to_string(), tag_identifier);
1181 }
1182
1183 pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) {
1184 for t in tags {
1185 match self.tags.get_mut(&t.value) {
1186 Some(id) => {
1187 id.merge_info(t);
1188 }
1189 None => {
1190 self.tags.insert(t.value.clone(), t.clone());
1191 }
1192 }
1193 }
1194 }
1195
1196 pub(crate) fn latest_path(&self) -> Option<&Path> {
1198 self.paths.last()
1199 }
1200
1201 pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
1205 let Some(path) = self.latest_path() else {
1206 return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
1207 };
1208
1209 let to = path.get_base().to;
1210 Ok(Point2d::new(to[0], to[1], path.get_base().units))
1211 }
1212
1213 pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
1214 let Some(path) = self.latest_path() else {
1215 return GetTangentialInfoFromPathsResult::PreviousPoint(self.start.to);
1216 };
1217 path.get_tangential_info()
1218 }
1219}
1220
1221#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1222#[ts(export)]
1223#[serde(tag = "type", rename_all = "camelCase")]
1224pub struct Solid {
1225 pub id: uuid::Uuid,
1227 #[serde(skip)]
1229 #[ts(skip)]
1230 pub value_id: uuid::Uuid,
1231 pub artifact_id: ArtifactId,
1233 pub value: Vec<ExtrudeSurface>,
1235 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
1238 pub faces: IndexMap<String, TagIdentifier>,
1239 #[serde(rename = "sketch")]
1241 pub creator: SolidCreator,
1242 pub start_cap_id: Option<uuid::Uuid>,
1244 pub end_cap_id: Option<uuid::Uuid>,
1246 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1248 pub edge_cuts: Vec<EdgeCut>,
1249 #[serde(skip)]
1251 #[ts(skip)]
1252 pub pending_edge_cut_ids: Vec<uuid::Uuid>,
1253 pub units: UnitLength,
1255 pub sectional: bool,
1257 #[serde(skip)]
1259 pub meta: Vec<Metadata>,
1260}
1261
1262#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1263#[ts(export)]
1264pub struct CreatorFace {
1265 pub face_id: uuid::Uuid,
1267 pub solid_id: uuid::Uuid,
1269 pub sketch: Sketch,
1271}
1272
1273#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1274#[ts(export)]
1275pub struct CreatorEdge {
1276 pub edge_id: uuid::Uuid,
1278 pub body_id: uuid::Uuid,
1280}
1281
1282#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1284#[ts(export)]
1285#[serde(tag = "creatorType", rename_all = "camelCase")]
1286pub enum SolidCreator {
1287 Sketch(Sketch),
1289 Face(CreatorFace),
1291 Edge(CreatorEdge),
1293 Procedural,
1295}
1296
1297impl Solid {
1298 pub fn sketch(&self) -> Option<&Sketch> {
1299 match &self.creator {
1300 SolidCreator::Sketch(sketch) => Some(sketch),
1301 SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1302 SolidCreator::Edge(_) => None,
1303 SolidCreator::Procedural => None,
1304 }
1305 }
1306
1307 pub fn sketch_mut(&mut self) -> Option<&mut Sketch> {
1308 match &mut self.creator {
1309 SolidCreator::Sketch(sketch) => Some(sketch),
1310 SolidCreator::Face(CreatorFace { sketch, .. }) => Some(sketch),
1311 SolidCreator::Edge(_) => None,
1312 SolidCreator::Procedural => None,
1313 }
1314 }
1315
1316 pub fn sketch_id(&self) -> Option<uuid::Uuid> {
1317 self.sketch().map(|sketch| sketch.id)
1318 }
1319
1320 pub fn original_id(&self) -> uuid::Uuid {
1321 self.sketch().map(|sketch| sketch.original_id).unwrap_or(self.id)
1322 }
1323
1324 pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
1325 self.edge_cuts
1326 .iter()
1327 .map(|foc| foc.id())
1328 .chain(self.pending_edge_cut_ids.iter().copied())
1329 }
1330}
1331
1332impl From<&Solid> for FaceParentSolid {
1333 fn from(solid: &Solid) -> Self {
1334 Self {
1335 solid_id: solid.id,
1336 creator_sketch_id: solid.sketch_id(),
1337 creator_sketch_is_closed: solid.sketch().map(|sketch| sketch.is_closed),
1338 edge_cut_ids: solid.get_all_edge_cut_ids().collect(),
1339 }
1340 }
1341}
1342
1343#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1345#[ts(export)]
1346#[serde(tag = "type", rename_all = "camelCase")]
1347pub enum EdgeCut {
1348 Fillet {
1350 id: uuid::Uuid,
1352 radius: TyF64,
1353 #[serde(rename = "edgeId")]
1355 edge_id: uuid::Uuid,
1356 tag: Box<Option<TagNode>>,
1357 },
1358 Chamfer {
1360 id: uuid::Uuid,
1362 length: TyF64,
1363 #[serde(rename = "edgeId")]
1365 edge_id: uuid::Uuid,
1366 tag: Box<Option<TagNode>>,
1367 },
1368}
1369
1370impl EdgeCut {
1371 pub fn id(&self) -> uuid::Uuid {
1372 match self {
1373 EdgeCut::Fillet { id, .. } => *id,
1374 EdgeCut::Chamfer { id, .. } => *id,
1375 }
1376 }
1377
1378 pub fn set_id(&mut self, id: uuid::Uuid) {
1379 match self {
1380 EdgeCut::Fillet { id: i, .. } => *i = id,
1381 EdgeCut::Chamfer { id: i, .. } => *i = id,
1382 }
1383 }
1384
1385 pub fn edge_id(&self) -> uuid::Uuid {
1386 match self {
1387 EdgeCut::Fillet { edge_id, .. } => *edge_id,
1388 EdgeCut::Chamfer { edge_id, .. } => *edge_id,
1389 }
1390 }
1391
1392 pub fn set_edge_id(&mut self, id: uuid::Uuid) {
1393 match self {
1394 EdgeCut::Fillet { edge_id: i, .. } => *i = id,
1395 EdgeCut::Chamfer { edge_id: i, .. } => *i = id,
1396 }
1397 }
1398
1399 pub fn tag(&self) -> Option<TagNode> {
1400 match self {
1401 EdgeCut::Fillet { tag, .. } => *tag.clone(),
1402 EdgeCut::Chamfer { tag, .. } => *tag.clone(),
1403 }
1404 }
1405}
1406
1407#[derive(Debug, Serialize, PartialEq, Clone, Copy, ts_rs::TS)]
1408#[ts(export)]
1409pub struct Point2d {
1410 pub x: f64,
1411 pub y: f64,
1412 pub units: UnitLength,
1413}
1414
1415impl Point2d {
1416 pub const ZERO: Self = Self {
1417 x: 0.0,
1418 y: 0.0,
1419 units: UnitLength::Millimeters,
1420 };
1421
1422 pub fn new(x: f64, y: f64, units: UnitLength) -> Self {
1423 Self { x, y, units }
1424 }
1425
1426 pub fn into_x(self) -> TyF64 {
1427 TyF64::new(self.x, NumericType::length(self.units))
1428 }
1429
1430 pub fn into_y(self) -> TyF64 {
1431 TyF64::new(self.y, NumericType::length(self.units))
1432 }
1433
1434 pub fn ignore_units(self) -> [f64; 2] {
1435 [self.x, self.y]
1436 }
1437}
1438
1439#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, Default)]
1440#[ts(export)]
1441pub struct Point3d {
1442 pub x: f64,
1443 pub y: f64,
1444 pub z: f64,
1445 pub units: Option<UnitLength>,
1446}
1447
1448impl Point3d {
1449 pub const ZERO: Self = Self {
1450 x: 0.0,
1451 y: 0.0,
1452 z: 0.0,
1453 units: Some(UnitLength::Millimeters),
1454 };
1455
1456 pub fn new(x: f64, y: f64, z: f64, units: Option<UnitLength>) -> Self {
1457 Self { x, y, z, units }
1458 }
1459
1460 pub const fn is_zero(&self) -> bool {
1461 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
1462 }
1463
1464 pub fn axes_cross_product(&self, other: &Self) -> Self {
1469 Self {
1470 x: self.y * other.z - self.z * other.y,
1471 y: self.z * other.x - self.x * other.z,
1472 z: self.x * other.y - self.y * other.x,
1473 units: None,
1474 }
1475 }
1476
1477 pub fn canonicalize_signed_zero(&mut self) {
1479 if self.x == 0.0 {
1480 self.x = 0.0;
1481 }
1482 if self.y == 0.0 {
1483 self.y = 0.0;
1484 }
1485 if self.z == 0.0 {
1486 self.z = 0.0;
1487 }
1488 }
1489
1490 pub fn axes_dot_product(&self, other: &Self) -> f64 {
1495 let x = self.x * other.x;
1496 let y = self.y * other.y;
1497 let z = self.z * other.z;
1498 x + y + z
1499 }
1500
1501 pub fn normalize(&self) -> Self {
1502 let len = f64::sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
1503 Point3d {
1504 x: self.x / len,
1505 y: self.y / len,
1506 z: self.z / len,
1507 units: None,
1508 }
1509 }
1510
1511 pub fn as_3_dims(&self) -> ([f64; 3], Option<UnitLength>) {
1512 let p = [self.x, self.y, self.z];
1513 let u = self.units;
1514 (p, u)
1515 }
1516
1517 pub(crate) fn negated(self) -> Self {
1518 Self {
1519 x: -self.x,
1520 y: -self.y,
1521 z: -self.z,
1522 units: self.units,
1523 }
1524 }
1525}
1526
1527impl From<[TyF64; 3]> for Point3d {
1528 fn from(p: [TyF64; 3]) -> Self {
1529 Self {
1530 x: p[0].n,
1531 y: p[1].n,
1532 z: p[2].n,
1533 units: p[0].ty.as_length(),
1534 }
1535 }
1536}
1537
1538impl From<Point3d> for Point3D {
1539 fn from(p: Point3d) -> Self {
1540 Self { x: p.x, y: p.y, z: p.z }
1541 }
1542}
1543
1544impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
1545 fn from(p: Point3d) -> Self {
1546 if let Some(units) = p.units {
1547 Self {
1548 x: LengthUnit(adjust_length(units, p.x, UnitLength::Millimeters).0),
1549 y: LengthUnit(adjust_length(units, p.y, UnitLength::Millimeters).0),
1550 z: LengthUnit(adjust_length(units, p.z, UnitLength::Millimeters).0),
1551 }
1552 } else {
1553 Self {
1554 x: LengthUnit(p.x),
1555 y: LengthUnit(p.y),
1556 z: LengthUnit(p.z),
1557 }
1558 }
1559 }
1560}
1561
1562impl Add for Point3d {
1563 type Output = Point3d;
1564
1565 fn add(self, rhs: Self) -> Self::Output {
1566 Point3d {
1568 x: self.x + rhs.x,
1569 y: self.y + rhs.y,
1570 z: self.z + rhs.z,
1571 units: self.units,
1572 }
1573 }
1574}
1575
1576impl AddAssign for Point3d {
1577 fn add_assign(&mut self, rhs: Self) {
1578 *self = *self + rhs
1579 }
1580}
1581
1582impl Sub for Point3d {
1583 type Output = Point3d;
1584
1585 fn sub(self, rhs: Self) -> Self::Output {
1586 let (x, y, z) = if rhs.units != self.units
1587 && let Some(sunits) = self.units
1588 && let Some(runits) = rhs.units
1589 {
1590 (
1591 adjust_length(runits, rhs.x, sunits).0,
1592 adjust_length(runits, rhs.y, sunits).0,
1593 adjust_length(runits, rhs.z, sunits).0,
1594 )
1595 } else {
1596 (rhs.x, rhs.y, rhs.z)
1597 };
1598 Point3d {
1599 x: self.x - x,
1600 y: self.y - y,
1601 z: self.z - z,
1602 units: self.units,
1603 }
1604 }
1605}
1606
1607impl SubAssign for Point3d {
1608 fn sub_assign(&mut self, rhs: Self) {
1609 *self = *self - rhs
1610 }
1611}
1612
1613impl Mul<f64> for Point3d {
1614 type Output = Point3d;
1615
1616 fn mul(self, rhs: f64) -> Self::Output {
1617 Point3d {
1618 x: self.x * rhs,
1619 y: self.y * rhs,
1620 z: self.z * rhs,
1621 units: self.units,
1622 }
1623 }
1624}
1625
1626#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1628#[ts(export)]
1629#[serde(rename_all = "camelCase")]
1630pub struct BasePath {
1631 #[ts(type = "[number, number]")]
1633 pub from: [f64; 2],
1634 #[ts(type = "[number, number]")]
1636 pub to: [f64; 2],
1637 pub units: UnitLength,
1638 pub tag: Option<TagNode>,
1640 #[serde(rename = "__geoMeta")]
1642 pub geo_meta: GeoMeta,
1643}
1644
1645impl BasePath {
1646 pub fn get_to(&self) -> [TyF64; 2] {
1647 let ty = NumericType::length(self.units);
1648 [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)]
1649 }
1650
1651 pub fn get_from(&self) -> [TyF64; 2] {
1652 let ty = NumericType::length(self.units);
1653 [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)]
1654 }
1655}
1656
1657#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1659#[ts(export)]
1660#[serde(rename_all = "camelCase")]
1661pub struct GeoMeta {
1662 pub id: uuid::Uuid,
1664 #[serde(flatten)]
1666 pub metadata: Metadata,
1667}
1668
1669#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
1671#[ts(export)]
1672#[serde(tag = "type")]
1673pub enum Path {
1674 ToPoint {
1676 #[serde(flatten)]
1677 base: BasePath,
1678 },
1679 TangentialArcTo {
1681 #[serde(flatten)]
1682 base: BasePath,
1683 #[ts(type = "[number, number]")]
1685 center: [f64; 2],
1686 ccw: bool,
1688 },
1689 TangentialArc {
1691 #[serde(flatten)]
1692 base: BasePath,
1693 #[ts(type = "[number, number]")]
1695 center: [f64; 2],
1696 ccw: bool,
1698 },
1699 Circle {
1702 #[serde(flatten)]
1703 base: BasePath,
1704 #[ts(type = "[number, number]")]
1706 center: [f64; 2],
1707 radius: f64,
1709 ccw: bool,
1712 },
1713 CircleThreePoint {
1714 #[serde(flatten)]
1715 base: BasePath,
1716 #[ts(type = "[number, number]")]
1718 p1: [f64; 2],
1719 #[ts(type = "[number, number]")]
1721 p2: [f64; 2],
1722 #[ts(type = "[number, number]")]
1724 p3: [f64; 2],
1725 },
1726 ArcThreePoint {
1727 #[serde(flatten)]
1728 base: BasePath,
1729 #[ts(type = "[number, number]")]
1731 p1: [f64; 2],
1732 #[ts(type = "[number, number]")]
1734 p2: [f64; 2],
1735 #[ts(type = "[number, number]")]
1737 p3: [f64; 2],
1738 },
1739 Horizontal {
1741 #[serde(flatten)]
1742 base: BasePath,
1743 x: f64,
1745 },
1746 AngledLineTo {
1748 #[serde(flatten)]
1749 base: BasePath,
1750 x: Option<f64>,
1752 y: Option<f64>,
1754 },
1755 Base {
1757 #[serde(flatten)]
1758 base: BasePath,
1759 },
1760 Arc {
1762 #[serde(flatten)]
1763 base: BasePath,
1764 center: [f64; 2],
1766 radius: f64,
1768 ccw: bool,
1770 },
1771 Ellipse {
1772 #[serde(flatten)]
1773 base: BasePath,
1774 center: [f64; 2],
1775 major_axis: [f64; 2],
1776 minor_radius: f64,
1777 ccw: bool,
1778 },
1779 Conic {
1781 #[serde(flatten)]
1782 base: BasePath,
1783 },
1784 Bezier {
1786 #[serde(flatten)]
1787 base: BasePath,
1788 #[ts(type = "[number, number]")]
1790 control1: [f64; 2],
1791 #[ts(type = "[number, number]")]
1793 control2: [f64; 2],
1794 },
1795}
1796
1797impl Path {
1798 pub fn get_id(&self) -> uuid::Uuid {
1799 match self {
1800 Path::ToPoint { base } => base.geo_meta.id,
1801 Path::Horizontal { base, .. } => base.geo_meta.id,
1802 Path::AngledLineTo { base, .. } => base.geo_meta.id,
1803 Path::Base { base } => base.geo_meta.id,
1804 Path::TangentialArcTo { base, .. } => base.geo_meta.id,
1805 Path::TangentialArc { base, .. } => base.geo_meta.id,
1806 Path::Circle { base, .. } => base.geo_meta.id,
1807 Path::CircleThreePoint { base, .. } => base.geo_meta.id,
1808 Path::Arc { base, .. } => base.geo_meta.id,
1809 Path::ArcThreePoint { base, .. } => base.geo_meta.id,
1810 Path::Ellipse { base, .. } => base.geo_meta.id,
1811 Path::Conic { base, .. } => base.geo_meta.id,
1812 Path::Bezier { base, .. } => base.geo_meta.id,
1813 }
1814 }
1815
1816 pub fn set_id(&mut self, id: uuid::Uuid) {
1817 match self {
1818 Path::ToPoint { base } => base.geo_meta.id = id,
1819 Path::Horizontal { base, .. } => base.geo_meta.id = id,
1820 Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
1821 Path::Base { base } => base.geo_meta.id = id,
1822 Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
1823 Path::TangentialArc { base, .. } => base.geo_meta.id = id,
1824 Path::Circle { base, .. } => base.geo_meta.id = id,
1825 Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
1826 Path::Arc { base, .. } => base.geo_meta.id = id,
1827 Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
1828 Path::Ellipse { base, .. } => base.geo_meta.id = id,
1829 Path::Conic { base, .. } => base.geo_meta.id = id,
1830 Path::Bezier { base, .. } => base.geo_meta.id = id,
1831 }
1832 }
1833
1834 pub fn get_tag(&self) -> Option<TagNode> {
1835 match self {
1836 Path::ToPoint { base } => base.tag.clone(),
1837 Path::Horizontal { base, .. } => base.tag.clone(),
1838 Path::AngledLineTo { base, .. } => base.tag.clone(),
1839 Path::Base { base } => base.tag.clone(),
1840 Path::TangentialArcTo { base, .. } => base.tag.clone(),
1841 Path::TangentialArc { base, .. } => base.tag.clone(),
1842 Path::Circle { base, .. } => base.tag.clone(),
1843 Path::CircleThreePoint { base, .. } => base.tag.clone(),
1844 Path::Arc { base, .. } => base.tag.clone(),
1845 Path::ArcThreePoint { base, .. } => base.tag.clone(),
1846 Path::Ellipse { base, .. } => base.tag.clone(),
1847 Path::Conic { base, .. } => base.tag.clone(),
1848 Path::Bezier { base, .. } => base.tag.clone(),
1849 }
1850 }
1851
1852 pub fn get_base(&self) -> &BasePath {
1853 match self {
1854 Path::ToPoint { base } => base,
1855 Path::Horizontal { base, .. } => base,
1856 Path::AngledLineTo { base, .. } => base,
1857 Path::Base { base } => base,
1858 Path::TangentialArcTo { base, .. } => base,
1859 Path::TangentialArc { base, .. } => base,
1860 Path::Circle { base, .. } => base,
1861 Path::CircleThreePoint { base, .. } => base,
1862 Path::Arc { base, .. } => base,
1863 Path::ArcThreePoint { base, .. } => base,
1864 Path::Ellipse { base, .. } => base,
1865 Path::Conic { base, .. } => base,
1866 Path::Bezier { base, .. } => base,
1867 }
1868 }
1869
1870 pub fn get_from(&self) -> [TyF64; 2] {
1872 let p = &self.get_base().from;
1873 let ty = NumericType::length(self.get_base().units);
1874 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1875 }
1876
1877 pub fn get_to(&self) -> [TyF64; 2] {
1879 let p = &self.get_base().to;
1880 let ty = NumericType::length(self.get_base().units);
1881 [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]
1882 }
1883
1884 pub fn start_point_components(&self) -> ([f64; 2], NumericType) {
1886 let p = &self.get_base().from;
1887 let ty = NumericType::length(self.get_base().units);
1888 (*p, ty)
1889 }
1890
1891 pub fn end_point_components(&self) -> ([f64; 2], NumericType) {
1893 let p = &self.get_base().to;
1894 let ty = NumericType::length(self.get_base().units);
1895 (*p, ty)
1896 }
1897
1898 pub fn length(&self) -> Option<TyF64> {
1901 let n = match self {
1902 Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
1903 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1904 }
1905 Self::TangentialArc {
1906 base: _,
1907 center,
1908 ccw: _,
1909 }
1910 | Self::TangentialArcTo {
1911 base: _,
1912 center,
1913 ccw: _,
1914 } => {
1915 let radius = linear_distance(&self.get_base().from, center);
1918 debug_assert_eq!(radius, linear_distance(&self.get_base().to, center));
1919 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1921 }
1922 Self::Circle { radius, .. } => Some(TAU * radius),
1923 Self::CircleThreePoint { .. } => {
1924 let circle_center = crate::std::utils::calculate_circle_from_3_points([
1925 self.get_base().from,
1926 self.get_base().to,
1927 self.get_base().to,
1928 ]);
1929 let radius = linear_distance(
1930 &[circle_center.center[0], circle_center.center[1]],
1931 &self.get_base().from,
1932 );
1933 Some(TAU * radius)
1934 }
1935 Self::Arc { .. } => {
1936 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1938 }
1939 Self::ArcThreePoint { .. } => {
1940 Some(linear_distance(&self.get_base().from, &self.get_base().to))
1942 }
1943 Self::Ellipse { .. } => {
1944 None
1946 }
1947 Self::Conic { .. } => {
1948 None
1950 }
1951 Self::Bezier { .. } => {
1952 None
1954 }
1955 };
1956 n.map(|n| TyF64::new(n, NumericType::length(self.get_base().units)))
1957 }
1958
1959 pub fn get_base_mut(&mut self) -> &mut BasePath {
1960 match self {
1961 Path::ToPoint { base } => base,
1962 Path::Horizontal { base, .. } => base,
1963 Path::AngledLineTo { base, .. } => base,
1964 Path::Base { base } => base,
1965 Path::TangentialArcTo { base, .. } => base,
1966 Path::TangentialArc { base, .. } => base,
1967 Path::Circle { base, .. } => base,
1968 Path::CircleThreePoint { base, .. } => base,
1969 Path::Arc { base, .. } => base,
1970 Path::ArcThreePoint { base, .. } => base,
1971 Path::Ellipse { base, .. } => base,
1972 Path::Conic { base, .. } => base,
1973 Path::Bezier { base, .. } => base,
1974 }
1975 }
1976
1977 pub(crate) fn get_tangential_info(&self) -> GetTangentialInfoFromPathsResult {
1978 match self {
1979 Path::TangentialArc { center, ccw, .. }
1980 | Path::TangentialArcTo { center, ccw, .. }
1981 | Path::Arc { center, ccw, .. } => GetTangentialInfoFromPathsResult::Arc {
1982 center: *center,
1983 ccw: *ccw,
1984 },
1985 Path::ArcThreePoint { p1, p2, p3, .. } => {
1986 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
1987 GetTangentialInfoFromPathsResult::Arc {
1988 center: circle.center,
1989 ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
1990 }
1991 }
1992 Path::Circle {
1993 center, ccw, radius, ..
1994 } => GetTangentialInfoFromPathsResult::Circle {
1995 center: *center,
1996 ccw: *ccw,
1997 radius: *radius,
1998 },
1999 Path::CircleThreePoint { p1, p2, p3, .. } => {
2000 let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
2001 let center_point = [circle.center[0], circle.center[1]];
2002 GetTangentialInfoFromPathsResult::Circle {
2003 center: center_point,
2004 ccw: true,
2006 radius: circle.radius,
2007 }
2008 }
2009 Path::Ellipse {
2011 center,
2012 major_axis,
2013 minor_radius,
2014 ccw,
2015 ..
2016 } => GetTangentialInfoFromPathsResult::Ellipse {
2017 center: *center,
2018 major_axis: *major_axis,
2019 _minor_radius: *minor_radius,
2020 ccw: *ccw,
2021 },
2022 Path::Conic { .. }
2023 | Path::ToPoint { .. }
2024 | Path::Horizontal { .. }
2025 | Path::AngledLineTo { .. }
2026 | Path::Base { .. }
2027 | Path::Bezier { .. } => {
2028 let base = self.get_base();
2029 GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
2030 }
2031 }
2032 }
2033
2034 pub(crate) fn is_straight_line(&self) -> bool {
2036 matches!(self, Path::AngledLineTo { .. } | Path::ToPoint { .. })
2037 }
2038}
2039
2040#[rustfmt::skip]
2042fn linear_distance(
2043 [x0, y0]: &[f64; 2],
2044 [x1, y1]: &[f64; 2]
2045) -> f64 {
2046 let y_sq = (y1 - y0).squared();
2047 let x_sq = (x1 - x0).squared();
2048 (y_sq + x_sq).sqrt()
2049}
2050
2051#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2053#[ts(export)]
2054#[serde(tag = "type", rename_all = "camelCase")]
2055pub enum ExtrudeSurface {
2056 ExtrudePlane(ExtrudePlane),
2058 ExtrudeArc(ExtrudeArc),
2059 Chamfer(ChamferSurface),
2060 Fillet(FilletSurface),
2061}
2062
2063#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2065#[ts(export)]
2066#[serde(rename_all = "camelCase")]
2067pub struct ChamferSurface {
2068 pub face_id: uuid::Uuid,
2070 pub tag: Option<Node<TagDeclarator>>,
2072 #[serde(flatten)]
2074 pub geo_meta: GeoMeta,
2075}
2076
2077#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2079#[ts(export)]
2080#[serde(rename_all = "camelCase")]
2081pub struct FilletSurface {
2082 pub face_id: uuid::Uuid,
2084 pub tag: Option<Node<TagDeclarator>>,
2086 #[serde(flatten)]
2088 pub geo_meta: GeoMeta,
2089}
2090
2091#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2093#[ts(export)]
2094#[serde(rename_all = "camelCase")]
2095pub struct ExtrudePlane {
2096 pub face_id: uuid::Uuid,
2098 pub tag: Option<Node<TagDeclarator>>,
2100 #[serde(flatten)]
2102 pub geo_meta: GeoMeta,
2103}
2104
2105#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2107#[ts(export)]
2108#[serde(rename_all = "camelCase")]
2109pub struct ExtrudeArc {
2110 pub face_id: uuid::Uuid,
2112 pub tag: Option<Node<TagDeclarator>>,
2114 #[serde(flatten)]
2116 pub geo_meta: GeoMeta,
2117}
2118
2119impl ExtrudeSurface {
2120 pub fn get_id(&self) -> uuid::Uuid {
2121 match self {
2122 ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
2123 ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
2124 ExtrudeSurface::Fillet(f) => f.geo_meta.id,
2125 ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
2126 }
2127 }
2128
2129 pub fn face_id(&self) -> uuid::Uuid {
2130 match self {
2131 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id,
2132 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id,
2133 ExtrudeSurface::Fillet(f) => f.face_id,
2134 ExtrudeSurface::Chamfer(c) => c.face_id,
2135 }
2136 }
2137
2138 pub fn set_face_id(&mut self, face_id: uuid::Uuid) {
2139 match self {
2140 ExtrudeSurface::ExtrudePlane(ep) => ep.face_id = face_id,
2141 ExtrudeSurface::ExtrudeArc(ea) => ea.face_id = face_id,
2142 ExtrudeSurface::Fillet(f) => f.face_id = face_id,
2143 ExtrudeSurface::Chamfer(c) => c.face_id = face_id,
2144 }
2145 }
2146
2147 pub fn set_surface_tag(&mut self, tag: &TagNode) {
2148 match self {
2149 ExtrudeSurface::ExtrudePlane(extrude_plane) => extrude_plane.tag = Some(tag.clone()),
2150 ExtrudeSurface::ExtrudeArc(extrude_arc) => extrude_arc.tag = Some(tag.clone()),
2151 ExtrudeSurface::Chamfer(chamfer) => chamfer.tag = Some(tag.clone()),
2152 ExtrudeSurface::Fillet(fillet) => fillet.tag = Some(tag.clone()),
2153 }
2154 }
2155
2156 pub fn get_tag(&self) -> Option<Node<TagDeclarator>> {
2157 match self {
2158 ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
2159 ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
2160 ExtrudeSurface::Fillet(f) => f.tag.clone(),
2161 ExtrudeSurface::Chamfer(c) => c.tag.clone(),
2162 }
2163 }
2164}
2165
2166#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ts_rs::TS)]
2167pub struct SketchVarId(pub usize);
2168
2169impl SketchVarId {
2170 pub const INVALID: Self = Self(usize::MAX);
2171
2172 pub fn to_constraint_id(self, range: SourceRange) -> Result<ezpz::Id, KclError> {
2173 self.0.try_into().map_err(|_| {
2174 KclError::new_type(KclErrorDetails::new(
2175 "Cannot convert to constraint ID since the sketch variable ID is too large".to_owned(),
2176 vec![range],
2177 ))
2178 })
2179 }
2180}
2181
2182#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2183#[ts(export_to = "Geometry.ts")]
2184#[serde(rename_all = "camelCase")]
2185pub struct SketchVar {
2186 pub id: SketchVarId,
2187 pub initial_value: f64,
2188 pub ty: NumericType,
2189 pub node_path: Option<NodePath>,
2191 #[serde(skip)]
2192 pub meta: Vec<Metadata>,
2193}
2194
2195impl SketchVar {
2196 pub fn initial_value_to_solver_units(
2197 &self,
2198 exec_state: &mut ExecState,
2199 source_range: SourceRange,
2200 description: &str,
2201 ) -> Result<TyF64, KclError> {
2202 let x_initial_value = KclValue::Number {
2203 value: self.initial_value,
2204 ty: self.ty,
2205 meta: vec![source_range.into()],
2206 };
2207 let normalized_value =
2208 normalize_to_solver_distance_unit(&x_initial_value, source_range, exec_state, description)?;
2209 normalized_value.as_ty_f64().ok_or_else(|| {
2210 let message = format!(
2211 "Expected number after coercion, but found {}",
2212 normalized_value.human_friendly_type()
2213 );
2214 debug_assert!(false, "{}", &message);
2215 KclError::new_internal(KclErrorDetails::new(message, vec![source_range]))
2216 })
2217 }
2218}
2219
2220#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2221#[ts(export_to = "Geometry.ts")]
2222#[serde(tag = "type")]
2223pub enum UnsolvedExpr {
2224 Known(TyF64),
2225 Unknown(SketchVarId),
2226}
2227
2228impl UnsolvedExpr {
2229 pub fn var(&self) -> Option<SketchVarId> {
2230 match self {
2231 UnsolvedExpr::Known(_) => None,
2232 UnsolvedExpr::Unknown(id) => Some(*id),
2233 }
2234 }
2235}
2236
2237pub type UnsolvedPoint2dExpr = [UnsolvedExpr; 2];
2238
2239#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2240#[ts(export_to = "Geometry.ts")]
2241#[serde(rename_all = "camelCase")]
2242pub struct ConstrainablePoint2d {
2243 pub vars: crate::front::Point2d<SketchVarId>,
2244 pub object_id: ObjectId,
2245}
2246
2247#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2248#[ts(export_to = "Geometry.ts")]
2249pub enum ConstrainablePoint2dOrOrigin {
2250 Point(ConstrainablePoint2d),
2251 Origin,
2252}
2253
2254#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2255#[ts(export_to = "Geometry.ts")]
2256#[serde(rename_all = "camelCase")]
2257pub struct ConstrainableLine2d {
2258 pub vars: [crate::front::Point2d<SketchVarId>; 2],
2259 pub object_id: ObjectId,
2260}
2261
2262#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2263#[ts(export_to = "Geometry.ts")]
2264#[serde(rename_all = "camelCase")]
2265pub struct UnsolvedSegment {
2266 pub id: Uuid,
2268 pub object_id: ObjectId,
2269 pub kind: UnsolvedSegmentKind,
2270 #[serde(skip_serializing_if = "Option::is_none")]
2271 pub tag: Option<TagIdentifier>,
2272 #[serde(skip)]
2273 pub node_path: Option<NodePath>,
2274 #[serde(skip)]
2275 pub meta: Vec<Metadata>,
2276}
2277
2278#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2279#[ts(export_to = "Geometry.ts")]
2280#[serde(rename_all = "camelCase")]
2281pub enum UnsolvedSegmentKind {
2282 Point {
2283 position: UnsolvedPoint2dExpr,
2284 ctor: Box<PointCtor>,
2285 },
2286 Line {
2287 start: UnsolvedPoint2dExpr,
2288 end: UnsolvedPoint2dExpr,
2289 ctor: Box<LineCtor>,
2290 start_object_id: ObjectId,
2291 end_object_id: ObjectId,
2292 construction: bool,
2293 },
2294 Arc {
2295 start: UnsolvedPoint2dExpr,
2296 end: UnsolvedPoint2dExpr,
2297 center: UnsolvedPoint2dExpr,
2298 ctor: Box<ArcCtor>,
2299 start_object_id: ObjectId,
2300 end_object_id: ObjectId,
2301 center_object_id: ObjectId,
2302 construction: bool,
2303 },
2304 Circle {
2305 start: UnsolvedPoint2dExpr,
2306 center: UnsolvedPoint2dExpr,
2307 ctor: Box<CircleCtor>,
2308 start_object_id: ObjectId,
2309 center_object_id: ObjectId,
2310 construction: bool,
2311 },
2312 ControlPointSpline {
2313 controls: Vec<UnsolvedPoint2dExpr>,
2314 ctor: Box<ControlPointSplineCtor>,
2315 control_object_ids: Vec<ObjectId>,
2316 control_polygon_edge_object_ids: Vec<ObjectId>,
2317 degree: u32,
2318 construction: bool,
2319 },
2320}
2321
2322impl UnsolvedSegmentKind {
2323 pub fn human_friendly_kind_with_article(&self) -> &'static str {
2326 match self {
2327 Self::Point { .. } => "a Point",
2328 Self::Line { .. } => "a Line",
2329 Self::Arc { .. } => "an Arc",
2330 Self::Circle { .. } => "a Circle",
2331 Self::ControlPointSpline { .. } => "a Control Point Spline",
2332 }
2333 }
2334}
2335
2336#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2337#[ts(export_to = "Geometry.ts")]
2338#[serde(rename_all = "camelCase")]
2339pub struct Segment {
2340 pub id: Uuid,
2342 pub object_id: ObjectId,
2343 pub kind: SegmentKind,
2344 pub surface: SketchSurface,
2345 pub sketch_id: Uuid,
2347 #[serde(skip)]
2348 #[ts(skip)]
2349 pub sketch: Option<Arc<Sketch>>,
2350 #[serde(skip_serializing_if = "Option::is_none")]
2351 pub tag: Option<TagIdentifier>,
2352 #[serde(skip)]
2353 pub node_path: Option<NodePath>,
2354 #[serde(skip)]
2355 pub meta: Vec<Metadata>,
2356}
2357
2358impl Segment {
2359 pub fn is_construction(&self) -> bool {
2360 match &self.kind {
2361 SegmentKind::Point { .. } => true,
2362 SegmentKind::Line { construction, .. } => *construction,
2363 SegmentKind::Arc { construction, .. } => *construction,
2364 SegmentKind::Circle { construction, .. } => *construction,
2365 SegmentKind::ControlPointSpline { construction, .. } => *construction,
2366 }
2367 }
2368}
2369
2370#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2371#[ts(export_to = "Geometry.ts")]
2372#[serde(rename_all = "camelCase")]
2373pub enum SegmentKind {
2374 Point {
2375 position: [TyF64; 2],
2376 ctor: Box<PointCtor>,
2377 #[serde(skip_serializing_if = "Option::is_none")]
2378 freedom: Option<Freedom>,
2379 },
2380 Line {
2381 start: [TyF64; 2],
2382 end: [TyF64; 2],
2383 ctor: Box<LineCtor>,
2384 start_object_id: ObjectId,
2385 end_object_id: ObjectId,
2386 #[serde(skip_serializing_if = "Option::is_none")]
2387 start_freedom: Option<Freedom>,
2388 #[serde(skip_serializing_if = "Option::is_none")]
2389 end_freedom: Option<Freedom>,
2390 construction: bool,
2391 },
2392 Arc {
2393 start: [TyF64; 2],
2394 end: [TyF64; 2],
2395 center: [TyF64; 2],
2396 ctor: Box<ArcCtor>,
2397 start_object_id: ObjectId,
2398 end_object_id: ObjectId,
2399 center_object_id: ObjectId,
2400 #[serde(skip_serializing_if = "Option::is_none")]
2401 start_freedom: Option<Freedom>,
2402 #[serde(skip_serializing_if = "Option::is_none")]
2403 end_freedom: Option<Freedom>,
2404 #[serde(skip_serializing_if = "Option::is_none")]
2405 center_freedom: Option<Freedom>,
2406 construction: bool,
2407 },
2408 Circle {
2409 start: [TyF64; 2],
2410 center: [TyF64; 2],
2411 ctor: Box<CircleCtor>,
2412 start_object_id: ObjectId,
2413 center_object_id: ObjectId,
2414 #[serde(skip_serializing_if = "Option::is_none")]
2415 start_freedom: Option<Freedom>,
2416 #[serde(skip_serializing_if = "Option::is_none")]
2417 center_freedom: Option<Freedom>,
2418 construction: bool,
2419 },
2420 ControlPointSpline {
2421 controls: Vec<[TyF64; 2]>,
2422 ctor: Box<ControlPointSplineCtor>,
2423 control_object_ids: Vec<ObjectId>,
2424 control_polygon_edge_object_ids: Vec<ObjectId>,
2425 #[serde(skip_serializing_if = "Vec::is_empty")]
2426 control_freedoms: Vec<Option<Freedom>>,
2427 degree: u32,
2428 construction: bool,
2429 },
2430}
2431
2432#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2433#[ts(export_to = "Geometry.ts")]
2434#[serde(rename_all = "camelCase")]
2435pub struct AbstractSegment {
2436 pub repr: SegmentRepr,
2437 #[serde(skip)]
2438 pub meta: Vec<Metadata>,
2439}
2440
2441#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2442pub enum SegmentRepr {
2443 Unsolved { segment: Box<UnsolvedSegment> },
2444 Solved { segment: Box<Segment> },
2445}
2446
2447#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2448#[ts(export_to = "Geometry.ts")]
2449#[serde(rename_all = "camelCase")]
2450pub struct SketchConstraint {
2451 pub kind: SketchConstraintKind,
2452 #[serde(skip)]
2453 pub meta: Vec<Metadata>,
2454}
2455
2456#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
2457#[ts(export_to = "Geometry.ts")]
2458#[serde(rename_all = "camelCase")]
2459pub enum SketchConstraintKind {
2460 Angle {
2461 line0: ConstrainableLine2d,
2462 line1: ConstrainableLine2d,
2463 },
2464 Distance {
2465 points: [ConstrainablePoint2dOrOrigin; 2],
2466 #[serde(rename = "labelPosition")]
2467 #[serde(skip_serializing_if = "Option::is_none")]
2468 #[ts(rename = "labelPosition")]
2469 #[ts(optional)]
2470 label_position: Option<ApiPoint2d<Number>>,
2471 },
2472 PointLineDistance {
2473 point: ConstrainablePoint2dOrOrigin,
2474 line: ConstrainableLine2d,
2475 input_object_ids: [Option<ObjectId>; 2],
2476 #[serde(rename = "labelPosition")]
2477 #[serde(skip_serializing_if = "Option::is_none")]
2478 #[ts(rename = "labelPosition")]
2479 #[ts(optional)]
2480 label_position: Option<ApiPoint2d<Number>>,
2481 },
2482 LineLineDistance {
2483 line0: ConstrainableLine2d,
2484 line1: ConstrainableLine2d,
2485 input_object_ids: [ObjectId; 2],
2486 #[serde(rename = "labelPosition")]
2487 #[serde(skip_serializing_if = "Option::is_none")]
2488 #[ts(rename = "labelPosition")]
2489 #[ts(optional)]
2490 label_position: Option<ApiPoint2d<Number>>,
2491 },
2492 PointCircularDistance {
2493 point: ConstrainablePoint2dOrOrigin,
2494 center: ConstrainablePoint2d,
2495 start: ConstrainablePoint2d,
2496 end: Option<ConstrainablePoint2d>,
2497 input_object_ids: [Option<ObjectId>; 2],
2498 #[serde(rename = "labelPosition")]
2499 #[serde(skip_serializing_if = "Option::is_none")]
2500 #[ts(rename = "labelPosition")]
2501 #[ts(optional)]
2502 label_position: Option<ApiPoint2d<Number>>,
2503 },
2504 LineCircularDistance {
2505 line: ConstrainableLine2d,
2506 center: ConstrainablePoint2d,
2507 start: ConstrainablePoint2d,
2508 end: Option<ConstrainablePoint2d>,
2509 input_object_ids: [ObjectId; 2],
2510 #[serde(rename = "labelPosition")]
2511 #[serde(skip_serializing_if = "Option::is_none")]
2512 #[ts(rename = "labelPosition")]
2513 #[ts(optional)]
2514 label_position: Option<ApiPoint2d<Number>>,
2515 },
2516 CircularCircularDistance {
2517 center0: ConstrainablePoint2d,
2518 start0: ConstrainablePoint2d,
2519 end0: Option<ConstrainablePoint2d>,
2520 center1: ConstrainablePoint2d,
2521 start1: ConstrainablePoint2d,
2522 end1: Option<ConstrainablePoint2d>,
2523 input_object_ids: [ObjectId; 2],
2524 #[serde(rename = "labelPosition")]
2525 #[serde(skip_serializing_if = "Option::is_none")]
2526 #[ts(rename = "labelPosition")]
2527 #[ts(optional)]
2528 label_position: Option<ApiPoint2d<Number>>,
2529 },
2530 Radius {
2531 points: [ConstrainablePoint2d; 2],
2532 #[serde(rename = "labelPosition")]
2533 #[serde(skip_serializing_if = "Option::is_none")]
2534 #[ts(rename = "labelPosition")]
2535 #[ts(optional)]
2536 label_position: Option<ApiPoint2d<Number>>,
2537 },
2538 Diameter {
2539 points: [ConstrainablePoint2d; 2],
2540 #[serde(rename = "labelPosition")]
2541 #[serde(skip_serializing_if = "Option::is_none")]
2542 #[ts(rename = "labelPosition")]
2543 #[ts(optional)]
2544 label_position: Option<ApiPoint2d<Number>>,
2545 },
2546 HorizontalDistance {
2547 points: [ConstrainablePoint2dOrOrigin; 2],
2548 #[serde(rename = "labelPosition")]
2549 #[serde(skip_serializing_if = "Option::is_none")]
2550 #[ts(rename = "labelPosition")]
2551 #[ts(optional)]
2552 label_position: Option<ApiPoint2d<Number>>,
2553 },
2554 VerticalDistance {
2555 points: [ConstrainablePoint2dOrOrigin; 2],
2556 #[serde(rename = "labelPosition")]
2557 #[serde(skip_serializing_if = "Option::is_none")]
2558 #[ts(rename = "labelPosition")]
2559 #[ts(optional)]
2560 label_position: Option<ApiPoint2d<Number>>,
2561 },
2562}
2563
2564impl SketchConstraintKind {
2565 pub fn name(&self) -> &'static str {
2566 match self {
2567 SketchConstraintKind::Angle { .. } => "angle",
2568 SketchConstraintKind::Distance { .. } => "distance",
2569 SketchConstraintKind::PointLineDistance { .. } => "distance",
2570 SketchConstraintKind::LineLineDistance { .. } => "distance",
2571 SketchConstraintKind::PointCircularDistance { .. } => "distance",
2572 SketchConstraintKind::LineCircularDistance { .. } => "distance",
2573 SketchConstraintKind::CircularCircularDistance { .. } => "distance",
2574 SketchConstraintKind::Radius { .. } => "radius",
2575 SketchConstraintKind::Diameter { .. } => "diameter",
2576 SketchConstraintKind::HorizontalDistance { .. } => "horizontalDistance",
2577 SketchConstraintKind::VerticalDistance { .. } => "verticalDistance",
2578 }
2579 }
2580}