Skip to main content

kcl_lib/execution/
artifact.rs

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