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