Add grouping of module instances in Feature Tree (#6125)

* Rename operations to be more generic grouping

* Add group enum

* Add module instance groups

* Change to export all operation ts-rs types to the same file

* Fix Feature Tree display of modules to use name

* Ignore clippy warning

* Update output after operation changes

* Change module instances in Feature Tree use to import icon

* Fix error message when attempting to delete module instance
This commit is contained in:
Jonathan Tran
2025-04-03 22:10:39 -04:00
committed by GitHub
parent c7b348390e
commit d38dcb9ba2
65 changed files with 12299 additions and 9315 deletions

View File

@ -3,12 +3,12 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::{types::NumericType, ArtifactId, KclValue};
use crate::{docs::StdLibFn, std::get_stdlib_fn, SourceRange};
use crate::{docs::StdLibFn, std::get_stdlib_fn, ModuleId, SourceRange};
/// A CAD modeling operation for display in the feature tree, AKA operations
/// timeline.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[ts(export_to = "Operation.ts")]
#[serde(tag = "type")]
pub enum Operation {
#[serde(rename_all = "camelCase")]
@ -40,7 +40,34 @@ pub enum Operation {
is_error: bool,
},
#[serde(rename_all = "camelCase")]
UserDefinedFunctionCall {
GroupBegin {
/// The details of the group.
group: Group,
/// The source range of the operation in the source code.
source_range: SourceRange,
},
GroupEnd,
}
impl Operation {
/// If the variant is `StdLibCall`, set the `is_error` field.
pub(crate) fn set_std_lib_call_is_error(&mut self, is_err: bool) {
match self {
Self::StdLibCall { ref mut is_error, .. } => *is_error = is_err,
Self::KclStdLibCall { ref mut is_error, .. } => *is_error = is_err,
Self::GroupBegin { .. } | Self::GroupEnd => {}
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export_to = "Operation.ts")]
#[serde(tag = "type")]
#[expect(clippy::large_enum_variant)]
pub enum Group {
/// A function call.
#[serde(rename_all = "camelCase")]
FunctionCall {
/// The name of the user-defined function being called. Anonymous
/// functions have no name.
name: Option<String>,
@ -51,26 +78,20 @@ pub enum Operation {
unlabeled_arg: Option<OpArg>,
/// The labeled keyword arguments to the function.
labeled_args: IndexMap<String, OpArg>,
/// The source range of the operation in the source code.
source_range: SourceRange,
},
UserDefinedFunctionReturn,
}
impl Operation {
/// If the variant is `StdLibCall`, set the `is_error` field.
pub(crate) fn set_std_lib_call_is_error(&mut self, is_err: bool) {
match self {
Self::StdLibCall { ref mut is_error, .. } => *is_error = is_err,
Self::KclStdLibCall { ref mut is_error, .. } => *is_error = is_err,
Self::UserDefinedFunctionCall { .. } | Self::UserDefinedFunctionReturn => {}
}
}
/// A whole-module import use.
#[serde(rename_all = "camelCase")]
ModuleInstance {
/// The name of the module being used.
name: String,
/// The ID of the module which can be used to determine its path.
module_id: ModuleId,
},
}
/// An argument to a CAD modeling operation.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[ts(export_to = "Operation.ts")]
#[serde(rename_all = "camelCase")]
pub struct OpArg {
/// The runtime value of the argument. Instead of using [`KclValue`], we
@ -90,7 +111,7 @@ impl OpArg {
/// A reference to a standard library function. This exists to implement
/// `PartialEq` and `Eq` for `Operation`.
#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[ts(export_to = "Operation.ts")]
#[serde(rename_all = "camelCase")]
pub struct StdLibFnRef {
// The following doc comment gets inlined into Operation, overriding what's
@ -154,7 +175,7 @@ fn is_false(b: &bool) -> bool {
/// A KCL value used in Operations. `ArtifactId`s are used to refer to the
/// actual scene objects. Any data not needed in the UI may be omitted.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[ts(export_to = "Operation.ts")]
#[serde(tag = "type")]
pub enum OpKclValue {
Uuid {
@ -212,21 +233,21 @@ pub enum OpKclValue {
pub type OpKclObjectFields = IndexMap<String, OpKclValue>;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[ts(export_to = "Operation.ts")]
#[serde(rename_all = "camelCase")]
pub struct OpSketch {
artifact_id: ArtifactId,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[ts(export_to = "Operation.ts")]
#[serde(rename_all = "camelCase")]
pub struct OpSolid {
artifact_id: ArtifactId,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[ts(export_to = "Operation.ts")]
#[serde(rename_all = "camelCase")]
pub struct OpHelix {
artifact_id: ArtifactId,

View File

@ -3,7 +3,7 @@ use std::collections::HashMap;
use async_recursion::async_recursion;
use indexmap::IndexMap;
use super::{kcl_value::TypeDef, types::PrimitiveType};
use super::{cad_op::Group, kcl_value::TypeDef, types::PrimitiveType};
use crate::{
engine::ExecutionKind,
errors::{KclError, KclErrorDetails},
@ -20,7 +20,7 @@ use crate::{
modules::{ModuleId, ModulePath, ModuleRepr},
parsing::ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
CallExpression, CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector,
BoxNode, CallExpression, CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector,
ItemVisibility, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, Node, NodeRef,
ObjectExpression, PipeExpression, Program, TagDeclarator, Type, UnaryExpression, UnaryOperator,
},
@ -512,10 +512,19 @@ impl ExecutorContext {
async fn exec_module_for_result(
&self,
module_id: ModuleId,
module_name: &BoxNode<Name>,
exec_state: &mut ExecState,
exec_kind: ExecutionKind,
source_range: SourceRange,
) -> Result<Option<KclValue>, KclError> {
exec_state.global.operations.push(Operation::GroupBegin {
group: Group::ModuleInstance {
name: module_name.to_string(),
module_id,
},
source_range,
});
let path = exec_state.global.module_infos[&module_id].path.clone();
let mut repr = exec_state.global.module_infos[&module_id].take_repr();
// DON'T EARLY RETURN! We need to restore the module repr
@ -541,6 +550,9 @@ impl ExecutorContext {
};
exec_state.global.module_infos[&module_id].restore_repr(repr);
exec_state.global.operations.push(Operation::GroupEnd);
result
}
@ -592,7 +604,7 @@ impl ExecutorContext {
Expr::Name(name) => {
let value = name.get_result(exec_state, self).await?.clone();
if let KclValue::Module { value: module_id, meta } = value {
self.exec_module_for_result(module_id, exec_state, ExecutionKind::Normal, metadata.source_range)
self.exec_module_for_result(module_id, name, exec_state, ExecutionKind::Normal, metadata.source_range)
.await?
.unwrap_or_else(|| {
exec_state.warn(CompilationError::err(
@ -1349,7 +1361,7 @@ impl Node<CallExpressionKw> {
if matches!(fn_src, FunctionSource::User { .. }) && !ctx.is_isolated_execution().await {
// Track return operation.
exec_state.global.operations.push(Operation::UserDefinedFunctionReturn);
exec_state.global.operations.push(Operation::GroupEnd);
}
let result = return_value.ok_or_else(move || {
@ -1459,12 +1471,14 @@ impl Node<CallExpression> {
if !ctx.is_isolated_execution().await {
// Track call operation.
exec_state.global.operations.push(Operation::UserDefinedFunctionCall {
name: Some(fn_name.to_string()),
function_source_range: func.function_def_source_range().unwrap_or_default(),
unlabeled_arg: None,
// TODO: Add the arguments for legacy positional parameters.
labeled_args: Default::default(),
exec_state.global.operations.push(Operation::GroupBegin {
group: Group::FunctionCall {
name: Some(fn_name.to_string()),
function_source_range: func.function_def_source_range().unwrap_or_default(),
unlabeled_arg: None,
// TODO: Add the arguments for legacy positional parameters.
labeled_args: Default::default(),
},
source_range: callsite,
});
}
@ -1498,7 +1512,7 @@ impl Node<CallExpression> {
if !ctx.is_isolated_execution().await {
// Track return operation.
exec_state.global.operations.push(Operation::UserDefinedFunctionReturn);
exec_state.global.operations.push(Operation::GroupEnd);
}
Ok(result)
@ -2279,15 +2293,17 @@ impl FunctionSource {
.iter()
.map(|(k, arg)| (k.clone(), OpArg::new(OpKclValue::from(&arg.value), arg.source_range)))
.collect();
exec_state.global.operations.push(Operation::UserDefinedFunctionCall {
name: fn_name,
function_source_range: ast.as_source_range(),
unlabeled_arg: args
.kw_args
.unlabeled
.as_ref()
.map(|arg| OpArg::new(OpKclValue::from(&arg.value), arg.source_range)),
labeled_args: op_labeled_args,
exec_state.global.operations.push(Operation::GroupBegin {
group: Group::FunctionCall {
name: fn_name,
function_source_range: ast.as_source_range(),
unlabeled_arg: args
.kw_args
.unlabeled
.as_ref()
.map(|arg| OpArg::new(OpKclValue::from(&arg.value), arg.source_range)),
labeled_args: op_labeled_args,
},
source_range: callsite,
});
}