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