1#![allow(async_fn_in_trait)]
2
3use serde::Deserialize;
4use serde::Serialize;
5
6use crate::ExecutorContext;
7use crate::KclErrorWithOutputs;
8use crate::front::Plane;
9use crate::frontend::api::Expr;
10use crate::frontend::api::FileId;
11use crate::frontend::api::Number;
12use crate::frontend::api::ObjectId;
13use crate::frontend::api::ProjectId;
14use crate::frontend::api::SceneGraph;
15use crate::frontend::api::SceneGraphDelta;
16use crate::frontend::api::SourceDelta;
17use crate::frontend::api::Version;
18
19pub type ExecResult<T> = std::result::Result<T, KclErrorWithOutputs>;
20
21#[derive(Debug, Clone)]
23pub struct NewSegmentInfo {
24 pub segment_id: ObjectId,
25 pub start_point_id: ObjectId,
26 pub end_point_id: ObjectId,
27 pub center_point_id: Option<ObjectId>,
28}
29
30pub trait SketchApi {
31 async fn execute_mock(
34 &mut self,
35 ctx: &ExecutorContext,
36 version: Version,
37 sketch: ObjectId,
38 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
39
40 async fn new_sketch(
41 &mut self,
42 ctx: &ExecutorContext,
43 project: ProjectId,
44 file: FileId,
45 version: Version,
46 args: SketchCtor,
47 ) -> ExecResult<(SourceDelta, SceneGraphDelta, ObjectId)>;
48
49 async fn edit_sketch(
51 &mut self,
52 ctx: &ExecutorContext,
53 project: ProjectId,
54 file: FileId,
55 version: Version,
56 sketch: ObjectId,
57 ) -> ExecResult<SceneGraphDelta>;
58
59 async fn exit_sketch(
60 &mut self,
61 ctx: &ExecutorContext,
62 version: Version,
63 sketch: ObjectId,
64 ) -> ExecResult<SceneGraph>;
65
66 async fn delete_sketch(
67 &mut self,
68 ctx: &ExecutorContext,
69 version: Version,
70 sketch: ObjectId,
71 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
72
73 async fn add_segment(
74 &mut self,
75 ctx: &ExecutorContext,
76 version: Version,
77 sketch: ObjectId,
78 segment: SegmentCtor,
79 label: Option<String>,
80 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
81
82 async fn edit_segments(
83 &mut self,
84 ctx: &ExecutorContext,
85 version: Version,
86 sketch: ObjectId,
87 segments: Vec<ExistingSegmentCtor>,
88 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
89
90 async fn delete_objects(
91 &mut self,
92 ctx: &ExecutorContext,
93 version: Version,
94 sketch: ObjectId,
95 constraint_ids: Vec<ObjectId>,
96 segment_ids: Vec<ObjectId>,
97 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
98
99 async fn add_constraint(
100 &mut self,
101 ctx: &ExecutorContext,
102 version: Version,
103 sketch: ObjectId,
104 constraint: Constraint,
105 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
106
107 async fn chain_segment(
108 &mut self,
109 ctx: &ExecutorContext,
110 version: Version,
111 sketch: ObjectId,
112 previous_segment_end_point_id: ObjectId,
113 segment: SegmentCtor,
114 label: Option<String>,
115 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
116
117 async fn edit_constraint_value(
118 &mut self,
119 ctx: &ExecutorContext,
120 version: Version,
121 sketch: ObjectId,
122 constraint_id: ObjectId,
123 value_expression: String,
124 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
125
126 async fn edit_distance_constraint_label_position(
127 &mut self,
128 ctx: &ExecutorContext,
129 version: Version,
130 sketch: ObjectId,
131 constraint_id: ObjectId,
132 label_position: Point2d<Number>,
133 anchor_segment_ids: Vec<ObjectId>,
134 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
135
136 #[allow(clippy::too_many_arguments)]
140 async fn batch_split_segment_operations(
141 &mut self,
142 ctx: &ExecutorContext,
143 version: Version,
144 sketch: ObjectId,
145 edit_segments: Vec<ExistingSegmentCtor>,
146 add_constraints: Vec<Constraint>,
147 delete_constraint_ids: Vec<ObjectId>,
148 new_segment_info: NewSegmentInfo,
149 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
150
151 #[allow(clippy::too_many_arguments)]
154 async fn batch_tail_cut_operations(
155 &mut self,
156 ctx: &ExecutorContext,
157 version: Version,
158 sketch: ObjectId,
159 edit_segments: Vec<ExistingSegmentCtor>,
160 add_constraints: Vec<Constraint>,
161 delete_constraint_ids: Vec<ObjectId>,
162 additional_edited_segment_ids: Vec<ObjectId>,
163 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
164}
165
166#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
167#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSketch")]
168pub struct Sketch {
169 pub args: SketchCtor,
170 pub plane: ObjectId,
171 pub segments: Vec<ObjectId>,
172 pub constraints: Vec<ObjectId>,
173}
174
175#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
179#[ts(export, export_to = "FrontendApi.ts")]
180pub struct SketchCtor {
181 pub on: Plane,
183}
184
185#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
186#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPoint")]
187pub struct Point {
188 pub position: Point2d<Number>,
189 pub ctor: Option<PointCtor>,
190 pub owner: Option<ObjectId>,
191 pub freedom: Freedom,
192 pub constraints: Vec<ObjectId>,
193}
194
195impl Point {
196 pub fn freedom(&self) -> Freedom {
198 self.freedom
199 }
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
203#[ts(export, export_to = "FrontendApi.ts")]
204pub enum Freedom {
205 Free,
206 Fixed,
207 Conflict,
208}
209
210impl Freedom {
211 pub fn merge(self, other: Self) -> Self {
217 match (self, other) {
218 (Self::Conflict, _) | (_, Self::Conflict) => Self::Conflict,
219 (Self::Free, _) | (_, Self::Free) => Self::Free,
220 (Self::Fixed, Self::Fixed) => Self::Fixed,
221 }
222 }
223}
224
225#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
226#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSegment")]
227#[serde(tag = "type")]
228pub enum Segment {
229 Point(Point),
230 Line(Line),
231 Arc(Arc),
232 Circle(Circle),
233 ControlPointSpline(ControlPointSpline),
234}
235
236impl Segment {
237 pub fn human_friendly_kind_with_article(&self) -> &'static str {
240 match self {
241 Self::Point(_) => "a Point",
242 Self::Line(_) => "a Line",
243 Self::Arc(_) => "an Arc",
244 Self::Circle(_) => "a Circle",
245 Self::ControlPointSpline(_) => "a Control Point Spline",
246 }
247 }
248
249 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
254 match self {
255 Self::Point(p) => Some(p.freedom()),
256 Self::Line(l) => l.freedom(&lookup),
257 Self::Arc(a) => a.freedom(&lookup),
258 Self::Circle(c) => c.freedom(&lookup),
259 Self::ControlPointSpline(s) => s.freedom(&lookup),
260 }
261 }
262}
263
264#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
265#[ts(export, export_to = "FrontendApi.ts")]
266pub struct ExistingSegmentCtor {
267 pub id: ObjectId,
268 pub ctor: SegmentCtor,
269}
270
271#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
272#[ts(export, export_to = "FrontendApi.ts")]
273#[serde(rename_all = "camelCase")]
274pub struct ConstraintLabelPositionEdit {
275 pub constraint_id: ObjectId,
276 pub label_position: Point2d<Number>,
277}
278
279#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
280#[ts(export, export_to = "FrontendApi.ts")]
281#[serde(tag = "type")]
282pub enum SegmentCtor {
283 Point(PointCtor),
284 Line(LineCtor),
285 Arc(ArcCtor),
286 Circle(CircleCtor),
287 ControlPointSpline(ControlPointSplineCtor),
288}
289
290impl SegmentCtor {
291 pub fn human_friendly_kind_with_article(&self) -> &'static str {
294 match self {
295 Self::Point(_) => "a Point constructor",
296 Self::Line(_) => "a Line constructor",
297 Self::Arc(_) => "an Arc constructor",
298 Self::Circle(_) => "a Circle constructor",
299 Self::ControlPointSpline(_) => "a Control Point Spline constructor",
300 }
301 }
302}
303
304#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
305#[ts(export, export_to = "FrontendApi.ts")]
306pub struct PointCtor {
307 pub position: Point2d<Expr>,
308}
309
310#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
311#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPoint2d")]
312pub struct Point2d<U: std::fmt::Debug + Clone + ts_rs::TS> {
313 pub x: U,
314 pub y: U,
315}
316
317#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
318#[ts(export, export_to = "FrontendApi.ts", rename = "ApiLine")]
319pub struct Line {
320 pub start: ObjectId,
321 pub end: ObjectId,
322 #[serde(skip_serializing_if = "Option::is_none")]
323 #[ts(optional)]
324 pub owner: Option<ObjectId>,
325 pub ctor: SegmentCtor,
327 pub ctor_applicable: bool,
333 pub construction: bool,
334}
335
336impl Line {
337 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
340 let start = lookup(self.start)?;
341 let end = lookup(self.end)?;
342 Some(start.merge(end))
343 }
344}
345
346#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
347#[ts(export, export_to = "FrontendApi.ts")]
348pub struct LineCtor {
349 pub start: Point2d<Expr>,
350 pub end: Point2d<Expr>,
351 #[serde(skip_serializing_if = "Option::is_none")]
352 #[ts(optional)]
353 pub construction: Option<bool>,
354}
355
356#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
357#[ts(export, export_to = "FrontendApi.ts", rename = "ApiStartOrEnd")]
358#[serde(tag = "type")]
359pub enum StartOrEnd<T> {
360 Start(T),
361 End(T),
362}
363
364#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS)]
366#[ts(export, export_to = "FrontendApi.ts")]
367#[serde(rename_all = "lowercase")]
368pub enum ArcDirection {
369 #[default]
370 Ccw,
371 Cw,
372}
373
374impl ArcDirection {
375 pub fn is_ccw(&self) -> bool {
376 matches!(self, ArcDirection::Ccw)
377 }
378
379 pub fn is_clockwise(self) -> bool {
380 matches!(self, ArcDirection::Cw)
381 }
382
383 pub fn ccw_order<T>(self, start: T, end: T) -> (T, T) {
388 match self {
389 ArcDirection::Ccw => (start, end),
390 ArcDirection::Cw => (end, start),
391 }
392 }
393}
394
395#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
396#[ts(export, export_to = "FrontendApi.ts", rename = "ApiArc")]
397pub struct Arc {
398 pub start: ObjectId,
399 pub end: ObjectId,
400 pub center: ObjectId,
401 pub ctor: SegmentCtor,
403 pub ctor_applicable: bool,
404 pub construction: bool,
405 #[serde(default, skip_serializing_if = "ArcDirection::is_ccw")]
408 #[ts(as = "Option<ArcDirection>")]
409 #[ts(optional)]
410 pub direction: ArcDirection,
411}
412
413impl Arc {
414 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
417 let start = lookup(self.start)?;
418 let end = lookup(self.end)?;
419 let center = lookup(self.center)?;
420 Some(start.merge(end).merge(center))
421 }
422}
423
424#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
425#[ts(export, export_to = "FrontendApi.ts")]
426pub struct ArcCtor {
427 pub start: Point2d<Expr>,
428 pub end: Point2d<Expr>,
429 pub center: Point2d<Expr>,
430 #[serde(default, skip_serializing_if = "Option::is_none")]
433 #[ts(optional)]
434 pub direction: Option<ArcDirection>,
435 #[serde(skip_serializing_if = "Option::is_none")]
436 #[ts(optional)]
437 pub construction: Option<bool>,
438}
439
440#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
441#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCircle")]
442pub struct Circle {
443 pub start: ObjectId,
444 pub center: ObjectId,
445 pub ctor: SegmentCtor,
447 pub ctor_applicable: bool,
448 pub construction: bool,
449}
450
451impl Circle {
452 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
455 let start = lookup(self.start)?;
456 let center = lookup(self.center)?;
457 Some(start.merge(center))
458 }
459}
460
461#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
462#[ts(export, export_to = "FrontendApi.ts")]
463pub struct CircleCtor {
464 pub start: Point2d<Expr>,
465 pub center: Point2d<Expr>,
466 #[serde(skip_serializing_if = "Option::is_none")]
467 #[ts(optional)]
468 pub construction: Option<bool>,
469}
470
471#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
472#[ts(export, export_to = "FrontendApi.ts", rename = "ApiControlPointSpline")]
473pub struct ControlPointSpline {
474 pub controls: Vec<ObjectId>,
475 pub degree: u32,
476 pub ctor: SegmentCtor,
477 pub ctor_applicable: bool,
478 pub construction: bool,
479}
480
481impl ControlPointSpline {
482 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
485 let mut controls = self.controls.iter();
486 let first = lookup(*controls.next()?)?;
487 let merged = controls.try_fold(first, |acc, id| lookup(*id).map(|freedom| acc.merge(freedom)))?;
488 Some(merged)
489 }
490}
491
492#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
493#[ts(export, export_to = "FrontendApi.ts")]
494pub struct ControlPointSplineCtor {
495 pub points: Vec<Point2d<Expr>>,
496 #[serde(skip_serializing_if = "Option::is_none")]
497 #[ts(optional)]
498 pub construction: Option<bool>,
499}
500
501#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
502#[ts(export, export_to = "FrontendApi.ts", rename = "ApiConstraint")]
503#[serde(tag = "type")]
504pub enum Constraint {
510 Coincident(Coincident),
511 Distance(Distance),
512 Angle(Angle),
513 Diameter(Diameter),
514 EqualRadius(EqualRadius),
515 Fixed(Fixed),
516 HorizontalDistance(Distance),
517 VerticalDistance(Distance),
518 Horizontal(Horizontal),
519 LinesEqualLength(LinesEqualLength),
520 Midpoint(Midpoint),
521 Parallel(Parallel),
522 Perpendicular(Perpendicular),
523 Radius(Radius),
524 Symmetric(Symmetric),
525 Tangent(Tangent),
526 Vertical(Vertical),
527}
528
529#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
530#[ts(export, export_to = "FrontendApi.ts")]
531pub struct Coincident {
532 pub segments: Vec<ConstraintSegment>,
533}
534
535impl Coincident {
536 pub fn get_segments(&self) -> Vec<ObjectId> {
537 self.segments
538 .iter()
539 .filter_map(|segment| match segment {
540 ConstraintSegment::Segment(id) => Some(*id),
541 ConstraintSegment::Origin(_) => None,
542 })
543 .collect()
544 }
545
546 pub fn segment_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
547 self.segments.iter().filter_map(|segment| match segment {
548 ConstraintSegment::Segment(id) => Some(*id),
549 ConstraintSegment::Origin(_) => None,
550 })
551 }
552
553 pub fn contains_segment(&self, segment_id: ObjectId) -> bool {
554 self.segment_ids().any(|id| id == segment_id)
555 }
556}
557
558#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
559#[ts(export, export_to = "FrontendApi.ts")]
560#[serde(untagged)]
561pub enum ConstraintSegment {
562 Segment(ObjectId),
563 Origin(OriginLiteral),
564}
565
566impl ConstraintSegment {
567 pub const ORIGIN: Self = Self::Origin(OriginLiteral::Origin);
568}
569
570#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
571#[ts(export, export_to = "FrontendApi.ts")]
572#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
573pub enum OriginLiteral {
574 Origin,
575}
576
577impl From<ObjectId> for ConstraintSegment {
578 fn from(value: ObjectId) -> Self {
579 Self::Segment(value)
580 }
581}
582
583#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
584#[ts(export, export_to = "FrontendApi.ts")]
585pub struct Distance {
586 pub segments: Vec<ConstraintSegment>,
587 pub distance: Number,
588 #[serde(rename = "labelPosition")]
589 #[serde(default, skip_serializing_if = "Option::is_none")]
590 #[ts(rename = "labelPosition")]
591 #[ts(optional)]
592 pub label_position: Option<Point2d<Number>>,
593 pub source: ConstraintSource,
594}
595
596impl Distance {
597 pub fn segment_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
598 self.segments.iter().filter_map(|segment| match segment {
599 ConstraintSegment::Segment(id) => Some(*id),
600 ConstraintSegment::Origin(_) => None,
601 })
602 }
603
604 pub fn contains_segment(&self, segment_id: ObjectId) -> bool {
605 self.segment_ids().any(|id| id == segment_id)
606 }
607}
608
609#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
610#[ts(export, export_to = "FrontendApi.ts")]
611pub struct Angle {
612 pub lines: Vec<ObjectId>,
613 pub angle: Number,
614 #[serde(default, skip_serializing_if = "Option::is_none")]
615 #[ts(optional)]
616 pub sector: Option<u8>,
617 #[serde(default, skip_serializing_if = "Option::is_none")]
618 #[ts(optional)]
619 pub inverse: Option<bool>,
620 #[serde(rename = "labelPosition")]
621 #[serde(default, skip_serializing_if = "Option::is_none")]
622 #[ts(rename = "labelPosition")]
623 #[ts(optional)]
624 pub label_position: Option<Point2d<Number>>,
625 pub source: ConstraintSource,
626}
627
628#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize, ts_rs::TS)]
629#[ts(export, export_to = "FrontendApi.ts")]
630pub struct ConstraintSource {
631 pub expr: String,
632 pub is_literal: bool,
633}
634
635#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
636#[ts(export, export_to = "FrontendApi.ts")]
637pub struct Radius {
638 pub arc: ObjectId,
639 pub radius: Number,
640 #[serde(rename = "labelPosition")]
641 #[serde(default, skip_serializing_if = "Option::is_none")]
642 #[ts(rename = "labelPosition")]
643 #[ts(optional)]
644 pub label_position: Option<Point2d<Number>>,
645 #[serde(default)]
646 pub source: ConstraintSource,
647}
648
649#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
650#[ts(export, export_to = "FrontendApi.ts")]
651pub struct Diameter {
652 pub arc: ObjectId,
653 pub diameter: Number,
654 #[serde(rename = "labelPosition")]
655 #[serde(default, skip_serializing_if = "Option::is_none")]
656 #[ts(rename = "labelPosition")]
657 #[ts(optional)]
658 pub label_position: Option<Point2d<Number>>,
659 #[serde(default)]
660 pub source: ConstraintSource,
661}
662
663#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
664#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
665pub struct EqualRadius {
666 pub input: Vec<ObjectId>,
667}
668
669#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
672#[ts(export, export_to = "FrontendApi.ts")]
673pub struct Fixed {
674 pub points: Vec<FixedPoint>,
675}
676
677#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
679#[ts(export, export_to = "FrontendApi.ts")]
680pub struct FixedPoint {
681 pub point: ObjectId,
682 pub position: Point2d<Number>,
683}
684
685#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
686#[ts(export, export_to = "FrontendApi.ts")]
687#[serde(untagged)]
688pub enum Horizontal {
689 Line { line: ObjectId },
690 Points { points: Vec<ConstraintSegment> },
691}
692
693#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
694#[ts(export, export_to = "FrontendApi.ts")]
695pub struct LinesEqualLength {
696 pub lines: Vec<ObjectId>,
697}
698
699#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
700#[ts(export, export_to = "FrontendApi.ts")]
701pub struct Midpoint {
702 pub point: ConstraintSegment,
703 #[serde(alias = "line")]
704 pub segment: ObjectId,
705}
706
707#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
708#[ts(export, export_to = "FrontendApi.ts")]
709#[serde(untagged)]
710pub enum Vertical {
711 Line { line: ObjectId },
712 Points { points: Vec<ConstraintSegment> },
713}
714
715#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
716#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
717pub struct Parallel {
718 pub lines: Vec<ObjectId>,
719}
720
721#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
722#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
723pub struct Perpendicular {
724 pub lines: Vec<ObjectId>,
725}
726
727#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
728#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
729pub struct Symmetric {
730 pub input: Vec<ObjectId>,
731 pub axis: ObjectId,
732}
733
734#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
735#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
736pub struct Tangent {
737 pub input: Vec<ObjectId>,
738}