Skip to main content

kcl_lib/execution/
cad_op.rs

1use indexmap::IndexMap;
2use serde::Serialize;
3
4use super::ArtifactId;
5use super::KclValue;
6use super::types::NumericType;
7use crate::ModuleId;
8use crate::NodePath;
9use crate::SourceRange;
10use crate::front::ObjectId;
11use crate::parsing::ast::types::ItemVisibility;
12
13/// A CAD modeling operation for display in the feature tree, AKA operations
14/// timeline.
15#[allow(clippy::large_enum_variant)]
16#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
17#[ts(export_to = "Operation.ts")]
18#[serde(tag = "type")]
19pub enum Operation {
20    #[serde(rename_all = "camelCase")]
21    StdLibCall {
22        name: String,
23        /// The unlabeled argument to the function.
24        unlabeled_arg: Option<OpArg>,
25        /// The labeled keyword arguments to the function.
26        labeled_args: IndexMap<String, OpArg>,
27        /// The node path of the operation in the source code.
28        node_path: NodePath,
29        /// The true source range of the operation in the source code.
30        source_range: SourceRange,
31        /// The source range that's the boundary of calling the standard
32        /// library.
33        #[serde(default, skip_serializing_if = "Option::is_none")]
34        stdlib_entry_source_range: Option<SourceRange>,
35        /// True if the operation resulted in an error.
36        #[serde(default, skip_serializing_if = "is_false")]
37        is_error: bool,
38    },
39    #[serde(rename_all = "camelCase")]
40    VariableDeclaration {
41        /// The variable name.
42        name: String,
43        /// The value of the variable.
44        value: OpKclValue,
45        /// The visibility modifier of the variable, e.g. `export`.  `Default`
46        /// means there is no visibility modifier.
47        visibility: ItemVisibility,
48        /// The node path of the operation in the source code.
49        node_path: NodePath,
50        /// The source range of the operation in the source code.
51        source_range: SourceRange,
52    },
53    #[serde(rename_all = "camelCase")]
54    GroupBegin {
55        /// The details of the group.
56        group: Group,
57        /// The node path of the operation in the source code.
58        node_path: NodePath,
59        /// The source range of the operation in the source code.
60        source_range: SourceRange,
61    },
62    GroupEnd,
63}
64
65impl Operation {
66    /// If the variant is `StdLibCall`, set the `is_error` field.
67    pub(crate) fn set_std_lib_call_is_error(&mut self, is_err: bool) {
68        match self {
69            Self::StdLibCall { is_error, .. } => *is_error = is_err,
70            Self::VariableDeclaration { .. } | Self::GroupBegin { .. } | Self::GroupEnd => {}
71        }
72    }
73
74    pub(crate) fn fill_node_paths(&mut self, programs: &crate::execution::ProgramLookup, cached_body_items: usize) {
75        match self {
76            Operation::StdLibCall {
77                node_path,
78                source_range,
79                stdlib_entry_source_range,
80                ..
81            } => {
82                // If there's a stdlib entry source range, use that to fill the
83                // node path. For example, this will point to the `hole()` call
84                // instead of the `subtract()` call that's deep inside the
85                // stdlib.
86                let range = stdlib_entry_source_range.as_ref().unwrap_or(source_range);
87                node_path.fill_placeholder(programs, cached_body_items, *range);
88            }
89            Operation::VariableDeclaration {
90                node_path,
91                source_range,
92                ..
93            }
94            | Operation::GroupBegin {
95                node_path,
96                source_range,
97                ..
98            } => {
99                node_path.fill_placeholder(programs, cached_body_items, *source_range);
100            }
101            Operation::GroupEnd => {}
102        }
103    }
104}
105
106#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
107#[ts(export_to = "Operation.ts")]
108#[serde(tag = "type")]
109#[expect(clippy::large_enum_variant)]
110pub enum Group {
111    /// A function call.
112    #[serde(rename_all = "camelCase")]
113    FunctionCall {
114        /// The name of the user-defined function being called.  Anonymous
115        /// functions have no name.
116        name: Option<String>,
117        /// The location of the function being called so that there's enough
118        /// info to go to its definition.
119        function_source_range: SourceRange,
120        /// The unlabeled argument to the function.
121        unlabeled_arg: Option<OpArg>,
122        /// The labeled keyword arguments to the function.
123        labeled_args: IndexMap<String, OpArg>,
124    },
125    /// A whole-module import use.
126    #[allow(dead_code)]
127    #[serde(rename_all = "camelCase")]
128    ModuleInstance {
129        /// The name of the module being used.
130        name: String,
131        /// The ID of the module which can be used to determine its path.
132        module_id: ModuleId,
133    },
134    /// A sketch block.
135    #[allow(dead_code)]
136    #[serde(rename_all = "camelCase")]
137    SketchBlock {
138        /// The ID of the sketch this group wraps.
139        sketch_id: ObjectId,
140    },
141}
142
143/// An argument to a CAD modeling operation.
144#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
145#[ts(export_to = "Operation.ts")]
146#[serde(rename_all = "camelCase")]
147pub struct OpArg {
148    /// The runtime value of the argument.  Instead of using [`KclValue`], we
149    /// refer to scene objects using their [`ArtifactId`]s.
150    value: OpKclValue,
151    /// The KCL code expression for the argument.  This is used in the UI so
152    /// that the user can edit the expression.
153    source_range: SourceRange,
154}
155
156impl OpArg {
157    pub(crate) fn new(value: OpKclValue, source_range: SourceRange) -> Self {
158        Self { value, source_range }
159    }
160}
161
162fn is_false(b: &bool) -> bool {
163    !*b
164}
165
166/// A KCL value used in Operations.  `ArtifactId`s are used to refer to the
167/// actual scene objects.  Any data not needed in the UI may be omitted.
168#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
169#[ts(export_to = "Operation.ts")]
170#[serde(tag = "type")]
171pub enum OpKclValue {
172    Uuid {
173        value: ::uuid::Uuid,
174    },
175    Bool {
176        value: bool,
177    },
178    Number {
179        value: f64,
180        ty: NumericType,
181    },
182    String {
183        value: String,
184    },
185    SketchVar {
186        value: f64,
187        ty: NumericType,
188    },
189    Array {
190        value: Vec<OpKclValue>,
191    },
192    Object {
193        value: OpKclObjectFields,
194    },
195    TagIdentifier {
196        /// The name of the tag identifier.
197        value: String,
198        /// The artifact ID of the object it refers to.
199        artifact_id: Option<ArtifactId>,
200    },
201    TagDeclarator {
202        name: String,
203    },
204    GdtAnnotation {
205        artifact_id: ArtifactId,
206    },
207    Plane {
208        artifact_id: ArtifactId,
209    },
210    Face {
211        artifact_id: ArtifactId,
212    },
213    Sketch {
214        value: Box<OpSketch>,
215    },
216    Segment {
217        artifact_id: ArtifactId,
218    },
219    Solid {
220        value: Box<OpSolid>,
221    },
222    Helix {
223        value: Box<OpHelix>,
224    },
225    ImportedGeometry {
226        artifact_id: ArtifactId,
227    },
228    Function {},
229    Module {},
230    Type {},
231    KclNone {},
232    BoundedEdge {},
233}
234
235pub type OpKclObjectFields = IndexMap<String, OpKclValue>;
236
237#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
238#[ts(export_to = "Operation.ts")]
239#[serde(rename_all = "camelCase")]
240pub struct OpSketch {
241    artifact_id: ArtifactId,
242}
243
244#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
245#[ts(export_to = "Operation.ts")]
246#[serde(rename_all = "camelCase")]
247pub struct OpSolid {
248    artifact_id: ArtifactId,
249}
250
251#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
252#[ts(export_to = "Operation.ts")]
253#[serde(rename_all = "camelCase")]
254pub struct OpHelix {
255    artifact_id: ArtifactId,
256}
257
258impl From<&KclValue> for OpKclValue {
259    fn from(value: &KclValue) -> Self {
260        match value {
261            KclValue::Uuid { value, .. } => Self::Uuid { value: *value },
262            KclValue::Bool { value, .. } => Self::Bool { value: *value },
263            KclValue::Number { value, ty, .. } => Self::Number { value: *value, ty: *ty },
264            KclValue::String { value, .. } => Self::String { value: value.clone() },
265            KclValue::SketchVar { value, .. } => Self::SketchVar {
266                value: value.initial_value,
267                ty: value.ty,
268            },
269            KclValue::SketchConstraint { .. } => {
270                debug_assert!(false, "Sketch constraint cannot be represented in operations");
271                Self::KclNone {}
272            }
273            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
274                let value = value.iter().map(Self::from).collect();
275                Self::Array { value }
276            }
277            KclValue::Object { value, .. } => {
278                let value = value.iter().map(|(k, v)| (k.clone(), Self::from(v))).collect();
279                Self::Object { value }
280            }
281            KclValue::TagIdentifier(tag_identifier) => Self::TagIdentifier {
282                value: tag_identifier.value.clone(),
283                artifact_id: tag_identifier.get_cur_info().map(|info| ArtifactId::new(info.id)),
284            },
285            KclValue::TagDeclarator(node) => Self::TagDeclarator {
286                name: node.name.clone(),
287            },
288            KclValue::GdtAnnotation { value } => Self::GdtAnnotation {
289                artifact_id: ArtifactId::new(value.id),
290            },
291            KclValue::Plane { value } => Self::Plane {
292                artifact_id: value.artifact_id,
293            },
294            KclValue::Face { value } => Self::Face {
295                artifact_id: value.artifact_id,
296            },
297            KclValue::Segment { value } => match &value.repr {
298                crate::execution::geometry::SegmentRepr::Unsolved { .. } => {
299                    // Arguments to constraint functions will be unsolved.
300                    Self::KclNone {}
301                }
302                crate::execution::geometry::SegmentRepr::Solved { segment, .. } => Self::Segment {
303                    artifact_id: ArtifactId::new(segment.id),
304                },
305            },
306            KclValue::Sketch { value } => Self::Sketch {
307                value: Box::new(OpSketch {
308                    artifact_id: value.artifact_id,
309                }),
310            },
311            KclValue::Solid { value } => Self::Solid {
312                value: Box::new(OpSolid {
313                    artifact_id: value.artifact_id,
314                }),
315            },
316            KclValue::Helix { value } => Self::Helix {
317                value: Box::new(OpHelix {
318                    artifact_id: value.artifact_id,
319                }),
320            },
321            KclValue::ImportedGeometry(imported_geometry) => Self::ImportedGeometry {
322                artifact_id: ArtifactId::new(imported_geometry.id),
323            },
324            KclValue::Function { .. } => Self::Function {},
325            KclValue::Module { .. } => Self::Module {},
326            KclValue::KclNone { .. } => Self::KclNone {},
327            KclValue::Type { .. } => Self::Type {},
328            KclValue::BoundedEdge { .. } => Self::BoundedEdge {},
329        }
330    }
331}