Skip to main content

kcl_lib/frontend/
sketch.rs

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