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