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