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 #[allow(clippy::too_many_arguments)]
130 async fn batch_split_segment_operations(
131 &mut self,
132 ctx: &ExecutorContext,
133 version: Version,
134 sketch: ObjectId,
135 edit_segments: Vec<ExistingSegmentCtor>,
136 add_constraints: Vec<Constraint>,
137 delete_constraint_ids: Vec<ObjectId>,
138 new_segment_info: NewSegmentInfo,
139 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
140
141 async fn batch_tail_cut_operations(
144 &mut self,
145 ctx: &ExecutorContext,
146 version: Version,
147 sketch: ObjectId,
148 edit_segments: Vec<ExistingSegmentCtor>,
149 add_constraints: Vec<Constraint>,
150 delete_constraint_ids: Vec<ObjectId>,
151 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
152}
153
154#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
155#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSketch")]
156pub struct Sketch {
157 pub args: SketchCtor,
158 pub plane: ObjectId,
159 pub segments: Vec<ObjectId>,
160 pub constraints: Vec<ObjectId>,
161}
162
163#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
167#[ts(export, export_to = "FrontendApi.ts")]
168pub struct SketchCtor {
169 pub on: Plane,
171}
172
173#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
174#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPoint")]
175pub struct Point {
176 pub position: Point2d<Number>,
177 pub ctor: Option<PointCtor>,
178 pub owner: Option<ObjectId>,
179 pub freedom: Freedom,
180 pub constraints: Vec<ObjectId>,
181}
182
183impl Point {
184 pub fn freedom(&self) -> Freedom {
186 self.freedom
187 }
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
191#[ts(export, export_to = "FrontendApi.ts")]
192pub enum Freedom {
193 Free,
194 Fixed,
195 Conflict,
196}
197
198impl Freedom {
199 pub fn merge(self, other: Self) -> Self {
205 match (self, other) {
206 (Self::Conflict, _) | (_, Self::Conflict) => Self::Conflict,
207 (Self::Free, _) | (_, Self::Free) => Self::Free,
208 (Self::Fixed, Self::Fixed) => Self::Fixed,
209 }
210 }
211}
212
213#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
214#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSegment")]
215#[serde(tag = "type")]
216pub enum Segment {
217 Point(Point),
218 Line(Line),
219 Arc(Arc),
220 Circle(Circle),
221}
222
223impl Segment {
224 pub fn human_friendly_kind_with_article(&self) -> &'static str {
227 match self {
228 Self::Point(_) => "a Point",
229 Self::Line(_) => "a Line",
230 Self::Arc(_) => "an Arc",
231 Self::Circle(_) => "a Circle",
232 }
233 }
234
235 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
240 match self {
241 Self::Point(p) => Some(p.freedom()),
242 Self::Line(l) => l.freedom(&lookup),
243 Self::Arc(a) => a.freedom(&lookup),
244 Self::Circle(c) => c.freedom(&lookup),
245 }
246 }
247}
248
249#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
250#[ts(export, export_to = "FrontendApi.ts")]
251pub struct ExistingSegmentCtor {
252 pub id: ObjectId,
253 pub ctor: SegmentCtor,
254}
255
256#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
257#[ts(export, export_to = "FrontendApi.ts")]
258#[serde(tag = "type")]
259pub enum SegmentCtor {
260 Point(PointCtor),
261 Line(LineCtor),
262 Arc(ArcCtor),
263 Circle(CircleCtor),
264}
265
266impl SegmentCtor {
267 pub fn human_friendly_kind_with_article(&self) -> &'static str {
270 match self {
271 Self::Point(_) => "a Point constructor",
272 Self::Line(_) => "a Line constructor",
273 Self::Arc(_) => "an Arc constructor",
274 Self::Circle(_) => "a Circle constructor",
275 }
276 }
277}
278
279#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
280#[ts(export, export_to = "FrontendApi.ts")]
281pub struct PointCtor {
282 pub position: Point2d<Expr>,
283}
284
285#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
286#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPoint2d")]
287pub struct Point2d<U: std::fmt::Debug + Clone + ts_rs::TS> {
288 pub x: U,
289 pub y: U,
290}
291
292#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
293#[ts(export, export_to = "FrontendApi.ts", rename = "ApiLine")]
294pub struct Line {
295 pub start: ObjectId,
296 pub end: ObjectId,
297 pub ctor: SegmentCtor,
299 pub ctor_applicable: bool,
305 pub construction: bool,
306}
307
308impl Line {
309 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
312 let start = lookup(self.start)?;
313 let end = lookup(self.end)?;
314 Some(start.merge(end))
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
319#[ts(export, export_to = "FrontendApi.ts")]
320pub struct LineCtor {
321 pub start: Point2d<Expr>,
322 pub end: Point2d<Expr>,
323 #[serde(skip_serializing_if = "Option::is_none")]
324 #[ts(optional)]
325 pub construction: Option<bool>,
326}
327
328#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
329#[ts(export, export_to = "FrontendApi.ts", rename = "ApiStartOrEnd")]
330#[serde(tag = "type")]
331pub enum StartOrEnd<T> {
332 Start(T),
333 End(T),
334}
335
336#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
337#[ts(export, export_to = "FrontendApi.ts", rename = "ApiArc")]
338pub struct Arc {
339 pub start: ObjectId,
340 pub end: ObjectId,
341 pub center: ObjectId,
342 pub ctor: SegmentCtor,
344 pub ctor_applicable: bool,
345 pub construction: bool,
346}
347
348impl Arc {
349 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
352 let start = lookup(self.start)?;
353 let end = lookup(self.end)?;
354 let center = lookup(self.center)?;
355 Some(start.merge(end).merge(center))
356 }
357}
358
359#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
360#[ts(export, export_to = "FrontendApi.ts")]
361pub struct ArcCtor {
362 pub start: Point2d<Expr>,
363 pub end: Point2d<Expr>,
364 pub center: Point2d<Expr>,
365 #[serde(skip_serializing_if = "Option::is_none")]
366 #[ts(optional)]
367 pub construction: Option<bool>,
368}
369
370#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
371#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCircle")]
372pub struct Circle {
373 pub start: ObjectId,
374 pub center: ObjectId,
375 pub ctor: SegmentCtor,
377 pub ctor_applicable: bool,
378 pub construction: bool,
379}
380
381impl Circle {
382 pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
385 let start = lookup(self.start)?;
386 let center = lookup(self.center)?;
387 Some(start.merge(center))
388 }
389}
390
391#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
392#[ts(export, export_to = "FrontendApi.ts")]
393pub struct CircleCtor {
394 pub start: Point2d<Expr>,
395 pub center: Point2d<Expr>,
396 #[serde(skip_serializing_if = "Option::is_none")]
397 #[ts(optional)]
398 pub construction: Option<bool>,
399}
400
401#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
402#[ts(export, export_to = "FrontendApi.ts", rename = "ApiConstraint")]
403#[serde(tag = "type")]
404pub enum Constraint {
405 Coincident(Coincident),
406 Distance(Distance),
407 Angle(Angle),
408 Diameter(Diameter),
409 EqualRadius(EqualRadius),
410 Fixed(Fixed),
411 HorizontalDistance(Distance),
412 VerticalDistance(Distance),
413 Horizontal(Horizontal),
414 LinesEqualLength(LinesEqualLength),
415 Midpoint(Midpoint),
416 Parallel(Parallel),
417 Perpendicular(Perpendicular),
418 Radius(Radius),
419 Tangent(Tangent),
420 Vertical(Vertical),
421}
422
423#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
424#[ts(export, export_to = "FrontendApi.ts")]
425pub struct Coincident {
426 pub segments: Vec<ConstraintSegment>,
427}
428
429impl Coincident {
430 pub fn get_segments(&self) -> Vec<ObjectId> {
431 self.segments
432 .iter()
433 .filter_map(|segment| match segment {
434 ConstraintSegment::Segment(id) => Some(*id),
435 ConstraintSegment::Origin(_) => None,
436 })
437 .collect()
438 }
439
440 pub fn segment_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
441 self.segments.iter().filter_map(|segment| match segment {
442 ConstraintSegment::Segment(id) => Some(*id),
443 ConstraintSegment::Origin(_) => None,
444 })
445 }
446
447 pub fn contains_segment(&self, segment_id: ObjectId) -> bool {
448 self.segment_ids().any(|id| id == segment_id)
449 }
450}
451
452#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
453#[ts(export, export_to = "FrontendApi.ts")]
454#[serde(untagged)]
455pub enum ConstraintSegment {
456 Segment(ObjectId),
457 Origin(OriginLiteral),
458}
459
460impl ConstraintSegment {
461 pub const ORIGIN: Self = Self::Origin(OriginLiteral::Origin);
462}
463
464#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
465#[ts(export, export_to = "FrontendApi.ts")]
466#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
467pub enum OriginLiteral {
468 Origin,
469}
470
471impl From<ObjectId> for ConstraintSegment {
472 fn from(value: ObjectId) -> Self {
473 Self::Segment(value)
474 }
475}
476
477#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
478#[ts(export, export_to = "FrontendApi.ts")]
479pub struct Distance {
480 pub points: Vec<ConstraintSegment>,
481 pub distance: Number,
482 pub source: ConstraintSource,
483}
484
485impl Distance {
486 pub fn point_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
487 self.points.iter().filter_map(|point| match point {
488 ConstraintSegment::Segment(id) => Some(*id),
489 ConstraintSegment::Origin(_) => None,
490 })
491 }
492
493 pub fn contains_point(&self, point_id: ObjectId) -> bool {
494 self.point_ids().any(|id| id == point_id)
495 }
496}
497
498#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
499#[ts(export, export_to = "FrontendApi.ts")]
500pub struct Angle {
501 pub lines: Vec<ObjectId>,
502 pub angle: Number,
503 pub source: ConstraintSource,
504}
505
506#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize, ts_rs::TS)]
507#[ts(export, export_to = "FrontendApi.ts")]
508pub struct ConstraintSource {
509 pub expr: String,
510 pub is_literal: bool,
511}
512
513#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
514#[ts(export, export_to = "FrontendApi.ts")]
515pub struct Radius {
516 pub arc: ObjectId,
517 pub radius: Number,
518 #[serde(default)]
519 pub source: ConstraintSource,
520}
521
522#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
523#[ts(export, export_to = "FrontendApi.ts")]
524pub struct Diameter {
525 pub arc: ObjectId,
526 pub diameter: Number,
527 #[serde(default)]
528 pub source: ConstraintSource,
529}
530
531#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
532#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
533pub struct EqualRadius {
534 pub input: Vec<ObjectId>,
535}
536
537#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
540#[ts(export, export_to = "FrontendApi.ts")]
541pub struct Fixed {
542 pub points: Vec<FixedPoint>,
543}
544
545#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
547#[ts(export, export_to = "FrontendApi.ts")]
548pub struct FixedPoint {
549 pub point: ObjectId,
550 pub position: Point2d<Number>,
551}
552
553#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
554#[ts(export, export_to = "FrontendApi.ts")]
555#[serde(untagged)]
556pub enum Horizontal {
557 Line { line: ObjectId },
558 Points { points: Vec<ConstraintSegment> },
559}
560
561#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
562#[ts(export, export_to = "FrontendApi.ts")]
563pub struct LinesEqualLength {
564 pub lines: Vec<ObjectId>,
565}
566
567#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
568#[ts(export, export_to = "FrontendApi.ts")]
569pub struct Midpoint {
570 pub point: ObjectId,
571 #[serde(alias = "line")]
572 pub segment: ObjectId,
573}
574
575#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
576#[ts(export, export_to = "FrontendApi.ts")]
577#[serde(untagged)]
578pub enum Vertical {
579 Line { line: ObjectId },
580 Points { points: Vec<ConstraintSegment> },
581}
582
583#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
584#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
585pub struct Parallel {
586 pub lines: Vec<ObjectId>,
587}
588
589#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
590#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
591pub struct Perpendicular {
592 pub lines: Vec<ObjectId>,
593}
594
595#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
596#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
597pub struct Tangent {
598 pub input: Vec<ObjectId>,
599}