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(tag = "type")]
274pub enum SegmentCtor {
275 Point(PointCtor),
276 Line(LineCtor),
277 Arc(ArcCtor),
278 Circle(CircleCtor),
279 ControlPointSpline(ControlPointSplineCtor),
280}
281
282impl SegmentCtor {
283 pub fn human_friendly_kind_with_article(&self) -> &'static str {
286 match self {
287 Self::Point(_) => "a Point constructor",
288 Self::Line(_) => "a Line constructor",
289 Self::Arc(_) => "an Arc constructor",
290 Self::Circle(_) => "a Circle constructor",
291 Self::ControlPointSpline(_) => "a Control Point Spline constructor",
292 }
293 }
294}
295
296#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
297#[ts(export, export_to = "FrontendApi.ts")]
298pub struct PointCtor {
299 pub position: Point2d<Expr>,
300}
301
302#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
303#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPoint2d")]
304pub struct Point2d<U: std::fmt::Debug + Clone + ts_rs::TS> {
305 pub x: U,
306 pub y: U,
307}
308
309#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
310#[ts(export, export_to = "FrontendApi.ts", rename = "ApiLine")]
311pub struct Line {
312 pub start: ObjectId,
313 pub end: ObjectId,
314 #[serde(skip_serializing_if = "Option::is_none")]
315 #[ts(optional)]
316 pub owner: Option<ObjectId>,
317 pub ctor: SegmentCtor,
319 pub ctor_applicable: bool,
325 pub construction: bool,
326}
327
328impl Line {
329 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
332 let start = lookup(self.start)?;
333 let end = lookup(self.end)?;
334 Some(start.merge(end))
335 }
336}
337
338#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
339#[ts(export, export_to = "FrontendApi.ts")]
340pub struct LineCtor {
341 pub start: Point2d<Expr>,
342 pub end: Point2d<Expr>,
343 #[serde(skip_serializing_if = "Option::is_none")]
344 #[ts(optional)]
345 pub construction: Option<bool>,
346}
347
348#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
349#[ts(export, export_to = "FrontendApi.ts", rename = "ApiStartOrEnd")]
350#[serde(tag = "type")]
351pub enum StartOrEnd<T> {
352 Start(T),
353 End(T),
354}
355
356#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
357#[ts(export, export_to = "FrontendApi.ts", rename = "ApiArc")]
358pub struct Arc {
359 pub start: ObjectId,
360 pub end: ObjectId,
361 pub center: ObjectId,
362 pub ctor: SegmentCtor,
364 pub ctor_applicable: bool,
365 pub construction: bool,
366}
367
368impl Arc {
369 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
372 let start = lookup(self.start)?;
373 let end = lookup(self.end)?;
374 let center = lookup(self.center)?;
375 Some(start.merge(end).merge(center))
376 }
377}
378
379#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
380#[ts(export, export_to = "FrontendApi.ts")]
381pub struct ArcCtor {
382 pub start: Point2d<Expr>,
383 pub end: Point2d<Expr>,
384 pub center: Point2d<Expr>,
385 #[serde(skip_serializing_if = "Option::is_none")]
386 #[ts(optional)]
387 pub construction: Option<bool>,
388}
389
390#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
391#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCircle")]
392pub struct Circle {
393 pub start: ObjectId,
394 pub center: ObjectId,
395 pub ctor: SegmentCtor,
397 pub ctor_applicable: bool,
398 pub construction: bool,
399}
400
401impl Circle {
402 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
405 let start = lookup(self.start)?;
406 let center = lookup(self.center)?;
407 Some(start.merge(center))
408 }
409}
410
411#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
412#[ts(export, export_to = "FrontendApi.ts")]
413pub struct CircleCtor {
414 pub start: Point2d<Expr>,
415 pub center: Point2d<Expr>,
416 #[serde(skip_serializing_if = "Option::is_none")]
417 #[ts(optional)]
418 pub construction: Option<bool>,
419}
420
421#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
422#[ts(export, export_to = "FrontendApi.ts", rename = "ApiControlPointSpline")]
423pub struct ControlPointSpline {
424 pub controls: Vec<ObjectId>,
425 pub degree: u32,
426 pub ctor: SegmentCtor,
427 pub ctor_applicable: bool,
428 pub construction: bool,
429}
430
431impl ControlPointSpline {
432 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
435 let mut controls = self.controls.iter();
436 let first = lookup(*controls.next()?)?;
437 let merged = controls.try_fold(first, |acc, id| lookup(*id).map(|freedom| acc.merge(freedom)))?;
438 Some(merged)
439 }
440}
441
442#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
443#[ts(export, export_to = "FrontendApi.ts")]
444pub struct ControlPointSplineCtor {
445 pub points: Vec<Point2d<Expr>>,
446 #[serde(skip_serializing_if = "Option::is_none")]
447 #[ts(optional)]
448 pub construction: Option<bool>,
449}
450
451#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
452#[ts(export, export_to = "FrontendApi.ts", rename = "ApiConstraint")]
453#[serde(tag = "type")]
454pub enum Constraint {
460 Coincident(Coincident),
461 Distance(Distance),
462 Angle(Angle),
463 Diameter(Diameter),
464 EqualRadius(EqualRadius),
465 Fixed(Fixed),
466 HorizontalDistance(Distance),
467 VerticalDistance(Distance),
468 Horizontal(Horizontal),
469 LinesEqualLength(LinesEqualLength),
470 Midpoint(Midpoint),
471 Parallel(Parallel),
472 Perpendicular(Perpendicular),
473 Radius(Radius),
474 Symmetric(Symmetric),
475 Tangent(Tangent),
476 Vertical(Vertical),
477}
478
479#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
480#[ts(export, export_to = "FrontendApi.ts")]
481pub struct Coincident {
482 pub segments: Vec<ConstraintSegment>,
483}
484
485impl Coincident {
486 pub fn get_segments(&self) -> Vec<ObjectId> {
487 self.segments
488 .iter()
489 .filter_map(|segment| match segment {
490 ConstraintSegment::Segment(id) => Some(*id),
491 ConstraintSegment::Origin(_) => None,
492 })
493 .collect()
494 }
495
496 pub fn segment_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
497 self.segments.iter().filter_map(|segment| match segment {
498 ConstraintSegment::Segment(id) => Some(*id),
499 ConstraintSegment::Origin(_) => None,
500 })
501 }
502
503 pub fn contains_segment(&self, segment_id: ObjectId) -> bool {
504 self.segment_ids().any(|id| id == segment_id)
505 }
506}
507
508#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
509#[ts(export, export_to = "FrontendApi.ts")]
510#[serde(untagged)]
511pub enum ConstraintSegment {
512 Segment(ObjectId),
513 Origin(OriginLiteral),
514}
515
516impl ConstraintSegment {
517 pub const ORIGIN: Self = Self::Origin(OriginLiteral::Origin);
518}
519
520#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
521#[ts(export, export_to = "FrontendApi.ts")]
522#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
523pub enum OriginLiteral {
524 Origin,
525}
526
527impl From<ObjectId> for ConstraintSegment {
528 fn from(value: ObjectId) -> Self {
529 Self::Segment(value)
530 }
531}
532
533#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
534#[ts(export, export_to = "FrontendApi.ts")]
535pub struct Distance {
536 pub points: Vec<ConstraintSegment>,
537 pub distance: Number,
538 #[serde(rename = "labelPosition")]
539 #[serde(default, skip_serializing_if = "Option::is_none")]
540 #[ts(rename = "labelPosition")]
541 #[ts(optional)]
542 pub label_position: Option<Point2d<Number>>,
543 pub source: ConstraintSource,
544}
545
546impl Distance {
547 pub fn point_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
548 self.points.iter().filter_map(|point| match point {
549 ConstraintSegment::Segment(id) => Some(*id),
550 ConstraintSegment::Origin(_) => None,
551 })
552 }
553
554 pub fn contains_point(&self, point_id: ObjectId) -> bool {
555 self.point_ids().any(|id| id == point_id)
556 }
557}
558
559#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
560#[ts(export, export_to = "FrontendApi.ts")]
561pub struct Angle {
562 pub lines: Vec<ObjectId>,
563 pub angle: Number,
564 pub source: ConstraintSource,
565}
566
567#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize, ts_rs::TS)]
568#[ts(export, export_to = "FrontendApi.ts")]
569pub struct ConstraintSource {
570 pub expr: String,
571 pub is_literal: bool,
572}
573
574#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
575#[ts(export, export_to = "FrontendApi.ts")]
576pub struct Radius {
577 pub arc: ObjectId,
578 pub radius: Number,
579 #[serde(rename = "labelPosition")]
580 #[serde(default, skip_serializing_if = "Option::is_none")]
581 #[ts(rename = "labelPosition")]
582 #[ts(optional)]
583 pub label_position: Option<Point2d<Number>>,
584 #[serde(default)]
585 pub source: ConstraintSource,
586}
587
588#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
589#[ts(export, export_to = "FrontendApi.ts")]
590pub struct Diameter {
591 pub arc: ObjectId,
592 pub diameter: Number,
593 #[serde(rename = "labelPosition")]
594 #[serde(default, skip_serializing_if = "Option::is_none")]
595 #[ts(rename = "labelPosition")]
596 #[ts(optional)]
597 pub label_position: Option<Point2d<Number>>,
598 #[serde(default)]
599 pub source: ConstraintSource,
600}
601
602#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
603#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
604pub struct EqualRadius {
605 pub input: Vec<ObjectId>,
606}
607
608#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
611#[ts(export, export_to = "FrontendApi.ts")]
612pub struct Fixed {
613 pub points: Vec<FixedPoint>,
614}
615
616#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
618#[ts(export, export_to = "FrontendApi.ts")]
619pub struct FixedPoint {
620 pub point: ObjectId,
621 pub position: Point2d<Number>,
622}
623
624#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
625#[ts(export, export_to = "FrontendApi.ts")]
626#[serde(untagged)]
627pub enum Horizontal {
628 Line { line: ObjectId },
629 Points { points: Vec<ConstraintSegment> },
630}
631
632#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
633#[ts(export, export_to = "FrontendApi.ts")]
634pub struct LinesEqualLength {
635 pub lines: Vec<ObjectId>,
636}
637
638#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
639#[ts(export, export_to = "FrontendApi.ts")]
640pub struct Midpoint {
641 pub point: ObjectId,
642 #[serde(alias = "line")]
643 pub segment: ObjectId,
644}
645
646#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
647#[ts(export, export_to = "FrontendApi.ts")]
648#[serde(untagged)]
649pub enum Vertical {
650 Line { line: ObjectId },
651 Points { points: Vec<ConstraintSegment> },
652}
653
654#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
655#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
656pub struct Parallel {
657 pub lines: Vec<ObjectId>,
658}
659
660#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
661#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
662pub struct Perpendicular {
663 pub lines: Vec<ObjectId>,
664}
665
666#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
667#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
668pub struct Symmetric {
669 pub input: Vec<ObjectId>,
670 pub axis: ObjectId,
671}
672
673#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
674#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
675pub struct Tangent {
676 pub input: Vec<ObjectId>,
677}