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