kittycad_modeling_cmds/
shared.rs

1use enum_iterator::Sequence;
2use parse_display_derive::{Display, FromStr};
3use schemars::{schema::SchemaObject, JsonSchema};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7#[cfg(feature = "cxx")]
8use crate::impl_extern_type;
9use crate::{length_unit::LengthUnit, output::ExtrusionFaceInfo, units::UnitAngle};
10
11pub use point::{Point2d, Point3d, Point4d, Quaternion};
12
13mod point;
14
15/// What kind of cut to do
16#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema, Default)]
17#[serde(rename_all = "snake_case")]
18#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
19#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
20pub enum CutType {
21    /// Round off an edge.
22    #[default]
23    Fillet,
24    /// Cut away an edge.
25    Chamfer,
26}
27
28/// A rotation defined by an axis, origin of rotation, and an angle.
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
30#[serde(rename_all = "snake_case")]
31#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
32#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
33pub struct Rotation {
34    /// Rotation axis.
35    /// Defaults to (0, 0, 1) (i.e. the Z axis).
36    pub axis: Point3d<f64>,
37    /// Rotate this far about the rotation axis.
38    /// Defaults to zero (i.e. no rotation).
39    pub angle: Angle,
40    /// Origin of the rotation. If one isn't provided, the object will rotate about its own bounding box center.
41    pub origin: OriginType,
42}
43
44impl Default for Rotation {
45    /// z-axis, 0 degree angle, and local origin.
46    fn default() -> Self {
47        Self {
48            axis: z_axis(),
49            angle: Angle::default(),
50            origin: OriginType::Local,
51        }
52    }
53}
54
55/// Ways to transform each solid being replicated in a repeating pattern.
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
57#[serde(rename_all = "snake_case")]
58#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
59#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
60pub struct Transform {
61    /// Translate the replica this far along each dimension.
62    /// Defaults to zero vector (i.e. same position as the original).
63    #[serde(default)]
64    pub translate: Point3d<LengthUnit>,
65    /// Scale the replica's size along each axis.
66    /// Defaults to (1, 1, 1) (i.e. the same size as the original).
67    #[serde(default = "same_scale")]
68    pub scale: Point3d<f64>,
69    /// Rotate the replica about the specified rotation axis and origin.
70    /// Defaults to no rotation.
71    #[serde(default)]
72    pub rotation: Rotation,
73    /// Whether to replicate the original solid in this instance.
74    #[serde(default = "bool_true")]
75    pub replicate: bool,
76}
77
78impl Default for Transform {
79    fn default() -> Self {
80        Self {
81            scale: same_scale(),
82            replicate: true,
83            translate: Default::default(),
84            rotation: Rotation::default(),
85        }
86    }
87}
88
89/// Options for annotations
90#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
91#[serde(rename_all = "snake_case")]
92#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
93#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
94pub struct AnnotationOptions {
95    /// Text displayed on the annotation
96    pub text: Option<AnnotationTextOptions>,
97    /// How to style the start and end of the line
98    pub line_ends: Option<AnnotationLineEndOptions>,
99    /// Width of the annotation's line
100    pub line_width: Option<f32>,
101    /// Color to render the annotation
102    pub color: Option<Color>,
103    /// Position to put the annotation
104    pub position: Option<Point3d<f32>>,
105}
106
107/// Options for annotation text
108#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
109#[serde(rename_all = "snake_case")]
110#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
111#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
112pub struct AnnotationLineEndOptions {
113    /// How to style the start of the annotation line.
114    pub start: AnnotationLineEnd,
115    /// How to style the end of the annotation line.
116    pub end: AnnotationLineEnd,
117}
118
119/// Options for annotation text
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
121#[serde(rename_all = "snake_case")]
122#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
123#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
124pub struct AnnotationTextOptions {
125    /// Alignment along the X axis
126    pub x: AnnotationTextAlignmentX,
127    /// Alignment along the Y axis
128    pub y: AnnotationTextAlignmentY,
129    /// Text displayed on the annotation
130    pub text: String,
131    /// Text font's point size
132    pub point_size: u32,
133}
134
135/// The type of distance
136/// Distances can vary depending on
137/// the objects used as input.
138#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
139#[serde(rename_all = "snake_case", tag = "type")]
140#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
141#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
142pub enum DistanceType {
143    /// Euclidean Distance.
144    Euclidean {},
145    /// The distance between objects along the specified axis
146    OnAxis {
147        /// Global axis
148        axis: GlobalAxis,
149    },
150}
151
152/// The type of origin
153#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema, Default)]
154#[serde(rename_all = "snake_case", tag = "type")]
155#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
156#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
157pub enum OriginType {
158    /// Local Origin (center of object bounding box).
159    #[default]
160    Local,
161    /// Global Origin (0, 0, 0).
162    Global,
163    /// Custom Origin (user specified point).
164    Custom {
165        /// Custom origin point.
166        origin: Point3d<f64>,
167    },
168}
169
170/// An RGBA color
171#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
172#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
173#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
174pub struct Color {
175    /// Red
176    pub r: f32,
177    /// Green
178    pub g: f32,
179    /// Blue
180    pub b: f32,
181    /// Alpha
182    pub a: f32,
183}
184
185/// Horizontal Text alignment
186#[allow(missing_docs)]
187#[derive(
188    Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Sequence, Clone, Ord, PartialOrd,
189)]
190#[serde(rename_all = "lowercase")]
191#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
192#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
193pub enum AnnotationTextAlignmentX {
194    Left,
195    Center,
196    Right,
197}
198
199/// Vertical Text alignment
200#[allow(missing_docs)]
201#[derive(
202    Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Sequence, Clone, Ord, PartialOrd,
203)]
204#[serde(rename_all = "lowercase")]
205#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
206#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
207pub enum AnnotationTextAlignmentY {
208    Bottom,
209    Center,
210    Top,
211}
212
213/// Annotation line end type
214#[allow(missing_docs)]
215#[derive(
216    Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Sequence, Clone, Ord, PartialOrd,
217)]
218#[serde(rename_all = "lowercase")]
219#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
220#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
221pub enum AnnotationLineEnd {
222    None,
223    Arrow,
224}
225
226/// The type of annotation
227#[derive(
228    Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Sequence, Clone, Ord, PartialOrd,
229)]
230#[serde(rename_all = "lowercase")]
231#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
232#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
233pub enum AnnotationType {
234    /// 2D annotation type (screen or planar space)
235    T2D,
236    /// 3D annotation type
237    T3D,
238}
239
240/// The type of camera drag interaction.
241#[derive(
242    Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Sequence, Clone, Ord, PartialOrd,
243)]
244#[serde(rename_all = "lowercase")]
245#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
246#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
247pub enum CameraDragInteractionType {
248    /// Camera pan
249    Pan,
250    /// Camera rotate (spherical camera revolve/orbit)
251    Rotate,
252    /// Camera rotate (trackball with 3 degrees of freedom)
253    RotateTrackball,
254    /// Camera zoom (increase or decrease distance to reference point center)
255    Zoom,
256}
257
258/// A segment of a path.
259/// Paths are composed of many segments.
260#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)]
261#[serde(rename_all = "snake_case", tag = "type")]
262#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
263#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
264pub enum PathSegment {
265    /// A straight line segment.
266    /// Goes from the current path "pen" to the given endpoint.
267    Line {
268        /// End point of the line.
269        end: Point3d<LengthUnit>,
270        ///Whether or not this line is a relative offset
271        relative: bool,
272    },
273    /// A circular arc segment.
274    /// Arcs can be drawn clockwise when start > end.
275    Arc {
276        /// Center of the circle
277        center: Point2d<LengthUnit>,
278        /// Radius of the circle
279        radius: LengthUnit,
280        /// Start of the arc along circle's perimeter.
281        start: Angle,
282        /// End of the arc along circle's perimeter.
283        end: Angle,
284        ///Whether or not this arc is a relative offset
285        relative: bool,
286    },
287    /// A cubic bezier curve segment.
288    /// Start at the end of the current line, go through control point 1 and 2, then end at a
289    /// given point.
290    Bezier {
291        /// First control point.
292        control1: Point3d<LengthUnit>,
293        /// Second control point.
294        control2: Point3d<LengthUnit>,
295        /// Final control point.
296        end: Point3d<LengthUnit>,
297        ///Whether or not this bezier is a relative offset
298        relative: bool,
299    },
300    /// Adds a tangent arc from current pen position with the given radius and angle.
301    TangentialArc {
302        /// Radius of the arc.
303        /// Not to be confused with Raiders of the Lost Ark.
304        radius: LengthUnit,
305        /// Offset of the arc. Negative values will arc clockwise.
306        offset: Angle,
307    },
308    /// Adds a tangent arc from current pen position to the new position.
309    /// Arcs will choose a clockwise or counter-clockwise direction based on the arc end position.
310    TangentialArcTo {
311        /// Where the arc should end.
312        /// Must lie in the same plane as the current path pen position.
313        /// Must not be colinear with current path pen position.
314        to: Point3d<LengthUnit>,
315        /// 0 will be interpreted as none/null.
316        angle_snap_increment: Option<Angle>,
317    },
318    ///Adds an arc from the current position that goes through the given interior point and ends at the given end position
319    ArcTo {
320        /// Interior point of the arc.
321        interior: Point3d<LengthUnit>,
322        /// End point of the arc.
323        end: Point3d<LengthUnit>,
324        ///Whether or not interior and end are relative to the previous path position
325        relative: bool,
326    },
327    ///Adds a circular involute from the current position that goes through the given end_radius
328    ///and is rotated around the current point by angle.
329    CircularInvolute {
330        ///The involute is described between two circles, start_radius is the radius of the inner
331        ///circle.
332        start_radius: LengthUnit,
333        ///The involute is described between two circles, end_radius is the radius of the outer
334        ///circle.
335        end_radius: LengthUnit,
336        ///The angle to rotate the involute by. A value of zero will produce a curve with a tangent
337        ///along the x-axis at the start point of the curve.
338        angle: Angle,
339        ///If reverse is true, the segment will start
340        ///from the end of the involute, otherwise it will start from that start.
341        reverse: bool,
342    },
343    ///Adds an elliptical arc segment.
344    Ellipse {
345        /// The center point of the ellipse.
346        center: Point2d<LengthUnit>,
347        /// Major axis of the ellipse.
348        major_axis: Point2d<LengthUnit>,
349        /// Minor radius of the ellipse.
350        minor_radius: LengthUnit,
351        /// Start of the path along the perimeter of the ellipse.
352        start_angle: Angle,
353        /// End of the path along the perimeter of the ellipse.
354        end_angle: Angle,
355    },
356    ///Adds a generic conic section specified by the end point, interior point and tangents at the
357    ///start and end of the section.
358    ConicTo {
359        /// Interior point that lies on the conic.
360        interior: Point2d<LengthUnit>,
361        /// End point of the conic.
362        end: Point2d<LengthUnit>,
363        /// Tangent at the start of the conic.
364        start_tangent: Point2d<LengthUnit>,
365        /// Tangent at the end of the conic.
366        end_tangent: Point2d<LengthUnit>,
367        /// Whether or not the interior and end points are relative to the previous path position.
368        relative: bool,
369    },
370}
371
372/// An angle, with a specific unit.
373#[derive(Clone, Copy, PartialEq, Debug, JsonSchema, Deserialize, Serialize)]
374#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
375#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
376pub struct Angle {
377    /// What unit is the measurement?
378    pub unit: UnitAngle,
379    /// The size of the angle, measured in the chosen unit.
380    pub value: f64,
381}
382
383impl Angle {
384    /// Converts a given angle to degrees.
385    pub fn to_degrees(self) -> f64 {
386        match self.unit {
387            UnitAngle::Degrees => self.value,
388            UnitAngle::Radians => self.value.to_degrees(),
389        }
390    }
391    /// Converts a given angle to radians.
392    pub fn to_radians(self) -> f64 {
393        match self.unit {
394            UnitAngle::Degrees => self.value.to_radians(),
395            UnitAngle::Radians => self.value,
396        }
397    }
398    /// Create an angle in degrees.
399    pub const fn from_degrees(value: f64) -> Self {
400        Self {
401            unit: UnitAngle::Degrees,
402            value,
403        }
404    }
405    /// Create an angle in radians.
406    pub const fn from_radians(value: f64) -> Self {
407        Self {
408            unit: UnitAngle::Radians,
409            value,
410        }
411    }
412    /// 360 degrees.
413    pub const fn turn() -> Self {
414        Self::from_degrees(360.0)
415    }
416    /// 180 degrees.
417    pub const fn half_circle() -> Self {
418        Self::from_degrees(180.0)
419    }
420    /// 90 degrees.
421    pub const fn quarter_circle() -> Self {
422        Self::from_degrees(90.0)
423    }
424    /// 0 degrees.
425    pub const fn zero() -> Self {
426        Self::from_degrees(0.0)
427    }
428}
429
430/// 0 degrees.
431impl Default for Angle {
432    /// 0 degrees.
433    fn default() -> Self {
434        Self::zero()
435    }
436}
437
438impl PartialOrd for Angle {
439    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
440        match (self.unit, other.unit) {
441            // Avoid unnecessary floating point operations.
442            (UnitAngle::Degrees, UnitAngle::Degrees) => self.value.partial_cmp(&other.value),
443            (UnitAngle::Radians, UnitAngle::Radians) => self.value.partial_cmp(&other.value),
444            _ => self.to_degrees().partial_cmp(&other.to_degrees()),
445        }
446    }
447}
448
449impl std::ops::Add for Angle {
450    type Output = Self;
451
452    fn add(self, rhs: Self) -> Self::Output {
453        Self {
454            unit: UnitAngle::Degrees,
455            value: self.to_degrees() + rhs.to_degrees(),
456        }
457    }
458}
459
460impl std::ops::AddAssign for Angle {
461    fn add_assign(&mut self, rhs: Self) {
462        match self.unit {
463            UnitAngle::Degrees => {
464                self.value += rhs.to_degrees();
465            }
466            UnitAngle::Radians => {
467                self.value += rhs.to_radians();
468            }
469        }
470    }
471}
472
473/// The type of scene selection change
474#[derive(
475    Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Sequence, Clone, Ord, PartialOrd,
476)]
477#[serde(rename_all = "lowercase")]
478#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
479#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
480pub enum SceneSelectionType {
481    /// Replaces the selection
482    Replace,
483    /// Adds to the selection
484    Add,
485    /// Removes from the selection
486    Remove,
487}
488
489/// The type of scene's active tool
490#[allow(missing_docs)]
491#[derive(
492    Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Sequence, Clone, Ord, PartialOrd,
493)]
494#[serde(rename_all = "snake_case")]
495#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
496#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
497pub enum SceneToolType {
498    CameraRevolve,
499    Select,
500    Move,
501    SketchLine,
502    SketchTangentialArc,
503    SketchCurve,
504    SketchCurveMod,
505}
506
507/// The path component constraint bounds type
508#[allow(missing_docs)]
509#[derive(
510    Display,
511    FromStr,
512    Copy,
513    Eq,
514    PartialEq,
515    Debug,
516    JsonSchema,
517    Deserialize,
518    Serialize,
519    Sequence,
520    Clone,
521    Ord,
522    PartialOrd,
523    Default,
524)]
525#[serde(rename_all = "snake_case")]
526#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
527#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
528pub enum PathComponentConstraintBound {
529    #[default]
530    Unconstrained,
531    PartiallyConstrained,
532    FullyConstrained,
533}
534
535/// The path component constraint type
536#[allow(missing_docs)]
537#[derive(
538    Display,
539    FromStr,
540    Copy,
541    Eq,
542    PartialEq,
543    Debug,
544    JsonSchema,
545    Deserialize,
546    Serialize,
547    Sequence,
548    Clone,
549    Ord,
550    PartialOrd,
551    Default,
552)]
553#[serde(rename_all = "snake_case")]
554#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
555#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
556pub enum PathComponentConstraintType {
557    #[default]
558    Unconstrained,
559    Vertical,
560    Horizontal,
561    EqualLength,
562    Parallel,
563    AngleBetween,
564}
565
566/// The path component command type (within a Path)
567#[allow(missing_docs)]
568#[derive(
569    Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Sequence, Clone, Ord, PartialOrd,
570)]
571#[serde(rename_all = "snake_case")]
572#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
573#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
574pub enum PathCommand {
575    MoveTo,
576    LineTo,
577    BezCurveTo,
578    NurbsCurveTo,
579    AddArc,
580}
581
582/// The type of entity
583#[allow(missing_docs)]
584#[derive(
585    Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Sequence, Clone, Ord, PartialOrd,
586)]
587#[serde(rename_all = "lowercase")]
588#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
589#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
590#[repr(u8)]
591pub enum EntityType {
592    Entity,
593    Object,
594    Path,
595    Curve,
596    Solid2D,
597    Solid3D,
598    Edge,
599    Face,
600    Plane,
601    Vertex,
602}
603
604/// The type of Curve (embedded within path)
605#[allow(missing_docs)]
606#[derive(
607    Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Sequence, Clone, Ord, PartialOrd,
608)]
609#[serde(rename_all = "snake_case")]
610#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
611#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
612pub enum CurveType {
613    Line,
614    Arc,
615    Nurbs,
616}
617
618/// A file to be exported to the client.
619#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
620#[cfg_attr(feature = "python", pyo3::pyclass, pyo3_stub_gen::derive::gen_stub_pyclass)]
621pub struct ExportFile {
622    /// The name of the file.
623    pub name: String,
624    /// The contents of the file, base64 encoded.
625    pub contents: crate::base64::Base64Data,
626}
627
628#[cfg(feature = "python")]
629#[pyo3_stub_gen::derive::gen_stub_pymethods]
630#[pyo3::pymethods]
631impl ExportFile {
632    #[getter]
633    fn contents(&self) -> Vec<u8> {
634        self.contents.0.clone()
635    }
636
637    #[getter]
638    fn name(&self) -> String {
639        self.name.clone()
640    }
641}
642
643/// The valid types of output file formats.
644#[derive(
645    Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Clone, Ord, PartialOrd, Sequence,
646)]
647#[serde(rename_all = "lowercase")]
648#[display(style = "lowercase")]
649#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
650#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
651#[cfg_attr(feature = "python", pyo3::pyclass, pyo3_stub_gen::derive::gen_stub_pyclass_enum)]
652pub enum FileExportFormat {
653    /// Autodesk Filmbox (FBX) format. <https://en.wikipedia.org/wiki/FBX>
654    Fbx,
655    /// Binary glTF 2.0.
656    ///
657    /// This is a single binary with .glb extension.
658    ///
659    /// This is better if you want a compressed format as opposed to the human readable
660    /// glTF that lacks compression.
661    Glb,
662    /// glTF 2.0.
663    /// Embedded glTF 2.0 (pretty printed).
664    ///
665    /// Single JSON file with .gltf extension binary data encoded as
666    /// base64 data URIs.
667    ///
668    /// The JSON contents are pretty printed.
669    ///
670    /// It is human readable, single file, and you can view the
671    /// diff easily in a git commit.
672    Gltf,
673    /// The OBJ file format. <https://en.wikipedia.org/wiki/Wavefront_.obj_file>
674    /// It may or may not have an an attached material (mtl // mtllib) within the file,
675    /// but we interact with it as if it does not.
676    Obj,
677    /// The PLY file format. <https://en.wikipedia.org/wiki/PLY_(file_format)>
678    Ply,
679    /// The STEP file format. <https://en.wikipedia.org/wiki/ISO_10303-21>
680    Step,
681    /// The STL file format. <https://en.wikipedia.org/wiki/STL_(file_format)>
682    Stl,
683}
684
685/// The valid types of 2D output file formats.
686#[derive(
687    Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Clone, Ord, PartialOrd, Sequence,
688)]
689#[serde(rename_all = "lowercase")]
690#[display(style = "lowercase")]
691#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
692#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
693pub enum FileExportFormat2d {
694    /// AutoCAD drawing interchange format.
695    Dxf,
696}
697
698/// The valid types of source file formats.
699#[derive(
700    Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Clone, Ord, PartialOrd, Sequence,
701)]
702#[serde(rename_all = "lowercase")]
703#[display(style = "lowercase")]
704#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
705#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
706pub enum FileImportFormat {
707    /// Autodesk Filmbox (FBX) format. <https://en.wikipedia.org/wiki/FBX>
708    Fbx,
709    /// glTF 2.0.
710    Gltf,
711    /// The OBJ file format. <https://en.wikipedia.org/wiki/Wavefront_.obj_file>
712    /// It may or may not have an an attached material (mtl // mtllib) within the file,
713    /// but we interact with it as if it does not.
714    Obj,
715    /// The PLY file format. <https://en.wikipedia.org/wiki/PLY_(file_format)>
716    Ply,
717    /// SolidWorks part (SLDPRT) format.
718    Sldprt,
719    /// The STEP file format. <https://en.wikipedia.org/wiki/ISO_10303-21>
720    Step,
721    /// The STL file format. <https://en.wikipedia.org/wiki/STL_(file_format)>
722    Stl,
723}
724
725/// The type of error sent by the KittyCAD graphics engine.
726#[derive(Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Clone, Ord, PartialOrd)]
727#[serde(rename_all = "snake_case")]
728#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
729#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
730pub enum EngineErrorCode {
731    /// User requested something geometrically or graphically impossible.
732    /// Don't retry this request, as it's inherently impossible. Instead, read the error message
733    /// and change your request.
734    BadRequest = 1,
735    /// Graphics engine failed to complete request, consider retrying
736    InternalEngine,
737}
738
739impl From<EngineErrorCode> for http::StatusCode {
740    fn from(e: EngineErrorCode) -> Self {
741        match e {
742            EngineErrorCode::BadRequest => Self::BAD_REQUEST,
743            EngineErrorCode::InternalEngine => Self::INTERNAL_SERVER_ERROR,
744        }
745    }
746}
747
748/// Extrusion method determining if the extrusion will be part of the existing object or an
749/// entirely new object.
750#[derive(Default, Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
751#[serde(rename_all = "snake_case")]
752#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
753#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
754pub enum ExtrudeMethod {
755    /// Create a new object that is not connected to the object it is extruded from. This will
756    /// result in two objects after the operation.
757    New,
758    /// This extrusion will be part of object it is extruded from. This will result in one object
759    /// after the operation.
760    #[default]
761    Merge,
762}
763
764/// IDs for the extruded faces.
765#[derive(Debug, PartialEq, Serialize, Deserialize, JsonSchema, Clone)]
766#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
767#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
768pub struct ExtrudedFaceInfo {
769    /// The face made from the original 2D shape being extruded.
770    /// If the solid is extruded from a shape which already has an ID
771    /// (e.g. extruding something which was sketched on a face), this
772    /// doesn't need to be sent.
773    pub bottom: Option<Uuid>,
774    /// Top face of the extrusion (parallel and further away from the original 2D shape being extruded).
775    pub top: Uuid,
776    /// Any intermediate sides between the top and bottom.
777    pub sides: Vec<SideFace>,
778}
779
780/// IDs for a side face, extruded from the path of some sketch/2D shape.
781#[derive(Debug, PartialEq, Serialize, Deserialize, JsonSchema, Clone)]
782#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
783#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
784pub struct SideFace {
785    /// ID of the path this face is being extruded from.
786    pub path_id: Uuid,
787    /// Desired ID for the resulting face.
788    pub face_id: Uuid,
789}
790
791/// Camera settings including position, center, fov etc
792#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
793#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
794#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
795pub struct CameraSettings {
796    ///Camera position (vantage)
797    pub pos: Point3d,
798
799    ///Camera's look-at center (center-pos gives viewing vector)
800    pub center: Point3d,
801
802    ///Camera's world-space up vector
803    pub up: Point3d,
804
805    ///The Camera's orientation (in the form of a quaternion)
806    pub orientation: Quaternion,
807
808    ///Camera's field-of-view angle (if ortho is false)
809    pub fov_y: Option<f32>,
810
811    ///The camera's ortho scale (derived from viewing distance if ortho is true)
812    pub ortho_scale: Option<f32>,
813
814    ///Whether or not the camera is in ortho mode
815    pub ortho: bool,
816}
817
818#[allow(missing_docs)]
819#[repr(u8)]
820#[derive(Default, Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
821#[serde(rename_all = "snake_case")]
822#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
823#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
824pub enum WorldCoordinateSystem {
825    #[default]
826    RightHandedUpZ,
827    RightHandedUpY,
828}
829
830#[allow(missing_docs)]
831#[repr(C)]
832#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
833#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
834#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
835pub struct CameraViewState {
836    pub pivot_rotation: Quaternion,
837    pub pivot_position: Point3d,
838    pub eye_offset: f32,
839    pub fov_y: f32,
840    pub ortho_scale_factor: f32,
841    pub is_ortho: bool,
842    pub ortho_scale_enabled: bool,
843    pub world_coord_system: WorldCoordinateSystem,
844}
845
846impl Default for CameraViewState {
847    fn default() -> Self {
848        CameraViewState {
849            pivot_rotation: Default::default(),
850            pivot_position: Default::default(),
851            eye_offset: 10.0,
852            fov_y: 45.0,
853            ortho_scale_factor: 1.6,
854            is_ortho: false,
855            ortho_scale_enabled: true,
856            world_coord_system: Default::default(),
857        }
858    }
859}
860
861#[cfg(feature = "cxx")]
862impl_extern_type! {
863    [Trivial]
864    CameraViewState = "Endpoints::CameraViewState"
865}
866
867impl From<CameraSettings> for crate::output::DefaultCameraZoom {
868    fn from(settings: CameraSettings) -> Self {
869        Self { settings }
870    }
871}
872impl From<CameraSettings> for crate::output::CameraDragMove {
873    fn from(settings: CameraSettings) -> Self {
874        Self { settings }
875    }
876}
877impl From<CameraSettings> for crate::output::CameraDragEnd {
878    fn from(settings: CameraSettings) -> Self {
879        Self { settings }
880    }
881}
882impl From<CameraSettings> for crate::output::DefaultCameraGetSettings {
883    fn from(settings: CameraSettings) -> Self {
884        Self { settings }
885    }
886}
887impl From<CameraSettings> for crate::output::ZoomToFit {
888    fn from(settings: CameraSettings) -> Self {
889        Self { settings }
890    }
891}
892impl From<CameraSettings> for crate::output::OrientToFace {
893    fn from(settings: CameraSettings) -> Self {
894        Self { settings }
895    }
896}
897impl From<CameraSettings> for crate::output::ViewIsometric {
898    fn from(settings: CameraSettings) -> Self {
899        Self { settings }
900    }
901}
902
903/// Defines a perspective view.
904#[derive(Copy, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Clone, PartialOrd, Default)]
905#[serde(rename_all = "snake_case")]
906#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
907#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
908pub struct PerspectiveCameraParameters {
909    /// Camera frustum vertical field of view.
910    pub fov_y: Option<f32>,
911    /// Camera frustum near plane.
912    pub z_near: Option<f32>,
913    /// Camera frustum far plane.
914    pub z_far: Option<f32>,
915}
916
917/// A type of camera movement applied after certain camera operations
918#[derive(
919    Default,
920    Display,
921    FromStr,
922    Copy,
923    Eq,
924    PartialEq,
925    Debug,
926    JsonSchema,
927    Deserialize,
928    Serialize,
929    Sequence,
930    Clone,
931    Ord,
932    PartialOrd,
933)]
934#[serde(rename_all = "snake_case")]
935#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
936#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
937pub enum CameraMovement {
938    /// Adjusts the camera position during the camera operation
939    #[default]
940    Vantage,
941    /// Keeps the camera position in place
942    None,
943}
944
945/// The global axes.
946#[derive(
947    Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Sequence, Clone, Ord, PartialOrd,
948)]
949#[serde(rename_all = "lowercase")]
950#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
951#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
952pub enum GlobalAxis {
953    /// The X axis
954    X,
955    /// The Y axis
956    Y,
957    /// The Z axis
958    Z,
959}
960
961/// Possible types of faces which can be extruded from a 3D solid.
962#[derive(
963    Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Sequence, Clone, Ord, PartialOrd,
964)]
965#[serde(rename_all = "snake_case")]
966#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
967#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
968#[repr(u8)]
969pub enum ExtrusionFaceCapType {
970    /// Uncapped.
971    None,
972    /// Capped on top.
973    Top,
974    /// Capped below.
975    Bottom,
976    /// Capped on both ends.
977    Both,
978}
979
980/// Post effect type
981#[allow(missing_docs)]
982#[derive(
983    Display,
984    FromStr,
985    Copy,
986    Eq,
987    PartialEq,
988    Debug,
989    JsonSchema,
990    Deserialize,
991    Serialize,
992    Sequence,
993    Clone,
994    Ord,
995    PartialOrd,
996    Default,
997)]
998#[serde(rename_all = "lowercase")]
999#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
1000#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
1001pub enum PostEffectType {
1002    Phosphor,
1003    Ssao,
1004    #[default]
1005    NoEffect,
1006}
1007
1008// Enum: Connect Rust Enums to Cpp
1009// add our native c++ names for our cxx::ExternType implementation
1010#[cfg(feature = "cxx")]
1011impl_extern_type! {
1012    [Trivial]
1013    // File
1014    FileImportFormat = "Enums::_FileImportFormat"
1015    FileExportFormat = "Enums::_FileExportFormat"
1016    // Camera
1017    CameraDragInteractionType = "Enums::_CameraDragInteractionType"
1018    // Scene
1019    SceneSelectionType = "Enums::_SceneSelectionType"
1020    SceneToolType = "Enums::_SceneToolType"
1021    EntityType = "Enums::_EntityType"
1022    AnnotationType = "Enums::_AnnotationType"
1023    AnnotationTextAlignmentX = "Enums::_AnnotationTextAlignmentX"
1024    AnnotationTextAlignmentY = "Enums::_AnnotationTextAlignmentY"
1025    AnnotationLineEnd = "Enums::_AnnotationLineEnd"
1026
1027    CurveType = "Enums::_CurveType"
1028    PathCommand = "Enums::_PathCommand"
1029    PathComponentConstraintBound = "Enums::_PathComponentConstraintBound"
1030    PathComponentConstraintType = "Enums::_PathComponentConstraintType"
1031    ExtrusionFaceCapType  = "Enums::_ExtrusionFaceCapType"
1032
1033    // Utils
1034    EngineErrorCode = "Enums::_ErrorCode"
1035    GlobalAxis = "Enums::_GlobalAxis"
1036    OriginType = "Enums::_OriginType"
1037
1038    // Graphics engine
1039    PostEffectType = "Enums::_PostEffectType"
1040}
1041
1042fn bool_true() -> bool {
1043    true
1044}
1045fn same_scale() -> Point3d<f64> {
1046    Point3d::uniform(1.0)
1047}
1048
1049fn z_axis() -> Point3d<f64> {
1050    Point3d { x: 0.0, y: 0.0, z: 1.0 }
1051}
1052
1053impl ExtrudedFaceInfo {
1054    /// Converts from the representation used in the Extrude modeling command,
1055    /// to a flat representation.
1056    pub fn list_faces(self) -> Vec<ExtrusionFaceInfo> {
1057        let mut face_infos: Vec<_> = self
1058            .sides
1059            .into_iter()
1060            .map(|side| ExtrusionFaceInfo {
1061                curve_id: Some(side.path_id),
1062                face_id: Some(side.face_id),
1063                cap: ExtrusionFaceCapType::None,
1064            })
1065            .collect();
1066        face_infos.push(ExtrusionFaceInfo {
1067            curve_id: None,
1068            face_id: Some(self.top),
1069            cap: ExtrusionFaceCapType::Top,
1070        });
1071        if let Some(bottom) = self.bottom {
1072            face_infos.push(ExtrusionFaceInfo {
1073                curve_id: None,
1074                face_id: Some(bottom),
1075                cap: ExtrusionFaceCapType::Bottom,
1076            });
1077        }
1078        face_infos
1079    }
1080}
1081
1082#[cfg(test)]
1083mod tests {
1084    use schemars::schema_for;
1085
1086    use super::*;
1087
1088    #[test]
1089    fn check_transformby_deprecated() {
1090        let s = schema_for!(TransformBy<Point3d>);
1091        let pretty = serde_json::to_string_pretty(&s).unwrap();
1092        println!("{pretty}");
1093        let tests: Vec<(OriginType, TransformBy<Point3d>)> = vec![
1094            // get_origin should fall back to `is_local`, because `origin` is none.
1095            (
1096                OriginType::Local,
1097                TransformBy {
1098                    property: Point3d::default(),
1099                    set: true,
1100                    #[allow(deprecated)] // still need to test deprecated code
1101                    is_local: true,
1102                    origin: None,
1103                },
1104            ),
1105            // get_origin should ignore `is_local`, because `origin` is given.
1106            // test the case where origin is not custom
1107            (
1108                OriginType::Local,
1109                TransformBy {
1110                    property: Point3d::default(),
1111                    set: true,
1112                    #[allow(deprecated)] // still need to test deprecated code
1113                    is_local: false,
1114                    origin: Some(OriginType::Local),
1115                },
1116            ),
1117            // get_origin should ignore `is_local`, because `origin` is given.
1118            // test the case where origin is custom.
1119            (
1120                OriginType::Custom {
1121                    origin: Point3d::uniform(2.0),
1122                },
1123                TransformBy {
1124                    property: Point3d::default(),
1125                    set: true,
1126                    #[allow(deprecated)] // still need to test deprecated code
1127                    is_local: false,
1128                    origin: Some(OriginType::Custom{origin: Point3d::uniform(2.0)}),
1129                },
1130            ),
1131        ];
1132        for (expected, input) in tests {
1133            let actual = input.get_origin();
1134            assert_eq!(actual, expected);
1135        }
1136    }
1137
1138    #[test]
1139    fn test_angle_comparison() {
1140        let a = Angle::from_degrees(90.0);
1141        assert!(a < Angle::from_degrees(91.0));
1142        assert!(a > Angle::from_degrees(89.0));
1143        assert!(a <= Angle::from_degrees(90.0));
1144        assert!(a >= Angle::from_degrees(90.0));
1145        let b = Angle::from_radians(std::f64::consts::FRAC_PI_4);
1146        assert!(b < Angle::from_radians(std::f64::consts::FRAC_PI_2));
1147        assert!(b > Angle::from_radians(std::f64::consts::FRAC_PI_8));
1148        assert!(b <= Angle::from_radians(std::f64::consts::FRAC_PI_4));
1149        assert!(b >= Angle::from_radians(std::f64::consts::FRAC_PI_4));
1150        // Mixed units.
1151        assert!(a > b);
1152        assert!(a >= b);
1153        assert!(b < a);
1154        assert!(b <= a);
1155        let c = Angle::from_radians(std::f64::consts::FRAC_PI_2 * 3.0);
1156        assert!(a < c);
1157        assert!(a <= c);
1158        assert!(c > a);
1159        assert!(c >= a);
1160    }
1161}
1162
1163/// How a property of an object should be transformed.
1164#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema)]
1165#[schemars(rename = "TransformByFor{T}")]
1166#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
1167#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
1168pub struct TransformBy<T> {
1169    /// The scale, or rotation, or translation.
1170    pub property: T,
1171    /// If true, overwrite the previous value with this.
1172    /// If false, the previous value will be modified.
1173    /// E.g. when translating, `set=true` will set a new location,
1174    /// and `set=false` will translate the current location by the given X/Y/Z.
1175    pub set: bool,
1176    /// If true, the transform is applied in local space.
1177    /// If false, the transform is applied in global space.
1178    #[deprecated(note = "Use the `origin` field instead.")]
1179    pub is_local: bool,
1180    /// What to use as the origin for the transformation.
1181    /// If not provided, will fall back to local or global origin, depending on
1182    /// whatever the `is_local` field was set to.
1183    #[serde(default)]
1184    pub origin: Option<OriginType>,
1185}
1186
1187impl<T> TransformBy<T> {
1188    /// Get the origin of this transformation.
1189    /// Reads from the `origin` field if it's set, otherwise
1190    /// falls back to the `is_local` field.
1191    pub fn get_origin(&self) -> OriginType {
1192        if let Some(origin) = self.origin {
1193            return origin;
1194        }
1195        #[expect(
1196            deprecated,
1197            reason = "Must fall back to the deprecated field if the API client isn't using the new field yet."
1198        )]
1199        if self.is_local {
1200            OriginType::Local
1201        } else {
1202            OriginType::Global
1203        }
1204    }
1205}
1206
1207/// Container that holds a translate, rotate and scale.
1208/// Defaults to no change, everything stays the same (i.e. the identity function).
1209#[derive(Clone, Debug, PartialEq, Deserialize, JsonSchema, Serialize, Default)]
1210#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
1211#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
1212pub struct ComponentTransform {
1213    /// Translate component of the transform.
1214    pub translate: Option<TransformBy<Point3d<LengthUnit>>>,
1215    /// Rotate component of the transform.
1216    /// The rotation is specified as a roll, pitch, yaw.
1217    pub rotate_rpy: Option<TransformBy<Point3d<f64>>>,
1218    /// Rotate component of the transform.
1219    /// The rotation is specified as an axis and an angle (xyz are the components of the axis, w is
1220    /// the angle in degrees).
1221    pub rotate_angle_axis: Option<TransformBy<Point4d<f64>>>,
1222    /// Scale component of the transform.
1223    pub scale: Option<TransformBy<Point3d<f64>>>,
1224}
1225
1226///If bidirectional or symmetric operations are needed this enum encapsulates the required
1227///information.
1228#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
1229#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
1230#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
1231pub enum Opposite<T> {
1232    /// No opposite. The operation will only occur on one side.
1233    #[default]
1234    None,
1235    /// Operation will occur from both sides, with the same value.
1236    Symmetric,
1237    /// Operation will occur from both sides, with this value for the opposite.
1238    Other(T),
1239}
1240
1241impl<T: JsonSchema> JsonSchema for Opposite<T> {
1242    fn schema_name() -> String {
1243        format!("OppositeFor{}", T::schema_name())
1244    }
1245
1246    fn schema_id() -> std::borrow::Cow<'static, str> {
1247        std::borrow::Cow::Owned(format!("{}::Opposite<{}>", module_path!(), T::schema_id()))
1248    }
1249
1250    fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1251        SchemaObject {
1252            instance_type: Some(schemars::schema::InstanceType::String.into()),
1253            ..Default::default()
1254        }
1255        .into()
1256    }
1257}
1258
1259/// What strategy (algorithm) should be used for cutting?
1260/// Defaults to Automatic.
1261#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema, Default)]
1262#[serde(rename_all = "snake_case")]
1263#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
1264#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
1265pub enum CutStrategy {
1266    /// Basic fillet cut. This has limitations, like the filletted edges
1267    /// can't touch each other. But it's very fast and simple.
1268    Basic,
1269    /// More complicated fillet cut. It works for more use-cases, like
1270    /// edges that touch each other. But it's slower than the Basic method.
1271    Csg,
1272    /// Tries the Basic method, and if that doesn't work, tries the CSG strategy.
1273    #[default]
1274    Automatic,
1275}
1276
1277/// What is the given geometry relative to?
1278#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema, Default)]
1279#[serde(rename_all = "snake_case")]
1280#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
1281#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
1282pub enum RelativeTo {
1283    /// Local/relative to a position centered within the plane being sketched on
1284    #[default]
1285    SketchPlane,
1286    /// Local/relative to the trajectory curve
1287    TrajectoryCurve,
1288}