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