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 Symmetric(Symmetric),
420 Tangent(Tangent),
421 Vertical(Vertical),
422}
423
424#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
425#[ts(export, export_to = "FrontendApi.ts")]
426pub struct Coincident {
427 pub segments: Vec<ConstraintSegment>,
428}
429
430impl Coincident {
431 pub fn get_segments(&self) -> Vec<ObjectId> {
432 self.segments
433 .iter()
434 .filter_map(|segment| match segment {
435 ConstraintSegment::Segment(id) => Some(*id),
436 ConstraintSegment::Origin(_) => None,
437 })
438 .collect()
439 }
440
441 pub fn segment_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
442 self.segments.iter().filter_map(|segment| match segment {
443 ConstraintSegment::Segment(id) => Some(*id),
444 ConstraintSegment::Origin(_) => None,
445 })
446 }
447
448 pub fn contains_segment(&self, segment_id: ObjectId) -> bool {
449 self.segment_ids().any(|id| id == segment_id)
450 }
451}
452
453#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
454#[ts(export, export_to = "FrontendApi.ts")]
455#[serde(untagged)]
456pub enum ConstraintSegment {
457 Segment(ObjectId),
458 Origin(OriginLiteral),
459}
460
461impl ConstraintSegment {
462 pub const ORIGIN: Self = Self::Origin(OriginLiteral::Origin);
463}
464
465#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
466#[ts(export, export_to = "FrontendApi.ts")]
467#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
468pub enum OriginLiteral {
469 Origin,
470}
471
472impl From<ObjectId> for ConstraintSegment {
473 fn from(value: ObjectId) -> Self {
474 Self::Segment(value)
475 }
476}
477
478#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
479#[ts(export, export_to = "FrontendApi.ts")]
480pub struct Distance {
481 pub points: Vec<ConstraintSegment>,
482 pub distance: Number,
483 pub source: ConstraintSource,
484}
485
486impl Distance {
487 pub fn point_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
488 self.points.iter().filter_map(|point| match point {
489 ConstraintSegment::Segment(id) => Some(*id),
490 ConstraintSegment::Origin(_) => None,
491 })
492 }
493
494 pub fn contains_point(&self, point_id: ObjectId) -> bool {
495 self.point_ids().any(|id| id == point_id)
496 }
497}
498
499#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
500#[ts(export, export_to = "FrontendApi.ts")]
501pub struct Angle {
502 pub lines: Vec<ObjectId>,
503 pub angle: Number,
504 pub source: ConstraintSource,
505}
506
507#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize, ts_rs::TS)]
508#[ts(export, export_to = "FrontendApi.ts")]
509pub struct ConstraintSource {
510 pub expr: String,
511 pub is_literal: bool,
512}
513
514#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
515#[ts(export, export_to = "FrontendApi.ts")]
516pub struct Radius {
517 pub arc: ObjectId,
518 pub radius: Number,
519 #[serde(default)]
520 pub source: ConstraintSource,
521}
522
523#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
524#[ts(export, export_to = "FrontendApi.ts")]
525pub struct Diameter {
526 pub arc: ObjectId,
527 pub diameter: Number,
528 #[serde(default)]
529 pub source: ConstraintSource,
530}
531
532#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
533#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
534pub struct EqualRadius {
535 pub input: Vec<ObjectId>,
536}
537
538#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
541#[ts(export, export_to = "FrontendApi.ts")]
542pub struct Fixed {
543 pub points: Vec<FixedPoint>,
544}
545
546#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
548#[ts(export, export_to = "FrontendApi.ts")]
549pub struct FixedPoint {
550 pub point: ObjectId,
551 pub position: Point2d<Number>,
552}
553
554#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
555#[ts(export, export_to = "FrontendApi.ts")]
556#[serde(untagged)]
557pub enum Horizontal {
558 Line { line: ObjectId },
559 Points { points: Vec<ConstraintSegment> },
560}
561
562#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
563#[ts(export, export_to = "FrontendApi.ts")]
564pub struct LinesEqualLength {
565 pub lines: Vec<ObjectId>,
566}
567
568#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
569#[ts(export, export_to = "FrontendApi.ts")]
570pub struct Midpoint {
571 pub point: ObjectId,
572 #[serde(alias = "line")]
573 pub segment: ObjectId,
574}
575
576#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
577#[ts(export, export_to = "FrontendApi.ts")]
578#[serde(untagged)]
579pub enum Vertical {
580 Line { line: ObjectId },
581 Points { points: Vec<ConstraintSegment> },
582}
583
584#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
585#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
586pub struct Parallel {
587 pub lines: Vec<ObjectId>,
588}
589
590#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
591#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
592pub struct Perpendicular {
593 pub lines: Vec<ObjectId>,
594}
595
596#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
597#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
598pub struct Symmetric {
599 pub input: Vec<ObjectId>,
600 pub axis: ObjectId,
601}
602
603#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
604#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
605pub struct Tangent {
606 pub input: Vec<ObjectId>,
607}