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