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 = entity_id_map.contains_key(&sweep.path_id)
794                || sweep.surface_ids.iter().any(|id| entity_id_map.contains_key(id))
795                || sweep.edge_ids.iter().any(|id| entity_id_map.contains_key(id));
796            has_mapped_topology.then_some(sweep.id)
797        })
798        .collect::<Vec<_>>();
799
800    for source_sweep_id in source_sweep_ids {
801        let source_uuid = Uuid::from(source_sweep_id);
802        let cloned_sweep_id = ArtifactId::new(Uuid::new_v5(&clone_cmd_id, source_uuid.as_bytes()));
803        entity_id_map.insert(source_sweep_id, cloned_sweep_id);
804    }
805}
806
807fn remap_artifact_for_clone(
808    artifact: &Artifact,
809    entity_id_map: &AHashMap<ArtifactId, ArtifactId>,
810    clone_code_ref: &CodeRef,
811    clone_cmd_id: Uuid,
812    source_root_id: ArtifactId,
813) -> Artifact {
814    match artifact {
815        Artifact::CompositeSolid(source) => Artifact::CompositeSolid(CompositeSolid {
816            id: remap_id_for_clone(source.id, entity_id_map),
817            consumed: if source.id == source_root_id {
818                false
819            } else {
820                source.consumed
821            },
822            sub_type: source.sub_type,
823            // clone() returns one new top-level body, even when its source was
824            // an indexed output or belonged to another composite solid.
825            output_index: if source.id == source_root_id {
826                None
827            } else {
828                source.output_index
829            },
830            solid_ids: remap_ids_for_clone(&source.solid_ids, entity_id_map),
831            tool_ids: remap_ids_for_clone(&source.tool_ids, entity_id_map),
832            pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
833            code_ref: clone_code_ref.clone(),
834            composite_solid_id: if source.id == source_root_id {
835                None
836            } else {
837                remap_opt_id_for_clone(source.composite_solid_id, entity_id_map)
838            },
839        }),
840        Artifact::Plane(source) => Artifact::Plane(Plane {
841            id: remap_id_for_clone(source.id, entity_id_map),
842            path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
843            code_ref: clone_code_ref.clone(),
844        }),
845        Artifact::Path(source) => Artifact::Path(Path {
846            id: remap_id_for_clone(source.id, entity_id_map),
847            sub_type: source.sub_type,
848            plane_id: remap_id_for_clone(source.plane_id, entity_id_map),
849            seg_ids: remap_ids_for_clone(&source.seg_ids, entity_id_map),
850            consumed: if source.id == source_root_id {
851                false
852            } else {
853                source.consumed
854            },
855            sweep_id: remap_opt_id_for_clone(source.sweep_id, entity_id_map),
856            trajectory_sweep_id: remap_opt_id_for_clone(source.trajectory_sweep_id, entity_id_map),
857            solid2d_id: remap_opt_id_for_clone(source.solid2d_id, entity_id_map),
858            code_ref: clone_code_ref.clone(),
859            composite_solid_id: remap_opt_id_for_clone(source.composite_solid_id, entity_id_map),
860            sketch_block_id: remap_opt_id_for_clone(source.sketch_block_id, entity_id_map),
861            origin_path_id: remap_opt_id_for_clone(source.origin_path_id, entity_id_map),
862            inner_path_id: remap_opt_id_for_clone(source.inner_path_id, entity_id_map),
863            outer_path_id: remap_opt_id_for_clone(source.outer_path_id, entity_id_map),
864            pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
865        }),
866        Artifact::Segment(source) => Artifact::Segment(Segment {
867            id: remap_id_for_clone(source.id, entity_id_map),
868            path_id: remap_id_for_clone(source.path_id, entity_id_map),
869            source_segment_id: source.source_segment_id.or(Some(source.id)),
870            original_seg_id: remap_opt_id_for_clone(source.original_seg_id, entity_id_map),
871            surface_id: remap_opt_id_for_clone(source.surface_id, entity_id_map),
872            edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
873            edge_cut_id: remap_opt_id_for_clone(source.edge_cut_id, entity_id_map),
874            code_ref: clone_code_ref.clone(),
875            common_surface_ids: remap_ids_for_clone(&source.common_surface_ids, entity_id_map),
876        }),
877        Artifact::Solid2d(source) => Artifact::Solid2d(Solid2d {
878            id: remap_id_for_clone(source.id, entity_id_map),
879            path_id: remap_id_for_clone(source.path_id, entity_id_map),
880        }),
881        Artifact::PrimitiveFace(source) => Artifact::PrimitiveFace(PrimitiveFace {
882            id: remap_id_for_clone(source.id, entity_id_map),
883            solid_id: remap_id_for_clone(source.solid_id, entity_id_map),
884            code_ref: clone_code_ref.clone(),
885        }),
886        Artifact::PrimitiveEdge(source) => Artifact::PrimitiveEdge(PrimitiveEdge {
887            id: remap_id_for_clone(source.id, entity_id_map),
888            solid_id: remap_id_for_clone(source.solid_id, entity_id_map),
889            code_ref: clone_code_ref.clone(),
890        }),
891        Artifact::PlaneOfFace(source) => Artifact::PlaneOfFace(PlaneOfFace {
892            id: remap_id_for_clone(source.id, entity_id_map),
893            face_id: remap_id_for_clone(source.face_id, entity_id_map),
894            code_ref: clone_code_ref.clone(),
895        }),
896        Artifact::StartSketchOnFace(source) => Artifact::StartSketchOnFace(StartSketchOnFace {
897            id: remap_id_for_clone(source.id, entity_id_map),
898            face_id: remap_id_for_clone(source.face_id, entity_id_map),
899            code_ref: clone_code_ref.clone(),
900        }),
901        Artifact::StartSketchOnPlane(source) => Artifact::StartSketchOnPlane(StartSketchOnPlane {
902            id: remap_id_for_clone(source.id, entity_id_map),
903            plane_id: remap_id_for_clone(source.plane_id, entity_id_map),
904            code_ref: clone_code_ref.clone(),
905        }),
906        Artifact::SketchBlock(source) => Artifact::SketchBlock(SketchBlock {
907            id: remap_id_for_clone(source.id, entity_id_map),
908            standard_plane: source.standard_plane,
909            plane_id: remap_opt_id_for_clone(source.plane_id, entity_id_map),
910            plane_info: source.plane_info.clone(),
911            path_id: remap_opt_id_for_clone(source.path_id, entity_id_map),
912            code_ref: clone_code_ref.clone(),
913            sketch_id: source.sketch_id,
914        }),
915        Artifact::SketchBlockConstraint(source) => Artifact::SketchBlockConstraint(SketchBlockConstraint {
916            id: remap_id_for_clone(source.id, entity_id_map),
917            sketch_id: source.sketch_id,
918            constraint_id: source.constraint_id,
919            constraint_type: source.constraint_type,
920            code_ref: clone_code_ref.clone(),
921        }),
922        Artifact::Sweep(source) => Artifact::Sweep(Sweep {
923            id: remap_id_for_clone(source.id, entity_id_map),
924            sub_type: source.sub_type,
925            path_id: remap_id_for_clone(source.path_id, entity_id_map),
926            surface_ids: remap_ids_for_clone(&source.surface_ids, entity_id_map),
927            edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
928            code_ref: clone_code_ref.clone(),
929            source_sweep_id: source.source_sweep_id.or(Some(source.id)),
930            trajectory_id: remap_opt_id_for_clone(source.trajectory_id, entity_id_map),
931            method: source.method,
932            consumed: if source.id == source_root_id {
933                false
934            } else {
935                source.consumed
936            },
937            pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
938        }),
939        Artifact::Wall(source) => Artifact::Wall(Wall {
940            id: remap_id_for_clone(source.id, entity_id_map),
941            seg_id: remap_id_for_clone(source.seg_id, entity_id_map),
942            edge_cut_edge_ids: remap_ids_for_clone(&source.edge_cut_edge_ids, entity_id_map),
943            sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
944            path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
945            face_code_ref: source.face_code_ref.clone(),
946            cmd_id: clone_cmd_id,
947        }),
948        Artifact::Cap(source) => Artifact::Cap(Cap {
949            id: remap_id_for_clone(source.id, entity_id_map),
950            sub_type: source.sub_type,
951            edge_cut_edge_ids: remap_ids_for_clone(&source.edge_cut_edge_ids, entity_id_map),
952            sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
953            path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
954            face_code_ref: source.face_code_ref.clone(),
955            cmd_id: clone_cmd_id,
956        }),
957        Artifact::SweepEdge(source) => Artifact::SweepEdge(SweepEdge {
958            id: remap_id_for_clone(source.id, entity_id_map),
959            sub_type: source.sub_type,
960            seg_id: remap_id_for_clone(source.seg_id, entity_id_map),
961            cmd_id: clone_cmd_id,
962            index: source.index,
963            sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
964            common_surface_ids: remap_ids_for_clone(&source.common_surface_ids, entity_id_map),
965        }),
966        Artifact::EdgeCut(source) => Artifact::EdgeCut(EdgeCut {
967            id: remap_id_for_clone(source.id, entity_id_map),
968            sub_type: source.sub_type,
969            consumed_edge_id: remap_id_for_clone(source.consumed_edge_id, entity_id_map),
970            edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
971            surface_id: remap_opt_id_for_clone(source.surface_id, entity_id_map),
972            code_ref: clone_code_ref.clone(),
973        }),
974        Artifact::EdgeCutEdge(source) => Artifact::EdgeCutEdge(EdgeCutEdge {
975            id: remap_id_for_clone(source.id, entity_id_map),
976            edge_cut_id: remap_id_for_clone(source.edge_cut_id, entity_id_map),
977            surface_id: remap_id_for_clone(source.surface_id, entity_id_map),
978        }),
979        Artifact::Helix(source) => Artifact::Helix(Helix {
980            id: remap_id_for_clone(source.id, entity_id_map),
981            axis_id: remap_opt_id_for_clone(source.axis_id, entity_id_map),
982            code_ref: clone_code_ref.clone(),
983            trajectory_sweep_id: remap_opt_id_for_clone(source.trajectory_sweep_id, entity_id_map),
984            consumed: if source.id == source_root_id {
985                false
986            } else {
987                source.consumed
988            },
989        }),
990        Artifact::ImportedGeometry(source) => Artifact::ImportedGeometry(ImportedGeometryArtifact {
991            id: remap_id_for_clone(source.id, entity_id_map),
992            code_ref: clone_code_ref.clone(),
993            consumed: if source.id == source_root_id {
994                false
995            } else {
996                source.consumed
997            },
998        }),
999        Artifact::GdtAnnotation(source) => Artifact::GdtAnnotation(GdtAnnotationArtifact {
1000            id: remap_id_for_clone(source.id, entity_id_map),
1001            code_ref: clone_code_ref.clone(),
1002            consumed: source.consumed,
1003        }),
1004        // A named view has no engine entity, so it can never appear in a
1005        // clone's id map, and `clone()` takes only a sketch, solid or imported
1006        // geometry as its source. This arm exists because the match is
1007        // exhaustive: returning the artifact unchanged makes it a no-op re-insert
1008        // rather than inventing a second view, which would carry a duplicate
1009        // name. The assertion is a tripwire if the assumption ever breaks.
1010        Artifact::NamedView(_) => {
1011            debug_assert!(false, "a named view is not reachable from a cloned body");
1012            artifact.clone()
1013        }
1014        Artifact::Pattern(source) => Artifact::Pattern(Pattern {
1015            id: remap_id_for_clone(source.id, entity_id_map),
1016            sub_type: source.sub_type,
1017            source_id: remap_id_for_clone(source.source_id, entity_id_map),
1018            copy_ids: remap_ids_for_clone(&source.copy_ids, entity_id_map),
1019            copy_face_ids: remap_ids_for_clone(&source.copy_face_ids, entity_id_map),
1020            copy_edge_ids: remap_ids_for_clone(&source.copy_edge_ids, entity_id_map),
1021            code_ref: clone_code_ref.clone(),
1022        }),
1023    }
1024}
1025
1026fn pattern_source_ids(artifacts: &IndexMap<ArtifactId, Artifact>, source_id: ArtifactId) -> Vec<ArtifactId> {
1027    let mut source_ids = vec![source_id];
1028
1029    if let Some(Artifact::Path(path)) = artifacts.get(&source_id) {
1030        if let Some(sweep_id) = path.sweep_id {
1031            source_ids.push(sweep_id);
1032        }
1033        if let Some(composite_solid_id) = path.composite_solid_id {
1034            source_ids.push(composite_solid_id);
1035        }
1036    }
1037
1038    for artifact in artifacts.values() {
1039        match artifact {
1040            Artifact::Sweep(sweep) if sweep.path_id == source_id => source_ids.push(sweep.id),
1041            Artifact::CompositeSolid(composite)
1042                if composite.solid_ids.contains(&source_id) || composite.tool_ids.contains(&source_id) =>
1043            {
1044                source_ids.push(composite.id)
1045            }
1046            _ => {}
1047        }
1048    }
1049
1050    let mut unique = Vec::new();
1051    merge_ids(&mut unique, source_ids);
1052    unique
1053}
1054
1055fn pattern_source_body_id_for_copy(
1056    artifacts: &IndexMap<ArtifactId, Artifact>,
1057    copy_id: ArtifactId,
1058) -> Option<ArtifactId> {
1059    artifacts.values().find_map(|artifact| {
1060        let Artifact::Pattern(pattern) = artifact else {
1061            return None;
1062        };
1063        if !pattern.copy_ids.contains(&copy_id) {
1064            return None;
1065        }
1066
1067        pattern_source_ids(artifacts, pattern.source_id).into_iter().find(|id| {
1068            matches!(
1069                artifacts.get(id),
1070                Some(Artifact::Sweep(_) | Artifact::CompositeSolid(_))
1071            )
1072        })
1073    })
1074}
1075
1076fn pattern_artifact_updates(
1077    artifacts: &IndexMap<ArtifactId, Artifact>,
1078    pattern_id: ArtifactId,
1079    sub_type: PatternSubType,
1080    source_id: ArtifactId,
1081    face_edge_infos: &[kcmc::output::FaceEdgeInfo],
1082    code_ref: CodeRef,
1083) -> Vec<Artifact> {
1084    let copy_ids = face_edge_infos
1085        .iter()
1086        .map(|info| ArtifactId::new(info.object_id))
1087        .collect::<Vec<_>>();
1088    let copy_face_ids = face_edge_infos
1089        .iter()
1090        .flat_map(|info| info.faces.iter().copied().map(ArtifactId::new))
1091        .collect::<Vec<_>>();
1092    let copy_edge_ids = face_edge_infos
1093        .iter()
1094        .flat_map(|info| info.edges.iter().copied().map(ArtifactId::new))
1095        .collect::<Vec<_>>();
1096
1097    let source_ids = pattern_source_ids(artifacts, source_id);
1098    let mut return_arr = vec![Artifact::Pattern(Pattern {
1099        id: pattern_id,
1100        sub_type,
1101        source_id,
1102        copy_ids,
1103        copy_face_ids,
1104        copy_edge_ids,
1105        code_ref,
1106    })];
1107
1108    for source_id in source_ids {
1109        let Some(artifact) = artifacts.get(&source_id) else {
1110            continue;
1111        };
1112        match artifact {
1113            Artifact::Path(path) => {
1114                let mut new_path = path.clone();
1115                new_path.pattern_ids = vec![pattern_id];
1116                return_arr.push(Artifact::Path(new_path));
1117            }
1118            Artifact::Sweep(sweep) => {
1119                let mut new_sweep = sweep.clone();
1120                new_sweep.pattern_ids = vec![pattern_id];
1121                return_arr.push(Artifact::Sweep(new_sweep));
1122            }
1123            Artifact::CompositeSolid(composite) => {
1124                let mut new_composite = composite.clone();
1125                new_composite.pattern_ids = vec![pattern_id];
1126                return_arr.push(Artifact::CompositeSolid(new_composite));
1127            }
1128            _ => {}
1129        }
1130    }
1131
1132    return_arr
1133}
1134
1135fn is_single_target_self_subtract(target_ids: &[Uuid], tool_ids: &[Uuid]) -> bool {
1136    target_ids.len() == 1 && tool_ids.len() == 1 && target_ids[0] == tool_ids[0]
1137}
1138
1139fn boolean_subtract_output_artifact_ids(
1140    cmd_id: ArtifactId,
1141    target_ids: &[Uuid],
1142    tool_ids: &[Uuid],
1143    extra_solid_ids: &[Uuid],
1144) -> Vec<ArtifactId> {
1145    if is_single_target_self_subtract(target_ids, tool_ids) {
1146        return Vec::new();
1147    }
1148
1149    let mut output_ids = if target_ids.len() == 1 {
1150        vec![cmd_id]
1151    } else {
1152        Vec::new()
1153    };
1154
1155    for extra_solid_id in extra_solid_ids {
1156        let artifact_id = ArtifactId::new(*extra_solid_id);
1157        if !output_ids.contains(&artifact_id) {
1158            output_ids.push(artifact_id);
1159        }
1160    }
1161
1162    output_ids
1163}
1164
1165fn update_consumed_csg_sweep(
1166    return_arr: &mut Vec<Artifact>,
1167    artifacts: &IndexMap<ArtifactId, Artifact>,
1168    sweep_id: ArtifactId,
1169    consumed_sweep_ids: &mut AHashSet<ArtifactId>,
1170) {
1171    if consumed_sweep_ids.insert(sweep_id)
1172        && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
1173    {
1174        let mut new_sweep = sweep.clone();
1175        new_sweep.consumed = true;
1176        return_arr.push(Artifact::Sweep(new_sweep));
1177    }
1178}
1179
1180fn mark_artifact_consumed_by_id(
1181    return_arr: &mut Vec<Artifact>,
1182    artifacts: &IndexMap<ArtifactId, Artifact>,
1183    artifact_id: ArtifactId,
1184    consumed_ids: &mut AHashSet<ArtifactId>,
1185) {
1186    let already_marked_as_consumed = !consumed_ids.insert(artifact_id);
1187    if already_marked_as_consumed {
1188        return;
1189    }
1190
1191    let Some(artifact) = artifacts.get(&artifact_id) else {
1192        return;
1193    };
1194
1195    match artifact {
1196        Artifact::CompositeSolid(composite) => {
1197            let mut new_composite = composite.clone();
1198            new_composite.consumed = true;
1199            return_arr.push(Artifact::CompositeSolid(new_composite));
1200        }
1201        Artifact::Path(path) => {
1202            let mut new_path = path.clone();
1203            new_path.consumed = true;
1204            return_arr.push(Artifact::Path(new_path));
1205
1206            if let Some(sweep_id) = path.sweep_id {
1207                mark_artifact_consumed_by_id(return_arr, artifacts, sweep_id, consumed_ids);
1208            }
1209            if let Some(composite_solid_id) = path.composite_solid_id {
1210                mark_artifact_consumed_by_id(return_arr, artifacts, composite_solid_id, consumed_ids);
1211            }
1212        }
1213        Artifact::Sweep(sweep) => {
1214            let mut new_sweep = sweep.clone();
1215            new_sweep.consumed = true;
1216            return_arr.push(Artifact::Sweep(new_sweep));
1217        }
1218        Artifact::Helix(helix) => {
1219            let mut new_helix = helix.clone();
1220            new_helix.consumed = true;
1221            return_arr.push(Artifact::Helix(new_helix));
1222        }
1223        Artifact::ImportedGeometry(imported_geometry) => {
1224            let mut new_imported_geometry = imported_geometry.clone();
1225            new_imported_geometry.consumed = true;
1226            return_arr.push(Artifact::ImportedGeometry(new_imported_geometry));
1227        }
1228        Artifact::GdtAnnotation(annotation) => {
1229            let mut new_annotation = annotation.clone();
1230            new_annotation.consumed = true;
1231            return_arr.push(Artifact::GdtAnnotation(new_annotation));
1232        }
1233        _ => {}
1234    }
1235}
1236
1237fn mark_deleted_artifacts_consumed(
1238    artifacts: &IndexMap<ArtifactId, Artifact>,
1239    object_ids: &std::collections::HashSet<Uuid>,
1240) -> Vec<Artifact> {
1241    let mut return_arr = Vec::new();
1242    let mut consumed_ids = AHashSet::default();
1243
1244    // The order of iteration doesn't matter here, as all artifacts get marked as consumed.
1245    // Also the set comes from the API crate which uses HashSet.
1246    #[allow(clippy::iter_over_hash_type)]
1247    for object_id in object_ids {
1248        let artifact_id = ArtifactId::new(*object_id);
1249        mark_artifact_consumed_by_id(&mut return_arr, artifacts, artifact_id, &mut consumed_ids);
1250    }
1251
1252    return_arr
1253}
1254
1255fn update_csg_input_artifacts(
1256    return_arr: &mut Vec<Artifact>,
1257    artifacts: &IndexMap<ArtifactId, Artifact>,
1258    input_ids: &[ArtifactId],
1259    composite_solid_id: Option<ArtifactId>,
1260    consumed_sweep_ids: &mut AHashSet<ArtifactId>,
1261) {
1262    for input_id in input_ids {
1263        if let Some(artifact) = artifacts.get(input_id) {
1264            match artifact {
1265                Artifact::CompositeSolid(comp) => {
1266                    let mut new_comp = comp.clone();
1267                    new_comp.composite_solid_id = composite_solid_id;
1268                    new_comp.consumed = true;
1269                    return_arr.push(Artifact::CompositeSolid(new_comp));
1270                }
1271                Artifact::Path(path) => {
1272                    let mut new_path = path.clone();
1273                    new_path.composite_solid_id = composite_solid_id;
1274
1275                    // We want to mark any sweeps of the path used in this operation
1276                    // as consumed. The path itself is already consumed by sweeping.
1277                    if let Some(sweep_id) = new_path.sweep_id {
1278                        update_consumed_csg_sweep(return_arr, artifacts, sweep_id, consumed_sweep_ids);
1279                    }
1280
1281                    return_arr.push(Artifact::Path(new_path));
1282                }
1283                Artifact::Sweep(sweep) => {
1284                    update_consumed_csg_sweep(return_arr, artifacts, sweep.id, consumed_sweep_ids);
1285                }
1286                _ => {}
1287            }
1288        }
1289    }
1290}
1291
1292fn mirror_3d_artifact_updates(
1293    artifacts: &IndexMap<ArtifactId, Artifact>,
1294    original_solid_ids: &[Uuid],
1295    face_edge_infos: &[kcmc::output::FaceEdgeInfo],
1296    code_ref: CodeRef,
1297    range: SourceRange,
1298    cmd: &ModelingCmd,
1299) -> Result<Vec<Artifact>, KclError> {
1300    if original_solid_ids.len() != face_edge_infos.len() {
1301        internal_error!(
1302            range,
1303            "EntityMirrorAcross response has different number face edge info than original mirrored solids: cmd={cmd:?}, face_edge_infos={face_edge_infos:?}"
1304        );
1305    }
1306
1307    let mut return_arr = Vec::new();
1308    for (face_edge_info, original_solid_id) in face_edge_infos.iter().zip(original_solid_ids) {
1309        let original_solid_id = ArtifactId::new(*original_solid_id);
1310        let mirrored_solid_id = ArtifactId::new(face_edge_info.object_id);
1311        let source_solid = match artifacts.get(&original_solid_id) {
1312            Some(Artifact::Path(path)) => path.sweep_id.and_then(|sweep_id| artifacts.get(&sweep_id)).or_else(|| {
1313                path.composite_solid_id
1314                    .and_then(|composite_id| artifacts.get(&composite_id))
1315            }),
1316            source => source,
1317        };
1318        match source_solid {
1319            Some(Artifact::Sweep(sweep)) => {
1320                let mut mirrored_sweep = sweep.clone();
1321                mirrored_sweep.id = mirrored_solid_id;
1322                mirrored_sweep.surface_ids = face_edge_info.faces.iter().copied().map(ArtifactId::new).collect();
1323                mirrored_sweep.edge_ids = face_edge_info.edges.iter().copied().map(ArtifactId::new).collect();
1324                mirrored_sweep.code_ref = code_ref.clone();
1325                mirrored_sweep.consumed = false;
1326                mirrored_sweep.pattern_ids = Vec::new();
1327                return_arr.push(Artifact::Sweep(mirrored_sweep));
1328            }
1329            Some(Artifact::CompositeSolid(composite)) => {
1330                let mut mirrored_composite = composite.clone();
1331                mirrored_composite.id = mirrored_solid_id;
1332                mirrored_composite.code_ref = code_ref.clone();
1333                mirrored_composite.consumed = false;
1334                mirrored_composite.composite_solid_id = None;
1335                mirrored_composite.pattern_ids = Vec::new();
1336                return_arr.push(Artifact::CompositeSolid(mirrored_composite));
1337            }
1338            Some(_) | None => continue,
1339        }
1340    }
1341
1342    Ok(return_arr)
1343}
1344
1345#[allow(clippy::too_many_arguments)]
1346fn artifacts_to_update(
1347    artifacts: &IndexMap<ArtifactId, Artifact>,
1348    artifact_command: &ArtifactCommand,
1349    responses: &AHashMap<Uuid, OkModelingCmdResponse>,
1350    entity_clone_id_maps: &AHashMap<Uuid, AHashMap<ArtifactId, ArtifactId>>,
1351    path_to_plane_id_map: &AHashMap<Uuid, Uuid>,
1352    programs: &crate::execution::ProgramLookup,
1353    cached_body_items: usize,
1354    exec_artifacts: &IndexMap<ArtifactId, Artifact>,
1355    import_code_refs: &AHashMap<ModuleId, ImportCodeRef>,
1356) -> Result<Vec<Artifact>, KclError> {
1357    let uuid = artifact_command.cmd_id;
1358    let response = responses.get(&uuid);
1359
1360    // TODO: Build path-to-node from artifact_command source range.  Right now,
1361    // we're serializing an empty array, and the TS wrapper fills it in with the
1362    // correct value based on NodePath.
1363    let path_to_node = Vec::new();
1364    let range = artifact_command.range;
1365    let (code_ref_range, node_path) = code_ref_for_range(programs, cached_body_items, range, import_code_refs);
1366    let code_ref = CodeRef {
1367        range: code_ref_range,
1368        node_path,
1369        path_to_node,
1370    };
1371
1372    let id = ArtifactId::new(uuid);
1373    let cmd = &artifact_command.command;
1374
1375    match cmd {
1376        ModelingCmd::ImportFiles(_) => {
1377            return Ok(vec![Artifact::ImportedGeometry(ImportedGeometryArtifact {
1378                id,
1379                code_ref,
1380                consumed: false,
1381            })]);
1382        }
1383        ModelingCmd::MakePlane(_) => {
1384            if range.is_synthetic() {
1385                return Ok(Vec::new());
1386            }
1387            // If we're calling `make_plane` and the code range doesn't end at
1388            // `0` it's not a default plane, but a custom one from the
1389            // offsetPlane standard library function.
1390            return Ok(vec![Artifact::Plane(Plane {
1391                id,
1392                path_ids: Vec::new(),
1393                code_ref,
1394            })]);
1395        }
1396        ModelingCmd::FaceIsPlanar(FaceIsPlanar { object_id, .. }) => {
1397            return Ok(vec![Artifact::PlaneOfFace(PlaneOfFace {
1398                id,
1399                face_id: object_id.into(),
1400                code_ref,
1401            })]);
1402        }
1403        ModelingCmd::RemoveSceneObjects(remove) => {
1404            return Ok(mark_deleted_artifacts_consumed(artifacts, &remove.object_ids));
1405        }
1406        ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) => {
1407            let existing_plane = artifacts.get(&ArtifactId::new(*entity_id));
1408            match existing_plane {
1409                Some(Artifact::Wall(wall)) => {
1410                    return Ok(vec![Artifact::Wall(Wall {
1411                        id: entity_id.into(),
1412                        seg_id: wall.seg_id,
1413                        edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1414                        sweep_id: wall.sweep_id,
1415                        path_ids: wall.path_ids.clone(),
1416                        face_code_ref: wall.face_code_ref.clone(),
1417                        cmd_id: artifact_command.cmd_id,
1418                    })]);
1419                }
1420                Some(Artifact::Cap(cap)) => {
1421                    return Ok(vec![Artifact::Cap(Cap {
1422                        id: entity_id.into(),
1423                        sub_type: cap.sub_type,
1424                        edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1425                        sweep_id: cap.sweep_id,
1426                        path_ids: cap.path_ids.clone(),
1427                        face_code_ref: cap.face_code_ref.clone(),
1428                        cmd_id: artifact_command.cmd_id,
1429                    })]);
1430                }
1431                Some(_) | None => {
1432                    let path_ids = match existing_plane {
1433                        Some(Artifact::Plane(Plane { path_ids, .. })) => path_ids.clone(),
1434                        _ => Vec::new(),
1435                    };
1436                    // Create an entirely new plane
1437                    return Ok(vec![Artifact::Plane(Plane {
1438                        id: entity_id.into(),
1439                        path_ids,
1440                        code_ref,
1441                    })]);
1442                }
1443            }
1444        }
1445        ModelingCmd::StartPath(_) => {
1446            let mut return_arr = Vec::new();
1447            let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
1448                KclError::new_internal(KclErrorDetails::new(
1449                    format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"),
1450                    vec![range],
1451                ))
1452            })?;
1453            let sketch_block_id = exec_artifacts
1454                .values()
1455                .find(|a| {
1456                    if let Artifact::SketchBlock(s) = a {
1457                        if let Some(path_id) = s.path_id {
1458                            path_id == id
1459                        } else {
1460                            false
1461                        }
1462                    } else {
1463                        false
1464                    }
1465                })
1466                .map(|a| a.id());
1467            return_arr.push(Artifact::Path(Path {
1468                id,
1469                sub_type: PathSubType::Sketch,
1470                plane_id: (*current_plane_id).into(),
1471                seg_ids: Vec::new(),
1472                sweep_id: None,
1473                trajectory_sweep_id: None,
1474                solid2d_id: None,
1475                code_ref,
1476                composite_solid_id: None,
1477                sketch_block_id,
1478                origin_path_id: None,
1479                inner_path_id: None,
1480                outer_path_id: None,
1481                pattern_ids: Vec::new(),
1482                consumed: false,
1483            }));
1484            let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
1485            if let Some(Artifact::Plane(plane)) = plane {
1486                let plane_code_ref = plane.code_ref.clone();
1487                return_arr.push(Artifact::Plane(Plane {
1488                    id: (*current_plane_id).into(),
1489                    path_ids: vec![id],
1490                    code_ref: plane_code_ref,
1491                }));
1492            }
1493            if let Some(Artifact::Wall(wall)) = plane {
1494                return_arr.push(Artifact::Wall(Wall {
1495                    id: (*current_plane_id).into(),
1496                    seg_id: wall.seg_id,
1497                    edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1498                    sweep_id: wall.sweep_id,
1499                    path_ids: vec![id],
1500                    face_code_ref: wall.face_code_ref.clone(),
1501                    cmd_id: artifact_command.cmd_id,
1502                }));
1503            }
1504            if let Some(Artifact::Cap(cap)) = plane {
1505                return_arr.push(Artifact::Cap(Cap {
1506                    id: (*current_plane_id).into(),
1507                    sub_type: cap.sub_type,
1508                    edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1509                    sweep_id: cap.sweep_id,
1510                    path_ids: vec![id],
1511                    face_code_ref: cap.face_code_ref.clone(),
1512                    cmd_id: artifact_command.cmd_id,
1513                }));
1514            }
1515            return Ok(return_arr);
1516        }
1517        ModelingCmd::ClosePath(_) | ModelingCmd::ExtendPath(_) => {
1518            let path_id = ArtifactId::new(match cmd {
1519                ModelingCmd::ClosePath(c) => c.path_id,
1520                ModelingCmd::ExtendPath(e) => e.path.into(),
1521                _ => internal_error!(
1522                    range,
1523                    "Close or extend path command variant not handled: id={id:?}, cmd={cmd:?}"
1524                ),
1525            });
1526            let mut return_arr = Vec::new();
1527            return_arr.push(Artifact::Segment(Segment {
1528                id,
1529                path_id,
1530                source_segment_id: None,
1531                original_seg_id: None,
1532                surface_id: None,
1533                edge_ids: Vec::new(),
1534                edge_cut_id: None,
1535                code_ref,
1536                common_surface_ids: Vec::new(),
1537            }));
1538            let path = artifacts.get(&path_id);
1539            if let Some(Artifact::Path(path)) = path {
1540                let mut new_path = path.clone();
1541                new_path.seg_ids = vec![id];
1542                return_arr.push(Artifact::Path(new_path));
1543            }
1544            if let Some(OkModelingCmdResponse::ClosePath(close_path)) = response {
1545                return_arr.push(Artifact::Solid2d(Solid2d {
1546                    id: close_path.face_id.into(),
1547                    path_id,
1548                }));
1549                if let Some(Artifact::Path(path)) = path {
1550                    let mut new_path = path.clone();
1551                    new_path.solid2d_id = Some(close_path.face_id.into());
1552                    return_arr.push(Artifact::Path(new_path));
1553                }
1554            }
1555            return Ok(return_arr);
1556        }
1557        ModelingCmd::CreateRegion(kcmc::CreateRegion {
1558            object_id: origin_path_id,
1559            ..
1560        })
1561        | ModelingCmd::CreateRegionFromQueryPoint(kcmc::CreateRegionFromQueryPoint {
1562            object_id: origin_path_id,
1563            ..
1564        }) => {
1565            let mut return_arr = Vec::new();
1566            let origin_path = artifacts.get(&ArtifactId::new(*origin_path_id));
1567            let Some(Artifact::Path(path)) = origin_path else {
1568                internal_error!(
1569                    range,
1570                    "Expected to find an existing path for the origin path of CreateRegion or CreateRegionFromQueryPoint command, but found none: origin_path={origin_path:?}, cmd={cmd:?}"
1571                );
1572            };
1573            let region_path = |seg_ids, code_ref| {
1574                Artifact::Path(Path {
1575                    id,
1576                    sub_type: PathSubType::Region,
1577                    plane_id: path.plane_id,
1578                    seg_ids,
1579                    consumed: false,
1580                    sweep_id: None,
1581                    trajectory_sweep_id: None,
1582                    solid2d_id: None,
1583                    code_ref,
1584                    composite_solid_id: None,
1585                    sketch_block_id: None,
1586                    origin_path_id: Some(ArtifactId::new(*origin_path_id)),
1587                    inner_path_id: None,
1588                    outer_path_id: None,
1589                    pattern_ids: Vec::new(),
1590                })
1591            };
1592            // If we have a response, we can also create the segments in the
1593            // region.
1594            let Some(
1595                OkModelingCmdResponse::CreateRegion(kcmc::output::CreateRegion { region_mapping, .. })
1596                | OkModelingCmdResponse::CreateRegionFromQueryPoint(kcmc::output::CreateRegionFromQueryPoint {
1597                    region_mapping,
1598                    ..
1599                }),
1600            ) = response
1601            else {
1602                return_arr.push(region_path(Vec::new(), code_ref));
1603                return Ok(return_arr);
1604            };
1605            // Each key is a segment in the region. The value is the segment in
1606            // the original path. Build the reverse mapping.
1607            let original_segment_ids = path.seg_ids.iter().map(Uuid::from).collect::<Vec<_>>();
1608            let reverse = build_reverse_region_mapping(region_mapping, &original_segment_ids);
1609            let region_segment_ids = reverse
1610                .values()
1611                .flat_map(|region_segment_ids| region_segment_ids.iter().copied())
1612                .map(ArtifactId::new)
1613                .collect::<Vec<_>>();
1614            return_arr.push(region_path(region_segment_ids, code_ref.clone()));
1615            for (original_segment_id, region_segment_ids) in reverse.iter() {
1616                for segment_id in region_segment_ids {
1617                    return_arr.push(Artifact::Segment(Segment {
1618                        id: ArtifactId::new(*segment_id),
1619                        path_id: id,
1620                        source_segment_id: None,
1621                        original_seg_id: Some(ArtifactId::new(*original_segment_id)),
1622                        surface_id: None,
1623                        edge_ids: Vec::new(),
1624                        edge_cut_id: None,
1625                        code_ref: code_ref.clone(),
1626                        common_surface_ids: Vec::new(),
1627                    }))
1628                }
1629            }
1630            return Ok(return_arr);
1631        }
1632        ModelingCmd::Solid3dGetFaceUuid(kcmc::Solid3dGetFaceUuid { object_id, .. }) => {
1633            let Some(OkModelingCmdResponse::Solid3dGetFaceUuid(face_uuid)) = response else {
1634                return Ok(Vec::new());
1635            };
1636
1637            return Ok(vec![Artifact::PrimitiveFace(PrimitiveFace {
1638                id: face_uuid.face_id.into(),
1639                solid_id: (*object_id).into(),
1640                code_ref,
1641            })]);
1642        }
1643        ModelingCmd::Solid3dGetEdgeUuid(kcmc::Solid3dGetEdgeUuid { object_id, .. }) => {
1644            let Some(OkModelingCmdResponse::Solid3dGetEdgeUuid(edge_uuid)) = response else {
1645                return Ok(Vec::new());
1646            };
1647
1648            return Ok(vec![Artifact::PrimitiveEdge(PrimitiveEdge {
1649                id: edge_uuid.edge_id.into(),
1650                solid_id: (*object_id).into(),
1651                code_ref,
1652            })]);
1653        }
1654        ModelingCmd::EntityLinearPatternTransform(pattern_cmd) => {
1655            let face_edge_infos = match response {
1656                Some(OkModelingCmdResponse::EntityLinearPatternTransform(resp)) => resp.entity_face_edge_ids.as_slice(),
1657                _ => &[],
1658            };
1659            return Ok(pattern_artifact_updates(
1660                artifacts,
1661                id,
1662                PatternSubType::Transform,
1663                ArtifactId::new(pattern_cmd.entity_id),
1664                face_edge_infos,
1665                code_ref,
1666            ));
1667        }
1668        ModelingCmd::EntityLinearPattern(pattern_cmd) => {
1669            let face_edge_infos = match response {
1670                Some(OkModelingCmdResponse::EntityLinearPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
1671                _ => &[],
1672            };
1673            return Ok(pattern_artifact_updates(
1674                artifacts,
1675                id,
1676                PatternSubType::Linear,
1677                ArtifactId::new(pattern_cmd.entity_id),
1678                face_edge_infos,
1679                code_ref,
1680            ));
1681        }
1682        ModelingCmd::EntityCircularPattern(pattern_cmd) => {
1683            let face_edge_infos = match response {
1684                Some(OkModelingCmdResponse::EntityCircularPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
1685                _ => &[],
1686            };
1687            return Ok(pattern_artifact_updates(
1688                artifacts,
1689                id,
1690                PatternSubType::Circular,
1691                ArtifactId::new(pattern_cmd.entity_id),
1692                face_edge_infos,
1693                code_ref,
1694            ));
1695        }
1696        ModelingCmd::EntityMirrorAcross(kcmc::EntityMirrorAcross {
1697            ids: original_solid_ids,
1698            ..
1699        }) => {
1700            let face_edge_infos = match response {
1701                Some(OkModelingCmdResponse::EntityMirrorAcross(resp)) => resp.entity_face_edge_ids.as_slice(),
1702                // A rejected modeling command has no response. Execution will
1703                // report the engine error; there is no mirrored artifact to add.
1704                None => return Ok(Vec::new()),
1705                Some(_) => internal_error!(
1706                    range,
1707                    "EntityMirrorAcross response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
1708                ),
1709            };
1710            return mirror_3d_artifact_updates(artifacts, original_solid_ids, face_edge_infos, code_ref, range, cmd);
1711        }
1712        ModelingCmd::EntityMirror(kcmc::EntityMirror {
1713            ids: original_path_ids, ..
1714        })
1715        | ModelingCmd::EntityMirrorAcrossEdge(kcmc::EntityMirrorAcrossEdge {
1716            ids: original_path_ids, ..
1717        }) => {
1718            let face_edge_infos = match response {
1719                Some(OkModelingCmdResponse::EntityMirror(resp)) => &resp.entity_face_edge_ids,
1720                Some(OkModelingCmdResponse::EntityMirrorAcrossEdge(resp)) => &resp.entity_face_edge_ids,
1721                _ => internal_error!(
1722                    range,
1723                    "Mirror response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
1724                ),
1725            };
1726            if original_path_ids.len() != face_edge_infos.len() {
1727                internal_error!(
1728                    range,
1729                    "EntityMirror or EntityMirrorAcrossEdge response has different number face edge info than original mirrored paths: id={id:?}, cmd={cmd:?}, response={response:?}"
1730                );
1731            }
1732            let mut return_arr = Vec::new();
1733            for (face_edge_info, original_path_id) in face_edge_infos.iter().zip(original_path_ids) {
1734                let original_path_id = ArtifactId::new(*original_path_id);
1735                let path_id = ArtifactId::new(face_edge_info.object_id);
1736                // The path may be an existing path that was extended or a new
1737                // path.
1738                let mut path = if let Some(Artifact::Path(path)) = artifacts.get(&path_id) {
1739                    // Existing path.
1740                    path.clone()
1741                } else {
1742                    // It's a new path.  We need the original path to get some
1743                    // of its info.
1744                    let Some(Artifact::Path(original_path)) = artifacts.get(&original_path_id) else {
1745                        // We couldn't find the original path. This is a bug.
1746                        internal_error!(
1747                            range,
1748                            "Couldn't find original path for mirror2d: original_path_id={original_path_id:?}, cmd={cmd:?}"
1749                        );
1750                    };
1751                    Path {
1752                        id: path_id,
1753                        sub_type: original_path.sub_type,
1754                        plane_id: original_path.plane_id,
1755                        seg_ids: Vec::new(),
1756                        sweep_id: None,
1757                        trajectory_sweep_id: None,
1758                        solid2d_id: None,
1759                        code_ref: code_ref.clone(),
1760                        composite_solid_id: None,
1761                        sketch_block_id: None,
1762                        origin_path_id: original_path.origin_path_id,
1763                        inner_path_id: None,
1764                        outer_path_id: None,
1765                        pattern_ids: Vec::new(),
1766                        consumed: false,
1767                    }
1768                };
1769
1770                face_edge_info.edges.iter().for_each(|edge_id| {
1771                    let edge_id = ArtifactId::new(*edge_id);
1772                    return_arr.push(Artifact::Segment(Segment {
1773                        id: edge_id,
1774                        path_id: path.id,
1775                        source_segment_id: None,
1776                        original_seg_id: None,
1777                        surface_id: None,
1778                        edge_ids: Vec::new(),
1779                        edge_cut_id: None,
1780                        code_ref: code_ref.clone(),
1781                        common_surface_ids: Vec::new(),
1782                    }));
1783                    // Add the edge ID to the path.
1784                    path.seg_ids.push(edge_id);
1785                });
1786
1787                return_arr.push(Artifact::Path(path));
1788            }
1789            return Ok(return_arr);
1790        }
1791        ModelingCmd::EntityClone(kcmc::EntityClone { entity_id, .. }) => {
1792            let source_entity_id = ArtifactId::new(*entity_id);
1793            let entity_clone_info = artifact_command.entity_clone_info;
1794            let source_id = entity_clone_info
1795                .map(|info| info.source_artifact_id)
1796                .unwrap_or(source_entity_id);
1797            let result_id = entity_clone_info.map(|info| info.result_artifact_id).unwrap_or(id);
1798
1799            // Only solid clones provide this extra body identity. Without
1800            // this gate, cloning a lazy 2D pattern copy can resolve through
1801            // its source Path to a Sweep and fabricate a body artifact.
1802            let pattern_source_body_id = if entity_clone_info.is_some() && !artifacts.contains_key(&source_id) {
1803                pattern_source_body_id_for_copy(artifacts, source_id)
1804            } else {
1805                None
1806            };
1807            let source_artifact_id = pattern_source_body_id.unwrap_or(source_id);
1808            let Some(source_artifact) = artifacts.get(&source_artifact_id) else {
1809                return Ok(Vec::new());
1810            };
1811
1812            let mut entity_id_map = entity_clone_id_maps.get(&uuid).cloned().unwrap_or_default();
1813            entity_id_map.insert(source_entity_id, id);
1814            if let Some(info) = entity_clone_info {
1815                entity_id_map.insert(info.source_topology_id, id);
1816            }
1817            entity_id_map.insert(source_id, result_id);
1818            entity_id_map.insert(source_artifact_id, result_id);
1819            if matches!(source_artifact, Artifact::CompositeSolid(_)) {
1820                add_composite_sweep_clone_id_mappings(artifacts, artifact_command.cmd_id, &mut entity_id_map);
1821            }
1822
1823            let mut cloned_artifacts = Vec::new();
1824            cloned_artifacts.push(remap_artifact_for_clone(
1825                source_artifact,
1826                &entity_id_map,
1827                &code_ref,
1828                artifact_command.cmd_id,
1829                source_artifact_id,
1830            ));
1831
1832            for artifact in artifacts.values() {
1833                let artifact_id = artifact.id();
1834                if artifact_id == source_artifact_id || !entity_id_map.contains_key(&artifact_id) {
1835                    continue;
1836                }
1837                cloned_artifacts.push(remap_artifact_for_clone(
1838                    artifact,
1839                    &entity_id_map,
1840                    &code_ref,
1841                    artifact_command.cmd_id,
1842                    source_artifact_id,
1843                ));
1844            }
1845
1846            return Ok(cloned_artifacts);
1847        }
1848        ModelingCmd::Extrude(_)
1849        | ModelingCmd::TwistExtrude(_)
1850        | ModelingCmd::Revolve(_)
1851        | ModelingCmd::RevolveAboutEdge(_)
1852        | ModelingCmd::ExtrudeToReference(_) => {
1853            let target = match cmd {
1854                ModelingCmd::Extrude(kcmc::Extrude {
1855                    target: Some(target), ..
1856                }) => cmd_id_ref_to_artifact_id(target),
1857                ModelingCmd::Extrude(kcmc::Extrude {
1858                    target: None,
1859                    target_reference: Some(_),
1860                    ..
1861                }) => return Ok(Vec::new()),
1862                ModelingCmd::Extrude(kcmc::Extrude { target: None, .. }) => return Ok(Vec::new()),
1863                ModelingCmd::TwistExtrude(kcmc::TwistExtrude { target, .. })
1864                | ModelingCmd::Revolve(kcmc::Revolve { target, .. })
1865                | ModelingCmd::RevolveAboutEdge(kcmc::RevolveAboutEdge { target, .. }) => {
1866                    cmd_id_ref_to_artifact_id(target)
1867                }
1868                ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference {
1869                    target: Some(target), ..
1870                }) => cmd_id_ref_to_artifact_id(target),
1871                ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { target: None, .. }) => {
1872                    return Ok(Vec::new());
1873                }
1874                _ => internal_error!(range, "Sweep-like command variant not handled: id={id:?}, cmd={cmd:?}"),
1875            };
1876            // Determine the resulting method from the specific command, if provided
1877            let method = match cmd {
1878                ModelingCmd::Extrude(kcmc::Extrude { extrude_method, .. }) => *extrude_method,
1879                ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { extrude_method, .. }) => *extrude_method,
1880                // TwistExtrude and Sweep don't carry method in the command; treat as Merge
1881                ModelingCmd::TwistExtrude(_) | ModelingCmd::Sweep(_) => {
1882                    kittycad_modeling_cmds::shared::ExtrudeMethod::Merge
1883                }
1884                // Revolve variants behave like New bodies in std layer
1885                ModelingCmd::Revolve(_) | ModelingCmd::RevolveAboutEdge(_) => {
1886                    kittycad_modeling_cmds::shared::ExtrudeMethod::New
1887                }
1888                _ => kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
1889            };
1890            let method = artifact_sweep_method(method);
1891            let sub_type = match cmd {
1892                ModelingCmd::Extrude(_) => SweepSubType::Extrusion,
1893                ModelingCmd::ExtrudeToReference(_) => SweepSubType::Extrusion,
1894                ModelingCmd::TwistExtrude(_) => SweepSubType::ExtrusionTwist,
1895                ModelingCmd::Revolve(_) => SweepSubType::Revolve,
1896                ModelingCmd::RevolveAboutEdge(_) => SweepSubType::RevolveAboutEdge,
1897                _ => internal_error!(range, "Sweep-like command variant not handled: id={id:?}, cmd={cmd:?}",),
1898            };
1899            let mut return_arr = Vec::new();
1900            return_arr.push(Artifact::Sweep(Sweep {
1901                id,
1902                sub_type,
1903                path_id: target,
1904                surface_ids: Vec::new(),
1905                edge_ids: Vec::new(),
1906                code_ref,
1907                source_sweep_id: None,
1908                trajectory_id: None,
1909                method,
1910                consumed: false,
1911                pattern_ids: Vec::new(),
1912            }));
1913            let path = artifacts.get(&target);
1914            if let Some(Artifact::Path(path)) = path {
1915                let mut new_path = path.clone();
1916                new_path.sweep_id = Some(id);
1917                new_path.consumed = true;
1918                return_arr.push(Artifact::Path(new_path));
1919                if let Some(inner_path_id) = path.inner_path_id
1920                    && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
1921                    && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
1922                {
1923                    inner_path_artifact.sweep_id = Some(id);
1924                    inner_path_artifact.consumed = true;
1925                    return_arr.push(Artifact::Path(inner_path_artifact))
1926                }
1927            }
1928            return Ok(return_arr);
1929        }
1930        ModelingCmd::Sweep(kcmc::Sweep { target, trajectory, .. }) => {
1931            // Determine the resulting method from the specific command, if provided
1932            let method = ArtifactSweepMethod::Merge;
1933            let sub_type = SweepSubType::Sweep;
1934            let mut return_arr = Vec::new();
1935            let target = cmd_id_ref_to_artifact_id(target);
1936            let trajectory = cmd_id_ref_to_artifact_id(trajectory);
1937            return_arr.push(Artifact::Sweep(Sweep {
1938                id,
1939                sub_type,
1940                path_id: target,
1941                surface_ids: Vec::new(),
1942                edge_ids: Vec::new(),
1943                code_ref,
1944                source_sweep_id: None,
1945                trajectory_id: Some(trajectory),
1946                method,
1947                consumed: false,
1948                pattern_ids: Vec::new(),
1949            }));
1950            let path = artifacts.get(&target);
1951            if let Some(Artifact::Path(path)) = path {
1952                let mut new_path = path.clone();
1953                new_path.sweep_id = Some(id);
1954                new_path.consumed = true;
1955                return_arr.push(Artifact::Path(new_path));
1956                if let Some(inner_path_id) = path.inner_path_id
1957                    && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
1958                    && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
1959                {
1960                    inner_path_artifact.sweep_id = Some(id);
1961                    inner_path_artifact.consumed = true;
1962                    return_arr.push(Artifact::Path(inner_path_artifact))
1963                }
1964            }
1965            if let Some(trajectory_artifact) = artifacts.get(&trajectory) {
1966                match trajectory_artifact {
1967                    Artifact::Path(path) => {
1968                        let mut new_path = path.clone();
1969                        new_path.trajectory_sweep_id = Some(id);
1970                        new_path.consumed = true;
1971                        return_arr.push(Artifact::Path(new_path));
1972                    }
1973                    Artifact::Helix(helix) => {
1974                        let mut new_helix = helix.clone();
1975                        new_helix.trajectory_sweep_id = Some(id);
1976                        new_helix.consumed = true;
1977                        return_arr.push(Artifact::Helix(new_helix));
1978                    }
1979                    _ => {}
1980                }
1981            };
1982            return Ok(return_arr);
1983        }
1984        ModelingCmd::SurfaceBlend(surface_blend_cmd) => {
1985            let surface_id_to_path_id = |surface_id: ArtifactId| -> Option<ArtifactId> {
1986                match artifacts.get(&surface_id) {
1987                    Some(Artifact::Path(path)) => Some(path.id),
1988                    Some(Artifact::Segment(segment)) => Some(segment.path_id),
1989                    Some(Artifact::Sweep(sweep)) => Some(sweep.path_id),
1990                    Some(Artifact::Wall(wall)) => artifacts.get(&wall.sweep_id).and_then(|artifact| match artifact {
1991                        Artifact::Sweep(sweep) => Some(sweep.path_id),
1992                        _ => None,
1993                    }),
1994                    Some(Artifact::Cap(cap)) => artifacts.get(&cap.sweep_id).and_then(|artifact| match artifact {
1995                        Artifact::Sweep(sweep) => Some(sweep.path_id),
1996                        _ => None,
1997                    }),
1998                    _ => None,
1999                }
2000            };
2001            let Some(first_surface_ref) = surface_blend_cmd.surfaces.first() else {
2002                internal_error!(range, "SurfaceBlend command has no surfaces: id={id:?}, cmd={cmd:?}");
2003            };
2004            let first_surface_id = ArtifactId::new(first_surface_ref.object_id);
2005            let path_id = surface_id_to_path_id(first_surface_id).unwrap_or(first_surface_id);
2006            let trajectory_id = surface_blend_cmd
2007                .surfaces
2008                .get(1)
2009                .map(|surface| ArtifactId::new(surface.object_id))
2010                .and_then(surface_id_to_path_id);
2011            let return_arr = vec![Artifact::Sweep(Sweep {
2012                id,
2013                sub_type: SweepSubType::Blend,
2014                path_id,
2015                surface_ids: Vec::new(),
2016                edge_ids: Vec::new(),
2017                code_ref,
2018                source_sweep_id: None,
2019                trajectory_id,
2020                method: ArtifactSweepMethod::New,
2021                consumed: false,
2022                pattern_ids: Vec::new(),
2023            })];
2024            return Ok(return_arr);
2025        }
2026        ModelingCmd::Loft(loft_cmd) => {
2027            let Some(OkModelingCmdResponse::Loft(_)) = response else {
2028                return Ok(Vec::new());
2029            };
2030            let mut return_arr = Vec::new();
2031            return_arr.push(Artifact::Sweep(Sweep {
2032                id,
2033                sub_type: SweepSubType::Loft,
2034                // TODO: Using the first one.  Make sure to revisit this
2035                // choice, don't think it matters for now.
2036                path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
2037                    KclError::new_internal(KclErrorDetails::new(
2038                        format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
2039                        vec![range],
2040                    ))
2041                })?),
2042                surface_ids: Vec::new(),
2043                edge_ids: Vec::new(),
2044                code_ref,
2045                source_sweep_id: None,
2046                trajectory_id: None,
2047                method: ArtifactSweepMethod::Merge,
2048                consumed: false,
2049                pattern_ids: Vec::new(),
2050            }));
2051            for section_id in &loft_cmd.section_ids {
2052                let path = artifacts.get(&ArtifactId::new(*section_id));
2053                if let Some(Artifact::Path(path)) = path {
2054                    let mut new_path = path.clone();
2055                    new_path.consumed = true;
2056                    new_path.sweep_id = Some(id);
2057                    return_arr.push(Artifact::Path(new_path));
2058                }
2059            }
2060            return Ok(return_arr);
2061        }
2062        ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
2063            let Some(OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info)) = response else {
2064                return Ok(Vec::new());
2065            };
2066            let mut return_arr = Vec::new();
2067            let mut last_path = None;
2068            for face in &face_info.faces {
2069                if face.cap != ExtrusionFaceCapType::None {
2070                    continue;
2071                }
2072                let Some(curve_id) = face.curve_id.map(ArtifactId::new) else {
2073                    continue;
2074                };
2075                let Some(face_id) = face.face_id.map(ArtifactId::new) else {
2076                    continue;
2077                };
2078                let Some(Artifact::Segment(seg)) = artifacts.get(&curve_id) else {
2079                    continue;
2080                };
2081                let Some(Artifact::Path(path)) = artifacts.get(&seg.path_id) else {
2082                    continue;
2083                };
2084                last_path = Some(path);
2085                let Some(path_sweep_id) = path.sweep_id else {
2086                    // If the path doesn't have a sweep ID, check if it's a
2087                    // hole.
2088                    if path.outer_path_id.is_some() {
2089                        continue; // hole not handled
2090                    }
2091                    return Err(KclError::new_internal(KclErrorDetails::new(
2092                        format!(
2093                            "Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
2094                        ),
2095                        vec![range],
2096                    )));
2097                };
2098                let extra_artifact = exec_artifacts.values().find(|a| {
2099                    if let Artifact::StartSketchOnFace(s) = a {
2100                        s.face_id == face_id
2101                    } else if let Artifact::StartSketchOnPlane(s) = a {
2102                        s.plane_id == face_id
2103                    } else {
2104                        false
2105                    }
2106                });
2107                let sketch_on_face_code_ref = extra_artifact
2108                    .and_then(|a| match a {
2109                        Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
2110                        Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
2111                        _ => None,
2112                    })
2113                    // TODO: If we didn't find it, it's probably a bug.
2114                    .unwrap_or_default();
2115
2116                return_arr.push(Artifact::Wall(Wall {
2117                    id: face_id,
2118                    seg_id: curve_id,
2119                    edge_cut_edge_ids: Vec::new(),
2120                    sweep_id: path_sweep_id,
2121                    path_ids: Vec::new(),
2122                    face_code_ref: sketch_on_face_code_ref,
2123                    cmd_id: artifact_command.cmd_id,
2124                }));
2125                let mut new_seg = seg.clone();
2126                new_seg.surface_id = Some(face_id);
2127                return_arr.push(Artifact::Segment(new_seg));
2128                if let Some(Artifact::Sweep(sweep)) = path.sweep_id.and_then(|id| artifacts.get(&id)) {
2129                    let mut new_sweep = sweep.clone();
2130                    new_sweep.surface_ids = vec![face_id];
2131                    return_arr.push(Artifact::Sweep(new_sweep));
2132                }
2133            }
2134            if let Some(path) = last_path {
2135                for face in &face_info.faces {
2136                    let sub_type = match face.cap {
2137                        ExtrusionFaceCapType::Top => CapSubType::End,
2138                        ExtrusionFaceCapType::Bottom => CapSubType::Start,
2139                        ExtrusionFaceCapType::None | ExtrusionFaceCapType::Both => continue,
2140                        _other => {
2141                            // Modeling API has added something we're not aware of.
2142                            continue;
2143                        }
2144                    };
2145                    let Some(face_id) = face.face_id.map(ArtifactId::new) else {
2146                        continue;
2147                    };
2148                    let Some(path_sweep_id) = path.sweep_id else {
2149                        // If the path doesn't have a sweep ID, check if it's a
2150                        // hole.
2151                        if path.outer_path_id.is_some() {
2152                            continue; // hole not handled
2153                        }
2154                        return Err(KclError::new_internal(KclErrorDetails::new(
2155                            format!(
2156                                "Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
2157                            ),
2158                            vec![range],
2159                        )));
2160                    };
2161                    let extra_artifact = exec_artifacts.values().find(|a| {
2162                        if let Artifact::StartSketchOnFace(s) = a {
2163                            s.face_id == face_id
2164                        } else if let Artifact::StartSketchOnPlane(s) = a {
2165                            s.plane_id == face_id
2166                        } else {
2167                            false
2168                        }
2169                    });
2170                    let sketch_on_face_code_ref = extra_artifact
2171                        .and_then(|a| match a {
2172                            Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
2173                            Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
2174                            _ => None,
2175                        })
2176                        // TODO: If we didn't find it, it's probably a bug.
2177                        .unwrap_or_default();
2178                    return_arr.push(Artifact::Cap(Cap {
2179                        id: face_id,
2180                        sub_type,
2181                        edge_cut_edge_ids: Vec::new(),
2182                        sweep_id: path_sweep_id,
2183                        path_ids: Vec::new(),
2184                        face_code_ref: sketch_on_face_code_ref,
2185                        cmd_id: artifact_command.cmd_id,
2186                    }));
2187                    let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
2188                        continue;
2189                    };
2190                    let mut new_sweep = sweep.clone();
2191                    new_sweep.surface_ids = vec![face_id];
2192                    return_arr.push(Artifact::Sweep(new_sweep));
2193                }
2194            }
2195            return Ok(return_arr);
2196        }
2197        ModelingCmd::Solid3dGetAdjacencyInfo(kcmc::Solid3dGetAdjacencyInfo { .. }) => {
2198            let Some(OkModelingCmdResponse::Solid3dGetAdjacencyInfo(info)) = response else {
2199                return Ok(Vec::new());
2200            };
2201
2202            let mut return_arr = Vec::new();
2203            let adjacent_edge_ids = info
2204                .edges
2205                .iter()
2206                .filter_map(|edge| edge.adjacent_info.as_ref().map(|info| info.edge_id))
2207                .collect::<AHashSet<_>>();
2208            for (index, edge) in info.edges.iter().enumerate() {
2209                let Some(original_info) = &edge.original_info else {
2210                    continue;
2211                };
2212                let edge_id = ArtifactId::new(original_info.edge_id);
2213                let Some(artifact) = artifacts.get(&edge_id) else {
2214                    continue;
2215                };
2216                match artifact {
2217                    Artifact::Segment(segment) => {
2218                        let mut new_segment = segment.clone();
2219                        new_segment.common_surface_ids =
2220                            original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
2221                        return_arr.push(Artifact::Segment(new_segment));
2222                    }
2223                    Artifact::SweepEdge(sweep_edge) => {
2224                        let mut new_sweep_edge = sweep_edge.clone();
2225                        new_sweep_edge.common_surface_ids =
2226                            original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
2227                        return_arr.push(Artifact::SweepEdge(new_sweep_edge));
2228                    }
2229                    _ => {}
2230                };
2231
2232                let Some(Artifact::Segment(segment)) = artifacts.get(&edge_id) else {
2233                    continue;
2234                };
2235                let Some(surface_id) = segment.surface_id else {
2236                    continue;
2237                };
2238                let Some(Artifact::Wall(wall)) = artifacts.get(&surface_id) else {
2239                    continue;
2240                };
2241                let Some(Artifact::Sweep(sweep)) = artifacts.get(&wall.sweep_id) else {
2242                    continue;
2243                };
2244                let Some(Artifact::Path(_)) = artifacts.get(&sweep.path_id) else {
2245                    continue;
2246                };
2247
2248                if let Some(opposite_info) = &edge.opposite_info {
2249                    return_arr.push(Artifact::SweepEdge(SweepEdge {
2250                        id: opposite_info.edge_id.into(),
2251                        sub_type: SweepEdgeSubType::Opposite,
2252                        seg_id: edge_id,
2253                        cmd_id: artifact_command.cmd_id,
2254                        index,
2255                        sweep_id: sweep.id,
2256                        common_surface_ids: opposite_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
2257                    }));
2258                    let mut new_segment = segment.clone();
2259                    new_segment.edge_ids = vec![opposite_info.edge_id.into()];
2260                    return_arr.push(Artifact::Segment(new_segment));
2261                    let mut new_sweep = sweep.clone();
2262                    new_sweep.edge_ids = vec![opposite_info.edge_id.into()];
2263                    return_arr.push(Artifact::Sweep(new_sweep));
2264                    let mut new_wall = wall.clone();
2265                    new_wall.edge_cut_edge_ids = vec![opposite_info.edge_id.into()];
2266                    return_arr.push(Artifact::Wall(new_wall));
2267                }
2268                if let Some(adjacent_info) = &edge.adjacent_info {
2269                    return_arr.push(Artifact::SweepEdge(SweepEdge {
2270                        id: adjacent_info.edge_id.into(),
2271                        sub_type: SweepEdgeSubType::Adjacent,
2272                        seg_id: edge_id,
2273                        cmd_id: artifact_command.cmd_id,
2274                        index,
2275                        sweep_id: sweep.id,
2276                        common_surface_ids: adjacent_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
2277                    }));
2278                    let mut new_segment = segment.clone();
2279                    new_segment.edge_ids = vec![adjacent_info.edge_id.into()];
2280                    return_arr.push(Artifact::Segment(new_segment));
2281                    let mut new_sweep = sweep.clone();
2282                    new_sweep.edge_ids = vec![adjacent_info.edge_id.into()];
2283                    return_arr.push(Artifact::Sweep(new_sweep));
2284                    let mut new_wall = wall.clone();
2285                    new_wall.edge_cut_edge_ids = vec![adjacent_info.edge_id.into()];
2286                    return_arr.push(Artifact::Wall(new_wall));
2287                }
2288                // Internal edges are already represented as the next adjacent edge of
2289                // the preceding segment. Only add the open component's start edge.
2290                if let Some(previous_adjacent_info) = &edge.previous_adjacent_info
2291                    && !adjacent_edge_ids.contains(&previous_adjacent_info.edge_id)
2292                {
2293                    return_arr.push(Artifact::SweepEdge(SweepEdge {
2294                        id: previous_adjacent_info.edge_id.into(),
2295                        sub_type: SweepEdgeSubType::PreviousAdjacent,
2296                        seg_id: edge_id,
2297                        cmd_id: artifact_command.cmd_id,
2298                        index,
2299                        sweep_id: sweep.id,
2300                        common_surface_ids: previous_adjacent_info
2301                            .faces
2302                            .iter()
2303                            .map(|face| ArtifactId::new(*face))
2304                            .collect(),
2305                    }));
2306                    let mut new_segment = segment.clone();
2307                    new_segment.edge_ids = vec![previous_adjacent_info.edge_id.into()];
2308                    return_arr.push(Artifact::Segment(new_segment));
2309                    let mut new_sweep = sweep.clone();
2310                    new_sweep.edge_ids = vec![previous_adjacent_info.edge_id.into()];
2311                    return_arr.push(Artifact::Sweep(new_sweep));
2312                    let mut new_wall = wall.clone();
2313                    new_wall.edge_cut_edge_ids = vec![previous_adjacent_info.edge_id.into()];
2314                    return_arr.push(Artifact::Wall(new_wall));
2315                }
2316            }
2317            return Ok(return_arr);
2318        }
2319        ModelingCmd::Solid3dMultiJoin(cmd) => {
2320            let mut return_arr = Vec::new();
2321            return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2322                id,
2323                consumed: false,
2324                sub_type: CompositeSolidSubType::Union,
2325                output_index: None,
2326                solid_ids: cmd.object_ids.iter().map(|id| id.into()).collect(),
2327                tool_ids: vec![],
2328                code_ref,
2329                composite_solid_id: None,
2330                pattern_ids: Vec::new(),
2331            }));
2332
2333            let solid_ids = cmd.object_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2334
2335            for input_id in &solid_ids {
2336                if let Some(artifact) = artifacts.get(input_id)
2337                    && let Artifact::CompositeSolid(comp) = artifact
2338                {
2339                    let mut new_comp = comp.clone();
2340                    new_comp.composite_solid_id = Some(id);
2341                    new_comp.consumed = true;
2342                    return_arr.push(Artifact::CompositeSolid(new_comp));
2343                } else if let Some(Artifact::Sweep(sweep)) = artifacts.get(input_id) {
2344                    let mut new_sweep = sweep.clone();
2345                    new_sweep.consumed = true;
2346                    return_arr.push(Artifact::Sweep(new_sweep));
2347                }
2348            }
2349            return Ok(return_arr);
2350        }
2351        ModelingCmd::Solid3dFilletEdge(cmd) => {
2352            let mut return_arr = Vec::new();
2353            let edge_id = if let Some(edge_id) = cmd.edge_id {
2354                ArtifactId::new(edge_id)
2355            } else {
2356                let Some(edge_id) = cmd.edge_ids.first() else {
2357                    internal_error!(
2358                        range,
2359                        "Solid3dFilletEdge command has no edge ID: id={id:?}, cmd={cmd:?}"
2360                    );
2361                };
2362                edge_id.into()
2363            };
2364            return_arr.push(Artifact::EdgeCut(EdgeCut {
2365                id,
2366                sub_type: edge_cut_sub_type(cmd.cut_type),
2367                consumed_edge_id: edge_id,
2368                edge_ids: Vec::new(),
2369                surface_id: None,
2370                code_ref,
2371            }));
2372            let consumed_edge = artifacts.get(&edge_id);
2373            if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2374                let mut new_segment = consumed_edge.clone();
2375                new_segment.edge_cut_id = Some(id);
2376                return_arr.push(Artifact::Segment(new_segment));
2377            } else {
2378                // TODO: Handle other types like SweepEdge.
2379            }
2380            return Ok(return_arr);
2381        }
2382        ModelingCmd::Solid3dCutEdges(cmd) => {
2383            let mut return_arr = Vec::new();
2384            let edge_id = if let Some(edge_id) = cmd.edge_ids.first() {
2385                edge_id.into()
2386            } else {
2387                internal_error!(range, "Solid3dCutEdges command has no edge ID: id={id:?}, cmd={cmd:?}");
2388            };
2389            return_arr.push(Artifact::EdgeCut(EdgeCut {
2390                id,
2391                sub_type: edge_cut_sub_type_v2(cmd.cut_type),
2392                consumed_edge_id: edge_id,
2393                edge_ids: Vec::new(),
2394                surface_id: None,
2395                code_ref,
2396            }));
2397            let consumed_edge = artifacts.get(&edge_id);
2398            if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2399                let mut new_segment = consumed_edge.clone();
2400                new_segment.edge_cut_id = Some(id);
2401                return_arr.push(Artifact::Segment(new_segment));
2402            } else {
2403                // TODO: Handle other types like SweepEdge.
2404            }
2405            return Ok(return_arr);
2406        }
2407        ModelingCmd::EntityMakeHelix(cmd) => {
2408            let cylinder_id = ArtifactId::new(cmd.cylinder_id);
2409            let return_arr = vec![Artifact::Helix(Helix {
2410                id,
2411                axis_id: Some(cylinder_id),
2412                code_ref,
2413                trajectory_sweep_id: None,
2414                consumed: false,
2415            })];
2416            return Ok(return_arr);
2417        }
2418        ModelingCmd::EntityMakeHelixFromParams(_) => {
2419            let return_arr = vec![Artifact::Helix(Helix {
2420                id,
2421                axis_id: None,
2422                code_ref,
2423                trajectory_sweep_id: None,
2424                consumed: false,
2425            })];
2426            return Ok(return_arr);
2427        }
2428        ModelingCmd::EntityMakeHelixFromEdge(helix) => {
2429            let return_arr = vec![Artifact::Helix(Helix {
2430                id,
2431                axis_id: helix.edge_id.map(ArtifactId::new),
2432                code_ref,
2433                trajectory_sweep_id: None,
2434                consumed: false,
2435            })];
2436            // We could add the reverse graph edge connecting from the edge to
2437            // the helix here, but it's not useful right now.
2438            return Ok(return_arr);
2439        }
2440        ModelingCmd::Solid2dAddHole(solid2d_add_hole) => {
2441            let mut return_arr = Vec::new();
2442            // Add the hole to the outer.
2443            let outer_path = artifacts.get(&ArtifactId::new(solid2d_add_hole.object_id));
2444            if let Some(Artifact::Path(path)) = outer_path {
2445                let mut new_path = path.clone();
2446                new_path.inner_path_id = Some(ArtifactId::new(solid2d_add_hole.hole_id));
2447                return_arr.push(Artifact::Path(new_path));
2448            }
2449            // Add the outer to the hole.
2450            let inner_solid2d = artifacts.get(&ArtifactId::new(solid2d_add_hole.hole_id));
2451            if let Some(Artifact::Path(path)) = inner_solid2d {
2452                let mut new_path = path.clone();
2453                new_path.consumed = true;
2454                new_path.outer_path_id = Some(ArtifactId::new(solid2d_add_hole.object_id));
2455                return_arr.push(Artifact::Path(new_path));
2456            }
2457            return Ok(return_arr);
2458        }
2459        ModelingCmd::BooleanIntersection(_) | ModelingCmd::BooleanSubtract(_) | ModelingCmd::BooleanUnion(_) => {
2460            let (sub_type, solid_ids, tool_ids) = match cmd {
2461                ModelingCmd::BooleanIntersection(intersection) => {
2462                    let solid_ids = intersection
2463                        .solid_ids
2464                        .iter()
2465                        .copied()
2466                        .map(ArtifactId::new)
2467                        .collect::<Vec<_>>();
2468                    (CompositeSolidSubType::Intersect, solid_ids, Vec::new())
2469                }
2470                ModelingCmd::BooleanSubtract(subtract) => {
2471                    let solid_ids = subtract
2472                        .target_ids
2473                        .iter()
2474                        .copied()
2475                        .map(ArtifactId::new)
2476                        .collect::<Vec<_>>();
2477                    let tool_ids = subtract
2478                        .tool_ids
2479                        .iter()
2480                        .copied()
2481                        .map(ArtifactId::new)
2482                        .collect::<Vec<_>>();
2483                    (CompositeSolidSubType::Subtract, solid_ids, tool_ids)
2484                }
2485                ModelingCmd::BooleanUnion(union) => {
2486                    let solid_ids = union.solid_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2487                    (CompositeSolidSubType::Union, solid_ids, Vec::new())
2488                }
2489                _ => internal_error!(
2490                    range,
2491                    "Boolean or composite command variant not handled: id={id:?}, cmd={cmd:?}"
2492                ),
2493            };
2494
2495            let mut new_solid_ids = vec![id];
2496
2497            // Make sure we don't ever create a duplicate ID since merge_ids
2498            // can't handle it.
2499            let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2500
2501            match (cmd, response) {
2502                (
2503                    ModelingCmd::BooleanSubtract(subtract_cmd),
2504                    Some(OkModelingCmdResponse::BooleanSubtract(subtract_resp)),
2505                ) => {
2506                    new_solid_ids = boolean_subtract_output_artifact_ids(
2507                        id,
2508                        &subtract_cmd.target_ids,
2509                        &subtract_cmd.tool_ids,
2510                        &subtract_resp.extra_solid_ids,
2511                    );
2512                }
2513                (_, Some(OkModelingCmdResponse::BooleanIntersection(intersection))) => intersection
2514                    .extra_solid_ids
2515                    .iter()
2516                    .copied()
2517                    .map(ArtifactId::new)
2518                    .filter(not_cmd_id)
2519                    .for_each(|id| new_solid_ids.push(id)),
2520                (_, Some(OkModelingCmdResponse::BooleanUnion(union))) => union
2521                    .extra_solid_ids
2522                    .iter()
2523                    .copied()
2524                    .map(ArtifactId::new)
2525                    .filter(not_cmd_id)
2526                    .for_each(|id| new_solid_ids.push(id)),
2527                _ => {}
2528            }
2529
2530            let mut return_arr = Vec::new();
2531            let mut consumed_sweep_ids = AHashSet::default();
2532            let mut input_ids = solid_ids.clone();
2533            merge_ids(&mut input_ids, tool_ids.clone());
2534
2535            if new_solid_ids.is_empty() {
2536                update_csg_input_artifacts(&mut return_arr, artifacts, &input_ids, None, &mut consumed_sweep_ids);
2537            }
2538
2539            // Create the new composite solids and update their linked artifacts
2540            for solid_id in &new_solid_ids {
2541                // Create the composite solid
2542                return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2543                    id: *solid_id,
2544                    consumed: false,
2545                    sub_type,
2546                    output_index: None,
2547                    solid_ids: solid_ids.clone(),
2548                    tool_ids: tool_ids.clone(),
2549                    code_ref: code_ref.clone(),
2550                    composite_solid_id: None,
2551                    pattern_ids: Vec::new(),
2552                }));
2553
2554                update_csg_input_artifacts(
2555                    &mut return_arr,
2556                    artifacts,
2557                    &input_ids,
2558                    Some(*solid_id),
2559                    &mut consumed_sweep_ids,
2560                );
2561            }
2562
2563            return Ok(return_arr);
2564        }
2565        ModelingCmd::BooleanImprint(imprint) => {
2566            let solid_ids = imprint
2567                .body_ids
2568                .iter()
2569                .copied()
2570                .map(ArtifactId::new)
2571                .collect::<Vec<_>>();
2572            let tool_ids = imprint
2573                .tool_ids
2574                .as_ref()
2575                .map(|ids| ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>())
2576                .unwrap_or_default();
2577
2578            let mut new_solid_ids = vec![id];
2579            let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2580            if let Some(OkModelingCmdResponse::BooleanImprint(imprint)) = response {
2581                imprint
2582                    .extra_solid_ids
2583                    .iter()
2584                    .copied()
2585                    .map(ArtifactId::new)
2586                    .filter(not_cmd_id)
2587                    .for_each(|id| new_solid_ids.push(id));
2588            }
2589
2590            let mut return_arr = Vec::new();
2591            let mut consumed_sweep_ids = AHashSet::default();
2592
2593            for input_id in solid_ids.iter().chain(tool_ids.iter()) {
2594                let sweep_id = match artifacts.get(input_id) {
2595                    Some(Artifact::Sweep(sweep)) => Some(sweep.id),
2596                    Some(Artifact::Path(path)) => path.sweep_id,
2597                    _ => None,
2598                };
2599
2600                if let Some(sweep_id) = sweep_id
2601                    && consumed_sweep_ids.insert(sweep_id)
2602                    && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
2603                {
2604                    let mut new_sweep = sweep.clone();
2605                    new_sweep.consumed = true;
2606                    return_arr.push(Artifact::Sweep(new_sweep));
2607                }
2608            }
2609
2610            for (output_index, solid_id) in new_solid_ids.iter().enumerate() {
2611                return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2612                    id: *solid_id,
2613                    consumed: false,
2614                    sub_type: CompositeSolidSubType::Split,
2615                    output_index: Some(output_index),
2616                    solid_ids: solid_ids.clone(),
2617                    tool_ids: tool_ids.clone(),
2618                    code_ref: code_ref.clone(),
2619                    composite_solid_id: None,
2620                    pattern_ids: Vec::new(),
2621                }));
2622
2623                for input_id in solid_ids.iter().chain(tool_ids.iter()) {
2624                    if let Some(artifact) = artifacts.get(input_id) {
2625                        match artifact {
2626                            Artifact::CompositeSolid(comp) => {
2627                                let mut new_comp = comp.clone();
2628                                new_comp.composite_solid_id = Some(*solid_id);
2629                                new_comp.consumed = true;
2630                                return_arr.push(Artifact::CompositeSolid(new_comp));
2631                            }
2632                            Artifact::Path(path) => {
2633                                let mut new_path = path.clone();
2634                                new_path.composite_solid_id = Some(*solid_id);
2635
2636                                return_arr.push(Artifact::Path(new_path));
2637                            }
2638                            _ => {}
2639                        }
2640                    }
2641                }
2642            }
2643
2644            return Ok(return_arr);
2645        }
2646        _ => {}
2647    }
2648
2649    Ok(Vec::new())
2650}