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