1#![allow(async_fn_in_trait)]
2
3use kcl_api::Arc;
4use kcl_api::Circle;
5use kcl_api::Coincident;
6use kcl_api::Constraint;
7use kcl_api::ConstraintSegment;
8use kcl_api::ControlPointSpline;
9use kcl_api::Distance;
10use kcl_api::Freedom;
11use kcl_api::Line;
12use kcl_api::Number;
13use kcl_api::ObjectId;
14use kcl_api::Point;
15use kcl_api::Point2d;
16use kcl_api::Segment;
17use kcl_api::SegmentCtor;
18use kcl_api::SketchCtor;
19use serde::Deserialize;
20use serde::Serialize;
21
22use crate::ExecutorContext;
23use crate::KclErrorWithOutputs;
24use crate::frontend::api::FileId;
25use crate::frontend::api::ProjectId;
26use crate::frontend::api::SceneGraph;
27use crate::frontend::api::SceneGraphDelta;
28use crate::frontend::api::SourceDelta;
29use crate::frontend::api::Version;
30
31pub type ExecResult<T> = std::result::Result<T, KclErrorWithOutputs>;
32
33#[derive(Debug, Clone)]
35pub struct NewSegmentInfo {
36 pub segment_id: ObjectId,
37 pub start_point_id: ObjectId,
38 pub end_point_id: ObjectId,
39 pub center_point_id: Option<ObjectId>,
40}
41
42pub trait SketchApi {
43 async fn execute_mock(
46 &mut self,
47 ctx: &ExecutorContext,
48 version: Version,
49 sketch: ObjectId,
50 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
51
52 async fn new_sketch(
53 &mut self,
54 ctx: &ExecutorContext,
55 project: ProjectId,
56 file: FileId,
57 version: Version,
58 args: SketchCtor,
59 ) -> ExecResult<(SourceDelta, SceneGraphDelta, ObjectId)>;
60
61 async fn edit_sketch(
63 &mut self,
64 ctx: &ExecutorContext,
65 project: ProjectId,
66 file: FileId,
67 version: Version,
68 sketch: ObjectId,
69 ) -> ExecResult<SceneGraphDelta>;
70
71 async fn exit_sketch(
72 &mut self,
73 ctx: &ExecutorContext,
74 version: Version,
75 sketch: ObjectId,
76 ) -> ExecResult<SceneGraph>;
77
78 async fn delete_sketch(
79 &mut self,
80 ctx: &ExecutorContext,
81 version: Version,
82 sketch: ObjectId,
83 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
84
85 async fn add_segment(
86 &mut self,
87 ctx: &ExecutorContext,
88 version: Version,
89 sketch: ObjectId,
90 segment: SegmentCtor,
91 label: Option<String>,
92 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
93
94 async fn edit_segments(
95 &mut self,
96 ctx: &ExecutorContext,
97 version: Version,
98 sketch: ObjectId,
99 segments: Vec<ExistingSegmentCtor>,
100 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
101
102 async fn delete_objects(
103 &mut self,
104 ctx: &ExecutorContext,
105 version: Version,
106 sketch: ObjectId,
107 constraint_ids: Vec<ObjectId>,
108 segment_ids: Vec<ObjectId>,
109 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
110
111 async fn add_constraint(
112 &mut self,
113 ctx: &ExecutorContext,
114 version: Version,
115 sketch: ObjectId,
116 constraint: Constraint,
117 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
118
119 async fn chain_segment(
120 &mut self,
121 ctx: &ExecutorContext,
122 version: Version,
123 sketch: ObjectId,
124 previous_segment_end_point_id: ObjectId,
125 segment: SegmentCtor,
126 label: Option<String>,
127 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
128
129 async fn edit_constraint_value(
130 &mut self,
131 ctx: &ExecutorContext,
132 version: Version,
133 sketch: ObjectId,
134 constraint_id: ObjectId,
135 value_expression: String,
136 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
137
138 async fn edit_distance_constraint_label_position(
139 &mut self,
140 ctx: &ExecutorContext,
141 version: Version,
142 sketch: ObjectId,
143 constraint_id: ObjectId,
144 label_position: Point2d<Number>,
145 anchor_segment_ids: Vec<ObjectId>,
146 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
147
148 #[allow(clippy::too_many_arguments)]
152 async fn batch_split_segment_operations(
153 &mut self,
154 ctx: &ExecutorContext,
155 version: Version,
156 sketch: ObjectId,
157 edit_segments: Vec<ExistingSegmentCtor>,
158 add_constraints: Vec<Constraint>,
159 delete_constraint_ids: Vec<ObjectId>,
160 new_segment_info: NewSegmentInfo,
161 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
162
163 #[allow(clippy::too_many_arguments)]
166 async fn batch_tail_cut_operations(
167 &mut self,
168 ctx: &ExecutorContext,
169 version: Version,
170 sketch: ObjectId,
171 edit_segments: Vec<ExistingSegmentCtor>,
172 add_constraints: Vec<Constraint>,
173 delete_constraint_ids: Vec<ObjectId>,
174 additional_edited_segment_ids: Vec<ObjectId>,
175 ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
176}
177
178pub trait PointExt {
180 fn freedom(&self) -> Freedom;
182}
183
184impl PointExt for Point {
185 fn freedom(&self) -> Freedom {
186 self.freedom
187 }
188}
189
190pub trait SegmentExt {
192 fn human_friendly_kind_with_article(&self) -> &'static str;
195 fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom>;
200}
201
202impl SegmentExt for Segment {
203 fn human_friendly_kind_with_article(&self) -> &'static str {
204 match self {
205 Self::Point(_) => "a Point",
206 Self::Line(_) => "a Line",
207 Self::Arc(_) => "an Arc",
208 Self::Circle(_) => "a Circle",
209 Self::ControlPointSpline(_) => "a Control Point Spline",
210 }
211 }
212
213 fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
214 match self {
215 Self::Point(p) => Some(p.freedom()),
216 Self::Line(l) => l.freedom(&lookup),
217 Self::Arc(a) => a.freedom(&lookup),
218 Self::Circle(c) => c.freedom(&lookup),
219 Self::ControlPointSpline(s) => s.freedom(&lookup),
220 }
221 }
222}
223
224#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
225#[ts(export, export_to = "FrontendApi.ts")]
226pub struct ExistingSegmentCtor {
227 pub id: ObjectId,
228 pub ctor: SegmentCtor,
229}
230
231#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
232#[ts(export, export_to = "FrontendApi.ts")]
233#[serde(rename_all = "camelCase")]
234pub struct ConstraintLabelPositionEdit {
235 pub constraint_id: ObjectId,
236 pub label_position: Point2d<Number>,
237}
238
239pub trait LineExt {
241 fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom>;
244}
245
246impl LineExt for Line {
247 fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
248 let start = lookup(self.start)?;
249 let end = lookup(self.end)?;
250 Some(start.merge(end))
251 }
252}
253
254#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
255#[ts(export, export_to = "FrontendApi.ts", rename = "ApiStartOrEnd")]
256#[serde(tag = "type")]
257pub enum StartOrEnd<T> {
258 Start(T),
259 End(T),
260}
261
262pub trait ArcExt {
264 fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom>;
267}
268
269impl ArcExt for Arc {
270 fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
271 let start = lookup(self.start)?;
272 let end = lookup(self.end)?;
273 let center = lookup(self.center)?;
274 Some(start.merge(end).merge(center))
275 }
276}
277
278pub trait CircleExt {
280 fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom>;
283}
284
285impl CircleExt for Circle {
286 fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
287 let start = lookup(self.start)?;
288 let center = lookup(self.center)?;
289 Some(start.merge(center))
290 }
291}
292
293pub trait ControlPointSplineExt {
295 fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom>;
298}
299
300impl ControlPointSplineExt for ControlPointSpline {
301 fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
302 let mut controls = self.controls.iter();
303 let first = lookup(*controls.next()?)?;
304 let merged = controls.try_fold(first, |acc, id| lookup(*id).map(|freedom| acc.merge(freedom)))?;
305 Some(merged)
306 }
307}
308
309pub trait CoincidentExt {
311 fn get_segments(&self) -> Vec<ObjectId>;
312 fn segment_ids(&self) -> impl Iterator<Item = ObjectId> + '_;
313 fn contains_segment(&self, segment_id: ObjectId) -> bool;
314}
315
316impl CoincidentExt for Coincident {
317 fn get_segments(&self) -> Vec<ObjectId> {
318 self.segments
319 .iter()
320 .filter_map(|segment| match segment {
321 ConstraintSegment::Segment(id) => Some(*id),
322 ConstraintSegment::Origin(_) => None,
323 })
324 .collect()
325 }
326
327 fn segment_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
328 self.segments.iter().filter_map(|segment| match segment {
329 ConstraintSegment::Segment(id) => Some(*id),
330 ConstraintSegment::Origin(_) => None,
331 })
332 }
333
334 fn contains_segment(&self, segment_id: ObjectId) -> bool {
335 self.segment_ids().any(|id| id == segment_id)
336 }
337}
338
339pub trait DistanceExt {
341 fn segment_ids(&self) -> impl Iterator<Item = ObjectId> + '_;
342 fn contains_segment(&self, segment_id: ObjectId) -> bool;
343}
344
345impl DistanceExt for Distance {
346 fn segment_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
347 self.segments.iter().filter_map(|segment| match segment {
348 ConstraintSegment::Segment(id) => Some(*id),
349 ConstraintSegment::Origin(_) => None,
350 })
351 }
352
353 fn contains_segment(&self, segment_id: ObjectId) -> bool {
354 self.segment_ids().any(|id| id == segment_id)
355 }
356}