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
183#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
184#[ts(export, export_to = "FrontendApi.ts")]
185pub enum Freedom {
186 Free,
187 Fixed,
188 Conflict,
189}
190
191impl Freedom {
192 pub fn merge(self, other: Self) -> Self {
198 match (self, other) {
199 (Self::Conflict, _) | (_, Self::Conflict) => Self::Conflict,
200 (Self::Free, _) | (_, Self::Free) => Self::Free,
201 (Self::Fixed, Self::Fixed) => Self::Fixed,
202 }
203 }
204}
205
206#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
207#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSegment")]
208#[serde(tag = "type")]
209pub enum Segment {
210 Point(Point),
211 Line(Line),
212 Arc(Arc),
213 Circle(Circle),
214}
215
216impl Segment {
217 pub fn human_friendly_kind_with_article(&self) -> &'static str {
220 match self {
221 Self::Point(_) => "a Point",
222 Self::Line(_) => "a Line",
223 Self::Arc(_) => "an Arc",
224 Self::Circle(_) => "a Circle",
225 }
226 }
227}
228
229#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
230#[ts(export, export_to = "FrontendApi.ts")]
231pub struct ExistingSegmentCtor {
232 pub id: ObjectId,
233 pub ctor: SegmentCtor,
234}
235
236#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
237#[ts(export, export_to = "FrontendApi.ts")]
238#[serde(tag = "type")]
239pub enum SegmentCtor {
240 Point(PointCtor),
241 Line(LineCtor),
242 Arc(ArcCtor),
243 Circle(CircleCtor),
244}
245
246impl SegmentCtor {
247 pub fn human_friendly_kind_with_article(&self) -> &'static str {
250 match self {
251 Self::Point(_) => "a Point constructor",
252 Self::Line(_) => "a Line constructor",
253 Self::Arc(_) => "an Arc constructor",
254 Self::Circle(_) => "a Circle constructor",
255 }
256 }
257}
258
259#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
260#[ts(export, export_to = "FrontendApi.ts")]
261pub struct PointCtor {
262 pub position: Point2d<Expr>,
263}
264
265#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
266#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPoint2d")]
267pub struct Point2d<U: std::fmt::Debug + Clone + ts_rs::TS> {
268 pub x: U,
269 pub y: U,
270}
271
272#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
273#[ts(export, export_to = "FrontendApi.ts", rename = "ApiLine")]
274pub struct Line {
275 pub start: ObjectId,
276 pub end: ObjectId,
277 pub ctor: SegmentCtor,
279 pub ctor_applicable: bool,
285 pub construction: bool,
286}
287
288#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
289#[ts(export, export_to = "FrontendApi.ts")]
290pub struct LineCtor {
291 pub start: Point2d<Expr>,
292 pub end: Point2d<Expr>,
293 #[serde(skip_serializing_if = "Option::is_none")]
294 #[ts(optional)]
295 pub construction: Option<bool>,
296}
297
298#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
299#[ts(export, export_to = "FrontendApi.ts", rename = "ApiStartOrEnd")]
300#[serde(tag = "type")]
301pub enum StartOrEnd<T> {
302 Start(T),
303 End(T),
304}
305
306#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
307#[ts(export, export_to = "FrontendApi.ts", rename = "ApiArc")]
308pub struct Arc {
309 pub start: ObjectId,
310 pub end: ObjectId,
311 pub center: ObjectId,
312 pub ctor: SegmentCtor,
314 pub ctor_applicable: bool,
315 pub construction: bool,
316}
317
318#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
319#[ts(export, export_to = "FrontendApi.ts")]
320pub struct ArcCtor {
321 pub start: Point2d<Expr>,
322 pub end: Point2d<Expr>,
323 pub center: Point2d<Expr>,
324 #[serde(skip_serializing_if = "Option::is_none")]
325 #[ts(optional)]
326 pub construction: Option<bool>,
327}
328
329#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
330#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCircle")]
331pub struct Circle {
332 pub start: ObjectId,
333 pub center: ObjectId,
334 pub ctor: SegmentCtor,
336 pub ctor_applicable: bool,
337 pub construction: bool,
338}
339
340#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
341#[ts(export, export_to = "FrontendApi.ts")]
342pub struct CircleCtor {
343 pub start: Point2d<Expr>,
344 pub center: Point2d<Expr>,
345 #[serde(skip_serializing_if = "Option::is_none")]
346 #[ts(optional)]
347 pub construction: Option<bool>,
348}
349
350#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
351#[ts(export, export_to = "FrontendApi.ts", rename = "ApiConstraint")]
352#[serde(tag = "type")]
353pub enum Constraint {
354 Coincident(Coincident),
355 Distance(Distance),
356 Angle(Angle),
357 Diameter(Diameter),
358 Fixed(Fixed),
359 HorizontalDistance(Distance),
360 VerticalDistance(Distance),
361 Horizontal(Horizontal),
362 LinesEqualLength(LinesEqualLength),
363 Parallel(Parallel),
364 Perpendicular(Perpendicular),
365 Radius(Radius),
366 Tangent(Tangent),
367 Vertical(Vertical),
368}
369
370#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
371#[ts(export, export_to = "FrontendApi.ts")]
372pub struct Coincident {
373 pub segments: Vec<ConstraintSegment>,
374}
375
376impl Coincident {
377 pub fn get_segments(&self) -> Vec<ObjectId> {
378 self.segments
379 .iter()
380 .filter_map(|segment| match segment {
381 ConstraintSegment::Segment(id) => Some(*id),
382 ConstraintSegment::Origin(_) => None,
383 })
384 .collect()
385 }
386
387 pub fn segment_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
388 self.segments.iter().filter_map(|segment| match segment {
389 ConstraintSegment::Segment(id) => Some(*id),
390 ConstraintSegment::Origin(_) => None,
391 })
392 }
393
394 pub fn contains_segment(&self, segment_id: ObjectId) -> bool {
395 self.segment_ids().any(|id| id == segment_id)
396 }
397}
398
399#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
400#[ts(export, export_to = "FrontendApi.ts")]
401#[serde(untagged)]
402pub enum ConstraintSegment {
403 Segment(ObjectId),
404 Origin(OriginLiteral),
405}
406
407impl ConstraintSegment {
408 pub const ORIGIN: Self = Self::Origin(OriginLiteral::Origin);
409}
410
411#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
412#[ts(export, export_to = "FrontendApi.ts")]
413#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
414pub enum OriginLiteral {
415 Origin,
416}
417
418impl From<ObjectId> for ConstraintSegment {
419 fn from(value: ObjectId) -> Self {
420 Self::Segment(value)
421 }
422}
423
424#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
425#[ts(export, export_to = "FrontendApi.ts")]
426pub struct Distance {
427 pub points: Vec<ConstraintSegment>,
428 pub distance: Number,
429 pub source: ConstraintSource,
430}
431
432impl Distance {
433 pub fn point_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
434 self.points.iter().filter_map(|point| match point {
435 ConstraintSegment::Segment(id) => Some(*id),
436 ConstraintSegment::Origin(_) => None,
437 })
438 }
439
440 pub fn contains_point(&self, point_id: ObjectId) -> bool {
441 self.point_ids().any(|id| id == point_id)
442 }
443}
444
445#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
446#[ts(export, export_to = "FrontendApi.ts")]
447pub struct Angle {
448 pub lines: Vec<ObjectId>,
449 pub angle: Number,
450 pub source: ConstraintSource,
451}
452
453#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize, ts_rs::TS)]
454#[ts(export, export_to = "FrontendApi.ts")]
455pub struct ConstraintSource {
456 pub expr: String,
457 pub is_literal: bool,
458}
459
460#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
461#[ts(export, export_to = "FrontendApi.ts")]
462pub struct Radius {
463 pub arc: ObjectId,
464 pub radius: Number,
465 #[serde(default)]
466 pub source: ConstraintSource,
467}
468
469#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
470#[ts(export, export_to = "FrontendApi.ts")]
471pub struct Diameter {
472 pub arc: ObjectId,
473 pub diameter: Number,
474 #[serde(default)]
475 pub source: ConstraintSource,
476}
477
478#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
481#[ts(export, export_to = "FrontendApi.ts")]
482pub struct Fixed {
483 pub points: Vec<FixedPoint>,
484}
485
486#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
488#[ts(export, export_to = "FrontendApi.ts")]
489pub struct FixedPoint {
490 pub point: ObjectId,
491 pub position: Point2d<Number>,
492}
493
494#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
495#[ts(export, export_to = "FrontendApi.ts")]
496pub struct Horizontal {
497 pub line: ObjectId,
498}
499
500#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
501#[ts(export, export_to = "FrontendApi.ts")]
502pub struct LinesEqualLength {
503 pub lines: Vec<ObjectId>,
504}
505
506#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
507#[ts(export, export_to = "FrontendApi.ts")]
508pub struct Vertical {
509 pub line: ObjectId,
510}
511
512#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
513#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
514pub struct Parallel {
515 pub lines: Vec<ObjectId>,
516}
517
518#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
519#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
520pub struct Perpendicular {
521 pub lines: Vec<ObjectId>,
522}
523
524#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
525#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
526pub struct Tangent {
527 pub input: Vec<ObjectId>,
528}