Skip to content

Add building the artifact graph in sketch mode, take 2 #7557

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 15 additions & 25 deletions rust/kcl-lib/src/execution/artifact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,8 @@ pub struct CompositeSolid {
pub id: ArtifactId,
pub sub_type: CompositeSolidSubType,
/// Constituent solids of the composite solid.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub solid_ids: Vec<ArtifactId>,
/// Tool solids used for asymmetric operations like subtract.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_ids: Vec<ArtifactId>,
pub code_ref: CodeRef,
/// This is the ID of the composite solid that this is part of, if any, as a
Expand Down Expand Up @@ -141,12 +139,10 @@ pub struct Segment {
pub path_id: ArtifactId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub surface_id: Option<ArtifactId>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub edge_ids: Vec<ArtifactId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub edge_cut_id: Option<ArtifactId>,
pub code_ref: CodeRef,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub common_surface_ids: Vec<ArtifactId>,
}

Expand All @@ -158,9 +154,7 @@ pub struct Sweep {
pub id: ArtifactId,
pub sub_type: SweepSubType,
pub path_id: ArtifactId,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub surface_ids: Vec<ArtifactId>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub edge_ids: Vec<ArtifactId>,
pub code_ref: CodeRef,
}
Expand Down Expand Up @@ -209,10 +203,8 @@ pub struct StartSketchOnPlane {
pub struct Wall {
pub id: ArtifactId,
pub seg_id: ArtifactId,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub edge_cut_edge_ids: Vec<ArtifactId>,
pub sweep_id: ArtifactId,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub path_ids: Vec<ArtifactId>,
/// This is for the sketch-on-face plane, not for the wall itself. Traverse
/// to the extrude and/or segment to get the wall's code_ref.
Expand All @@ -227,10 +219,8 @@ pub struct Wall {
pub struct Cap {
pub id: ArtifactId,
pub sub_type: CapSubType,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub edge_cut_edge_ids: Vec<ArtifactId>,
pub sweep_id: ArtifactId,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub path_ids: Vec<ArtifactId>,
/// This is for the sketch-on-face plane, not for the cap itself. Traverse
/// to the extrude and/or segment to get the cap's code_ref.
Expand Down Expand Up @@ -259,7 +249,6 @@ pub struct SweepEdge {
#[serde(skip)]
pub index: usize,
pub sweep_id: ArtifactId,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub common_surface_ids: Vec<ArtifactId>,
}

Expand All @@ -278,7 +267,6 @@ pub struct EdgeCut {
pub id: ArtifactId,
pub sub_type: EdgeCutSubType,
pub consumed_edge_id: ArtifactId,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub edge_ids: Vec<ArtifactId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub surface_id: Option<ArtifactId>,
Expand Down Expand Up @@ -540,6 +528,11 @@ impl ArtifactGraph {
self.map.is_empty()
}

#[cfg(test)]
pub(crate) fn iter(&self) -> impl Iterator<Item = (&ArtifactId, &Artifact)> {
self.map.iter()
}

pub fn values(&self) -> impl Iterator<Item = &Artifact> {
self.map.values()
}
Expand Down Expand Up @@ -728,10 +721,7 @@ fn artifacts_to_update(
exec_artifacts: &IndexMap<ArtifactId, Artifact>,
) -> Result<Vec<Artifact>, KclError> {
let uuid = artifact_command.cmd_id;
let Some(response) = responses.get(&uuid) else {
// Response not found or not successful.
return Ok(Vec::new());
};
let response = responses.get(&uuid);

// TODO: Build path-to-node from artifact_command source range. Right now,
// we're serializing an empty array, and the TS wrapper fills it in with the
Expand Down Expand Up @@ -875,7 +865,7 @@ fn artifacts_to_update(
new_path.seg_ids = vec![id];
return_arr.push(Artifact::Path(new_path));
}
if let OkModelingCmdResponse::ClosePath(close_path) = response {
if let Some(OkModelingCmdResponse::ClosePath(close_path)) = response {
return_arr.push(Artifact::Solid2d(Solid2d {
id: close_path.face_id.into(),
path_id,
Expand All @@ -895,8 +885,8 @@ fn artifacts_to_update(
ids: original_path_ids, ..
}) => {
let face_edge_infos = match response {
OkModelingCmdResponse::EntityMirror(resp) => &resp.entity_face_edge_ids,
OkModelingCmdResponse::EntityMirrorAcrossEdge(resp) => &resp.entity_face_edge_ids,
Some(OkModelingCmdResponse::EntityMirror(resp)) => &resp.entity_face_edge_ids,
Some(OkModelingCmdResponse::EntityMirrorAcrossEdge(resp)) => &resp.entity_face_edge_ids,
_ => internal_error!(
range,
"Mirror response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
Expand Down Expand Up @@ -983,7 +973,7 @@ fn artifacts_to_update(
return Ok(return_arr);
}
ModelingCmd::Loft(loft_cmd) => {
let OkModelingCmdResponse::Loft(_) = response else {
let Some(OkModelingCmdResponse::Loft(_)) = response else {
return Ok(Vec::new());
};
let mut return_arr = Vec::new();
Expand Down Expand Up @@ -1013,7 +1003,7 @@ fn artifacts_to_update(
return Ok(return_arr);
}
ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
let OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info) = response else {
let Some(OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info)) = response else {
return Ok(Vec::new());
};
let mut return_arr = Vec::new();
Expand Down Expand Up @@ -1134,7 +1124,7 @@ fn artifacts_to_update(
return Ok(return_arr);
}
ModelingCmd::Solid3dGetAdjacencyInfo(kcmc::Solid3dGetAdjacencyInfo { .. }) => {
let OkModelingCmdResponse::Solid3dGetAdjacencyInfo(info) = response else {
let Some(OkModelingCmdResponse::Solid3dGetAdjacencyInfo(info)) = response else {
return Ok(Vec::new());
};

Expand Down Expand Up @@ -1315,21 +1305,21 @@ fn artifacts_to_update(
let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;

match response {
OkModelingCmdResponse::BooleanIntersection(intersection) => intersection
Some(OkModelingCmdResponse::BooleanIntersection(intersection)) => intersection
.extra_solid_ids
.iter()
.copied()
.map(ArtifactId::new)
.filter(not_cmd_id)
.for_each(|id| new_solid_ids.push(id)),
OkModelingCmdResponse::BooleanSubtract(subtract) => subtract
Some(OkModelingCmdResponse::BooleanSubtract(subtract)) => subtract
.extra_solid_ids
.iter()
.copied()
.map(ArtifactId::new)
.filter(not_cmd_id)
.for_each(|id| new_solid_ids.push(id)),
OkModelingCmdResponse::BooleanUnion(union) => union
Some(OkModelingCmdResponse::BooleanUnion(union)) => union
.extra_solid_ids
.iter()
.copied()
Expand Down
2 changes: 1 addition & 1 deletion rust/kcl-lib/src/execution/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::{
lazy_static::lazy_static! {
/// A static mutable lock for updating the last successful execution state for the cache.
static ref OLD_AST: Arc<RwLock<Option<GlobalState>>> = Default::default();
// The last successful run's memory. Not cleared after an unssuccessful run.
// The last successful run's memory. Not cleared after an unsuccessful run.
static ref PREV_MEMORY: Arc<RwLock<Option<(Stack, ModuleInfoMap)>>> = Default::default();
}

Expand Down
19 changes: 19 additions & 0 deletions rust/kcl-lib/src/execution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2286,6 +2286,25 @@ w = f() + f()
ctx2.close().await;
}

#[cfg(feature = "artifact-graph")]
#[tokio::test(flavor = "multi_thread")]
async fn mock_has_stable_ids() {
let ctx = ExecutorContext::new_mock(None).await;
let code = "sk = startSketchOn(XY)
|> startProfile(at = [0, 0])";
let program = crate::Program::parse_no_errs(code).unwrap();
let result = ctx.run_mock(program, false).await.unwrap();
let ids = result.artifact_graph.iter().map(|(k, _)| *k).collect::<Vec<_>>();
assert!(!ids.is_empty(), "IDs should not be empty");

let ctx2 = ExecutorContext::new_mock(None).await;
let program2 = crate::Program::parse_no_errs(code).unwrap();
let result = ctx2.run_mock(program2, false).await.unwrap();
let ids2 = result.artifact_graph.iter().map(|(k, _)| *k).collect::<Vec<_>>();

assert_eq!(ids, ids2, "Generated IDs should match");
}

#[cfg(feature = "artifact-graph")]
#[tokio::test(flavor = "multi_thread")]
async fn sim_sketch_mode_real_mock_real() {
Expand Down
2 changes: 1 addition & 1 deletion rust/kcl-lib/src/execution/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ impl ExecState {
#[cfg(feature = "artifact-graph")]
operations: Default::default(),
#[cfg(feature = "artifact-graph")]
artifact_graph: Default::default(),
artifact_graph: self.global.artifacts.graph,
errors: self.global.errors,
filenames: Default::default(),
default_planes: ctx.engine.get_default_planes().read().await.clone(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ flowchart LR
end
1["Plane<br>[10, 27, 0]"]
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 0 }]
7["Sweep Extrusion<br>[226, 245, 0]"]
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 6 }]
1 --- 2
2 --- 3
2 --- 4
2 --- 5
2 --- 6
2 ---- 7
```
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,9 @@ flowchart LR
%% face_code_ref=Missing NodePath
110["SweepEdge Opposite"]
111["SweepEdge Adjacent"]
112["StartSketchOnPlane<br>[1430, 1475, 0]"]
112["CompositeSolid Subtract<br>[1634, 1677, 0]"]
%% [ProgramBodyItem { index: 10 }, VariableDeclarationDeclaration, VariableDeclarationInit]
113["StartSketchOnPlane<br>[1430, 1475, 0]"]
%% [ProgramBodyItem { index: 7 }, VariableDeclarationDeclaration, VariableDeclarationInit]
1 --- 2
1 --- 13
Expand Down Expand Up @@ -302,6 +304,7 @@ flowchart LR
44 --- 50
44 --- 51
44 ---- 60
44 --- 112
45 --- 61
45 x--> 67
45 --- 69
Expand Down Expand Up @@ -334,6 +337,7 @@ flowchart LR
52 --- 58
52 --- 59
52 ---- 81
52 --- 112
53 --- 87
53 x--> 88
53 --- 100
Expand Down Expand Up @@ -447,10 +451,11 @@ flowchart LR
98 <--x 89
100 <--x 89
102 --- 103
102 <--x 112
102 <--x 113
103 --- 104
103 --- 105
103 ---- 106
103 --- 112
104 --- 107
104 x--> 109
104 --- 110
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ flowchart LR
83["SweepEdge Adjacent"]
84["EdgeCut Fillet<br>[1691, 1773, 0]"]
%% [ProgramBodyItem { index: 14 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 7 }]
85["CompositeSolid Union<br>[1939, 1960, 0]"]
%% [ProgramBodyItem { index: 15 }, ExpressionStatementExpr]
1 --- 2
2 --- 3
2 --- 4
Expand All @@ -150,6 +152,7 @@ flowchart LR
2 --- 10
2 --- 11
2 ---- 12
2 --- 85
3 --- 13
3 x--> 22
3 --- 23
Expand Down Expand Up @@ -247,6 +250,7 @@ flowchart LR
40 --- 44
40 --- 45
40 ---- 46
40 --- 85
41 --- 47
41 x--> 52
41 --- 53
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,16 @@ flowchart LR
38["SweepEdge Adjacent"]
39["SweepEdge Opposite"]
40["SweepEdge Adjacent"]
41["CompositeSolid Union<br>[1346, 1377, 0]"]
%% [ProgramBodyItem { index: 11 }, VariableDeclarationDeclaration, VariableDeclarationInit]
1 --- 2
2 --- 3
2 --- 4
2 --- 5
2 --- 6
2 --- 7
2 ---- 8
2 --- 41
3 --- 9
3 x--> 13
3 --- 15
Expand Down Expand Up @@ -127,6 +130,7 @@ flowchart LR
24 --- 27
24 --- 28
24 ---- 29
24 --- 41
25 --- 30
25 x--> 33
25 --- 35
Expand Down
1 change: 1 addition & 0 deletions src/lang/KclSingleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ export class KclManager extends EventTarget {
this.lastSuccessfulVariables = execState.variables
this.lastSuccessfulOperations = execState.operations
}
await this.updateArtifactGraph(execState.artifactGraph)
return null
}
cancelAllExecutions() {
Expand Down
38 changes: 14 additions & 24 deletions src/lang/wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,14 +314,7 @@ export function emptyExecState(): ExecState {
}

export function execStateFromRust(execOutcome: RustExecOutcome): ExecState {
const artifactGraph = rustArtifactGraphToMap(execOutcome.artifactGraph)
// Translate NodePath to PathToNode.
for (const [_id, artifact] of artifactGraph) {
if (!artifact) continue
if (!('codeRef' in artifact)) continue
const pathToNode = pathToNodeFromRustNodePath(artifact.codeRef.nodePath)
artifact.codeRef.pathToNode = pathToNode
}
const artifactGraph = artifactGraphFromRust(execOutcome.artifactGraph)

return {
variables: execOutcome.variables,
Expand All @@ -333,29 +326,26 @@ export function execStateFromRust(execOutcome: RustExecOutcome): ExecState {
}
}

export function mockExecStateFromRust(execOutcome: RustExecOutcome): ExecState {
return {
variables: execOutcome.variables,
operations: execOutcome.operations,
artifactGraph: new Map<ArtifactId, Artifact>(),
errors: execOutcome.errors,
filenames: execOutcome.filenames,
defaultPlanes: execOutcome.defaultPlanes,
}
}

export type ArtifactGraph = Map<ArtifactId, Artifact>

function rustArtifactGraphToMap(
function artifactGraphFromRust(
rustArtifactGraph: RustArtifactGraph
): ArtifactGraph {
const map = new Map<ArtifactId, Artifact>()
const artifactGraph = new Map<ArtifactId, Artifact>()
// Convert to a Map.
for (const [id, artifact] of Object.entries(rustArtifactGraph.map)) {
if (!artifact) continue
map.set(id, artifact)
artifactGraph.set(id, artifact)
}

return map
// Translate NodePath to PathToNode.
for (const [_id, artifact] of artifactGraph) {
if (!artifact) continue
if (!('codeRef' in artifact)) continue
const pathToNode = pathToNodeFromRustNodePath(artifact.codeRef.nodePath)
artifact.codeRef.pathToNode = pathToNode
}
return artifactGraph
}

export function sketchFromKclValueOptional(
Expand Down Expand Up @@ -407,7 +397,7 @@ export const errFromErrWithOutputs = (e: any): KCLError => {
parsed.error.details.backtrace,
parsed.nonFatal,
parsed.operations,
rustArtifactGraphToMap(parsed.artifactGraph),
artifactGraphFromRust(parsed.artifactGraph),
parsed.filenames,
parsed.defaultPlanes
)
Expand Down
Loading
Loading