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