Skip to main content

kcl_lib/frontend/
sketch.rs

1#![allow(async_fn_in_trait)]
2
3use serde::Deserialize;
4use serde::Serialize;
5
6use crate::ExecutorContext;
7use crate::KclErrorWithOutputs;
8use crate::front::Plane;
9use crate::frontend::api::Expr;
10use crate::frontend::api::FileId;
11use crate::frontend::api::Number;
12use crate::frontend::api::ObjectId;
13use crate::frontend::api::ProjectId;
14use crate::frontend::api::SceneGraph;
15use crate::frontend::api::SceneGraphDelta;
16use crate::frontend::api::SourceDelta;
17use crate::frontend::api::Version;
18
19pub type ExecResult<T> = std::result::Result<T, KclErrorWithOutputs>;
20
21/// Information about a newly created segment for batch operations
22#[derive(Debug, Clone)]
23pub struct NewSegmentInfo {
24    pub segment_id: ObjectId,
25    pub start_point_id: ObjectId,
26    pub end_point_id: ObjectId,
27    pub center_point_id: Option<ObjectId>,
28}
29
30pub trait SketchApi {
31    /// Execute the sketch in mock mode, without changing anything. This is
32    /// useful after editing segments, and the user releases the mouse button.
33    async fn execute_mock(
34        &mut self,
35        ctx: &ExecutorContext,
36        version: Version,
37        sketch: ObjectId,
38    ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
39
40    async fn new_sketch(
41        &mut self,
42        ctx: &ExecutorContext,
43        project: ProjectId,
44        file: FileId,
45        version: Version,
46        args: SketchCtor,
47    ) -> ExecResult<(SourceDelta, SceneGraphDelta, ObjectId)>;
48
49    // Enters sketch mode
50    async fn edit_sketch(
51        &mut self,
52        ctx: &ExecutorContext,
53        project: ProjectId,
54        file: FileId,
55        version: Version,
56        sketch: ObjectId,
57    ) -> ExecResult<SceneGraphDelta>;
58
59    async fn exit_sketch(
60        &mut self,
61        ctx: &ExecutorContext,
62        version: Version,
63        sketch: ObjectId,
64    ) -> ExecResult<SceneGraph>;
65
66    async fn delete_sketch(
67        &mut self,
68        ctx: &ExecutorContext,
69        version: Version,
70        sketch: ObjectId,
71    ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
72
73    async fn add_segment(
74        &mut self,
75        ctx: &ExecutorContext,
76        version: Version,
77        sketch: ObjectId,
78        segment: SegmentCtor,
79        label: Option<String>,
80    ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
81
82    async fn edit_segments(
83        &mut self,
84        ctx: &ExecutorContext,
85        version: Version,
86        sketch: ObjectId,
87        segments: Vec<ExistingSegmentCtor>,
88    ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
89
90    async fn delete_objects(
91        &mut self,
92        ctx: &ExecutorContext,
93        version: Version,
94        sketch: ObjectId,
95        constraint_ids: Vec<ObjectId>,
96        segment_ids: Vec<ObjectId>,
97    ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
98
99    async fn add_constraint(
100        &mut self,
101        ctx: &ExecutorContext,
102        version: Version,
103        sketch: ObjectId,
104        constraint: Constraint,
105    ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
106
107    async fn chain_segment(
108        &mut self,
109        ctx: &ExecutorContext,
110        version: Version,
111        sketch: ObjectId,
112        previous_segment_end_point_id: ObjectId,
113        segment: SegmentCtor,
114        label: Option<String>,
115    ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
116
117    async fn edit_constraint(
118        &mut self,
119        ctx: &ExecutorContext,
120        version: Version,
121        sketch: ObjectId,
122        constraint_id: ObjectId,
123        value_expression: String,
124    ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
125
126    async fn edit_distance_constraint_label_position(
127        &mut self,
128        ctx: &ExecutorContext,
129        version: Version,
130        sketch: ObjectId,
131        constraint_id: ObjectId,
132        label_position: Point2d<Number>,
133        anchor_segment_ids: Vec<ObjectId>,
134    ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
135
136    /// Batch operations for split segment: edit segments, add constraints, delete objects.
137    /// All operations are applied to a single AST and execute_after_edit is called once at the end.
138    /// new_segment_info contains the IDs from the segment(s) added in a previous step.
139    #[allow(clippy::too_many_arguments)]
140    async fn batch_split_segment_operations(
141        &mut self,
142        ctx: &ExecutorContext,
143        version: Version,
144        sketch: ObjectId,
145        edit_segments: Vec<ExistingSegmentCtor>,
146        add_constraints: Vec<Constraint>,
147        delete_constraint_ids: Vec<ObjectId>,
148        new_segment_info: NewSegmentInfo,
149    ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
150
151    /// Batch operations for tail-cut trim: edit a segment, add coincident constraints,
152    /// delete constraints, and execute once.
153    #[allow(clippy::too_many_arguments)]
154    async fn batch_tail_cut_operations(
155        &mut self,
156        ctx: &ExecutorContext,
157        version: Version,
158        sketch: ObjectId,
159        edit_segments: Vec<ExistingSegmentCtor>,
160        add_constraints: Vec<Constraint>,
161        delete_constraint_ids: Vec<ObjectId>,
162        additional_edited_segment_ids: Vec<ObjectId>,
163    ) -> ExecResult<(SourceDelta, SceneGraphDelta)>;
164}
165
166#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
167#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSketch")]
168pub struct Sketch {
169    pub args: SketchCtor,
170    pub plane: ObjectId,
171    pub segments: Vec<ObjectId>,
172    pub constraints: Vec<ObjectId>,
173}
174
175/// Arguments for creating a new sketch. This is similar to the constructor of
176/// other kinds of objects in that it is the inputs to the sketch, not the
177/// outputs.
178#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
179#[ts(export, export_to = "FrontendApi.ts")]
180pub struct SketchCtor {
181    /// The sketch surface.
182    pub on: Plane,
183}
184
185#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
186#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPoint")]
187pub struct Point {
188    pub position: Point2d<Number>,
189    pub ctor: Option<PointCtor>,
190    pub owner: Option<ObjectId>,
191    pub freedom: Freedom,
192    pub constraints: Vec<ObjectId>,
193}
194
195impl Point {
196    /// The freedom of this point.
197    pub fn freedom(&self) -> Freedom {
198        self.freedom
199    }
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
203#[ts(export, export_to = "FrontendApi.ts")]
204pub enum Freedom {
205    Free,
206    Fixed,
207    Conflict,
208}
209
210impl Freedom {
211    /// Merges two Freedom values. For example, a point has a solver variable
212    /// for each dimension, x and y. If one dimension is `Free` and the other is
213    /// `Fixed`, the point overall is `Free` since it isn't fully constrained.
214    /// `Conflict` infects the most, followed by `Free`. An object must be fully
215    /// `Fixed` to be `Fixed` overall.
216    pub fn merge(self, other: Self) -> Self {
217        match (self, other) {
218            (Self::Conflict, _) | (_, Self::Conflict) => Self::Conflict,
219            (Self::Free, _) | (_, Self::Free) => Self::Free,
220            (Self::Fixed, Self::Fixed) => Self::Fixed,
221        }
222    }
223}
224
225#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
226#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSegment")]
227#[serde(tag = "type")]
228pub enum Segment {
229    Point(Point),
230    Line(Line),
231    Arc(Arc),
232    Circle(Circle),
233}
234
235impl Segment {
236    /// What kind of geometry is this (point, line, arc, etc)
237    /// Suitable for use in user-facing messages.
238    pub fn human_friendly_kind_with_article(&self) -> &'static str {
239        match self {
240            Self::Point(_) => "a Point",
241            Self::Line(_) => "a Line",
242            Self::Arc(_) => "an Arc",
243            Self::Circle(_) => "a Circle",
244        }
245    }
246
247    /// Compute the overall freedom of this segment. For geometry types (Line,
248    /// Arc, Circle) this looks up and merges the freedom of their constituent
249    /// points. For points, returns the point's own freedom directly.
250    /// Returns `None` if a required point lookup failed.
251    pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
252        match self {
253            Self::Point(p) => Some(p.freedom()),
254            Self::Line(l) => l.freedom(&lookup),
255            Self::Arc(a) => a.freedom(&lookup),
256            Self::Circle(c) => c.freedom(&lookup),
257        }
258    }
259}
260
261#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
262#[ts(export, export_to = "FrontendApi.ts")]
263pub struct ExistingSegmentCtor {
264    pub id: ObjectId,
265    pub ctor: SegmentCtor,
266}
267
268#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
269#[ts(export, export_to = "FrontendApi.ts")]
270#[serde(tag = "type")]
271pub enum SegmentCtor {
272    Point(PointCtor),
273    Line(LineCtor),
274    Arc(ArcCtor),
275    Circle(CircleCtor),
276}
277
278impl SegmentCtor {
279    /// What kind of geometry is this (point, line, arc, etc)
280    /// Suitable for use in user-facing messages.
281    pub fn human_friendly_kind_with_article(&self) -> &'static str {
282        match self {
283            Self::Point(_) => "a Point constructor",
284            Self::Line(_) => "a Line constructor",
285            Self::Arc(_) => "an Arc constructor",
286            Self::Circle(_) => "a Circle constructor",
287        }
288    }
289}
290
291#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
292#[ts(export, export_to = "FrontendApi.ts")]
293pub struct PointCtor {
294    pub position: Point2d<Expr>,
295}
296
297#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
298#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPoint2d")]
299pub struct Point2d<U: std::fmt::Debug + Clone + ts_rs::TS> {
300    pub x: U,
301    pub y: U,
302}
303
304#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
305#[ts(export, export_to = "FrontendApi.ts", rename = "ApiLine")]
306pub struct Line {
307    pub start: ObjectId,
308    pub end: ObjectId,
309    // Invariant: Line or MidPointLine
310    pub ctor: SegmentCtor,
311    // The constructor is applicable if changing the values of the constructor will change the rendering
312    // of the segment (modulo multiple valid solutions). I.e., whether the object is constrained with
313    // respect to the constructor inputs.
314    // The frontend should only display handles for the constructor inputs if the ctor is applicable.
315    // (Or because they are the (locked) start/end of the segment).
316    pub ctor_applicable: bool,
317    pub construction: bool,
318}
319
320impl Line {
321    /// Compute the overall freedom of this line by merging the freedom of its
322    /// start and end points. Returns `None` if a point lookup failed.
323    pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
324        let start = lookup(self.start)?;
325        let end = lookup(self.end)?;
326        Some(start.merge(end))
327    }
328}
329
330#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
331#[ts(export, export_to = "FrontendApi.ts")]
332pub struct LineCtor {
333    pub start: Point2d<Expr>,
334    pub end: Point2d<Expr>,
335    #[serde(skip_serializing_if = "Option::is_none")]
336    #[ts(optional)]
337    pub construction: Option<bool>,
338}
339
340#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
341#[ts(export, export_to = "FrontendApi.ts", rename = "ApiStartOrEnd")]
342#[serde(tag = "type")]
343pub enum StartOrEnd<T> {
344    Start(T),
345    End(T),
346}
347
348#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
349#[ts(export, export_to = "FrontendApi.ts", rename = "ApiArc")]
350pub struct Arc {
351    pub start: ObjectId,
352    pub end: ObjectId,
353    pub center: ObjectId,
354    // Invariant: Arc
355    pub ctor: SegmentCtor,
356    pub ctor_applicable: bool,
357    pub construction: bool,
358}
359
360impl Arc {
361    /// Compute the overall freedom of this arc by merging the freedom of its
362    /// start, end, and center points. Returns `None` if a point lookup failed.
363    pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
364        let start = lookup(self.start)?;
365        let end = lookup(self.end)?;
366        let center = lookup(self.center)?;
367        Some(start.merge(end).merge(center))
368    }
369}
370
371#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
372#[ts(export, export_to = "FrontendApi.ts")]
373pub struct ArcCtor {
374    pub start: Point2d<Expr>,
375    pub end: Point2d<Expr>,
376    pub center: Point2d<Expr>,
377    #[serde(skip_serializing_if = "Option::is_none")]
378    #[ts(optional)]
379    pub construction: Option<bool>,
380}
381
382#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
383#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCircle")]
384pub struct Circle {
385    pub start: ObjectId,
386    pub center: ObjectId,
387    // Invariant: Circle
388    pub ctor: SegmentCtor,
389    pub ctor_applicable: bool,
390    pub construction: bool,
391}
392
393impl Circle {
394    /// Compute the overall freedom of this circle by merging the freedom of its
395    /// start and center points. Returns `None` if a point lookup failed.
396    pub fn freedom(&self, lookup: impl Fn(ObjectId) -> Option<Freedom>) -> Option<Freedom> {
397        let start = lookup(self.start)?;
398        let center = lookup(self.center)?;
399        Some(start.merge(center))
400    }
401}
402
403#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
404#[ts(export, export_to = "FrontendApi.ts")]
405pub struct CircleCtor {
406    pub start: Point2d<Expr>,
407    pub center: Point2d<Expr>,
408    #[serde(skip_serializing_if = "Option::is_none")]
409    #[ts(optional)]
410    pub construction: Option<bool>,
411}
412
413#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
414#[ts(export, export_to = "FrontendApi.ts", rename = "ApiConstraint")]
415#[serde(tag = "type")]
416// When adding a new constraint type, check trim compatibility. New constraints
417// can break trim in unexpected ways, especially when endpoints are edited,
418// segments are split, or constraints are migrated. Try the trim tool on sketches
419// using the new constraint, and talk to Kurt, Max, or a mechanical engineer if
420// the intended trim behavior is unclear.
421pub enum Constraint {
422    Coincident(Coincident),
423    Distance(Distance),
424    Angle(Angle),
425    Diameter(Diameter),
426    EqualRadius(EqualRadius),
427    Fixed(Fixed),
428    HorizontalDistance(Distance),
429    VerticalDistance(Distance),
430    Horizontal(Horizontal),
431    LinesEqualLength(LinesEqualLength),
432    Midpoint(Midpoint),
433    Parallel(Parallel),
434    Perpendicular(Perpendicular),
435    Radius(Radius),
436    Symmetric(Symmetric),
437    Tangent(Tangent),
438    Vertical(Vertical),
439}
440
441#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
442#[ts(export, export_to = "FrontendApi.ts")]
443pub struct Coincident {
444    pub segments: Vec<ConstraintSegment>,
445}
446
447impl Coincident {
448    pub fn get_segments(&self) -> Vec<ObjectId> {
449        self.segments
450            .iter()
451            .filter_map(|segment| match segment {
452                ConstraintSegment::Segment(id) => Some(*id),
453                ConstraintSegment::Origin(_) => None,
454            })
455            .collect()
456    }
457
458    pub fn segment_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
459        self.segments.iter().filter_map(|segment| match segment {
460            ConstraintSegment::Segment(id) => Some(*id),
461            ConstraintSegment::Origin(_) => None,
462        })
463    }
464
465    pub fn contains_segment(&self, segment_id: ObjectId) -> bool {
466        self.segment_ids().any(|id| id == segment_id)
467    }
468}
469
470#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
471#[ts(export, export_to = "FrontendApi.ts")]
472#[serde(untagged)]
473pub enum ConstraintSegment {
474    Segment(ObjectId),
475    Origin(OriginLiteral),
476}
477
478impl ConstraintSegment {
479    pub const ORIGIN: Self = Self::Origin(OriginLiteral::Origin);
480}
481
482#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
483#[ts(export, export_to = "FrontendApi.ts")]
484#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
485pub enum OriginLiteral {
486    Origin,
487}
488
489impl From<ObjectId> for ConstraintSegment {
490    fn from(value: ObjectId) -> Self {
491        Self::Segment(value)
492    }
493}
494
495#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
496#[ts(export, export_to = "FrontendApi.ts")]
497pub struct Distance {
498    pub points: Vec<ConstraintSegment>,
499    pub distance: Number,
500    #[serde(rename = "labelPosition")]
501    #[serde(default, skip_serializing_if = "Option::is_none")]
502    #[ts(rename = "labelPosition")]
503    #[ts(optional)]
504    pub label_position: Option<Point2d<Number>>,
505    pub source: ConstraintSource,
506}
507
508impl Distance {
509    pub fn point_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
510        self.points.iter().filter_map(|point| match point {
511            ConstraintSegment::Segment(id) => Some(*id),
512            ConstraintSegment::Origin(_) => None,
513        })
514    }
515
516    pub fn contains_point(&self, point_id: ObjectId) -> bool {
517        self.point_ids().any(|id| id == point_id)
518    }
519}
520
521#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
522#[ts(export, export_to = "FrontendApi.ts")]
523pub struct Angle {
524    pub lines: Vec<ObjectId>,
525    pub angle: Number,
526    pub source: ConstraintSource,
527}
528
529#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize, ts_rs::TS)]
530#[ts(export, export_to = "FrontendApi.ts")]
531pub struct ConstraintSource {
532    pub expr: String,
533    pub is_literal: bool,
534}
535
536#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
537#[ts(export, export_to = "FrontendApi.ts")]
538pub struct Radius {
539    pub arc: ObjectId,
540    pub radius: Number,
541    #[serde(rename = "labelPosition")]
542    #[serde(default, skip_serializing_if = "Option::is_none")]
543    #[ts(rename = "labelPosition")]
544    #[ts(optional)]
545    pub label_position: Option<Point2d<Number>>,
546    #[serde(default)]
547    pub source: ConstraintSource,
548}
549
550#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
551#[ts(export, export_to = "FrontendApi.ts")]
552pub struct Diameter {
553    pub arc: ObjectId,
554    pub diameter: Number,
555    #[serde(rename = "labelPosition")]
556    #[serde(default, skip_serializing_if = "Option::is_none")]
557    #[ts(rename = "labelPosition")]
558    #[ts(optional)]
559    pub label_position: Option<Point2d<Number>>,
560    #[serde(default)]
561    pub source: ConstraintSource,
562}
563
564#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
565#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
566pub struct EqualRadius {
567    pub input: Vec<ObjectId>,
568}
569
570/// Multiple fixed constraints, allowing callers to add fixed constraints on
571/// multiple points at once.
572#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
573#[ts(export, export_to = "FrontendApi.ts")]
574pub struct Fixed {
575    pub points: Vec<FixedPoint>,
576}
577
578/// A fixed constraint on a single point.
579#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
580#[ts(export, export_to = "FrontendApi.ts")]
581pub struct FixedPoint {
582    pub point: ObjectId,
583    pub position: Point2d<Number>,
584}
585
586#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
587#[ts(export, export_to = "FrontendApi.ts")]
588#[serde(untagged)]
589pub enum Horizontal {
590    Line { line: ObjectId },
591    Points { points: Vec<ConstraintSegment> },
592}
593
594#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
595#[ts(export, export_to = "FrontendApi.ts")]
596pub struct LinesEqualLength {
597    pub lines: Vec<ObjectId>,
598}
599
600#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
601#[ts(export, export_to = "FrontendApi.ts")]
602pub struct Midpoint {
603    pub point: ObjectId,
604    #[serde(alias = "line")]
605    pub segment: ObjectId,
606}
607
608#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
609#[ts(export, export_to = "FrontendApi.ts")]
610#[serde(untagged)]
611pub enum Vertical {
612    Line { line: ObjectId },
613    Points { points: Vec<ConstraintSegment> },
614}
615
616#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
617#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
618pub struct Parallel {
619    pub lines: Vec<ObjectId>,
620}
621
622#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
623#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
624pub struct Perpendicular {
625    pub lines: Vec<ObjectId>,
626}
627
628#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
629#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
630pub struct Symmetric {
631    pub input: Vec<ObjectId>,
632    pub axis: ObjectId,
633}
634
635#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
636#[ts(export, export_to = "FrontendApi.ts", optional_fields)]
637pub struct Tangent {
638    pub input: Vec<ObjectId>,
639}