Skip to main content

kcl_lib/frontend/
sketch.rs

1#![allow(async_fn_in_trait)]
2
3use serde::Deserialize;
4use serde::Serialize;
5
6use crate::ExecutorContext;
7use crate::front::Plane;
8use crate::frontend::api::Expr;
9use crate::frontend::api::FileId;
10use crate::frontend::api::Number;
11use crate::frontend::api::ObjectId;
12use crate::frontend::api::ProjectId;
13use crate::frontend::api::Result;
14use crate::frontend::api::SceneGraph;
15use crate::frontend::api::SceneGraphDelta;
16use crate::frontend::api::SourceDelta;
17use crate::frontend::api::Version;
18
19/// Information about a newly created segment for batch operations
20#[derive(Debug, Clone)]
21pub struct NewSegmentInfo {
22    pub segment_id: ObjectId,
23    pub start_point_id: ObjectId,
24    pub end_point_id: ObjectId,
25    pub center_point_id: Option<ObjectId>,
26}
27
28pub trait SketchApi {
29    /// Execute the sketch in mock mode, without changing anything. This is
30    /// useful after editing segments, and the user releases the mouse button.
31    async fn execute_mock(
32        &mut self,
33        ctx: &ExecutorContext,
34        version: Version,
35        sketch: ObjectId,
36    ) -> Result<(SourceDelta, SceneGraphDelta)>;
37
38    async fn new_sketch(
39        &mut self,
40        ctx: &ExecutorContext,
41        project: ProjectId,
42        file: FileId,
43        version: Version,
44        args: SketchCtor,
45    ) -> Result<(SourceDelta, SceneGraphDelta, ObjectId)>;
46
47    // Enters sketch mode
48    async fn edit_sketch(
49        &mut self,
50        ctx: &ExecutorContext,
51        project: ProjectId,
52        file: FileId,
53        version: Version,
54        sketch: ObjectId,
55    ) -> Result<SceneGraphDelta>;
56
57    async fn exit_sketch(&mut self, ctx: &ExecutorContext, version: Version, sketch: ObjectId) -> Result<SceneGraph>;
58
59    async fn delete_sketch(
60        &mut self,
61        ctx: &ExecutorContext,
62        version: Version,
63        sketch: ObjectId,
64    ) -> Result<(SourceDelta, SceneGraphDelta)>;
65
66    async fn add_segment(
67        &mut self,
68        ctx: &ExecutorContext,
69        version: Version,
70        sketch: ObjectId,
71        segment: SegmentCtor,
72        label: Option<String>,
73    ) -> Result<(SourceDelta, SceneGraphDelta)>;
74
75    async fn edit_segments(
76        &mut self,
77        ctx: &ExecutorContext,
78        version: Version,
79        sketch: ObjectId,
80        segments: Vec<ExistingSegmentCtor>,
81    ) -> Result<(SourceDelta, SceneGraphDelta)>;
82
83    async fn delete_objects(
84        &mut self,
85        ctx: &ExecutorContext,
86        version: Version,
87        sketch: ObjectId,
88        constraint_ids: Vec<ObjectId>,
89        segment_ids: Vec<ObjectId>,
90    ) -> Result<(SourceDelta, SceneGraphDelta)>;
91
92    async fn add_constraint(
93        &mut self,
94        ctx: &ExecutorContext,
95        version: Version,
96        sketch: ObjectId,
97        constraint: Constraint,
98    ) -> Result<(SourceDelta, SceneGraphDelta)>;
99
100    async fn chain_segment(
101        &mut self,
102        ctx: &ExecutorContext,
103        version: Version,
104        sketch: ObjectId,
105        previous_segment_end_point_id: ObjectId,
106        segment: SegmentCtor,
107        label: Option<String>,
108    ) -> Result<(SourceDelta, SceneGraphDelta)>;
109
110    async fn edit_constraint(
111        &mut self,
112        ctx: &ExecutorContext,
113        version: Version,
114        sketch: ObjectId,
115        constraint_id: ObjectId,
116        value_expression: String,
117    ) -> Result<(SourceDelta, SceneGraphDelta)>;
118
119    /// Batch operations for split segment: edit segments, add constraints, delete objects.
120    /// All operations are applied to a single AST and execute_after_edit is called once at the end.
121    /// new_segment_info contains the IDs from the segment(s) added in a previous step.
122    #[allow(clippy::too_many_arguments)]
123    async fn batch_split_segment_operations(
124        &mut self,
125        ctx: &ExecutorContext,
126        version: Version,
127        sketch: ObjectId,
128        edit_segments: Vec<ExistingSegmentCtor>,
129        add_constraints: Vec<Constraint>,
130        delete_constraint_ids: Vec<ObjectId>,
131        new_segment_info: NewSegmentInfo,
132    ) -> Result<(SourceDelta, SceneGraphDelta)>;
133
134    /// Batch operations for tail-cut trim: edit a segment, add coincident constraints,
135    /// delete constraints, and execute once.
136    async fn batch_tail_cut_operations(
137        &mut self,
138        ctx: &ExecutorContext,
139        version: Version,
140        sketch: ObjectId,
141        edit_segments: Vec<ExistingSegmentCtor>,
142        add_constraints: Vec<Constraint>,
143        delete_constraint_ids: Vec<ObjectId>,
144    ) -> Result<(SourceDelta, SceneGraphDelta)>;
145}
146
147#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
148#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSketch")]
149pub struct Sketch {
150    pub args: SketchCtor,
151    pub plane: ObjectId,
152    pub segments: Vec<ObjectId>,
153    pub constraints: Vec<ObjectId>,
154}
155
156/// Arguments for creating a new sketch. This is similar to the constructor of
157/// other kinds of objects in that it is the inputs to the sketch, not the
158/// outputs.
159#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
160#[ts(export, export_to = "FrontendApi.ts")]
161pub struct SketchCtor {
162    /// The sketch surface.
163    pub on: Plane,
164}
165
166#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
167#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPoint")]
168pub struct Point {
169    pub position: Point2d<Number>,
170    pub ctor: Option<PointCtor>,
171    pub owner: Option<ObjectId>,
172    pub freedom: Freedom,
173    pub constraints: Vec<ObjectId>,
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
177#[ts(export, export_to = "FrontendApi.ts")]
178pub enum Freedom {
179    Free,
180    Fixed,
181    Conflict,
182}
183
184impl Freedom {
185    /// Merges two Freedom values. For example, a point has a solver variable
186    /// for each dimension, x and y. If one dimension is `Free` and the other is
187    /// `Fixed`, the point overall is `Free` since it isn't fully constrained.
188    /// `Conflict` infects the most, followed by `Free`. An object must be fully
189    /// `Fixed` to be `Fixed` overall.
190    pub fn merge(self, other: Self) -> Self {
191        match (self, other) {
192            (Self::Conflict, _) | (_, Self::Conflict) => Self::Conflict,
193            (Self::Free, _) | (_, Self::Free) => Self::Free,
194            (Self::Fixed, Self::Fixed) => Self::Fixed,
195        }
196    }
197}
198
199#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
200#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSegment")]
201#[serde(tag = "type")]
202pub enum Segment {
203    Point(Point),
204    Line(Line),
205    Arc(Arc),
206    Circle(Circle),
207}
208
209#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
210#[ts(export, export_to = "FrontendApi.ts")]
211pub struct ExistingSegmentCtor {
212    pub id: ObjectId,
213    pub ctor: SegmentCtor,
214}
215
216#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
217#[ts(export, export_to = "FrontendApi.ts")]
218#[serde(tag = "type")]
219pub enum SegmentCtor {
220    Point(PointCtor),
221    Line(LineCtor),
222    Arc(ArcCtor),
223    Circle(CircleCtor),
224}
225
226#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
227#[ts(export, export_to = "FrontendApi.ts")]
228pub struct PointCtor {
229    pub position: Point2d<Expr>,
230}
231
232#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
233#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPoint2d")]
234pub struct Point2d<U: std::fmt::Debug + Clone + ts_rs::TS> {
235    pub x: U,
236    pub y: U,
237}
238
239#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
240#[ts(export, export_to = "FrontendApi.ts", rename = "ApiLine")]
241pub struct Line {
242    pub start: ObjectId,
243    pub end: ObjectId,
244    // Invariant: Line or MidPointLine
245    pub ctor: SegmentCtor,
246    // The constructor is applicable if changing the values of the constructor will change the rendering
247    // of the segment (modulo multiple valid solutions). I.e., whether the object is constrained with
248    // respect to the constructor inputs.
249    // The frontend should only display handles for the constructor inputs if the ctor is applicable.
250    // (Or because they are the (locked) start/end of the segment).
251    pub ctor_applicable: bool,
252    pub construction: bool,
253}
254
255#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
256#[ts(export, export_to = "FrontendApi.ts")]
257pub struct LineCtor {
258    pub start: Point2d<Expr>,
259    pub end: Point2d<Expr>,
260    #[serde(skip_serializing_if = "Option::is_none")]
261    #[ts(optional)]
262    pub construction: Option<bool>,
263}
264
265#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
266#[ts(export, export_to = "FrontendApi.ts", rename = "ApiStartOrEnd")]
267#[serde(tag = "type")]
268pub enum StartOrEnd<T> {
269    Start(T),
270    End(T),
271}
272
273#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
274#[ts(export, export_to = "FrontendApi.ts", rename = "ApiArc")]
275pub struct Arc {
276    pub start: ObjectId,
277    pub end: ObjectId,
278    pub center: ObjectId,
279    // Invariant: Arc
280    pub ctor: SegmentCtor,
281    pub ctor_applicable: bool,
282    pub construction: bool,
283}
284
285#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
286#[ts(export, export_to = "FrontendApi.ts")]
287pub struct ArcCtor {
288    pub start: Point2d<Expr>,
289    pub end: Point2d<Expr>,
290    pub center: Point2d<Expr>,
291    #[serde(skip_serializing_if = "Option::is_none")]
292    #[ts(optional)]
293    pub construction: Option<bool>,
294}
295
296#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
297#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCircle")]
298pub struct Circle {
299    pub start: ObjectId,
300    pub center: ObjectId,
301    // Invariant: Circle
302    pub ctor: SegmentCtor,
303    pub ctor_applicable: bool,
304    pub construction: bool,
305}
306
307#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
308#[ts(export, export_to = "FrontendApi.ts")]
309pub struct CircleCtor {
310    pub start: Point2d<Expr>,
311    pub center: Point2d<Expr>,
312    #[serde(skip_serializing_if = "Option::is_none")]
313    #[ts(optional)]
314    pub construction: Option<bool>,
315}
316
317#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
318#[ts(export, export_to = "FrontendApi.ts", rename = "ApiConstraint")]
319#[serde(tag = "type")]
320pub enum Constraint {
321    Coincident(Coincident),
322    Distance(Distance),
323    Angle(Angle),
324    Diameter(Diameter),
325    Fixed(Fixed),
326    HorizontalDistance(Distance),
327    VerticalDistance(Distance),
328    Horizontal(Horizontal),
329    LinesEqualLength(LinesEqualLength),
330    Parallel(Parallel),
331    Perpendicular(Perpendicular),
332    Radius(Radius),
333    Tangent(Tangent),
334    Vertical(Vertical),
335}
336
337#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
338#[ts(export, export_to = "FrontendApi.ts")]
339pub struct Coincident {
340    pub segments: Vec<ObjectId>,
341}
342
343#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
344#[ts(export, export_to = "FrontendApi.ts")]
345pub struct Distance {
346    pub points: Vec<ObjectId>,
347    pub distance: Number,
348    pub source: ConstraintSource,
349}
350
351#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
352#[ts(export, export_to = "FrontendApi.ts")]
353pub struct Angle {
354    pub lines: Vec<ObjectId>,
355    pub angle: Number,
356    pub source: ConstraintSource,
357}
358
359#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize, ts_rs::TS)]
360#[ts(export, export_to = "FrontendApi.ts")]
361pub struct ConstraintSource {
362    pub expr: String,
363    pub is_literal: bool,
364}
365
366#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
367#[ts(export, export_to = "FrontendApi.ts")]
368pub struct Radius {
369    pub arc: ObjectId,
370    pub radius: Number,
371    #[serde(default)]
372    pub source: ConstraintSource,
373}
374
375#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
376#[ts(export, export_to = "FrontendApi.ts")]
377pub struct Diameter {
378    pub arc: ObjectId,
379    pub diameter: Number,
380    #[serde(default)]
381    pub source: ConstraintSource,
382}
383
384/// Multiple fixed constraints, allowing callers to add fixed constraints on
385/// multiple points at once.
386#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
387#[ts(export, export_to = "FrontendApi.ts")]
388pub struct Fixed {
389    pub points: Vec<FixedPoint>,
390}
391
392/// A fixed constraint on a single point.
393#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
394#[ts(export, export_to = "FrontendApi.ts")]
395pub struct FixedPoint {
396    pub point: ObjectId,
397    pub position: Point2d<Number>,
398}
399
400#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
401#[ts(export, export_to = "FrontendApi.ts")]
402pub struct Horizontal {
403    pub line: ObjectId,
404}
405
406#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
407#[ts(export, export_to = "FrontendApi.ts")]
408pub struct LinesEqualLength {
409    pub lines: Vec<ObjectId>,
410}
411
412#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
413#[ts(export, export_to = "FrontendApi.ts")]
414pub struct Vertical {
415    pub line: ObjectId,
416}
417
418#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
419#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
420pub struct Parallel {
421    pub lines: Vec<ObjectId>,
422}
423
424#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
425#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
426pub struct Perpendicular {
427    pub lines: Vec<ObjectId>,
428}
429
430#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
431#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
432pub struct Tangent {
433    pub input: Vec<ObjectId>,
434}