Skip to main content

kcl_lib/execution/
artifact.rs

1use ahash::AHashMap;
2use ahash::AHashSet;
3use indexmap::IndexMap;
4use kcl_api::Constraint;
5use kcl_api::NodePath;
6use kcl_api::artifact::*;
7use kittycad_modeling_cmds::EnableSketchMode;
8use kittycad_modeling_cmds::FaceIsPlanar;
9use kittycad_modeling_cmds::ModelingCmd;
10use kittycad_modeling_cmds::ok_response::OkModelingCmdResponse;
11use kittycad_modeling_cmds::shared::ExtrusionFaceCapType;
12use kittycad_modeling_cmds::websocket::BatchResponse;
13use kittycad_modeling_cmds::websocket::OkWebSocketResponseData;
14use kittycad_modeling_cmds::websocket::WebSocketResponse;
15use kittycad_modeling_cmds::{self as kcmc};
16use serde::Serialize;
17use uuid::Uuid;
18
19use crate::KclError;
20use crate::ModuleId;
21use crate::NodePathExt;
22use crate::SourceRange;
23use crate::errors::KclErrorDetails;
24use crate::execution::ArtifactId;
25use crate::execution::CameraLook;
26use crate::execution::CameraView;
27use crate::execution::NamedViewValue;
28use crate::execution::Orientation;
29use crate::execution::Projection;
30use crate::execution::Visibility;
31use crate::execution::cmd_id_ref_to_artifact_id;
32use crate::execution::geometry::PlaneInfo;
33use crate::execution::state::ModuleInfoMap;
34use crate::modules::ModulePath;
35use crate::parsing::ast::types::BodyItem;
36use crate::parsing::ast::types::ImportPath;
37use crate::parsing::ast::types::ImportSelector;
38use crate::parsing::ast::types::Node;
39use crate::parsing::ast::types::Program;
40use crate::std::sketch::build_reverse_region_mapping;
41
42#[cfg(test)]
43mod engine_tests;
44#[cfg(test)]
45pub(crate) mod mermaid_tests;
46#[cfg(test)]
47mod tests;
48
49macro_rules! internal_error {
50    ($range:expr, $($rest:tt)*) => {{
51        let message = format!($($rest)*);
52        debug_assert!(false, "{}", &message);
53        return Err(KclError::new_internal(KclErrorDetails::new(message, vec![$range])));
54    }};
55}
56
57/// A command that may create or update artifacts on the TS side.  Because
58/// engine commands are batched, we don't have the response yet when these are
59/// created.
60#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
61#[ts(export_to = "Artifact.ts")]
62#[serde(rename_all = "camelCase")]
63pub struct ArtifactCommand {
64    /// Identifier of the command that can be matched with its response.
65    pub cmd_id: Uuid,
66    /// The source range that's the boundary of calling the standard
67    /// library, not necessarily the true source range of the command.
68    pub range: SourceRange,
69    /// The engine command.  Each artifact command is backed by an engine
70    /// command.  In the future, we may need to send information to the TS side
71    /// without an engine command, in which case, we would make this field
72    /// optional. Imported file commands retain paths and format but omit raw
73    /// file bytes after the command has been sent to the engine.
74    pub command: ModelingCmd,
75    /// Extra artifact identity needed when an engine clone represents a KCL
76    /// solid whose body artifact ID differs from its engine entity ID.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    #[ts(skip)]
79    pub(crate) entity_clone_info: Option<EntityCloneInfo>,
80    /// Whether this command should be omitted when deriving the semantic
81    /// artifact graph. Query-only commands can still be useful in command
82    /// snapshots without becoming frontend selection artifacts.
83    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
84    pub omit_from_graph: bool,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
88#[serde(rename_all = "camelCase")]
89pub(crate) struct EntityCloneInfo {
90    pub source_artifact_id: ArtifactId,
91    pub result_artifact_id: ArtifactId,
92    /// The engine entity whose children describe the source body's topology.
93    /// Pattern copies have their own root and child IDs, but retain the
94    /// topology of the body from which they were patterned.
95    pub source_topology_id: ArtifactId,
96}
97
98pub(super) fn artifact_plane_info(info: &PlaneInfo) -> ArtifactPlaneInfo {
99    ArtifactPlaneInfo {
100        origin: artifact_point3d(info.origin),
101        x_axis: artifact_point3d(info.x_axis),
102        y_axis: artifact_point3d(info.y_axis),
103        z_axis: artifact_point3d(info.z_axis),
104    }
105}
106
107fn artifact_point3d(point: crate::execution::Point3d) -> ArtifactPoint3d {
108    ArtifactPoint3d {
109        x: point.x,
110        y: point.y,
111        z: point.z,
112        units: point.units,
113    }
114}
115
116/// Builds the artifact for a named view.
117///
118/// The runtime value uses kcl-lib's camera and visibility types. The artifact
119/// uses the kcl-api types that correspond to them, so this function converts
120/// every field.
121///
122/// The value holds a baseline and one exception list, while the artifact holds
123/// separate `show_ids` and `hide_ids`. The baseline decides which of those two
124/// lists the exception becomes, and the other list is left empty: under a `Show`
125/// baseline the excepted objects are the hidden ones, and under a `Hide`
126/// baseline they are the only visible ones.
127///
128/// Each match below is exhaustive. A variant added to one of kcl-lib's enums
129/// therefore fails to compile here instead of reaching a consumer as a
130/// different variant.
131pub(crate) fn named_view_artifact(view: &NamedViewValue, code_ref: CodeRef) -> NamedViewArtifact {
132    let (show_ids, hide_ids) = match view.baseline() {
133        Visibility::Show => (Vec::new(), view.except_ids().to_vec()),
134        Visibility::Hide => (view.except_ids().to_vec(), Vec::new()),
135    };
136
137    NamedViewArtifact {
138        id: view.artifact_id(),
139        name: view.name().to_owned(),
140        declaring_module_id: code_ref.range.module_id(),
141        camera: artifact_camera_view(view.camera()),
142        baseline: artifact_visibility(view.baseline()),
143        show_ids,
144        hide_ids,
145        code_ref,
146    }
147}
148
149fn artifact_camera_view(camera: &CameraView) -> ArtifactCameraView {
150    ArtifactCameraView {
151        look: artifact_camera_look(camera.look()),
152        target: camera.target().copied().map(artifact_point3d),
153        // The value already stores millimeters; this reads the number in the
154        // unit the wire model documents rather than trusting the tag.
155        distance: camera.distance().map(|distance| distance.unwrap_to_mm()),
156        projection: artifact_projection(camera.projection()),
157    }
158}
159
160fn artifact_camera_look(look: &CameraLook) -> ArtifactCameraLook {
161    match look {
162        CameraLook::Oriented { orientation } => ArtifactCameraLook::Oriented {
163            orientation: artifact_orientation(*orientation),
164        },
165        CameraLook::Directed { direction, up } => ArtifactCameraLook::Directed {
166            direction: artifact_point3d(*direction),
167            up: artifact_point3d(*up),
168        },
169    }
170}
171
172fn artifact_orientation(orientation: Orientation) -> ArtifactOrientation {
173    match orientation {
174        Orientation::Front => ArtifactOrientation::Front,
175        Orientation::Back => ArtifactOrientation::Back,
176        Orientation::Left => ArtifactOrientation::Left,
177        Orientation::Right => ArtifactOrientation::Right,
178        Orientation::Top => ArtifactOrientation::Top,
179        Orientation::Bottom => ArtifactOrientation::Bottom,
180        Orientation::Isometric => ArtifactOrientation::Isometric,
181    }
182}
183
184fn artifact_projection(projection: Projection) -> ArtifactProjection {
185    match projection {
186        Projection::Orthographic => ArtifactProjection::Orthographic,
187        Projection::Perspective => ArtifactProjection::Perspective,
188    }
189}
190
191fn artifact_visibility(visibility: Visibility) -> ArtifactVisibility {
192    match visibility {
193        Visibility::Show => ArtifactVisibility::Show,
194        Visibility::Hide => ArtifactVisibility::Hide,
195    }
196}
197
198fn artifact_sweep_method(method: kcmc::shared::ExtrudeMethod) -> ArtifactSweepMethod {
199    match method {
200        kcmc::shared::ExtrudeMethod::New => ArtifactSweepMethod::New,
201        kcmc::shared::ExtrudeMethod::Merge => ArtifactSweepMethod::Merge,
202        _ => ArtifactSweepMethod::Merge,
203    }
204}
205
206fn edge_cut_sub_type(cut_type: kcmc::shared::CutType) -> EdgeCutSubType {
207    match cut_type {
208        kcmc::shared::CutType::Fillet => EdgeCutSubType::Fillet,
209        kcmc::shared::CutType::Chamfer => EdgeCutSubType::Chamfer,
210    }
211}
212
213fn edge_cut_sub_type_v2(cut_type: kcmc::shared::CutTypeV2) -> EdgeCutSubType {
214    match cut_type {
215        kcmc::shared::CutTypeV2::Fillet { .. } => EdgeCutSubType::Fillet,
216        kcmc::shared::CutTypeV2::Chamfer { .. } => EdgeCutSubType::Chamfer,
217        kcmc::shared::CutTypeV2::Custom { .. } => EdgeCutSubType::Custom,
218        _ => EdgeCutSubType::Custom,
219    }
220}
221
222pub(crate) fn sketch_block_constraint_type(constraint: &Constraint) -> SketchBlockConstraintType {
223    match constraint {
224        Constraint::Coincident { .. } => SketchBlockConstraintType::Coincident,
225        Constraint::Distance { .. } => SketchBlockConstraintType::Distance,
226        Constraint::Diameter { .. } => SketchBlockConstraintType::Diameter,
227        Constraint::EqualRadius { .. } => SketchBlockConstraintType::EqualRadius,
228        Constraint::Fixed { .. } => SketchBlockConstraintType::Fixed,
229        Constraint::HorizontalDistance { .. } => SketchBlockConstraintType::HorizontalDistance,
230        Constraint::VerticalDistance { .. } => SketchBlockConstraintType::VerticalDistance,
231        Constraint::Horizontal { .. } => SketchBlockConstraintType::Horizontal,
232        Constraint::LinesEqualLength { .. } => SketchBlockConstraintType::LinesEqualLength,
233        Constraint::Midpoint(..) => SketchBlockConstraintType::Midpoint,
234        Constraint::Parallel { .. } => SketchBlockConstraintType::Parallel,
235        Constraint::Perpendicular { .. } => SketchBlockConstraintType::Perpendicular,
236        Constraint::Radius { .. } => SketchBlockConstraintType::Radius,
237        Constraint::Symmetric { .. } => SketchBlockConstraintType::Symmetric,
238        Constraint::Tangent { .. } => SketchBlockConstraintType::Tangent,
239        Constraint::Vertical { .. } => SketchBlockConstraintType::Vertical,
240        Constraint::Angle(..) => SketchBlockConstraintType::Angle,
241    }
242}
243
244/// Merge the new artifact into the old one, returning a replacement when the
245/// artifact types differ.
246fn merge_artifacts(old: &mut Artifact, new: Artifact) -> Option<Artifact> {
247    match old {
248        Artifact::CompositeSolid(a) => merge_composite_solid(a, new),
249        Artifact::Plane(a) => merge_plane(a, new),
250        Artifact::Path(a) => merge_path(a, new),
251        Artifact::Segment(a) => merge_segment(a, new),
252        Artifact::Solid2d(_) => Some(new),
253        Artifact::PrimitiveFace(_) => Some(new),
254        Artifact::PrimitiveEdge(_) => Some(new),
255        Artifact::StartSketchOnFace { .. } => Some(new),
256        Artifact::StartSketchOnPlane { .. } => Some(new),
257        Artifact::SketchBlock { .. } => Some(new),
258        Artifact::SketchBlockConstraint { .. } => Some(new),
259        Artifact::PlaneOfFace { .. } => Some(new),
260        Artifact::Sweep(a) => merge_sweep(a, new),
261        Artifact::Wall(a) => merge_wall(a, new),
262        Artifact::Cap(a) => merge_cap(a, new),
263        Artifact::SweepEdge(_) => Some(new),
264        Artifact::EdgeCut(a) => merge_edge_cut(a, new),
265        Artifact::EdgeCutEdge(_) => Some(new),
266        Artifact::Helix(a) => merge_helix(a, new),
267        Artifact::ImportedGeometry(_) => Some(new),
268        Artifact::GdtAnnotation(a) => merge_gdt_annotation(a, new),
269        // One `view::named` call supplies every field, so nothing accumulates.
270        // Replacing wholesale keeps `show_ids`/`hide_ids` exactly as that call
271        // wrote them; unioning would mix in ids from an earlier execution of the
272        // same call site, which reuses the same artifact id.
273        Artifact::NamedView(_) => Some(new),
274        Artifact::Pattern(a) => merge_pattern(a, new),
275    }
276}
277
278fn merge_composite_solid(old: &mut CompositeSolid, new: Artifact) -> Option<Artifact> {
279    let Artifact::CompositeSolid(new) = new else {
280        return Some(new);
281    };
282    merge_ids(&mut old.solid_ids, new.solid_ids);
283    merge_ids(&mut old.tool_ids, new.tool_ids);
284    merge_opt_id(&mut old.composite_solid_id, new.composite_solid_id);
285    merge_ids(&mut old.pattern_ids, new.pattern_ids);
286    old.output_index = new.output_index;
287    old.consumed = new.consumed;
288    None
289}
290
291fn merge_plane(old: &mut Plane, new: Artifact) -> Option<Artifact> {
292    let Artifact::Plane(new) = new else { return Some(new) };
293    merge_ids(&mut old.path_ids, new.path_ids);
294    None
295}
296
297fn merge_path(old: &mut Path, new: Artifact) -> Option<Artifact> {
298    let Artifact::Path(new) = new else { return Some(new) };
299    merge_opt_id(&mut old.sweep_id, new.sweep_id);
300    merge_opt_id(&mut old.trajectory_sweep_id, new.trajectory_sweep_id);
301    merge_ids(&mut old.seg_ids, new.seg_ids);
302    merge_opt_id(&mut old.solid2d_id, new.solid2d_id);
303    merge_opt_id(&mut old.composite_solid_id, new.composite_solid_id);
304    merge_opt_id(&mut old.sketch_block_id, new.sketch_block_id);
305    merge_opt_id(&mut old.origin_path_id, new.origin_path_id);
306    merge_opt_id(&mut old.inner_path_id, new.inner_path_id);
307    merge_opt_id(&mut old.outer_path_id, new.outer_path_id);
308    merge_ids(&mut old.pattern_ids, new.pattern_ids);
309    old.consumed = new.consumed;
310    None
311}
312
313fn merge_segment(old: &mut Segment, new: Artifact) -> Option<Artifact> {
314    let Artifact::Segment(new) = new else { return Some(new) };
315    // Clone provenance is sticky across partial updates: unlike
316    // `merge_opt_id`, a missing new value preserves the existing source.
317    old.source_segment_id = new.source_segment_id.or(old.source_segment_id);
318    merge_opt_id(&mut old.original_seg_id, new.original_seg_id);
319    merge_opt_id(&mut old.surface_id, new.surface_id);
320    merge_ids(&mut old.edge_ids, new.edge_ids);
321    merge_opt_id(&mut old.edge_cut_id, new.edge_cut_id);
322    merge_ids(&mut old.common_surface_ids, new.common_surface_ids);
323    None
324}
325
326fn merge_sweep(old: &mut Sweep, new: Artifact) -> Option<Artifact> {
327    let Artifact::Sweep(new) = new else { return Some(new) };
328    merge_ids(&mut old.surface_ids, new.surface_ids);
329    merge_ids(&mut old.edge_ids, new.edge_ids);
330    // Clone provenance is sticky across partial updates: unlike
331    // `merge_opt_id`, a missing new value preserves the existing source.
332    old.source_sweep_id = new.source_sweep_id.or(old.source_sweep_id);
333    merge_opt_id(&mut old.trajectory_id, new.trajectory_id);
334    merge_ids(&mut old.pattern_ids, new.pattern_ids);
335    old.consumed = new.consumed;
336    None
337}
338
339fn merge_wall(old: &mut Wall, new: Artifact) -> Option<Artifact> {
340    let Artifact::Wall(new) = new else { return Some(new) };
341    merge_ids(&mut old.edge_cut_edge_ids, new.edge_cut_edge_ids);
342    merge_ids(&mut old.path_ids, new.path_ids);
343    None
344}
345
346fn merge_cap(old: &mut Cap, new: Artifact) -> Option<Artifact> {
347    let Artifact::Cap(new) = new else { return Some(new) };
348    merge_ids(&mut old.edge_cut_edge_ids, new.edge_cut_edge_ids);
349    merge_ids(&mut old.path_ids, new.path_ids);
350    None
351}
352
353fn merge_edge_cut(old: &mut EdgeCut, new: Artifact) -> Option<Artifact> {
354    let Artifact::EdgeCut(new) = new else { return Some(new) };
355    merge_opt_id(&mut old.surface_id, new.surface_id);
356    merge_ids(&mut old.edge_ids, new.edge_ids);
357    None
358}
359
360fn merge_helix(old: &mut Helix, new: Artifact) -> Option<Artifact> {
361    let Artifact::Helix(new) = new else { return Some(new) };
362    merge_opt_id(&mut old.axis_id, new.axis_id);
363    merge_opt_id(&mut old.trajectory_sweep_id, new.trajectory_sweep_id);
364    old.consumed = new.consumed;
365    None
366}
367
368fn merge_gdt_annotation(old: &mut GdtAnnotationArtifact, new: Artifact) -> Option<Artifact> {
369    let Artifact::GdtAnnotation(new) = new else {
370        return Some(new);
371    };
372    old.code_ref = new.code_ref;
373    old.consumed = new.consumed;
374    None
375}
376
377fn merge_pattern(old: &mut Pattern, new: Artifact) -> Option<Artifact> {
378    let Artifact::Pattern(new) = new else { return Some(new) };
379    merge_ids(&mut old.copy_ids, new.copy_ids);
380    merge_ids(&mut old.copy_face_ids, new.copy_face_ids);
381    merge_ids(&mut old.copy_edge_ids, new.copy_edge_ids);
382    None
383}
384
385#[derive(Debug, Clone)]
386struct ImportCodeRef {
387    node_path: NodePath,
388    range: SourceRange,
389}
390
391fn import_statement_code_refs(
392    ast: &Node<Program>,
393    module_infos: &ModuleInfoMap,
394    programs: &crate::execution::ProgramLookup,
395    cached_body_items: usize,
396) -> AHashMap<ModuleId, ImportCodeRef> {
397    let mut code_refs = AHashMap::default();
398    for body_item in &ast.body {
399        let BodyItem::ImportStatement(import_stmt) = body_item else {
400            continue;
401        };
402        if !matches!(import_stmt.selector, ImportSelector::None { .. }) {
403            continue;
404        }
405        let Some(module_id) = module_id_for_import_path(module_infos, &import_stmt.path) else {
406            continue;
407        };
408        let range = SourceRange::from(import_stmt);
409        let node_path = NodePath::from_range(programs, cached_body_items, range).unwrap_or_default();
410        code_refs.entry(module_id).or_insert(ImportCodeRef { node_path, range });
411    }
412    code_refs
413}
414
415fn module_id_for_import_path(module_infos: &ModuleInfoMap, import_path: &ImportPath) -> Option<ModuleId> {
416    let import_path = match import_path {
417        ImportPath::Kcl { filename } => filename,
418        ImportPath::Foreign { path } => path,
419        ImportPath::Std { .. } => return None,
420    };
421
422    module_infos.iter().find_map(|(module_id, module_info)| {
423        if let ModulePath::Local {
424            original_import_path: Some(original_import_path),
425            ..
426        } = &module_info.path
427            && original_import_path == import_path
428        {
429            return Some(*module_id);
430        }
431        None
432    })
433}
434
435fn code_ref_for_range(
436    programs: &crate::execution::ProgramLookup,
437    cached_body_items: usize,
438    range: SourceRange,
439    import_code_refs: &AHashMap<ModuleId, ImportCodeRef>,
440) -> (SourceRange, NodePath) {
441    if let Some(code_ref) = import_code_refs.get(&range.module_id()) {
442        return (code_ref.range, code_ref.node_path.clone());
443    }
444
445    (
446        range,
447        NodePath::from_range(programs, cached_body_items, range).unwrap_or_default(),
448    )
449}
450
451/// Build the artifact graph from the artifact commands and the responses.  The
452/// initial graph is the graph cached from a previous execution.  NodePaths of
453/// `exec_artifacts` are filled in from the AST.
454pub(super) fn build_artifact_graph(
455    artifact_commands: &[ArtifactCommand],
456    responses: &IndexMap<Uuid, WebSocketResponse>,
457    ast: &Node<Program>,
458    exec_artifacts: &mut IndexMap<ArtifactId, Artifact>,
459    initial_graph: ArtifactGraph,
460    programs: &crate::execution::ProgramLookup,
461    module_infos: &ModuleInfoMap,
462) -> Result<ArtifactGraph, KclError> {
463    let (mut map, item_count) = initial_graph.into_parts();
464
465    let mut path_to_plane_id_map = AHashMap::default();
466    let mut current_plane_id = None;
467    let import_code_refs = import_statement_code_refs(ast, module_infos, programs, item_count);
468    let flattened_responses = flatten_modeling_command_responses(responses);
469    let entity_clone_id_maps = build_entity_clone_id_maps(artifact_commands, &flattened_responses);
470
471    // Fill in NodePaths for artifacts that were added directly to the map
472    // during execution.
473    for exec_artifact in exec_artifacts.values_mut() {
474        // Note: We only have access to the new AST. So if these artifacts
475        // somehow came from cached AST, this won't fill in anything.
476        fill_in_node_paths(exec_artifact, programs, item_count, &import_code_refs);
477    }
478
479    for artifact_command in artifact_commands {
480        if artifact_command.omit_from_graph {
481            continue;
482        }
483        if let ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) = artifact_command.command {
484            current_plane_id = Some(entity_id);
485        }
486        // If we get a start path command, we need to set the plane ID to the
487        // current plane ID.
488        // THIS IS THE ONLY THING WE CAN ASSUME IS ALWAYS SEQUENTIAL SINCE ITS PART OF THE
489        // SAME ATOMIC COMMANDS BATCHING.
490        if let ModelingCmd::StartPath(_) = artifact_command.command
491            && let Some(plane_id) = current_plane_id
492        {
493            path_to_plane_id_map.insert(artifact_command.cmd_id, plane_id);
494        }
495        if let ModelingCmd::SketchModeDisable(_) = artifact_command.command {
496            current_plane_id = None;
497        }
498
499        // Some artifacts, including GD&T annotations, are recorded directly
500        // during execution instead of being created from an artifact command.
501        // Apply deletion before those artifacts are merged into the graph.
502        if let ModelingCmd::RemoveSceneObjects(remove) = &artifact_command.command {
503            let updates = mark_deleted_artifacts_consumed(exec_artifacts, &remove.object_ids);
504            for artifact in updates {
505                merge_artifact_into_map(exec_artifacts, artifact);
506            }
507        }
508
509        let artifact_updates = artifacts_to_update(
510            &map,
511            artifact_command,
512            &flattened_responses,
513            &entity_clone_id_maps,
514            &path_to_plane_id_map,
515            programs,
516            item_count,
517            exec_artifacts,
518            &import_code_refs,
519        )?;
520        for artifact in artifact_updates {
521            // Merge with existing artifacts.
522            merge_artifact_into_map(&mut map, artifact);
523        }
524    }
525
526    for exec_artifact in exec_artifacts.values() {
527        merge_artifact_into_map(&mut map, exec_artifact.clone());
528    }
529
530    Ok(ArtifactGraph::from_parts(map, item_count + ast.body.len()))
531}
532
533/// These may have been created with placeholder `CodeRef`s because we didn't
534/// have the entire AST available. Now we fill them in.
535fn fill_in_node_paths(
536    artifact: &mut Artifact,
537    programs: &crate::execution::ProgramLookup,
538    cached_body_items: usize,
539    import_code_refs: &AHashMap<ModuleId, ImportCodeRef>,
540) {
541    match artifact {
542        Artifact::StartSketchOnFace(face) if face.code_ref.node_path.is_empty() => {
543            let (range, node_path) =
544                code_ref_for_range(programs, cached_body_items, face.code_ref.range, import_code_refs);
545            face.code_ref.range = range;
546            face.code_ref.node_path = node_path;
547        }
548        Artifact::StartSketchOnPlane(plane) if plane.code_ref.node_path.is_empty() => {
549            let (range, node_path) =
550                code_ref_for_range(programs, cached_body_items, plane.code_ref.range, import_code_refs);
551            plane.code_ref.range = range;
552            plane.code_ref.node_path = node_path;
553        }
554        Artifact::SketchBlock(block) if block.code_ref.node_path.is_empty() => {
555            let (range, node_path) =
556                code_ref_for_range(programs, cached_body_items, block.code_ref.range, import_code_refs);
557            block.code_ref.range = range;
558            block.code_ref.node_path = node_path;
559        }
560        Artifact::SketchBlockConstraint(constraint) if constraint.code_ref.node_path.is_empty() => {
561            constraint.code_ref.node_path =
562                NodePath::from_range(programs, cached_body_items, constraint.code_ref.range).unwrap_or_default();
563        }
564        Artifact::GdtAnnotation(annotation) if annotation.code_ref.node_path.is_empty() => {
565            let (range, node_path) =
566                code_ref_for_range(programs, cached_body_items, annotation.code_ref.range, import_code_refs);
567            annotation.code_ref.range = range;
568            annotation.code_ref.node_path = node_path;
569        }
570        Artifact::NamedView(view) if view.code_ref.node_path.is_empty() => {
571            let (range, node_path) =
572                code_ref_for_range(programs, cached_body_items, view.code_ref.range, import_code_refs);
573            view.code_ref.range = range;
574            view.code_ref.node_path = node_path;
575        }
576        _ => {}
577    }
578}
579
580/// Flatten the responses into a map of command IDs to modeling command
581/// responses.  The raw responses from the engine contain batches.
582fn flatten_modeling_command_responses(
583    responses: &IndexMap<Uuid, WebSocketResponse>,
584) -> AHashMap<Uuid, OkModelingCmdResponse> {
585    let mut map = AHashMap::default();
586    for (cmd_id, ws_response) in responses {
587        let WebSocketResponse::Success(response) = ws_response else {
588            // Response not successful.
589            continue;
590        };
591        match &response.resp {
592            OkWebSocketResponseData::Modeling { modeling_response } => {
593                map.insert(*cmd_id, modeling_response.clone());
594            }
595            OkWebSocketResponseData::ModelingBatch { responses } =>
596            {
597                #[expect(
598                    clippy::iter_over_hash_type,
599                    reason = "Since we're moving entries to another unordered map, it's fine that the order is undefined"
600                )]
601                for (cmd_id, batch_response) in responses {
602                    if let BatchResponse::Success {
603                        response: modeling_response,
604                    } = batch_response
605                    {
606                        map.insert(*cmd_id.as_ref(), modeling_response.clone());
607                    }
608                }
609            }
610            OkWebSocketResponseData::IceServerInfo { .. }
611            | OkWebSocketResponseData::TrickleIce { .. }
612            | OkWebSocketResponseData::SdpAnswer { .. }
613            | OkWebSocketResponseData::Export { .. }
614            | OkWebSocketResponseData::MetricsRequest { .. }
615            | OkWebSocketResponseData::ModelingSessionData { .. }
616            | OkWebSocketResponseData::Debug { .. }
617            | OkWebSocketResponseData::Pong { .. } => {}
618            _other => {}
619        }
620    }
621
622    map
623}
624
625#[derive(Debug, Clone)]
626struct PendingEntityCloneMapping {
627    clone_cmd_id: Uuid,
628    old_entity_id: Uuid,
629    source_topology_id: Uuid,
630    old_child_ids: Option<Vec<Uuid>>,
631    source_topology_child_ids: Option<Vec<Uuid>>,
632}
633
634/// Build old->new entity ID maps for each clone command by pairing the
635/// `EntityGetAllChildUuids` queries emitted by `std::clone`.
636fn build_entity_clone_id_maps(
637    artifact_commands: &[ArtifactCommand],
638    responses: &AHashMap<Uuid, OkModelingCmdResponse>,
639) -> AHashMap<Uuid, AHashMap<ArtifactId, ArtifactId>> {
640    let mut clone_id_maps = AHashMap::default();
641    let mut pending = Vec::new();
642
643    for artifact_command in artifact_commands {
644        match &artifact_command.command {
645            ModelingCmd::EntityClone(kcmc::EntityClone { entity_id, .. }) => {
646                let source_topology_id = artifact_command
647                    .entity_clone_info
648                    .map(|info| Uuid::from(info.source_topology_id))
649                    .unwrap_or(*entity_id);
650                pending.push(PendingEntityCloneMapping {
651                    clone_cmd_id: artifact_command.cmd_id,
652                    old_entity_id: *entity_id,
653                    source_topology_id,
654                    old_child_ids: None,
655                    source_topology_child_ids: None,
656                });
657            }
658            ModelingCmd::EntityGetAllChildUuids(kcmc::EntityGetAllChildUuids { entity_id, .. }) => {
659                let Some(OkModelingCmdResponse::EntityGetAllChildUuids(child_ids_response)) =
660                    responses.get(&artifact_command.cmd_id)
661                else {
662                    continue;
663                };
664                let child_ids = child_ids_response.entity_ids.clone();
665
666                let mut completed_index = None;
667                for index in (0..pending.len()).rev() {
668                    let pending_map = &mut pending[index];
669                    if let Some(old_child_ids) = &pending_map.old_child_ids
670                        && *entity_id == pending_map.clone_cmd_id
671                    {
672                        let mut id_map = AHashMap::default();
673                        id_map.insert(
674                            ArtifactId::new(pending_map.old_entity_id),
675                            ArtifactId::new(pending_map.clone_cmd_id),
676                        );
677                        for (old_id, new_id) in old_child_ids.iter().zip(child_ids.iter()) {
678                            id_map.insert(ArtifactId::new(*old_id), ArtifactId::new(*new_id));
679                        }
680                        if pending_map.source_topology_id != pending_map.old_entity_id
681                            && let Some(source_topology_child_ids) = &pending_map.source_topology_child_ids
682                        {
683                            for (source_id, new_id) in source_topology_child_ids.iter().zip(child_ids.iter()) {
684                                id_map.insert(ArtifactId::new(*source_id), ArtifactId::new(*new_id));
685                            }
686                        }
687                        clone_id_maps.insert(pending_map.clone_cmd_id, id_map);
688                        completed_index = Some(index);
689                        break;
690                    }
691                    if pending_map.old_child_ids.is_none() && *entity_id == pending_map.old_entity_id {
692                        pending_map.old_child_ids = Some(child_ids.clone());
693                        if pending_map.source_topology_id == pending_map.old_entity_id {
694                            pending_map.source_topology_child_ids = Some(child_ids.clone());
695                        }
696                        break;
697                    }
698                    if pending_map.source_topology_child_ids.is_none() && *entity_id == pending_map.source_topology_id {
699                        pending_map.source_topology_child_ids = Some(child_ids.clone());
700                        break;
701                    }
702                }
703
704                if let Some(index) = completed_index {
705                    pending.swap_remove(index);
706                }
707            }
708            _ => {}
709        }
710    }
711
712    clone_id_maps
713}
714
715fn merge_artifact_into_map(map: &mut IndexMap<ArtifactId, Artifact>, new_artifact: Artifact) {
716    fn is_primitive_artifact(artifact: &Artifact) -> bool {
717        matches!(artifact, Artifact::PrimitiveFace(_) | Artifact::PrimitiveEdge(_))
718    }
719
720    let id = new_artifact.id();
721    let Some(old_artifact) = map.get_mut(&id) else {
722        // No old artifact exists.  Insert the new one.
723        map.insert(id, new_artifact);
724        return;
725    };
726
727    // Primitive lookups (faceId/edgeId) may resolve to an ID that already has
728    // a richer artifact (for example Segment/Cap/Wall). Keep the existing node
729    // to avoid erasing structural graph links.
730    if is_primitive_artifact(&new_artifact) && !is_primitive_artifact(old_artifact) {
731        return;
732    }
733
734    if let Some(replacement) = merge_artifacts(old_artifact, new_artifact) {
735        *old_artifact = replacement;
736    }
737}
738
739/// Merge the new IDs into the base vector, avoiding duplicates.  This is O(nm)
740/// runtime.  Rationale is that most of the ID collections in the artifact graph
741/// are pretty small, but we may want to change this in the future.
742fn merge_ids(base: &mut Vec<ArtifactId>, new: Vec<ArtifactId>) {
743    let original_len = base.len();
744    for id in new {
745        // Don't bother inspecting new items that we just pushed.
746        let original_base = &base[..original_len];
747        if !original_base.contains(&id) {
748            base.push(id);
749        }
750    }
751}
752
753/// Merge optional Artifact ID
754fn merge_opt_id(base: &mut Option<ArtifactId>, new: Option<ArtifactId>) {
755    // Always use the new one, even if it clears it.
756    *base = new;
757}
758
759fn remap_id_for_clone(id: ArtifactId, entity_id_map: &AHashMap<ArtifactId, ArtifactId>) -> ArtifactId {
760    entity_id_map.get(&id).copied().unwrap_or(id)
761}
762
763fn remap_opt_id_for_clone(
764    id: Option<ArtifactId>,
765    entity_id_map: &AHashMap<ArtifactId, ArtifactId>,
766) -> Option<ArtifactId> {
767    id.map(|id| remap_id_for_clone(id, entity_id_map))
768}
769
770fn remap_ids_for_clone(ids: &[ArtifactId], entity_id_map: &AHashMap<ArtifactId, ArtifactId>) -> Vec<ArtifactId> {
771    ids.iter()
772        .copied()
773        .map(|id| remap_id_for_clone(id, entity_id_map))
774        .collect()
775}
776
777fn remap_mapped_ids_for_clone(ids: &[ArtifactId], entity_id_map: &AHashMap<ArtifactId, ArtifactId>) -> Vec<ArtifactId> {
778    ids.iter().filter_map(|id| entity_id_map.get(id).copied()).collect()
779}
780
781fn add_composite_sweep_clone_id_mappings(
782    artifacts: &IndexMap<ArtifactId, Artifact>,
783    clone_cmd_id: Uuid,
784    entity_id_map: &mut AHashMap<ArtifactId, ArtifactId>,
785) {
786    let source_sweep_ids = artifacts
787        .values()
788        .filter_map(|artifact| {
789            let Artifact::Sweep(sweep) = artifact else {
790                return None;
791            };
792            if entity_id_map.contains_key(&sweep.id) {
793                return None;
794            }
795
796            let has_mapped_topology = sweep
797                .path_id
798                .is_some_and(|path_id| entity_id_map.contains_key(&path_id))
799                || sweep.surface_ids.iter().any(|id| entity_id_map.contains_key(id))
800                || sweep.edge_ids.iter().any(|id| entity_id_map.contains_key(id));
801            has_mapped_topology.then_some(sweep.id)
802        })
803        .collect::<Vec<_>>();
804
805    for source_sweep_id in source_sweep_ids {
806        let source_uuid = Uuid::from(source_sweep_id);
807        let cloned_sweep_id = ArtifactId::new(Uuid::new_v5(&clone_cmd_id, source_uuid.as_bytes()));
808        entity_id_map.insert(source_sweep_id, cloned_sweep_id);
809    }
810}
811
812fn remap_artifact_for_clone(
813    artifact: &Artifact,
814    entity_id_map: &AHashMap<ArtifactId, ArtifactId>,
815    clone_code_ref: &CodeRef,
816    clone_cmd_id: Uuid,
817    source_root_id: ArtifactId,
818) -> Artifact {
819    match artifact {
820        Artifact::CompositeSolid(source) => Artifact::CompositeSolid(CompositeSolid {
821            id: remap_id_for_clone(source.id, entity_id_map),
822            consumed: if source.id == source_root_id {
823                false
824            } else {
825                source.consumed
826            },
827            sub_type: source.sub_type,
828            // clone() returns one new top-level body, even when its source was
829            // an indexed output or belonged to another composite solid.
830            output_index: if source.id == source_root_id {
831                None
832            } else {
833                source.output_index
834            },
835            solid_ids: remap_ids_for_clone(&source.solid_ids, entity_id_map),
836            tool_ids: remap_ids_for_clone(&source.tool_ids, entity_id_map),
837            pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
838            code_ref: clone_code_ref.clone(),
839            composite_solid_id: if source.id == source_root_id {
840                None
841            } else {
842                remap_opt_id_for_clone(source.composite_solid_id, entity_id_map)
843            },
844        }),
845        Artifact::Plane(source) => Artifact::Plane(Plane {
846            id: remap_id_for_clone(source.id, entity_id_map),
847            path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
848            code_ref: clone_code_ref.clone(),
849        }),
850        Artifact::Path(source) => Artifact::Path(Path {
851            id: remap_id_for_clone(source.id, entity_id_map),
852            sub_type: source.sub_type,
853            plane_id: remap_id_for_clone(source.plane_id, entity_id_map),
854            seg_ids: remap_ids_for_clone(&source.seg_ids, entity_id_map),
855            consumed: if source.id == source_root_id {
856                false
857            } else {
858                source.consumed
859            },
860            sweep_id: remap_opt_id_for_clone(source.sweep_id, entity_id_map),
861            trajectory_sweep_id: remap_opt_id_for_clone(source.trajectory_sweep_id, entity_id_map),
862            solid2d_id: remap_opt_id_for_clone(source.solid2d_id, entity_id_map),
863            code_ref: clone_code_ref.clone(),
864            composite_solid_id: remap_opt_id_for_clone(source.composite_solid_id, entity_id_map),
865            sketch_block_id: remap_opt_id_for_clone(source.sketch_block_id, entity_id_map),
866            origin_path_id: remap_opt_id_for_clone(source.origin_path_id, entity_id_map),
867            inner_path_id: remap_opt_id_for_clone(source.inner_path_id, entity_id_map),
868            outer_path_id: remap_opt_id_for_clone(source.outer_path_id, entity_id_map),
869            pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
870        }),
871        Artifact::Segment(source) => Artifact::Segment(Segment {
872            id: remap_id_for_clone(source.id, entity_id_map),
873            path_id: remap_id_for_clone(source.path_id, entity_id_map),
874            source_segment_id: source.source_segment_id.or(Some(source.id)),
875            original_seg_id: remap_opt_id_for_clone(source.original_seg_id, entity_id_map),
876            surface_id: remap_opt_id_for_clone(source.surface_id, entity_id_map),
877            edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
878            edge_cut_id: remap_opt_id_for_clone(source.edge_cut_id, entity_id_map),
879            code_ref: clone_code_ref.clone(),
880            common_surface_ids: remap_ids_for_clone(&source.common_surface_ids, entity_id_map),
881        }),
882        Artifact::Solid2d(source) => Artifact::Solid2d(Solid2d {
883            id: remap_id_for_clone(source.id, entity_id_map),
884            path_id: remap_id_for_clone(source.path_id, entity_id_map),
885        }),
886        Artifact::PrimitiveFace(source) => Artifact::PrimitiveFace(PrimitiveFace {
887            id: remap_id_for_clone(source.id, entity_id_map),
888            solid_id: remap_id_for_clone(source.solid_id, entity_id_map),
889            code_ref: clone_code_ref.clone(),
890        }),
891        Artifact::PrimitiveEdge(source) => Artifact::PrimitiveEdge(PrimitiveEdge {
892            id: remap_id_for_clone(source.id, entity_id_map),
893            solid_id: remap_id_for_clone(source.solid_id, entity_id_map),
894            code_ref: clone_code_ref.clone(),
895        }),
896        Artifact::PlaneOfFace(source) => Artifact::PlaneOfFace(PlaneOfFace {
897            id: remap_id_for_clone(source.id, entity_id_map),
898            face_id: remap_id_for_clone(source.face_id, entity_id_map),
899            code_ref: clone_code_ref.clone(),
900        }),
901        Artifact::StartSketchOnFace(source) => Artifact::StartSketchOnFace(StartSketchOnFace {
902            id: remap_id_for_clone(source.id, entity_id_map),
903            face_id: remap_id_for_clone(source.face_id, entity_id_map),
904            code_ref: clone_code_ref.clone(),
905        }),
906        Artifact::StartSketchOnPlane(source) => Artifact::StartSketchOnPlane(StartSketchOnPlane {
907            id: remap_id_for_clone(source.id, entity_id_map),
908            plane_id: remap_id_for_clone(source.plane_id, entity_id_map),
909            code_ref: clone_code_ref.clone(),
910        }),
911        Artifact::SketchBlock(source) => Artifact::SketchBlock(SketchBlock {
912            id: remap_id_for_clone(source.id, entity_id_map),
913            standard_plane: source.standard_plane,
914            plane_id: remap_opt_id_for_clone(source.plane_id, entity_id_map),
915            plane_info: source.plane_info.clone(),
916            path_id: remap_opt_id_for_clone(source.path_id, entity_id_map),
917            code_ref: clone_code_ref.clone(),
918            sketch_id: source.sketch_id,
919        }),
920        Artifact::SketchBlockConstraint(source) => Artifact::SketchBlockConstraint(SketchBlockConstraint {
921            id: remap_id_for_clone(source.id, entity_id_map),
922            sketch_id: source.sketch_id,
923            constraint_id: source.constraint_id,
924            constraint_type: source.constraint_type,
925            code_ref: clone_code_ref.clone(),
926        }),
927        Artifact::Sweep(source) => Artifact::Sweep(Sweep {
928            id: remap_id_for_clone(source.id, entity_id_map),
929            sub_type: source.sub_type,
930            path_id: remap_opt_id_for_clone(source.path_id, entity_id_map),
931            surface_ids: remap_ids_for_clone(&source.surface_ids, entity_id_map),
932            edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
933            code_ref: clone_code_ref.clone(),
934            source_sweep_id: source.source_sweep_id.or(Some(source.id)),
935            trajectory_id: remap_opt_id_for_clone(source.trajectory_id, entity_id_map),
936            method: source.method,
937            consumed: if source.id == source_root_id {
938                false
939            } else {
940                source.consumed
941            },
942            pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
943        }),
944        Artifact::Wall(source) => Artifact::Wall(Wall {
945            id: remap_id_for_clone(source.id, entity_id_map),
946            seg_id: remap_id_for_clone(source.seg_id, entity_id_map),
947            edge_cut_edge_ids: remap_ids_for_clone(&source.edge_cut_edge_ids, entity_id_map),
948            sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
949            path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
950            face_code_ref: source.face_code_ref.clone(),
951            cmd_id: clone_cmd_id,
952        }),
953        Artifact::Cap(source) => Artifact::Cap(Cap {
954            id: remap_id_for_clone(source.id, entity_id_map),
955            sub_type: source.sub_type,
956            edge_cut_edge_ids: remap_ids_for_clone(&source.edge_cut_edge_ids, entity_id_map),
957            sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
958            path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
959            face_code_ref: source.face_code_ref.clone(),
960            cmd_id: clone_cmd_id,
961        }),
962        Artifact::SweepEdge(source) => Artifact::SweepEdge(SweepEdge {
963            id: remap_id_for_clone(source.id, entity_id_map),
964            sub_type: source.sub_type,
965            seg_id: remap_id_for_clone(source.seg_id, entity_id_map),
966            cmd_id: clone_cmd_id,
967            index: source.index,
968            sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
969            common_surface_ids: remap_ids_for_clone(&source.common_surface_ids, entity_id_map),
970        }),
971        Artifact::EdgeCut(source) => Artifact::EdgeCut(EdgeCut {
972            id: remap_id_for_clone(source.id, entity_id_map),
973            sub_type: source.sub_type,
974            source_selector_index: source.source_selector_index,
975            consumed_edge_id: remap_opt_id_for_clone(source.consumed_edge_id, entity_id_map),
976            edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
977            surface_id: remap_opt_id_for_clone(source.surface_id, entity_id_map),
978            code_ref: clone_code_ref.clone(),
979        }),
980        Artifact::EdgeCutEdge(source) => Artifact::EdgeCutEdge(EdgeCutEdge {
981            id: remap_id_for_clone(source.id, entity_id_map),
982            edge_cut_id: remap_id_for_clone(source.edge_cut_id, entity_id_map),
983            surface_id: remap_id_for_clone(source.surface_id, entity_id_map),
984        }),
985        Artifact::Helix(source) => Artifact::Helix(Helix {
986            id: remap_id_for_clone(source.id, entity_id_map),
987            axis_id: remap_opt_id_for_clone(source.axis_id, entity_id_map),
988            code_ref: clone_code_ref.clone(),
989            trajectory_sweep_id: remap_opt_id_for_clone(source.trajectory_sweep_id, entity_id_map),
990            consumed: if source.id == source_root_id {
991                false
992            } else {
993                source.consumed
994            },
995        }),
996        Artifact::ImportedGeometry(source) => Artifact::ImportedGeometry(ImportedGeometryArtifact {
997            id: remap_id_for_clone(source.id, entity_id_map),
998            code_ref: clone_code_ref.clone(),
999            consumed: if source.id == source_root_id {
1000                false
1001            } else {
1002                source.consumed
1003            },
1004        }),
1005        Artifact::GdtAnnotation(source) => Artifact::GdtAnnotation(GdtAnnotationArtifact {
1006            id: remap_id_for_clone(source.id, entity_id_map),
1007            code_ref: clone_code_ref.clone(),
1008            consumed: source.consumed,
1009        }),
1010        // A named view has no engine entity, so it can never appear in a
1011        // clone's id map, and `clone()` takes only a sketch, solid or imported
1012        // geometry as its source. This arm exists because the match is
1013        // exhaustive: returning the artifact unchanged makes it a no-op re-insert
1014        // rather than inventing a second view, which would carry a duplicate
1015        // name. The assertion is a tripwire if the assumption ever breaks.
1016        Artifact::NamedView(_) => {
1017            debug_assert!(false, "a named view is not reachable from a cloned body");
1018            artifact.clone()
1019        }
1020        Artifact::Pattern(source) => Artifact::Pattern(Pattern {
1021            id: remap_id_for_clone(source.id, entity_id_map),
1022            sub_type: source.sub_type,
1023            source_id: remap_id_for_clone(source.source_id, entity_id_map),
1024            copy_ids: remap_ids_for_clone(&source.copy_ids, entity_id_map),
1025            copy_face_ids: remap_ids_for_clone(&source.copy_face_ids, entity_id_map),
1026            copy_edge_ids: remap_ids_for_clone(&source.copy_edge_ids, entity_id_map),
1027            code_ref: clone_code_ref.clone(),
1028        }),
1029    }
1030}
1031
1032fn pattern_source_ids(artifacts: &IndexMap<ArtifactId, Artifact>, source_id: ArtifactId) -> Vec<ArtifactId> {
1033    let mut source_ids = vec![source_id];
1034
1035    if let Some(Artifact::Path(path)) = artifacts.get(&source_id) {
1036        if let Some(sweep_id) = path.sweep_id {
1037            source_ids.push(sweep_id);
1038        }
1039        if let Some(composite_solid_id) = path.composite_solid_id {
1040            source_ids.push(composite_solid_id);
1041        }
1042    }
1043
1044    for artifact in artifacts.values() {
1045        match artifact {
1046            Artifact::Sweep(sweep) if sweep.path_id == Some(source_id) => source_ids.push(sweep.id),
1047            Artifact::CompositeSolid(composite)
1048                if composite.solid_ids.contains(&source_id) || composite.tool_ids.contains(&source_id) =>
1049            {
1050                source_ids.push(composite.id)
1051            }
1052            _ => {}
1053        }
1054    }
1055
1056    let mut unique = Vec::new();
1057    merge_ids(&mut unique, source_ids);
1058    unique
1059}
1060
1061fn pattern_source_body_id_for_copy(
1062    artifacts: &IndexMap<ArtifactId, Artifact>,
1063    copy_id: ArtifactId,
1064) -> Option<ArtifactId> {
1065    artifacts.values().find_map(|artifact| {
1066        let Artifact::Pattern(pattern) = artifact else {
1067            return None;
1068        };
1069        if !pattern.copy_ids.contains(&copy_id) {
1070            return None;
1071        }
1072
1073        pattern_source_ids(artifacts, pattern.source_id).into_iter().find(|id| {
1074            matches!(
1075                artifacts.get(id),
1076                Some(Artifact::Sweep(_) | Artifact::CompositeSolid(_))
1077            )
1078        })
1079    })
1080}
1081
1082fn pattern_artifact_updates(
1083    artifacts: &IndexMap<ArtifactId, Artifact>,
1084    pattern_id: ArtifactId,
1085    sub_type: PatternSubType,
1086    source_id: ArtifactId,
1087    face_edge_infos: &[kcmc::output::FaceEdgeInfo],
1088    code_ref: CodeRef,
1089) -> Vec<Artifact> {
1090    let copy_ids = face_edge_infos
1091        .iter()
1092        .map(|info| ArtifactId::new(info.object_id))
1093        .collect::<Vec<_>>();
1094    let copy_face_ids = face_edge_infos
1095        .iter()
1096        .flat_map(|info| info.faces.iter().copied().map(ArtifactId::new))
1097        .collect::<Vec<_>>();
1098    let copy_edge_ids = face_edge_infos
1099        .iter()
1100        .flat_map(|info| info.edges.iter().copied().map(ArtifactId::new))
1101        .collect::<Vec<_>>();
1102
1103    let source_ids = pattern_source_ids(artifacts, source_id);
1104    let mut return_arr = vec![Artifact::Pattern(Pattern {
1105        id: pattern_id,
1106        sub_type,
1107        source_id,
1108        copy_ids,
1109        copy_face_ids,
1110        copy_edge_ids,
1111        code_ref,
1112    })];
1113
1114    for source_id in source_ids {
1115        let Some(artifact) = artifacts.get(&source_id) else {
1116            continue;
1117        };
1118        match artifact {
1119            Artifact::Path(path) => {
1120                let mut new_path = path.clone();
1121                new_path.pattern_ids = vec![pattern_id];
1122                return_arr.push(Artifact::Path(new_path));
1123            }
1124            Artifact::Sweep(sweep) => {
1125                let mut new_sweep = sweep.clone();
1126                new_sweep.pattern_ids = vec![pattern_id];
1127                return_arr.push(Artifact::Sweep(new_sweep));
1128            }
1129            Artifact::CompositeSolid(composite) => {
1130                let mut new_composite = composite.clone();
1131                new_composite.pattern_ids = vec![pattern_id];
1132                return_arr.push(Artifact::CompositeSolid(new_composite));
1133            }
1134            _ => {}
1135        }
1136    }
1137
1138    return_arr
1139}
1140
1141fn is_single_target_self_subtract(target_ids: &[Uuid], tool_ids: &[Uuid]) -> bool {
1142    target_ids.len() == 1 && tool_ids.len() == 1 && target_ids[0] == tool_ids[0]
1143}
1144
1145fn boolean_subtract_output_artifact_ids(
1146    cmd_id: ArtifactId,
1147    target_ids: &[Uuid],
1148    tool_ids: &[Uuid],
1149    extra_solid_ids: &[Uuid],
1150) -> Vec<ArtifactId> {
1151    if is_single_target_self_subtract(target_ids, tool_ids) {
1152        return Vec::new();
1153    }
1154
1155    let mut output_ids = if target_ids.len() == 1 {
1156        vec![cmd_id]
1157    } else {
1158        Vec::new()
1159    };
1160
1161    for extra_solid_id in extra_solid_ids {
1162        let artifact_id = ArtifactId::new(*extra_solid_id);
1163        if !output_ids.contains(&artifact_id) {
1164            output_ids.push(artifact_id);
1165        }
1166    }
1167
1168    output_ids
1169}
1170
1171fn update_consumed_csg_sweep(
1172    return_arr: &mut Vec<Artifact>,
1173    artifacts: &IndexMap<ArtifactId, Artifact>,
1174    sweep_id: ArtifactId,
1175    consumed_sweep_ids: &mut AHashSet<ArtifactId>,
1176) {
1177    if consumed_sweep_ids.insert(sweep_id)
1178        && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
1179    {
1180        let mut new_sweep = sweep.clone();
1181        new_sweep.consumed = true;
1182        return_arr.push(Artifact::Sweep(new_sweep));
1183    }
1184}
1185
1186fn mark_artifact_consumed_by_id(
1187    return_arr: &mut Vec<Artifact>,
1188    artifacts: &IndexMap<ArtifactId, Artifact>,
1189    artifact_id: ArtifactId,
1190    consumed_ids: &mut AHashSet<ArtifactId>,
1191) {
1192    let already_marked_as_consumed = !consumed_ids.insert(artifact_id);
1193    if already_marked_as_consumed {
1194        return;
1195    }
1196
1197    let Some(artifact) = artifacts.get(&artifact_id) else {
1198        return;
1199    };
1200
1201    match artifact {
1202        Artifact::CompositeSolid(composite) => {
1203            let mut new_composite = composite.clone();
1204            new_composite.consumed = true;
1205            return_arr.push(Artifact::CompositeSolid(new_composite));
1206        }
1207        Artifact::Path(path) => {
1208            let mut new_path = path.clone();
1209            new_path.consumed = true;
1210            return_arr.push(Artifact::Path(new_path));
1211
1212            if let Some(sweep_id) = path.sweep_id {
1213                mark_artifact_consumed_by_id(return_arr, artifacts, sweep_id, consumed_ids);
1214            }
1215            if let Some(composite_solid_id) = path.composite_solid_id {
1216                mark_artifact_consumed_by_id(return_arr, artifacts, composite_solid_id, consumed_ids);
1217            }
1218        }
1219        Artifact::Sweep(sweep) => {
1220            let mut new_sweep = sweep.clone();
1221            new_sweep.consumed = true;
1222            return_arr.push(Artifact::Sweep(new_sweep));
1223        }
1224        Artifact::Helix(helix) => {
1225            let mut new_helix = helix.clone();
1226            new_helix.consumed = true;
1227            return_arr.push(Artifact::Helix(new_helix));
1228        }
1229        Artifact::ImportedGeometry(imported_geometry) => {
1230            let mut new_imported_geometry = imported_geometry.clone();
1231            new_imported_geometry.consumed = true;
1232            return_arr.push(Artifact::ImportedGeometry(new_imported_geometry));
1233        }
1234        Artifact::GdtAnnotation(annotation) => {
1235            let mut new_annotation = annotation.clone();
1236            new_annotation.consumed = true;
1237            return_arr.push(Artifact::GdtAnnotation(new_annotation));
1238        }
1239        _ => {}
1240    }
1241}
1242
1243fn mark_deleted_artifacts_consumed(
1244    artifacts: &IndexMap<ArtifactId, Artifact>,
1245    object_ids: &std::collections::HashSet<Uuid>,
1246) -> Vec<Artifact> {
1247    let mut return_arr = Vec::new();
1248    let mut consumed_ids = AHashSet::default();
1249
1250    // The order of iteration doesn't matter here, as all artifacts get marked as consumed.
1251    // Also the set comes from the API crate which uses HashSet.
1252    #[allow(clippy::iter_over_hash_type)]
1253    for object_id in object_ids {
1254        let artifact_id = ArtifactId::new(*object_id);
1255        mark_artifact_consumed_by_id(&mut return_arr, artifacts, artifact_id, &mut consumed_ids);
1256    }
1257
1258    return_arr
1259}
1260
1261fn update_csg_input_artifacts(
1262    return_arr: &mut Vec<Artifact>,
1263    artifacts: &IndexMap<ArtifactId, Artifact>,
1264    input_ids: &[ArtifactId],
1265    composite_solid_id: Option<ArtifactId>,
1266    consumed_sweep_ids: &mut AHashSet<ArtifactId>,
1267) {
1268    for input_id in input_ids {
1269        if let Some(artifact) = artifacts.get(input_id) {
1270            match artifact {
1271                Artifact::CompositeSolid(comp) => {
1272                    let mut new_comp = comp.clone();
1273                    new_comp.composite_solid_id = composite_solid_id;
1274                    new_comp.consumed = true;
1275                    return_arr.push(Artifact::CompositeSolid(new_comp));
1276                }
1277                Artifact::Path(path) => {
1278                    let mut new_path = path.clone();
1279                    new_path.composite_solid_id = composite_solid_id;
1280
1281                    // We want to mark any sweeps of the path used in this operation
1282                    // as consumed. The path itself is already consumed by sweeping.
1283                    if let Some(sweep_id) = new_path.sweep_id {
1284                        update_consumed_csg_sweep(return_arr, artifacts, sweep_id, consumed_sweep_ids);
1285                    }
1286
1287                    return_arr.push(Artifact::Path(new_path));
1288                }
1289                Artifact::Sweep(sweep) => {
1290                    update_consumed_csg_sweep(return_arr, artifacts, sweep.id, consumed_sweep_ids);
1291                }
1292                _ => {}
1293            }
1294        }
1295    }
1296}
1297
1298fn mirror_3d_artifact_updates(
1299    artifacts: &IndexMap<ArtifactId, Artifact>,
1300    original_solid_ids: &[Uuid],
1301    face_edge_infos: &[kcmc::output::FaceEdgeInfo],
1302    code_ref: CodeRef,
1303    range: SourceRange,
1304    cmd: &ModelingCmd,
1305) -> Result<Vec<Artifact>, KclError> {
1306    if original_solid_ids.len() != face_edge_infos.len() {
1307        internal_error!(
1308            range,
1309            "EntityMirrorAcross response has different number face edge info than original mirrored solids: cmd={cmd:?}, face_edge_infos={face_edge_infos:?}"
1310        );
1311    }
1312
1313    let mut return_arr = Vec::new();
1314    for (face_edge_info, original_solid_id) in face_edge_infos.iter().zip(original_solid_ids) {
1315        let original_solid_id = ArtifactId::new(*original_solid_id);
1316        let mirrored_solid_id = ArtifactId::new(face_edge_info.object_id);
1317        let source_solid = match artifacts.get(&original_solid_id) {
1318            Some(Artifact::Path(path)) => path.sweep_id.and_then(|sweep_id| artifacts.get(&sweep_id)).or_else(|| {
1319                path.composite_solid_id
1320                    .and_then(|composite_id| artifacts.get(&composite_id))
1321            }),
1322            source => source,
1323        };
1324        match source_solid {
1325            Some(Artifact::Sweep(sweep)) => {
1326                let mut mirrored_sweep = sweep.clone();
1327                mirrored_sweep.id = mirrored_solid_id;
1328                mirrored_sweep.surface_ids = face_edge_info.faces.iter().copied().map(ArtifactId::new).collect();
1329                mirrored_sweep.edge_ids = face_edge_info.edges.iter().copied().map(ArtifactId::new).collect();
1330                mirrored_sweep.code_ref = code_ref.clone();
1331                mirrored_sweep.consumed = false;
1332                mirrored_sweep.pattern_ids = Vec::new();
1333                return_arr.push(Artifact::Sweep(mirrored_sweep));
1334            }
1335            Some(Artifact::CompositeSolid(composite)) => {
1336                let mut mirrored_composite = composite.clone();
1337                mirrored_composite.id = mirrored_solid_id;
1338                mirrored_composite.code_ref = code_ref.clone();
1339                mirrored_composite.consumed = false;
1340                mirrored_composite.composite_solid_id = None;
1341                mirrored_composite.pattern_ids = Vec::new();
1342                return_arr.push(Artifact::CompositeSolid(mirrored_composite));
1343            }
1344            Some(_) | None => continue,
1345        }
1346    }
1347
1348    Ok(return_arr)
1349}
1350
1351#[allow(clippy::too_many_arguments)]
1352fn artifacts_to_update(
1353    artifacts: &IndexMap<ArtifactId, Artifact>,
1354    artifact_command: &ArtifactCommand,
1355    responses: &AHashMap<Uuid, OkModelingCmdResponse>,
1356    entity_clone_id_maps: &AHashMap<Uuid, AHashMap<ArtifactId, ArtifactId>>,
1357    path_to_plane_id_map: &AHashMap<Uuid, Uuid>,
1358    programs: &crate::execution::ProgramLookup,
1359    cached_body_items: usize,
1360    exec_artifacts: &IndexMap<ArtifactId, Artifact>,
1361    import_code_refs: &AHashMap<ModuleId, ImportCodeRef>,
1362) -> Result<Vec<Artifact>, KclError> {
1363    let uuid = artifact_command.cmd_id;
1364    let response = responses.get(&uuid);
1365
1366    // TODO: Build path-to-node from artifact_command source range.  Right now,
1367    // we're serializing an empty array, and the TS wrapper fills it in with the
1368    // correct value based on NodePath.
1369    let path_to_node = Vec::new();
1370    let range = artifact_command.range;
1371    let (code_ref_range, node_path) = code_ref_for_range(programs, cached_body_items, range, import_code_refs);
1372    let code_ref = CodeRef {
1373        range: code_ref_range,
1374        node_path,
1375        path_to_node,
1376    };
1377
1378    let id = ArtifactId::new(uuid);
1379    let cmd = &artifact_command.command;
1380
1381    match cmd {
1382        ModelingCmd::ImportFiles(_) => {
1383            return Ok(vec![Artifact::ImportedGeometry(ImportedGeometryArtifact {
1384                id,
1385                code_ref,
1386                consumed: false,
1387            })]);
1388        }
1389        ModelingCmd::MakePlane(_) => {
1390            if range.is_synthetic() {
1391                return Ok(Vec::new());
1392            }
1393            // If we're calling `make_plane` and the code range doesn't end at
1394            // `0` it's not a default plane, but a custom one from the
1395            // offsetPlane standard library function.
1396            return Ok(vec![Artifact::Plane(Plane {
1397                id,
1398                path_ids: Vec::new(),
1399                code_ref,
1400            })]);
1401        }
1402        ModelingCmd::FaceIsPlanar(FaceIsPlanar { object_id, .. }) => {
1403            return Ok(vec![Artifact::PlaneOfFace(PlaneOfFace {
1404                id,
1405                face_id: object_id.into(),
1406                code_ref,
1407            })]);
1408        }
1409        ModelingCmd::RemoveSceneObjects(remove) => {
1410            return Ok(mark_deleted_artifacts_consumed(artifacts, &remove.object_ids));
1411        }
1412        ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) => {
1413            let existing_plane = artifacts.get(&ArtifactId::new(*entity_id));
1414            match existing_plane {
1415                Some(Artifact::Wall(wall)) => {
1416                    return Ok(vec![Artifact::Wall(Wall {
1417                        id: entity_id.into(),
1418                        seg_id: wall.seg_id,
1419                        edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1420                        sweep_id: wall.sweep_id,
1421                        path_ids: wall.path_ids.clone(),
1422                        face_code_ref: wall.face_code_ref.clone(),
1423                        cmd_id: artifact_command.cmd_id,
1424                    })]);
1425                }
1426                Some(Artifact::Cap(cap)) => {
1427                    return Ok(vec![Artifact::Cap(Cap {
1428                        id: entity_id.into(),
1429                        sub_type: cap.sub_type,
1430                        edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1431                        sweep_id: cap.sweep_id,
1432                        path_ids: cap.path_ids.clone(),
1433                        face_code_ref: cap.face_code_ref.clone(),
1434                        cmd_id: artifact_command.cmd_id,
1435                    })]);
1436                }
1437                Some(_) | None => {
1438                    let path_ids = match existing_plane {
1439                        Some(Artifact::Plane(Plane { path_ids, .. })) => path_ids.clone(),
1440                        _ => Vec::new(),
1441                    };
1442                    // Create an entirely new plane
1443                    return Ok(vec![Artifact::Plane(Plane {
1444                        id: entity_id.into(),
1445                        path_ids,
1446                        code_ref,
1447                    })]);
1448                }
1449            }
1450        }
1451        ModelingCmd::StartPath(_) => {
1452            let mut return_arr = Vec::new();
1453            let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
1454                KclError::new_internal(KclErrorDetails::new(
1455                    format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"),
1456                    vec![range],
1457                ))
1458            })?;
1459            let sketch_block_id = exec_artifacts
1460                .values()
1461                .find(|a| {
1462                    if let Artifact::SketchBlock(s) = a {
1463                        if let Some(path_id) = s.path_id {
1464                            path_id == id
1465                        } else {
1466                            false
1467                        }
1468                    } else {
1469                        false
1470                    }
1471                })
1472                .map(|a| a.id());
1473            return_arr.push(Artifact::Path(Path {
1474                id,
1475                sub_type: PathSubType::Sketch,
1476                plane_id: (*current_plane_id).into(),
1477                seg_ids: Vec::new(),
1478                sweep_id: None,
1479                trajectory_sweep_id: None,
1480                solid2d_id: None,
1481                code_ref,
1482                composite_solid_id: None,
1483                sketch_block_id,
1484                origin_path_id: None,
1485                inner_path_id: None,
1486                outer_path_id: None,
1487                pattern_ids: Vec::new(),
1488                consumed: false,
1489            }));
1490            let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
1491            if let Some(Artifact::Plane(plane)) = plane {
1492                let plane_code_ref = plane.code_ref.clone();
1493                return_arr.push(Artifact::Plane(Plane {
1494                    id: (*current_plane_id).into(),
1495                    path_ids: vec![id],
1496                    code_ref: plane_code_ref,
1497                }));
1498            }
1499            if let Some(Artifact::Wall(wall)) = plane {
1500                return_arr.push(Artifact::Wall(Wall {
1501                    id: (*current_plane_id).into(),
1502                    seg_id: wall.seg_id,
1503                    edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1504                    sweep_id: wall.sweep_id,
1505                    path_ids: vec![id],
1506                    face_code_ref: wall.face_code_ref.clone(),
1507                    cmd_id: artifact_command.cmd_id,
1508                }));
1509            }
1510            if let Some(Artifact::Cap(cap)) = plane {
1511                return_arr.push(Artifact::Cap(Cap {
1512                    id: (*current_plane_id).into(),
1513                    sub_type: cap.sub_type,
1514                    edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1515                    sweep_id: cap.sweep_id,
1516                    path_ids: vec![id],
1517                    face_code_ref: cap.face_code_ref.clone(),
1518                    cmd_id: artifact_command.cmd_id,
1519                }));
1520            }
1521            return Ok(return_arr);
1522        }
1523        ModelingCmd::ClosePath(_) | ModelingCmd::ExtendPath(_) => {
1524            let path_id = ArtifactId::new(match cmd {
1525                ModelingCmd::ClosePath(c) => c.path_id,
1526                ModelingCmd::ExtendPath(e) => e.path.into(),
1527                _ => internal_error!(
1528                    range,
1529                    "Close or extend path command variant not handled: id={id:?}, cmd={cmd:?}"
1530                ),
1531            });
1532            let mut return_arr = Vec::new();
1533            return_arr.push(Artifact::Segment(Segment {
1534                id,
1535                path_id,
1536                source_segment_id: None,
1537                original_seg_id: None,
1538                surface_id: None,
1539                edge_ids: Vec::new(),
1540                edge_cut_id: None,
1541                code_ref,
1542                common_surface_ids: Vec::new(),
1543            }));
1544            let path = artifacts.get(&path_id);
1545            if let Some(Artifact::Path(path)) = path {
1546                let mut new_path = path.clone();
1547                new_path.seg_ids = vec![id];
1548                return_arr.push(Artifact::Path(new_path));
1549            }
1550            if let Some(OkModelingCmdResponse::ClosePath(close_path)) = response {
1551                return_arr.push(Artifact::Solid2d(Solid2d {
1552                    id: close_path.face_id.into(),
1553                    path_id,
1554                }));
1555                if let Some(Artifact::Path(path)) = path {
1556                    let mut new_path = path.clone();
1557                    new_path.solid2d_id = Some(close_path.face_id.into());
1558                    return_arr.push(Artifact::Path(new_path));
1559                }
1560            }
1561            return Ok(return_arr);
1562        }
1563        ModelingCmd::CreateRegion(kcmc::CreateRegion {
1564            object_id: origin_path_id,
1565            ..
1566        })
1567        | ModelingCmd::CreateRegionFromQueryPoint(kcmc::CreateRegionFromQueryPoint {
1568            object_id: origin_path_id,
1569            ..
1570        }) => {
1571            let mut return_arr = Vec::new();
1572            let origin_path = artifacts.get(&ArtifactId::new(*origin_path_id));
1573            let Some(Artifact::Path(path)) = origin_path else {
1574                internal_error!(
1575                    range,
1576                    "Expected to find an existing path for the origin path of CreateRegion or CreateRegionFromQueryPoint command, but found none: origin_path={origin_path:?}, cmd={cmd:?}"
1577                );
1578            };
1579            let region_path = |seg_ids, code_ref| {
1580                Artifact::Path(Path {
1581                    id,
1582                    sub_type: PathSubType::Region,
1583                    plane_id: path.plane_id,
1584                    seg_ids,
1585                    consumed: false,
1586                    sweep_id: None,
1587                    trajectory_sweep_id: None,
1588                    solid2d_id: None,
1589                    code_ref,
1590                    composite_solid_id: None,
1591                    sketch_block_id: None,
1592                    origin_path_id: Some(ArtifactId::new(*origin_path_id)),
1593                    inner_path_id: None,
1594                    outer_path_id: None,
1595                    pattern_ids: Vec::new(),
1596                })
1597            };
1598            // If we have a response, we can also create the segments in the
1599            // region.
1600            let Some(
1601                OkModelingCmdResponse::CreateRegion(kcmc::output::CreateRegion { region_mapping, .. })
1602                | OkModelingCmdResponse::CreateRegionFromQueryPoint(kcmc::output::CreateRegionFromQueryPoint {
1603                    region_mapping,
1604                    ..
1605                }),
1606            ) = response
1607            else {
1608                return_arr.push(region_path(Vec::new(), code_ref));
1609                return Ok(return_arr);
1610            };
1611            // Each key is a segment in the region. The value is the segment in
1612            // the original path. Build the reverse mapping.
1613            let original_segment_ids = path.seg_ids.iter().map(Uuid::from).collect::<Vec<_>>();
1614            let reverse = build_reverse_region_mapping(region_mapping, &original_segment_ids);
1615            let region_segment_ids = reverse
1616                .values()
1617                .flat_map(|region_segment_ids| region_segment_ids.iter().copied())
1618                .map(ArtifactId::new)
1619                .collect::<Vec<_>>();
1620            return_arr.push(region_path(region_segment_ids, code_ref.clone()));
1621            for (original_segment_id, region_segment_ids) in reverse.iter() {
1622                for segment_id in region_segment_ids {
1623                    return_arr.push(Artifact::Segment(Segment {
1624                        id: ArtifactId::new(*segment_id),
1625                        path_id: id,
1626                        source_segment_id: None,
1627                        original_seg_id: Some(ArtifactId::new(*original_segment_id)),
1628                        surface_id: None,
1629                        edge_ids: Vec::new(),
1630                        edge_cut_id: None,
1631                        code_ref: code_ref.clone(),
1632                        common_surface_ids: Vec::new(),
1633                    }))
1634                }
1635            }
1636            return Ok(return_arr);
1637        }
1638        ModelingCmd::Solid3dGetFaceUuid(kcmc::Solid3dGetFaceUuid { object_id, .. }) => {
1639            let Some(OkModelingCmdResponse::Solid3dGetFaceUuid(face_uuid)) = response else {
1640                return Ok(Vec::new());
1641            };
1642
1643            return Ok(vec![Artifact::PrimitiveFace(PrimitiveFace {
1644                id: face_uuid.face_id.into(),
1645                solid_id: (*object_id).into(),
1646                code_ref,
1647            })]);
1648        }
1649        ModelingCmd::Solid3dGetEdgeUuid(kcmc::Solid3dGetEdgeUuid { object_id, .. }) => {
1650            let Some(OkModelingCmdResponse::Solid3dGetEdgeUuid(edge_uuid)) = response else {
1651                return Ok(Vec::new());
1652            };
1653
1654            return Ok(vec![Artifact::PrimitiveEdge(PrimitiveEdge {
1655                id: edge_uuid.edge_id.into(),
1656                solid_id: (*object_id).into(),
1657                code_ref,
1658            })]);
1659        }
1660        ModelingCmd::EntityGetAllChildUuids(kcmc::EntityGetAllChildUuids { entity_id, .. }) => {
1661            let body_id = ArtifactId::new(*entity_id);
1662            let Some(Artifact::Sweep(sweep)) = artifacts.get(&body_id) else {
1663                return Ok(Vec::new());
1664            };
1665            if sweep.path_id.is_some() {
1666                return Ok(Vec::new());
1667            }
1668            let Some(OkModelingCmdResponse::EntityGetAllChildUuids(child_ids_response)) = response else {
1669                return Ok(Vec::new());
1670            };
1671
1672            let surface_ids = child_ids_response
1673                .entity_ids
1674                .first()
1675                .copied()
1676                .map(ArtifactId::new)
1677                .into_iter()
1678                .collect::<Vec<_>>();
1679            let edge_ids = child_ids_response
1680                .entity_ids
1681                .get(1)
1682                .copied()
1683                .map(ArtifactId::new)
1684                .into_iter()
1685                .collect::<Vec<_>>();
1686
1687            let mut updated_sweep = sweep.clone();
1688            updated_sweep.surface_ids = surface_ids.clone();
1689            updated_sweep.edge_ids = edge_ids.clone();
1690
1691            let mut return_arr = vec![Artifact::Sweep(updated_sweep)];
1692            return_arr.extend(surface_ids.into_iter().map(|id| {
1693                Artifact::PrimitiveFace(PrimitiveFace {
1694                    id,
1695                    solid_id: body_id,
1696                    code_ref: sweep.code_ref.clone(),
1697                })
1698            }));
1699            return_arr.extend(edge_ids.into_iter().map(|id| {
1700                Artifact::PrimitiveEdge(PrimitiveEdge {
1701                    id,
1702                    solid_id: body_id,
1703                    code_ref: sweep.code_ref.clone(),
1704                })
1705            }));
1706            return Ok(return_arr);
1707        }
1708        ModelingCmd::EntityLinearPatternTransform(pattern_cmd) => {
1709            let face_edge_infos = match response {
1710                Some(OkModelingCmdResponse::EntityLinearPatternTransform(resp)) => resp.entity_face_edge_ids.as_slice(),
1711                _ => &[],
1712            };
1713            return Ok(pattern_artifact_updates(
1714                artifacts,
1715                id,
1716                PatternSubType::Transform,
1717                ArtifactId::new(pattern_cmd.entity_id),
1718                face_edge_infos,
1719                code_ref,
1720            ));
1721        }
1722        ModelingCmd::EntityLinearPattern(pattern_cmd) => {
1723            let face_edge_infos = match response {
1724                Some(OkModelingCmdResponse::EntityLinearPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
1725                _ => &[],
1726            };
1727            return Ok(pattern_artifact_updates(
1728                artifacts,
1729                id,
1730                PatternSubType::Linear,
1731                ArtifactId::new(pattern_cmd.entity_id),
1732                face_edge_infos,
1733                code_ref,
1734            ));
1735        }
1736        ModelingCmd::EntityCircularPattern(pattern_cmd) => {
1737            let face_edge_infos = match response {
1738                Some(OkModelingCmdResponse::EntityCircularPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
1739                _ => &[],
1740            };
1741            return Ok(pattern_artifact_updates(
1742                artifacts,
1743                id,
1744                PatternSubType::Circular,
1745                ArtifactId::new(pattern_cmd.entity_id),
1746                face_edge_infos,
1747                code_ref,
1748            ));
1749        }
1750        ModelingCmd::EntityMirrorAcross(kcmc::EntityMirrorAcross {
1751            ids: original_solid_ids,
1752            ..
1753        }) => {
1754            let face_edge_infos = match response {
1755                Some(OkModelingCmdResponse::EntityMirrorAcross(resp)) => resp.entity_face_edge_ids.as_slice(),
1756                // A rejected modeling command has no response. Execution will
1757                // report the engine error; there is no mirrored artifact to add.
1758                None => return Ok(Vec::new()),
1759                Some(_) => internal_error!(
1760                    range,
1761                    "EntityMirrorAcross response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
1762                ),
1763            };
1764            return mirror_3d_artifact_updates(artifacts, original_solid_ids, face_edge_infos, code_ref, range, cmd);
1765        }
1766        ModelingCmd::EntityMirror(kcmc::EntityMirror {
1767            ids: original_path_ids, ..
1768        })
1769        | ModelingCmd::EntityMirrorAcrossEdge(kcmc::EntityMirrorAcrossEdge {
1770            ids: original_path_ids, ..
1771        }) => {
1772            let face_edge_infos = match response {
1773                Some(OkModelingCmdResponse::EntityMirror(resp)) => &resp.entity_face_edge_ids,
1774                Some(OkModelingCmdResponse::EntityMirrorAcrossEdge(resp)) => &resp.entity_face_edge_ids,
1775                _ => internal_error!(
1776                    range,
1777                    "Mirror response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
1778                ),
1779            };
1780            if original_path_ids.len() != face_edge_infos.len() {
1781                internal_error!(
1782                    range,
1783                    "EntityMirror or EntityMirrorAcrossEdge response has different number face edge info than original mirrored paths: id={id:?}, cmd={cmd:?}, response={response:?}"
1784                );
1785            }
1786            let mut return_arr = Vec::new();
1787            for (face_edge_info, original_path_id) in face_edge_infos.iter().zip(original_path_ids) {
1788                let original_path_id = ArtifactId::new(*original_path_id);
1789                let path_id = ArtifactId::new(face_edge_info.object_id);
1790                // The path may be an existing path that was extended or a new
1791                // path.
1792                let mut path = if let Some(Artifact::Path(path)) = artifacts.get(&path_id) {
1793                    // Existing path.
1794                    path.clone()
1795                } else {
1796                    // It's a new path.  We need the original path to get some
1797                    // of its info.
1798                    let Some(Artifact::Path(original_path)) = artifacts.get(&original_path_id) else {
1799                        // We couldn't find the original path. This is a bug.
1800                        internal_error!(
1801                            range,
1802                            "Couldn't find original path for mirror2d: original_path_id={original_path_id:?}, cmd={cmd:?}"
1803                        );
1804                    };
1805                    Path {
1806                        id: path_id,
1807                        sub_type: original_path.sub_type,
1808                        plane_id: original_path.plane_id,
1809                        seg_ids: Vec::new(),
1810                        sweep_id: None,
1811                        trajectory_sweep_id: None,
1812                        solid2d_id: None,
1813                        code_ref: code_ref.clone(),
1814                        composite_solid_id: None,
1815                        sketch_block_id: None,
1816                        origin_path_id: original_path.origin_path_id,
1817                        inner_path_id: None,
1818                        outer_path_id: None,
1819                        pattern_ids: Vec::new(),
1820                        consumed: false,
1821                    }
1822                };
1823
1824                face_edge_info.edges.iter().for_each(|edge_id| {
1825                    let edge_id = ArtifactId::new(*edge_id);
1826                    return_arr.push(Artifact::Segment(Segment {
1827                        id: edge_id,
1828                        path_id: path.id,
1829                        source_segment_id: None,
1830                        original_seg_id: None,
1831                        surface_id: None,
1832                        edge_ids: Vec::new(),
1833                        edge_cut_id: None,
1834                        code_ref: code_ref.clone(),
1835                        common_surface_ids: Vec::new(),
1836                    }));
1837                    // Add the edge ID to the path.
1838                    path.seg_ids.push(edge_id);
1839                });
1840
1841                return_arr.push(Artifact::Path(path));
1842            }
1843            return Ok(return_arr);
1844        }
1845        ModelingCmd::EntityClone(kcmc::EntityClone { entity_id, .. }) => {
1846            let source_entity_id = ArtifactId::new(*entity_id);
1847            let entity_clone_info = artifact_command.entity_clone_info;
1848            let source_id = entity_clone_info
1849                .map(|info| info.source_artifact_id)
1850                .unwrap_or(source_entity_id);
1851            let result_id = entity_clone_info.map(|info| info.result_artifact_id).unwrap_or(id);
1852
1853            // Only solid clones provide this extra body identity. Without
1854            // this gate, cloning a lazy 2D pattern copy can resolve through
1855            // its source Path to a Sweep and fabricate a body artifact.
1856            let pattern_source_body_id = if entity_clone_info.is_some() && !artifacts.contains_key(&source_id) {
1857                pattern_source_body_id_for_copy(artifacts, source_id)
1858            } else {
1859                None
1860            };
1861            let source_artifact_id = pattern_source_body_id.unwrap_or(source_id);
1862            let Some(source_artifact) = artifacts.get(&source_artifact_id) else {
1863                return Ok(Vec::new());
1864            };
1865
1866            let mut entity_id_map = entity_clone_id_maps.get(&uuid).cloned().unwrap_or_default();
1867            entity_id_map.insert(source_entity_id, id);
1868            if let Some(info) = entity_clone_info {
1869                entity_id_map.insert(info.source_topology_id, id);
1870            }
1871            entity_id_map.insert(source_id, result_id);
1872            entity_id_map.insert(source_artifact_id, result_id);
1873            if matches!(source_artifact, Artifact::CompositeSolid(_)) {
1874                add_composite_sweep_clone_id_mappings(artifacts, artifact_command.cmd_id, &mut entity_id_map);
1875            }
1876
1877            let mut cloned_artifacts = Vec::new();
1878            cloned_artifacts.push(remap_artifact_for_clone(
1879                source_artifact,
1880                &entity_id_map,
1881                &code_ref,
1882                artifact_command.cmd_id,
1883                source_artifact_id,
1884            ));
1885
1886            for artifact in artifacts.values() {
1887                let artifact_id = artifact.id();
1888                if artifact_id == source_artifact_id || !entity_id_map.contains_key(&artifact_id) {
1889                    continue;
1890                }
1891                cloned_artifacts.push(remap_artifact_for_clone(
1892                    artifact,
1893                    &entity_id_map,
1894                    &code_ref,
1895                    artifact_command.cmd_id,
1896                    source_artifact_id,
1897                ));
1898            }
1899
1900            return Ok(cloned_artifacts);
1901        }
1902        ModelingCmd::Extrude(_)
1903        | ModelingCmd::TwistExtrude(_)
1904        | ModelingCmd::Revolve(_)
1905        | ModelingCmd::RevolveAboutEdge(_)
1906        | ModelingCmd::ExtrudeToReference(_) => {
1907            let target = match cmd {
1908                ModelingCmd::Extrude(kcmc::Extrude {
1909                    target: Some(target), ..
1910                }) => cmd_id_ref_to_artifact_id(target),
1911                ModelingCmd::Extrude(kcmc::Extrude {
1912                    target: None,
1913                    target_reference: Some(_),
1914                    ..
1915                }) => {
1916                    return Ok(vec![Artifact::Sweep(Sweep {
1917                        id,
1918                        sub_type: SweepSubType::Extrusion,
1919                        path_id: None,
1920                        surface_ids: Vec::new(),
1921                        edge_ids: Vec::new(),
1922                        code_ref,
1923                        source_sweep_id: None,
1924                        trajectory_id: None,
1925                        method: ArtifactSweepMethod::New,
1926                        consumed: false,
1927                        pattern_ids: Vec::new(),
1928                    })]);
1929                }
1930                ModelingCmd::Extrude(kcmc::Extrude { target: None, .. }) => return Ok(Vec::new()),
1931                ModelingCmd::TwistExtrude(kcmc::TwistExtrude { target, .. })
1932                | ModelingCmd::Revolve(kcmc::Revolve { target, .. })
1933                | ModelingCmd::RevolveAboutEdge(kcmc::RevolveAboutEdge { target, .. }) => {
1934                    cmd_id_ref_to_artifact_id(target)
1935                }
1936                ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference {
1937                    target: Some(target), ..
1938                }) => cmd_id_ref_to_artifact_id(target),
1939                ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { target: None, .. }) => {
1940                    return Ok(Vec::new());
1941                }
1942                _ => internal_error!(range, "Sweep-like command variant not handled: id={id:?}, cmd={cmd:?}"),
1943            };
1944            // Determine the resulting method from the specific command, if provided
1945            let method = match cmd {
1946                ModelingCmd::Extrude(kcmc::Extrude { extrude_method, .. }) => *extrude_method,
1947                ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { extrude_method, .. }) => *extrude_method,
1948                // TwistExtrude and Sweep don't carry method in the command; treat as Merge
1949                ModelingCmd::TwistExtrude(_) | ModelingCmd::Sweep(_) => {
1950                    kittycad_modeling_cmds::shared::ExtrudeMethod::Merge
1951                }
1952                // Revolve variants behave like New bodies in std layer
1953                ModelingCmd::Revolve(_) | ModelingCmd::RevolveAboutEdge(_) => {
1954                    kittycad_modeling_cmds::shared::ExtrudeMethod::New
1955                }
1956                _ => kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
1957            };
1958            let method = artifact_sweep_method(method);
1959            let sub_type = match cmd {
1960                ModelingCmd::Extrude(_) => SweepSubType::Extrusion,
1961                ModelingCmd::ExtrudeToReference(_) => SweepSubType::Extrusion,
1962                ModelingCmd::TwistExtrude(_) => SweepSubType::ExtrusionTwist,
1963                ModelingCmd::Revolve(_) => SweepSubType::Revolve,
1964                ModelingCmd::RevolveAboutEdge(_) => SweepSubType::RevolveAboutEdge,
1965                _ => internal_error!(range, "Sweep-like command variant not handled: id={id:?}, cmd={cmd:?}",),
1966            };
1967            let mut return_arr = Vec::new();
1968            return_arr.push(Artifact::Sweep(Sweep {
1969                id,
1970                sub_type,
1971                path_id: Some(target),
1972                surface_ids: Vec::new(),
1973                edge_ids: Vec::new(),
1974                code_ref,
1975                source_sweep_id: None,
1976                trajectory_id: None,
1977                method,
1978                consumed: false,
1979                pattern_ids: Vec::new(),
1980            }));
1981            let path = artifacts.get(&target);
1982            if let Some(Artifact::Path(path)) = path {
1983                let mut new_path = path.clone();
1984                new_path.sweep_id = Some(id);
1985                new_path.consumed = true;
1986                return_arr.push(Artifact::Path(new_path));
1987                if let Some(inner_path_id) = path.inner_path_id
1988                    && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
1989                    && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
1990                {
1991                    inner_path_artifact.sweep_id = Some(id);
1992                    inner_path_artifact.consumed = true;
1993                    return_arr.push(Artifact::Path(inner_path_artifact))
1994                }
1995            }
1996            return Ok(return_arr);
1997        }
1998        ModelingCmd::Sweep(kcmc::Sweep { target, trajectory, .. }) => {
1999            // Determine the resulting method from the specific command, if provided
2000            let method = ArtifactSweepMethod::Merge;
2001            let sub_type = SweepSubType::Sweep;
2002            let mut return_arr = Vec::new();
2003            let target = cmd_id_ref_to_artifact_id(target);
2004            let trajectory = cmd_id_ref_to_artifact_id(trajectory);
2005            return_arr.push(Artifact::Sweep(Sweep {
2006                id,
2007                sub_type,
2008                path_id: Some(target),
2009                surface_ids: Vec::new(),
2010                edge_ids: Vec::new(),
2011                code_ref,
2012                source_sweep_id: None,
2013                trajectory_id: Some(trajectory),
2014                method,
2015                consumed: false,
2016                pattern_ids: Vec::new(),
2017            }));
2018            let path = artifacts.get(&target);
2019            if let Some(Artifact::Path(path)) = path {
2020                let mut new_path = path.clone();
2021                new_path.sweep_id = Some(id);
2022                new_path.consumed = true;
2023                return_arr.push(Artifact::Path(new_path));
2024                if let Some(inner_path_id) = path.inner_path_id
2025                    && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
2026                    && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
2027                {
2028                    inner_path_artifact.sweep_id = Some(id);
2029                    inner_path_artifact.consumed = true;
2030                    return_arr.push(Artifact::Path(inner_path_artifact))
2031                }
2032            }
2033            if let Some(trajectory_artifact) = artifacts.get(&trajectory) {
2034                match trajectory_artifact {
2035                    Artifact::Path(path) => {
2036                        let mut new_path = path.clone();
2037                        new_path.trajectory_sweep_id = Some(id);
2038                        new_path.consumed = true;
2039                        return_arr.push(Artifact::Path(new_path));
2040                    }
2041                    Artifact::Helix(helix) => {
2042                        let mut new_helix = helix.clone();
2043                        new_helix.trajectory_sweep_id = Some(id);
2044                        new_helix.consumed = true;
2045                        return_arr.push(Artifact::Helix(new_helix));
2046                    }
2047                    _ => {}
2048                }
2049            };
2050            return Ok(return_arr);
2051        }
2052        ModelingCmd::SurfaceBlend(surface_blend_cmd) => {
2053            let surface_id_to_path_id = |surface_id: ArtifactId| -> Option<ArtifactId> {
2054                match artifacts.get(&surface_id) {
2055                    Some(Artifact::Path(path)) => Some(path.id),
2056                    Some(Artifact::Segment(segment)) => Some(segment.path_id),
2057                    Some(Artifact::Sweep(sweep)) => sweep.path_id,
2058                    Some(Artifact::Wall(wall)) => artifacts.get(&wall.sweep_id).and_then(|artifact| match artifact {
2059                        Artifact::Sweep(sweep) => sweep.path_id,
2060                        _ => None,
2061                    }),
2062                    Some(Artifact::Cap(cap)) => artifacts.get(&cap.sweep_id).and_then(|artifact| match artifact {
2063                        Artifact::Sweep(sweep) => sweep.path_id,
2064                        _ => None,
2065                    }),
2066                    _ => None,
2067                }
2068            };
2069            let Some(first_surface_ref) = surface_blend_cmd.surfaces.first() else {
2070                internal_error!(range, "SurfaceBlend command has no surfaces: id={id:?}, cmd={cmd:?}");
2071            };
2072            let first_surface_id = ArtifactId::new(first_surface_ref.object_id);
2073            let path_id = surface_id_to_path_id(first_surface_id);
2074            let trajectory_id = surface_blend_cmd
2075                .surfaces
2076                .get(1)
2077                .map(|surface| ArtifactId::new(surface.object_id))
2078                .and_then(surface_id_to_path_id);
2079            let return_arr = vec![Artifact::Sweep(Sweep {
2080                id,
2081                sub_type: SweepSubType::Blend,
2082                path_id,
2083                surface_ids: Vec::new(),
2084                edge_ids: Vec::new(),
2085                code_ref,
2086                source_sweep_id: None,
2087                trajectory_id,
2088                method: ArtifactSweepMethod::New,
2089                consumed: false,
2090                pattern_ids: Vec::new(),
2091            })];
2092            return Ok(return_arr);
2093        }
2094        ModelingCmd::Loft(loft_cmd) => {
2095            let Some(OkModelingCmdResponse::Loft(_)) = response else {
2096                return Ok(Vec::new());
2097            };
2098            let mut return_arr = Vec::new();
2099            return_arr.push(Artifact::Sweep(Sweep {
2100                id,
2101                sub_type: SweepSubType::Loft,
2102                // TODO: Using the first one.  Make sure to revisit this
2103                // choice, don't think it matters for now.
2104                path_id: Some(ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
2105                    KclError::new_internal(KclErrorDetails::new(
2106                        format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
2107                        vec![range],
2108                    ))
2109                })?)),
2110                surface_ids: Vec::new(),
2111                edge_ids: Vec::new(),
2112                code_ref,
2113                source_sweep_id: None,
2114                trajectory_id: None,
2115                method: ArtifactSweepMethod::Merge,
2116                consumed: false,
2117                pattern_ids: Vec::new(),
2118            }));
2119            for section_id in &loft_cmd.section_ids {
2120                let path = artifacts.get(&ArtifactId::new(*section_id));
2121                if let Some(Artifact::Path(path)) = path {
2122                    let mut new_path = path.clone();
2123                    new_path.consumed = true;
2124                    new_path.sweep_id = Some(id);
2125                    return_arr.push(Artifact::Path(new_path));
2126                }
2127            }
2128            return Ok(return_arr);
2129        }
2130        ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
2131            let Some(OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info)) = response else {
2132                return Ok(Vec::new());
2133            };
2134            let mut return_arr = Vec::new();
2135            let mut last_path = None;
2136            for face in &face_info.faces {
2137                if face.cap != ExtrusionFaceCapType::None {
2138                    continue;
2139                }
2140                let Some(curve_id) = face.curve_id.map(ArtifactId::new) else {
2141                    continue;
2142                };
2143                let Some(face_id) = face.face_id.map(ArtifactId::new) else {
2144                    continue;
2145                };
2146                let Some(Artifact::Segment(seg)) = artifacts.get(&curve_id) else {
2147                    continue;
2148                };
2149                let Some(Artifact::Path(path)) = artifacts.get(&seg.path_id) else {
2150                    continue;
2151                };
2152                last_path = Some(path);
2153                let Some(path_sweep_id) = path.sweep_id else {
2154                    // If the path doesn't have a sweep ID, check if it's a
2155                    // hole.
2156                    if path.outer_path_id.is_some() {
2157                        continue; // hole not handled
2158                    }
2159                    return Err(KclError::new_internal(KclErrorDetails::new(
2160                        format!(
2161                            "Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
2162                        ),
2163                        vec![range],
2164                    )));
2165                };
2166                let extra_artifact = exec_artifacts.values().find(|a| {
2167                    if let Artifact::StartSketchOnFace(s) = a {
2168                        s.face_id == face_id
2169                    } else if let Artifact::StartSketchOnPlane(s) = a {
2170                        s.plane_id == face_id
2171                    } else {
2172                        false
2173                    }
2174                });
2175                let sketch_on_face_code_ref = extra_artifact
2176                    .and_then(|a| match a {
2177                        Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
2178                        Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
2179                        _ => None,
2180                    })
2181                    // TODO: If we didn't find it, it's probably a bug.
2182                    .unwrap_or_default();
2183
2184                return_arr.push(Artifact::Wall(Wall {
2185                    id: face_id,
2186                    seg_id: curve_id,
2187                    edge_cut_edge_ids: Vec::new(),
2188                    sweep_id: path_sweep_id,
2189                    path_ids: Vec::new(),
2190                    face_code_ref: sketch_on_face_code_ref,
2191                    cmd_id: artifact_command.cmd_id,
2192                }));
2193                let mut new_seg = seg.clone();
2194                new_seg.surface_id = Some(face_id);
2195                return_arr.push(Artifact::Segment(new_seg));
2196                if let Some(Artifact::Sweep(sweep)) = path.sweep_id.and_then(|id| artifacts.get(&id)) {
2197                    let mut new_sweep = sweep.clone();
2198                    new_sweep.surface_ids = vec![face_id];
2199                    return_arr.push(Artifact::Sweep(new_sweep));
2200                }
2201            }
2202            if let Some(path) = last_path {
2203                for face in &face_info.faces {
2204                    let sub_type = match face.cap {
2205                        ExtrusionFaceCapType::Top => CapSubType::End,
2206                        ExtrusionFaceCapType::Bottom => CapSubType::Start,
2207                        ExtrusionFaceCapType::None | ExtrusionFaceCapType::Both => continue,
2208                        _other => {
2209                            // Modeling API has added something we're not aware of.
2210                            continue;
2211                        }
2212                    };
2213                    let Some(face_id) = face.face_id.map(ArtifactId::new) else {
2214                        continue;
2215                    };
2216                    let Some(path_sweep_id) = path.sweep_id else {
2217                        // If the path doesn't have a sweep ID, check if it's a
2218                        // hole.
2219                        if path.outer_path_id.is_some() {
2220                            continue; // hole not handled
2221                        }
2222                        return Err(KclError::new_internal(KclErrorDetails::new(
2223                            format!(
2224                                "Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
2225                            ),
2226                            vec![range],
2227                        )));
2228                    };
2229                    let extra_artifact = exec_artifacts.values().find(|a| {
2230                        if let Artifact::StartSketchOnFace(s) = a {
2231                            s.face_id == face_id
2232                        } else if let Artifact::StartSketchOnPlane(s) = a {
2233                            s.plane_id == face_id
2234                        } else {
2235                            false
2236                        }
2237                    });
2238                    let sketch_on_face_code_ref = extra_artifact
2239                        .and_then(|a| match a {
2240                            Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
2241                            Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
2242                            _ => None,
2243                        })
2244                        // TODO: If we didn't find it, it's probably a bug.
2245                        .unwrap_or_default();
2246                    return_arr.push(Artifact::Cap(Cap {
2247                        id: face_id,
2248                        sub_type,
2249                        edge_cut_edge_ids: Vec::new(),
2250                        sweep_id: path_sweep_id,
2251                        path_ids: Vec::new(),
2252                        face_code_ref: sketch_on_face_code_ref,
2253                        cmd_id: artifact_command.cmd_id,
2254                    }));
2255                    let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
2256                        continue;
2257                    };
2258                    let mut new_sweep = sweep.clone();
2259                    new_sweep.surface_ids = vec![face_id];
2260                    return_arr.push(Artifact::Sweep(new_sweep));
2261                }
2262            }
2263            return Ok(return_arr);
2264        }
2265        ModelingCmd::Solid3dGetAdjacencyInfo(kcmc::Solid3dGetAdjacencyInfo { .. }) => {
2266            let Some(OkModelingCmdResponse::Solid3dGetAdjacencyInfo(info)) = response else {
2267                return Ok(Vec::new());
2268            };
2269
2270            let mut return_arr = Vec::new();
2271            let adjacent_edge_ids = info
2272                .edges
2273                .iter()
2274                .filter_map(|edge| edge.adjacent_info.as_ref().map(|info| info.edge_id))
2275                .collect::<AHashSet<_>>();
2276            for (index, edge) in info.edges.iter().enumerate() {
2277                let Some(original_info) = &edge.original_info else {
2278                    continue;
2279                };
2280                let edge_id = ArtifactId::new(original_info.edge_id);
2281                let Some(artifact) = artifacts.get(&edge_id) else {
2282                    continue;
2283                };
2284                match artifact {
2285                    Artifact::Segment(segment) => {
2286                        let mut new_segment = segment.clone();
2287                        new_segment.common_surface_ids =
2288                            original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
2289                        return_arr.push(Artifact::Segment(new_segment));
2290                    }
2291                    Artifact::SweepEdge(sweep_edge) => {
2292                        let mut new_sweep_edge = sweep_edge.clone();
2293                        new_sweep_edge.common_surface_ids =
2294                            original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
2295                        return_arr.push(Artifact::SweepEdge(new_sweep_edge));
2296                    }
2297                    _ => {}
2298                };
2299
2300                let Some(Artifact::Segment(segment)) = artifacts.get(&edge_id) else {
2301                    continue;
2302                };
2303                let Some(surface_id) = segment.surface_id else {
2304                    continue;
2305                };
2306                let Some(Artifact::Wall(wall)) = artifacts.get(&surface_id) else {
2307                    continue;
2308                };
2309                let Some(Artifact::Sweep(sweep)) = artifacts.get(&wall.sweep_id) else {
2310                    continue;
2311                };
2312                let Some(path_id) = sweep.path_id else {
2313                    continue;
2314                };
2315                let Some(Artifact::Path(_)) = artifacts.get(&path_id) else {
2316                    continue;
2317                };
2318
2319                if let Some(opposite_info) = &edge.opposite_info {
2320                    return_arr.push(Artifact::SweepEdge(SweepEdge {
2321                        id: opposite_info.edge_id.into(),
2322                        sub_type: SweepEdgeSubType::Opposite,
2323                        seg_id: edge_id,
2324                        cmd_id: artifact_command.cmd_id,
2325                        index,
2326                        sweep_id: sweep.id,
2327                        common_surface_ids: opposite_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
2328                    }));
2329                    let mut new_segment = segment.clone();
2330                    new_segment.edge_ids = vec![opposite_info.edge_id.into()];
2331                    return_arr.push(Artifact::Segment(new_segment));
2332                    let mut new_sweep = sweep.clone();
2333                    new_sweep.edge_ids = vec![opposite_info.edge_id.into()];
2334                    return_arr.push(Artifact::Sweep(new_sweep));
2335                    let mut new_wall = wall.clone();
2336                    new_wall.edge_cut_edge_ids = vec![opposite_info.edge_id.into()];
2337                    return_arr.push(Artifact::Wall(new_wall));
2338                }
2339                if let Some(adjacent_info) = &edge.adjacent_info {
2340                    return_arr.push(Artifact::SweepEdge(SweepEdge {
2341                        id: adjacent_info.edge_id.into(),
2342                        sub_type: SweepEdgeSubType::Adjacent,
2343                        seg_id: edge_id,
2344                        cmd_id: artifact_command.cmd_id,
2345                        index,
2346                        sweep_id: sweep.id,
2347                        common_surface_ids: adjacent_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
2348                    }));
2349                    let mut new_segment = segment.clone();
2350                    new_segment.edge_ids = vec![adjacent_info.edge_id.into()];
2351                    return_arr.push(Artifact::Segment(new_segment));
2352                    let mut new_sweep = sweep.clone();
2353                    new_sweep.edge_ids = vec![adjacent_info.edge_id.into()];
2354                    return_arr.push(Artifact::Sweep(new_sweep));
2355                    let mut new_wall = wall.clone();
2356                    new_wall.edge_cut_edge_ids = vec![adjacent_info.edge_id.into()];
2357                    return_arr.push(Artifact::Wall(new_wall));
2358                }
2359                // Internal edges are already represented as the next adjacent edge of
2360                // the preceding segment. Only add the open component's start edge.
2361                if let Some(previous_adjacent_info) = &edge.previous_adjacent_info
2362                    && !adjacent_edge_ids.contains(&previous_adjacent_info.edge_id)
2363                {
2364                    return_arr.push(Artifact::SweepEdge(SweepEdge {
2365                        id: previous_adjacent_info.edge_id.into(),
2366                        sub_type: SweepEdgeSubType::PreviousAdjacent,
2367                        seg_id: edge_id,
2368                        cmd_id: artifact_command.cmd_id,
2369                        index,
2370                        sweep_id: sweep.id,
2371                        common_surface_ids: previous_adjacent_info
2372                            .faces
2373                            .iter()
2374                            .map(|face| ArtifactId::new(*face))
2375                            .collect(),
2376                    }));
2377                    let mut new_segment = segment.clone();
2378                    new_segment.edge_ids = vec![previous_adjacent_info.edge_id.into()];
2379                    return_arr.push(Artifact::Segment(new_segment));
2380                    let mut new_sweep = sweep.clone();
2381                    new_sweep.edge_ids = vec![previous_adjacent_info.edge_id.into()];
2382                    return_arr.push(Artifact::Sweep(new_sweep));
2383                    let mut new_wall = wall.clone();
2384                    new_wall.edge_cut_edge_ids = vec![previous_adjacent_info.edge_id.into()];
2385                    return_arr.push(Artifact::Wall(new_wall));
2386                }
2387            }
2388            return Ok(return_arr);
2389        }
2390        ModelingCmd::Solid3dMultiJoin(cmd) => {
2391            let mut return_arr = Vec::new();
2392            return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2393                id,
2394                consumed: false,
2395                sub_type: CompositeSolidSubType::Union,
2396                output_index: None,
2397                solid_ids: cmd.object_ids.iter().map(|id| id.into()).collect(),
2398                tool_ids: vec![],
2399                code_ref,
2400                composite_solid_id: None,
2401                pattern_ids: Vec::new(),
2402            }));
2403
2404            let solid_ids = cmd.object_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2405
2406            for input_id in &solid_ids {
2407                if let Some(artifact) = artifacts.get(input_id)
2408                    && let Artifact::CompositeSolid(comp) = artifact
2409                {
2410                    let mut new_comp = comp.clone();
2411                    new_comp.composite_solid_id = Some(id);
2412                    new_comp.consumed = true;
2413                    return_arr.push(Artifact::CompositeSolid(new_comp));
2414                } else if let Some(Artifact::Sweep(sweep)) = artifacts.get(input_id) {
2415                    let mut new_sweep = sweep.clone();
2416                    new_sweep.consumed = true;
2417                    return_arr.push(Artifact::Sweep(new_sweep));
2418                }
2419            }
2420            return Ok(return_arr);
2421        }
2422        ModelingCmd::Solid3dFilletEdge(cmd) => {
2423            let mut return_arr = Vec::new();
2424            let edge_id = if let Some(edge_id) = cmd.edge_id {
2425                ArtifactId::new(edge_id)
2426            } else {
2427                let Some(edge_id) = cmd.edge_ids.first() else {
2428                    internal_error!(
2429                        range,
2430                        "Solid3dFilletEdge command has no edge ID: id={id:?}, cmd={cmd:?}"
2431                    );
2432                };
2433                edge_id.into()
2434            };
2435            return_arr.push(Artifact::EdgeCut(EdgeCut {
2436                id,
2437                sub_type: edge_cut_sub_type(cmd.cut_type),
2438                source_selector_index: None,
2439                consumed_edge_id: Some(edge_id),
2440                edge_ids: Vec::new(),
2441                surface_id: None,
2442                code_ref,
2443            }));
2444            let consumed_edge = artifacts.get(&edge_id);
2445            if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2446                let mut new_segment = consumed_edge.clone();
2447                new_segment.edge_cut_id = Some(id);
2448                return_arr.push(Artifact::Segment(new_segment));
2449            } else {
2450                // TODO: Handle other types like SweepEdge.
2451            }
2452            return Ok(return_arr);
2453        }
2454        ModelingCmd::Solid3dCutEdges(cmd) => {
2455            let mut return_arr = Vec::new();
2456            let edge_id = if let Some(edge_id) = cmd.edge_ids.first() {
2457                edge_id.into()
2458            } else {
2459                internal_error!(range, "Solid3dCutEdges command has no edge ID: id={id:?}, cmd={cmd:?}");
2460            };
2461            return_arr.push(Artifact::EdgeCut(EdgeCut {
2462                id,
2463                sub_type: edge_cut_sub_type_v2(cmd.cut_type),
2464                source_selector_index: None,
2465                consumed_edge_id: Some(edge_id),
2466                edge_ids: Vec::new(),
2467                surface_id: None,
2468                code_ref,
2469            }));
2470            let consumed_edge = artifacts.get(&edge_id);
2471            if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2472                let mut new_segment = consumed_edge.clone();
2473                new_segment.edge_cut_id = Some(id);
2474                return_arr.push(Artifact::Segment(new_segment));
2475            } else {
2476                // TODO: Handle other types like SweepEdge.
2477            }
2478            return Ok(return_arr);
2479        }
2480        ModelingCmd::Solid3dCutEdgeReferences(cmd) => {
2481            let face_ids = std::iter::once(id).chain(cmd.extra_face_ids.iter().copied().map(ArtifactId::new));
2482            return Ok(face_ids
2483                .take(cmd.edges_references.len())
2484                .enumerate()
2485                .map(|(source_selector_index, face_id)| {
2486                    Artifact::EdgeCut(EdgeCut {
2487                        id: face_id,
2488                        sub_type: edge_cut_sub_type_v2(cmd.cut_type),
2489                        source_selector_index: Some(source_selector_index),
2490                        consumed_edge_id: None,
2491                        edge_ids: Vec::new(),
2492                        surface_id: None,
2493                        code_ref: code_ref.clone(),
2494                    })
2495                })
2496                .collect());
2497        }
2498        ModelingCmd::EntityMakeHelix(cmd) => {
2499            let cylinder_id = ArtifactId::new(cmd.cylinder_id);
2500            let return_arr = vec![Artifact::Helix(Helix {
2501                id,
2502                axis_id: Some(cylinder_id),
2503                code_ref,
2504                trajectory_sweep_id: None,
2505                consumed: false,
2506            })];
2507            return Ok(return_arr);
2508        }
2509        ModelingCmd::EntityMakeHelixFromParams(_) => {
2510            let return_arr = vec![Artifact::Helix(Helix {
2511                id,
2512                axis_id: None,
2513                code_ref,
2514                trajectory_sweep_id: None,
2515                consumed: false,
2516            })];
2517            return Ok(return_arr);
2518        }
2519        ModelingCmd::EntityMakeHelixFromEdge(helix) => {
2520            let return_arr = vec![Artifact::Helix(Helix {
2521                id,
2522                axis_id: helix.edge_id.map(ArtifactId::new),
2523                code_ref,
2524                trajectory_sweep_id: None,
2525                consumed: false,
2526            })];
2527            // We could add the reverse graph edge connecting from the edge to
2528            // the helix here, but it's not useful right now.
2529            return Ok(return_arr);
2530        }
2531        ModelingCmd::Solid2dAddHole(solid2d_add_hole) => {
2532            let mut return_arr = Vec::new();
2533            // Add the hole to the outer.
2534            let outer_path = artifacts.get(&ArtifactId::new(solid2d_add_hole.object_id));
2535            if let Some(Artifact::Path(path)) = outer_path {
2536                let mut new_path = path.clone();
2537                new_path.inner_path_id = Some(ArtifactId::new(solid2d_add_hole.hole_id));
2538                return_arr.push(Artifact::Path(new_path));
2539            }
2540            // Add the outer to the hole.
2541            let inner_solid2d = artifacts.get(&ArtifactId::new(solid2d_add_hole.hole_id));
2542            if let Some(Artifact::Path(path)) = inner_solid2d {
2543                let mut new_path = path.clone();
2544                new_path.consumed = true;
2545                new_path.outer_path_id = Some(ArtifactId::new(solid2d_add_hole.object_id));
2546                return_arr.push(Artifact::Path(new_path));
2547            }
2548            return Ok(return_arr);
2549        }
2550        ModelingCmd::BooleanIntersection(_) | ModelingCmd::BooleanSubtract(_) | ModelingCmd::BooleanUnion(_) => {
2551            let (sub_type, solid_ids, tool_ids) = match cmd {
2552                ModelingCmd::BooleanIntersection(intersection) => {
2553                    let solid_ids = intersection
2554                        .solid_ids
2555                        .iter()
2556                        .copied()
2557                        .map(ArtifactId::new)
2558                        .collect::<Vec<_>>();
2559                    (CompositeSolidSubType::Intersect, solid_ids, Vec::new())
2560                }
2561                ModelingCmd::BooleanSubtract(subtract) => {
2562                    let solid_ids = subtract
2563                        .target_ids
2564                        .iter()
2565                        .copied()
2566                        .map(ArtifactId::new)
2567                        .collect::<Vec<_>>();
2568                    let tool_ids = subtract
2569                        .tool_ids
2570                        .iter()
2571                        .copied()
2572                        .map(ArtifactId::new)
2573                        .collect::<Vec<_>>();
2574                    (CompositeSolidSubType::Subtract, solid_ids, tool_ids)
2575                }
2576                ModelingCmd::BooleanUnion(union) => {
2577                    let solid_ids = union.solid_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2578                    (CompositeSolidSubType::Union, solid_ids, Vec::new())
2579                }
2580                _ => internal_error!(
2581                    range,
2582                    "Boolean or composite command variant not handled: id={id:?}, cmd={cmd:?}"
2583                ),
2584            };
2585
2586            let mut new_solid_ids = vec![id];
2587
2588            // Make sure we don't ever create a duplicate ID since merge_ids
2589            // can't handle it.
2590            let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2591
2592            match (cmd, response) {
2593                (
2594                    ModelingCmd::BooleanSubtract(subtract_cmd),
2595                    Some(OkModelingCmdResponse::BooleanSubtract(subtract_resp)),
2596                ) => {
2597                    new_solid_ids = boolean_subtract_output_artifact_ids(
2598                        id,
2599                        &subtract_cmd.target_ids,
2600                        &subtract_cmd.tool_ids,
2601                        &subtract_resp.extra_solid_ids,
2602                    );
2603                }
2604                (_, Some(OkModelingCmdResponse::BooleanIntersection(intersection))) => intersection
2605                    .extra_solid_ids
2606                    .iter()
2607                    .copied()
2608                    .map(ArtifactId::new)
2609                    .filter(not_cmd_id)
2610                    .for_each(|id| new_solid_ids.push(id)),
2611                (_, Some(OkModelingCmdResponse::BooleanUnion(union))) => union
2612                    .extra_solid_ids
2613                    .iter()
2614                    .copied()
2615                    .map(ArtifactId::new)
2616                    .filter(not_cmd_id)
2617                    .for_each(|id| new_solid_ids.push(id)),
2618                _ => {}
2619            }
2620
2621            let mut return_arr = Vec::new();
2622            let mut consumed_sweep_ids = AHashSet::default();
2623            let mut input_ids = solid_ids.clone();
2624            merge_ids(&mut input_ids, tool_ids.clone());
2625
2626            if new_solid_ids.is_empty() {
2627                update_csg_input_artifacts(&mut return_arr, artifacts, &input_ids, None, &mut consumed_sweep_ids);
2628            }
2629
2630            // A returned solid can reuse an input's artifact graph key.
2631            // Apply all input updates first so they cannot overwrite a live output.
2632            for solid_id in &new_solid_ids {
2633                update_csg_input_artifacts(
2634                    &mut return_arr,
2635                    artifacts,
2636                    &input_ids,
2637                    Some(*solid_id),
2638                    &mut consumed_sweep_ids,
2639                );
2640            }
2641
2642            for solid_id in &new_solid_ids {
2643                return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2644                    id: *solid_id,
2645                    consumed: false,
2646                    sub_type,
2647                    output_index: None,
2648                    solid_ids: solid_ids.clone(),
2649                    tool_ids: tool_ids.clone(),
2650                    code_ref: code_ref.clone(),
2651                    composite_solid_id: None,
2652                    pattern_ids: Vec::new(),
2653                }));
2654            }
2655
2656            return Ok(return_arr);
2657        }
2658        ModelingCmd::BooleanImprint(imprint) => {
2659            let solid_ids = imprint
2660                .body_ids
2661                .iter()
2662                .copied()
2663                .map(ArtifactId::new)
2664                .collect::<Vec<_>>();
2665            let tool_ids = imprint
2666                .tool_ids
2667                .as_ref()
2668                .map(|ids| ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>())
2669                .unwrap_or_default();
2670            let mut consumed_input_ids = solid_ids.clone();
2671            if !imprint.keep_tools {
2672                consumed_input_ids.extend_from_slice(&tool_ids);
2673            }
2674
2675            let mut new_solid_ids = vec![id];
2676            let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2677            if let Some(OkModelingCmdResponse::BooleanImprint(imprint)) = response {
2678                imprint
2679                    .extra_solid_ids
2680                    .iter()
2681                    .copied()
2682                    .map(ArtifactId::new)
2683                    .filter(not_cmd_id)
2684                    .for_each(|id| new_solid_ids.push(id));
2685            }
2686
2687            let mut return_arr = Vec::new();
2688            let mut consumed_sweep_ids = AHashSet::default();
2689
2690            for input_id in &consumed_input_ids {
2691                let sweep_id = match artifacts.get(input_id) {
2692                    Some(Artifact::Sweep(sweep)) => Some(sweep.id),
2693                    Some(Artifact::Path(path)) => path.sweep_id,
2694                    _ => None,
2695                };
2696
2697                if let Some(sweep_id) = sweep_id
2698                    && consumed_sweep_ids.insert(sweep_id)
2699                    && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
2700                {
2701                    let mut new_sweep = sweep.clone();
2702                    new_sweep.consumed = true;
2703                    return_arr.push(Artifact::Sweep(new_sweep));
2704                }
2705            }
2706
2707            for (output_index, solid_id) in new_solid_ids.iter().enumerate() {
2708                return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2709                    id: *solid_id,
2710                    consumed: false,
2711                    sub_type: CompositeSolidSubType::Split,
2712                    output_index: Some(output_index),
2713                    solid_ids: solid_ids.clone(),
2714                    tool_ids: tool_ids.clone(),
2715                    code_ref: code_ref.clone(),
2716                    composite_solid_id: None,
2717                    pattern_ids: Vec::new(),
2718                }));
2719
2720                for input_id in &consumed_input_ids {
2721                    if let Some(artifact) = artifacts.get(input_id) {
2722                        match artifact {
2723                            Artifact::CompositeSolid(comp) => {
2724                                let mut new_comp = comp.clone();
2725                                new_comp.composite_solid_id = Some(*solid_id);
2726                                new_comp.consumed = true;
2727                                return_arr.push(Artifact::CompositeSolid(new_comp));
2728                            }
2729                            Artifact::Path(path) => {
2730                                let mut new_path = path.clone();
2731                                new_path.composite_solid_id = Some(*solid_id);
2732
2733                                return_arr.push(Artifact::Path(new_path));
2734                            }
2735                            _ => {}
2736                        }
2737                    }
2738                }
2739            }
2740
2741            return Ok(return_arr);
2742        }
2743        _ => {}
2744    }
2745
2746    Ok(Vec::new())
2747}