Skip to main content

kcl_lib/execution/
artifact.rs

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