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(
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, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
365#[ts(export, export_to = "FrontendApi.ts", rename = "ApiArc")]
366pub struct Arc {
367 pub start: ObjectId,
368 pub end: ObjectId,
369 pub center: ObjectId,
370 pub ctor: SegmentCtor,
372 pub ctor_applicable: bool,
373 pub construction: bool,
374}
375
376impl Arc {
377 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
380 let start = lookup(self.start)?;
381 let end = lookup(self.end)?;
382 let center = lookup(self.center)?;
383 Some(start.merge(end).merge(center))
384 }
385}
386
387#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
388#[ts(export, export_to = "FrontendApi.ts")]
389pub struct ArcCtor {
390 pub start: Point2d<Expr>,
391 pub end: Point2d<Expr>,
392 pub center: Point2d<Expr>,
393 #[serde(skip_serializing_if = "Option::is_none")]
394 #[ts(optional)]
395 pub construction: Option<bool>,
396}
397
398#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
399#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCircle")]
400pub struct Circle {
401 pub start: ObjectId,
402 pub center: ObjectId,
403 pub ctor: SegmentCtor,
405 pub ctor_applicable: bool,
406 pub construction: bool,
407}
408
409impl Circle {
410 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
413 let start = lookup(self.start)?;
414 let center = lookup(self.center)?;
415 Some(start.merge(center))
416 }
417}
418
419#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
420#[ts(export, export_to = "FrontendApi.ts")]
421pub struct CircleCtor {
422 pub start: Point2d<Expr>,
423 pub center: Point2d<Expr>,
424 #[serde(skip_serializing_if = "Option::is_none")]
425 #[ts(optional)]
426 pub construction: Option<bool>,
427}
428
429#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
430#[ts(export, export_to = "FrontendApi.ts", rename = "ApiControlPointSpline")]
431pub struct ControlPointSpline {
432 pub controls: Vec<ObjectId>,
433 pub degree: u32,
434 pub ctor: SegmentCtor,
435 pub ctor_applicable: bool,
436 pub construction: bool,
437}
438
439impl ControlPointSpline {
440 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
443 let mut controls = self.controls.iter();
444 let first = lookup(*controls.next()?)?;
445 let merged = controls.try_fold(first, |acc, id| lookup(*id).map(|freedom| acc.merge(freedom)))?;
446 Some(merged)
447 }
448}
449
450#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
451#[ts(export, export_to = "FrontendApi.ts")]
452pub struct ControlPointSplineCtor {
453 pub points: Vec<Point2d<Expr>>,
454 #[serde(skip_serializing_if = "Option::is_none")]
455 #[ts(optional)]
456 pub construction: Option<bool>,
457}
458
459#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
460#[ts(export, export_to = "FrontendApi.ts", rename = "ApiConstraint")]
461#[serde(tag = "type")]
462pub enum Constraint {
468 Coincident(Coincident),
469 Distance(Distance),
470 Angle(Angle),
471 Diameter(Diameter),
472 EqualRadius(EqualRadius),
473 Fixed(Fixed),
474 HorizontalDistance(Distance),
475 VerticalDistance(Distance),
476 Horizontal(Horizontal),
477 LinesEqualLength(LinesEqualLength),
478 Midpoint(Midpoint),
479 Parallel(Parallel),
480 Perpendicular(Perpendicular),
481 Radius(Radius),
482 Symmetric(Symmetric),
483 Tangent(Tangent),
484 Vertical(Vertical),
485}
486
487#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
488#[ts(export, export_to = "FrontendApi.ts")]
489pub struct Coincident {
490 pub segments: Vec<ConstraintSegment>,
491}
492
493impl Coincident {
494 pub fn get_segments(&self) -> Vec<ObjectId> {
495 self.segments
496 .iter()
497 .filter_map(|segment| match segment {
498 ConstraintSegment::Segment(id) => Some(*id),
499 ConstraintSegment::Origin(_) => None,
500 })
501 .collect()
502 }
503
504 pub fn segment_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
505 self.segments.iter().filter_map(|segment| match segment {
506 ConstraintSegment::Segment(id) => Some(*id),
507 ConstraintSegment::Origin(_) => None,
508 })
509 }
510
511 pub fn contains_segment(&self, segment_id: ObjectId) -> bool {
512 self.segment_ids().any(|id| id == segment_id)
513 }
514}
515
516#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
517#[ts(export, export_to = "FrontendApi.ts")]
518#[serde(untagged)]
519pub enum ConstraintSegment {
520 Segment(ObjectId),
521 Origin(OriginLiteral),
522}
523
524impl ConstraintSegment {
525 pub const ORIGIN: Self = Self::Origin(OriginLiteral::Origin);
526}
527
528#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
529#[ts(export, export_to = "FrontendApi.ts")]
530#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
531pub enum OriginLiteral {
532 Origin,
533}
534
535impl From<ObjectId> for ConstraintSegment {
536 fn from(value: ObjectId) -> Self {
537 Self::Segment(value)
538 }
539}
540
541#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
542#[ts(export, export_to = "FrontendApi.ts")]
543pub struct Distance {
544 pub points: Vec<ConstraintSegment>,
545 pub distance: Number,
546 #[serde(rename = "labelPosition")]
547 #[serde(default, skip_serializing_if = "Option::is_none")]
548 #[ts(rename = "labelPosition")]
549 #[ts(optional)]
550 pub label_position: Option<Point2d<Number>>,
551 pub source: ConstraintSource,
552}
553
554impl Distance {
555 pub fn point_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
556 self.points.iter().filter_map(|point| match point {
557 ConstraintSegment::Segment(id) => Some(*id),
558 ConstraintSegment::Origin(_) => None,
559 })
560 }
561
562 pub fn contains_point(&self, point_id: ObjectId) -> bool {
563 self.point_ids().any(|id| id == point_id)
564 }
565}
566
567#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
568#[ts(export, export_to = "FrontendApi.ts")]
569pub struct Angle {
570 pub lines: Vec<ObjectId>,
571 pub angle: Number,
572 pub source: ConstraintSource,
573}
574
575#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize, ts_rs::TS)]
576#[ts(export, export_to = "FrontendApi.ts")]
577pub struct ConstraintSource {
578 pub expr: String,
579 pub is_literal: bool,
580}
581
582#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
583#[ts(export, export_to = "FrontendApi.ts")]
584pub struct Radius {
585 pub arc: ObjectId,
586 pub radius: Number,
587 #[serde(rename = "labelPosition")]
588 #[serde(default, skip_serializing_if = "Option::is_none")]
589 #[ts(rename = "labelPosition")]
590 #[ts(optional)]
591 pub label_position: Option<Point2d<Number>>,
592 #[serde(default)]
593 pub source: ConstraintSource,
594}
595
596#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
597#[ts(export, export_to = "FrontendApi.ts")]
598pub struct Diameter {
599 pub arc: ObjectId,
600 pub diameter: Number,
601 #[serde(rename = "labelPosition")]
602 #[serde(default, skip_serializing_if = "Option::is_none")]
603 #[ts(rename = "labelPosition")]
604 #[ts(optional)]
605 pub label_position: Option<Point2d<Number>>,
606 #[serde(default)]
607 pub source: ConstraintSource,
608}
609
610#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
611#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
612pub struct EqualRadius {
613 pub input: Vec<ObjectId>,
614}
615
616#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
619#[ts(export, export_to = "FrontendApi.ts")]
620pub struct Fixed {
621 pub points: Vec<FixedPoint>,
622}
623
624#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
626#[ts(export, export_to = "FrontendApi.ts")]
627pub struct FixedPoint {
628 pub point: ObjectId,
629 pub position: Point2d<Number>,
630}
631
632#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
633#[ts(export, export_to = "FrontendApi.ts")]
634#[serde(untagged)]
635pub enum Horizontal {
636 Line { line: ObjectId },
637 Points { points: Vec<ConstraintSegment> },
638}
639
640#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
641#[ts(export, export_to = "FrontendApi.ts")]
642pub struct LinesEqualLength {
643 pub lines: Vec<ObjectId>,
644}
645
646#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
647#[ts(export, export_to = "FrontendApi.ts")]
648pub struct Midpoint {
649 pub point: ConstraintSegment,
650 #[serde(alias = "line")]
651 pub segment: ObjectId,
652}
653
654#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
655#[ts(export, export_to = "FrontendApi.ts")]
656#[serde(untagged)]
657pub enum Vertical {
658 Line { line: ObjectId },
659 Points { points: Vec<ConstraintSegment> },
660}
661
662#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
663#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
664pub struct Parallel {
665 pub lines: Vec<ObjectId>,
666}
667
668#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
669#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
670pub struct Perpendicular {
671 pub lines: Vec<ObjectId>,
672}
673
674#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
675#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
676pub struct Symmetric {
677 pub input: Vec<ObjectId>,
678 pub axis: ObjectId,
679}
680
681#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
682#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
683pub struct Tangent {
684 pub input: Vec<ObjectId>,
685}