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