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