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(rename = "labelPosition")]
535 #[serde(default, skip_serializing_if = "Option::is_none")]
536 #[ts(rename = "labelPosition")]
537 #[ts(optional)]
538 pub label_position: Option<Point2d<Number>>,
539 #[serde(default)]
540 pub source: ConstraintSource,
541}
542
543#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
544#[ts(export, export_to = "FrontendApi.ts")]
545pub struct Diameter {
546 pub arc: ObjectId,
547 pub diameter: Number,
548 #[serde(rename = "labelPosition")]
549 #[serde(default, skip_serializing_if = "Option::is_none")]
550 #[ts(rename = "labelPosition")]
551 #[ts(optional)]
552 pub label_position: Option<Point2d<Number>>,
553 #[serde(default)]
554 pub source: ConstraintSource,
555}
556
557#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
558#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
559pub struct EqualRadius {
560 pub input: Vec<ObjectId>,
561}
562
563#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
566#[ts(export, export_to = "FrontendApi.ts")]
567pub struct Fixed {
568 pub points: Vec<FixedPoint>,
569}
570
571#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
573#[ts(export, export_to = "FrontendApi.ts")]
574pub struct FixedPoint {
575 pub point: ObjectId,
576 pub position: Point2d<Number>,
577}
578
579#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
580#[ts(export, export_to = "FrontendApi.ts")]
581#[serde(untagged)]
582pub enum Horizontal {
583 Line { line: ObjectId },
584 Points { points: Vec<ConstraintSegment> },
585}
586
587#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
588#[ts(export, export_to = "FrontendApi.ts")]
589pub struct LinesEqualLength {
590 pub lines: Vec<ObjectId>,
591}
592
593#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
594#[ts(export, export_to = "FrontendApi.ts")]
595pub struct Midpoint {
596 pub point: ObjectId,
597 #[serde(alias = "line")]
598 pub segment: ObjectId,
599}
600
601#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
602#[ts(export, export_to = "FrontendApi.ts")]
603#[serde(untagged)]
604pub enum Vertical {
605 Line { line: ObjectId },
606 Points { points: Vec<ConstraintSegment> },
607}
608
609#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
610#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
611pub struct Parallel {
612 pub lines: Vec<ObjectId>,
613}
614
615#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
616#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
617pub struct Perpendicular {
618 pub lines: Vec<ObjectId>,
619}
620
621#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
622#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
623pub struct Symmetric {
624 pub input: Vec<ObjectId>,
625 pub axis: ObjectId,
626}
627
628#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
629#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
630pub struct Tangent {
631 pub input: Vec<ObjectId>,
632}