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        /// 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    GroupEnd,
74}
75
76impl Operation {
77    /// If the variant is `StdLibCall`, set the `is_error` field.
78    pub(crate) fn set_std_lib_call_is_error(&mut self, is_err: bool) {
79        match self {
80            Self::StdLibCall { is_error, .. } => *is_error = is_err,
81            Self::VariableDeclaration { .. }
82            | Self::GroupBegin { .. }
83            | Self::ModuleInstance { .. }
84            | Self::GroupEnd => {}
85        }
86    }
87
88    pub(crate) fn fill_node_paths(&mut self, programs: &crate::execution::ProgramLookup, cached_body_items: usize) {
89        match self {
90            Operation::StdLibCall {
91                node_path,
92                source_range,
93                stdlib_entry_source_range,
94                ..
95            } => {
96                // If there's a stdlib entry source range, use that to fill the
97                // node path. For example, this will point to the `hole()` call
98                // instead of the `subtract()` call that's deep inside the
99                // stdlib.
100                let range = stdlib_entry_source_range.as_ref().unwrap_or(source_range);
101                node_path.fill_placeholder(programs, cached_body_items, *range);
102            }
103            Operation::VariableDeclaration {
104                node_path,
105                source_range,
106                ..
107            }
108            | Operation::GroupBegin {
109                node_path,
110                source_range,
111                ..
112            }
113            | Operation::ModuleInstance {
114                node_path,
115                source_range,
116                ..
117            } => {
118                node_path.fill_placeholder(programs, cached_body_items, *source_range);
119            }
120            Operation::GroupEnd => {}
121        }
122    }
123}
124
125#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
126#[ts(export_to = "Operation.ts")]
127#[serde(tag = "type")]
128#[expect(clippy::large_enum_variant)]
129pub enum Group {
130    /// A function call.
131    #[serde(rename_all = "camelCase")]
132    FunctionCall {
133        /// The name of the user-defined function being called.  Anonymous
134        /// functions have no name.
135        name: Option<String>,
136        /// The location of the function being called so that there's enough
137        /// info to go to its definition.
138        function_source_range: SourceRange,
139        /// The unlabeled argument to the function.
140        unlabeled_arg: Option<OpArg>,
141        /// The labeled keyword arguments to the function.
142        labeled_args: IndexMap<String, OpArg>,
143    },
144    /// A sketch block.
145    #[allow(dead_code)]
146    #[serde(rename_all = "camelCase")]
147    SketchBlock {
148        /// The ID of the sketch this group wraps.
149        sketch_id: ObjectId,
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}