kcl_lib/frontend/
sketch.rs

1#![allow(async_fn_in_trait)]
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    ExecOutcome, ExecutorContext,
7    frontend::api::{
8        Expr, FileId, Number, ObjectId, Plane, 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<(SceneGraph, ExecOutcome)>;
21
22    async fn new_sketch(
23        &mut self,
24        ctx: &ExecutorContext,
25        project: ProjectId,
26        file: FileId,
27        version: Version,
28        args: SketchArgs,
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 edit_constraint(
85        &mut self,
86        ctx: &ExecutorContext,
87        version: Version,
88        sketch: ObjectId,
89        constraint_id: ObjectId,
90        constraint: Constraint,
91    ) -> Result<(SourceDelta, SceneGraphDelta)>;
92}
93
94#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
95#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSketch")]
96pub struct Sketch {
97    pub args: SketchArgs,
98    pub segments: Vec<ObjectId>,
99    pub constraints: Vec<ObjectId>,
100}
101
102#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
103#[ts(export, export_to = "FrontendApi.ts")]
104pub struct SketchArgs {
105    pub on: Plane,
106}
107
108#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
109#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPoint")]
110pub struct Point {
111    pub position: Point2d<Number>,
112    pub ctor: Option<PointCtor>,
113    pub owner: Option<ObjectId>,
114    pub freedom: Freedom,
115    pub constraints: Vec<ObjectId>,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
119#[ts(export, export_to = "FrontendApi.ts")]
120pub enum Freedom {
121    Free,
122    Fixed,
123    Conflict,
124}
125
126impl Freedom {
127    /// Merges two Freedom values. For example, a point has a solver variable
128    /// for each dimension, x and y. If one dimension is `Free` and the other is
129    /// `Fixed`, the point overall is `Free` since it isn't fully constrained.
130    /// `Conflict` infects the most, followed by `Free`. An object must be fully
131    /// `Fixed` to be `Fixed` overall.
132    pub fn merge(self, other: Self) -> Self {
133        match (self, other) {
134            (Self::Conflict, _) | (_, Self::Conflict) => Self::Conflict,
135            (Self::Free, _) | (_, Self::Free) => Self::Free,
136            (Self::Fixed, Self::Fixed) => Self::Fixed,
137        }
138    }
139}
140
141#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
142#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSegment")]
143#[serde(tag = "type")]
144pub enum Segment {
145    Point(Point),
146    Line(Line),
147    Arc(Arc),
148    Circle(Circle),
149}
150
151#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
152#[ts(export, export_to = "FrontendApi.ts")]
153pub struct ExistingSegmentCtor {
154    pub id: ObjectId,
155    pub ctor: SegmentCtor,
156}
157
158#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
159#[ts(export, export_to = "FrontendApi.ts")]
160#[serde(tag = "type")]
161pub enum SegmentCtor {
162    Point(PointCtor),
163    Line(LineCtor),
164    Arc(ArcCtor),
165    TangentArc(TangentArcCtor),
166    Circle(CircleCtor),
167}
168
169#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
170#[ts(export, export_to = "FrontendApi.ts")]
171pub struct PointCtor {
172    pub position: Point2d<Expr>,
173}
174
175#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
176#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPoint2d")]
177pub struct Point2d<U: std::fmt::Debug + Clone + ts_rs::TS> {
178    pub x: U,
179    pub y: U,
180}
181
182#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
183#[ts(export, export_to = "FrontendApi.ts", rename = "ApiLine")]
184pub struct Line {
185    pub start: ObjectId,
186    pub end: ObjectId,
187    // Invariant: Line or MidPointLine
188    pub ctor: SegmentCtor,
189    // The constructor is applicable if changing the values of the constructor will change the rendering
190    // of the segment (modulo multiple valid solutions). I.e., whether the object is constrained with
191    // respect to the constructor inputs.
192    // The frontend should only display handles for the constructor inputs if the ctor is applicable.
193    // (Or because they are the (locked) start/end of the segment).
194    pub ctor_applicable: bool,
195}
196
197#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
198#[ts(export, export_to = "FrontendApi.ts")]
199pub struct LineCtor {
200    pub start: Point2d<Expr>,
201    pub end: Point2d<Expr>,
202}
203
204#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
205#[ts(export, export_to = "FrontendApi.ts", rename = "ApiStartOrEnd")]
206#[serde(tag = "type")]
207pub enum StartOrEnd<T> {
208    Start(T),
209    End(T),
210}
211
212#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
213#[ts(export, export_to = "FrontendApi.ts", rename = "ApiArc")]
214pub struct Arc {
215    pub start: ObjectId,
216    pub end: ObjectId,
217    pub center: ObjectId,
218    // Invariant: Arc or TangentArc
219    pub ctor: SegmentCtor,
220    pub ctor_applicable: bool,
221}
222
223#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
224#[ts(export, export_to = "FrontendApi.ts")]
225pub struct ArcCtor {
226    pub start: Point2d<Expr>,
227    pub end: Point2d<Expr>,
228    pub center: Point2d<Expr>,
229}
230
231#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
232#[ts(export, export_to = "FrontendApi.ts")]
233pub struct TangentArcCtor {
234    pub start: Point2d<Expr>,
235    pub end: Point2d<Expr>,
236    pub tangent: StartOrEnd<ObjectId>,
237}
238
239#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
240#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCircle")]
241pub struct Circle {
242    pub start: ObjectId,
243    pub radius: Number,
244    // Invariant: Circle or ThreePointCircle
245    pub ctor: SegmentCtor,
246    pub ctor_applicable: bool,
247}
248
249#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
250#[ts(export, export_to = "FrontendApi.ts")]
251pub struct CircleCtor {
252    pub center: Point2d<Expr>,
253    pub radius: Expr,
254}
255
256#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
257#[ts(export, export_to = "FrontendApi.ts", rename = "ApiConstraint")]
258#[serde(tag = "type")]
259pub enum Constraint {
260    Coincident(Coincident),
261    Distance(Distance),
262    Horizontal(Horizontal),
263    LinesEqualLength(LinesEqualLength),
264    Parallel(Parallel),
265    Perpendicular(Perpendicular),
266    Vertical(Vertical),
267}
268
269#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
270#[ts(export, export_to = "FrontendApi.ts")]
271pub struct Coincident {
272    pub segments: Vec<ObjectId>,
273}
274
275#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
276#[ts(export, export_to = "FrontendApi.ts")]
277pub struct Distance {
278    pub points: Vec<ObjectId>,
279    pub distance: Number,
280}
281
282#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
283#[ts(export, export_to = "FrontendApi.ts")]
284pub struct Horizontal {
285    pub line: ObjectId,
286}
287
288#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
289#[ts(export, export_to = "FrontendApi.ts")]
290pub struct LinesEqualLength {
291    pub lines: Vec<ObjectId>,
292}
293
294#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
295#[ts(export, export_to = "FrontendApi.ts")]
296pub struct Vertical {
297    pub line: ObjectId,
298}
299
300#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
301#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
302pub struct Parallel {
303    pub lines: Vec<ObjectId>,
304}
305
306#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
307#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
308pub struct Perpendicular {
309    pub lines: Vec<ObjectId>,
310}