Skip to main content

kcl_lib/execution/
artifact.rs

1use fnv::FnvHashMap;
2use fnv::FnvHashSet;
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) -> FnvHashMap<ModuleId, ImportCodeRef> {
896    let mut code_refs = FnvHashMap::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: &FnvHashMap<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 = FnvHashMap::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: &FnvHashMap<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) -> FnvHashMap<Uuid, OkModelingCmdResponse> {
1069    let mut map = FnvHashMap::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: &FnvHashMap<Uuid, OkModelingCmdResponse>,
1121) -> FnvHashMap<Uuid, FnvHashMap<ArtifactId, ArtifactId>> {
1122    let mut clone_id_maps = FnvHashMap::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 = FnvHashMap::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: &FnvHashMap<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: &FnvHashMap<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: &FnvHashMap<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(
1240    ids: &[ArtifactId],
1241    entity_id_map: &FnvHashMap<ArtifactId, ArtifactId>,
1242) -> Vec<ArtifactId> {
1243    ids.iter().filter_map(|id| entity_id_map.get(id).copied()).collect()
1244}
1245
1246fn remap_artifact_for_clone(
1247    artifact: &Artifact,
1248    entity_id_map: &FnvHashMap<ArtifactId, ArtifactId>,
1249    clone_code_ref: &CodeRef,
1250    clone_cmd_id: Uuid,
1251    source_root_id: ArtifactId,
1252) -> Artifact {
1253    match artifact {
1254        Artifact::CompositeSolid(source) => Artifact::CompositeSolid(CompositeSolid {
1255            id: remap_id_for_clone(source.id, entity_id_map),
1256            consumed: if source.id == source_root_id {
1257                false
1258            } else {
1259                source.consumed
1260            },
1261            sub_type: source.sub_type,
1262            output_index: source.output_index,
1263            solid_ids: remap_ids_for_clone(&source.solid_ids, entity_id_map),
1264            tool_ids: remap_ids_for_clone(&source.tool_ids, entity_id_map),
1265            pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
1266            code_ref: clone_code_ref.clone(),
1267            composite_solid_id: remap_opt_id_for_clone(source.composite_solid_id, entity_id_map),
1268        }),
1269        Artifact::Plane(source) => Artifact::Plane(Plane {
1270            id: remap_id_for_clone(source.id, entity_id_map),
1271            path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
1272            code_ref: clone_code_ref.clone(),
1273        }),
1274        Artifact::Path(source) => Artifact::Path(Path {
1275            id: remap_id_for_clone(source.id, entity_id_map),
1276            sub_type: source.sub_type,
1277            plane_id: remap_id_for_clone(source.plane_id, entity_id_map),
1278            seg_ids: remap_ids_for_clone(&source.seg_ids, entity_id_map),
1279            consumed: if source.id == source_root_id {
1280                false
1281            } else {
1282                source.consumed
1283            },
1284            sweep_id: remap_opt_id_for_clone(source.sweep_id, entity_id_map),
1285            trajectory_sweep_id: remap_opt_id_for_clone(source.trajectory_sweep_id, entity_id_map),
1286            solid2d_id: remap_opt_id_for_clone(source.solid2d_id, entity_id_map),
1287            code_ref: clone_code_ref.clone(),
1288            composite_solid_id: remap_opt_id_for_clone(source.composite_solid_id, entity_id_map),
1289            sketch_block_id: remap_opt_id_for_clone(source.sketch_block_id, entity_id_map),
1290            origin_path_id: remap_opt_id_for_clone(source.origin_path_id, entity_id_map),
1291            inner_path_id: remap_opt_id_for_clone(source.inner_path_id, entity_id_map),
1292            outer_path_id: remap_opt_id_for_clone(source.outer_path_id, entity_id_map),
1293            pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
1294        }),
1295        Artifact::Segment(source) => Artifact::Segment(Segment {
1296            id: remap_id_for_clone(source.id, entity_id_map),
1297            path_id: remap_id_for_clone(source.path_id, entity_id_map),
1298            original_seg_id: remap_opt_id_for_clone(source.original_seg_id, entity_id_map),
1299            surface_id: remap_opt_id_for_clone(source.surface_id, entity_id_map),
1300            edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
1301            edge_cut_id: remap_opt_id_for_clone(source.edge_cut_id, entity_id_map),
1302            code_ref: clone_code_ref.clone(),
1303            common_surface_ids: remap_ids_for_clone(&source.common_surface_ids, entity_id_map),
1304        }),
1305        Artifact::Solid2d(source) => Artifact::Solid2d(Solid2d {
1306            id: remap_id_for_clone(source.id, entity_id_map),
1307            path_id: remap_id_for_clone(source.path_id, entity_id_map),
1308        }),
1309        Artifact::PrimitiveFace(source) => Artifact::PrimitiveFace(PrimitiveFace {
1310            id: remap_id_for_clone(source.id, entity_id_map),
1311            solid_id: remap_id_for_clone(source.solid_id, entity_id_map),
1312            code_ref: clone_code_ref.clone(),
1313        }),
1314        Artifact::PrimitiveEdge(source) => Artifact::PrimitiveEdge(PrimitiveEdge {
1315            id: remap_id_for_clone(source.id, entity_id_map),
1316            solid_id: remap_id_for_clone(source.solid_id, entity_id_map),
1317            code_ref: clone_code_ref.clone(),
1318        }),
1319        Artifact::PlaneOfFace(source) => Artifact::PlaneOfFace(PlaneOfFace {
1320            id: remap_id_for_clone(source.id, entity_id_map),
1321            face_id: remap_id_for_clone(source.face_id, entity_id_map),
1322            code_ref: clone_code_ref.clone(),
1323        }),
1324        Artifact::StartSketchOnFace(source) => Artifact::StartSketchOnFace(StartSketchOnFace {
1325            id: remap_id_for_clone(source.id, entity_id_map),
1326            face_id: remap_id_for_clone(source.face_id, entity_id_map),
1327            code_ref: clone_code_ref.clone(),
1328        }),
1329        Artifact::StartSketchOnPlane(source) => Artifact::StartSketchOnPlane(StartSketchOnPlane {
1330            id: remap_id_for_clone(source.id, entity_id_map),
1331            plane_id: remap_id_for_clone(source.plane_id, entity_id_map),
1332            code_ref: clone_code_ref.clone(),
1333        }),
1334        Artifact::SketchBlock(source) => Artifact::SketchBlock(SketchBlock {
1335            id: remap_id_for_clone(source.id, entity_id_map),
1336            standard_plane: source.standard_plane,
1337            plane_id: remap_opt_id_for_clone(source.plane_id, entity_id_map),
1338            plane_info: source.plane_info.clone(),
1339            path_id: remap_opt_id_for_clone(source.path_id, entity_id_map),
1340            code_ref: clone_code_ref.clone(),
1341            sketch_id: source.sketch_id,
1342        }),
1343        Artifact::SketchBlockConstraint(source) => Artifact::SketchBlockConstraint(SketchBlockConstraint {
1344            id: remap_id_for_clone(source.id, entity_id_map),
1345            sketch_id: source.sketch_id,
1346            constraint_id: source.constraint_id,
1347            constraint_type: source.constraint_type,
1348            code_ref: clone_code_ref.clone(),
1349        }),
1350        Artifact::Sweep(source) => Artifact::Sweep(Sweep {
1351            id: remap_id_for_clone(source.id, entity_id_map),
1352            sub_type: source.sub_type,
1353            path_id: remap_id_for_clone(source.path_id, entity_id_map),
1354            surface_ids: remap_ids_for_clone(&source.surface_ids, entity_id_map),
1355            edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
1356            code_ref: clone_code_ref.clone(),
1357            trajectory_id: remap_opt_id_for_clone(source.trajectory_id, entity_id_map),
1358            method: source.method,
1359            consumed: if source.id == source_root_id {
1360                false
1361            } else {
1362                source.consumed
1363            },
1364            pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
1365        }),
1366        Artifact::Wall(source) => Artifact::Wall(Wall {
1367            id: remap_id_for_clone(source.id, entity_id_map),
1368            seg_id: remap_id_for_clone(source.seg_id, entity_id_map),
1369            edge_cut_edge_ids: remap_ids_for_clone(&source.edge_cut_edge_ids, entity_id_map),
1370            sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
1371            path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
1372            face_code_ref: source.face_code_ref.clone(),
1373            cmd_id: clone_cmd_id,
1374        }),
1375        Artifact::Cap(source) => Artifact::Cap(Cap {
1376            id: remap_id_for_clone(source.id, entity_id_map),
1377            sub_type: source.sub_type,
1378            edge_cut_edge_ids: remap_ids_for_clone(&source.edge_cut_edge_ids, entity_id_map),
1379            sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
1380            path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
1381            face_code_ref: source.face_code_ref.clone(),
1382            cmd_id: clone_cmd_id,
1383        }),
1384        Artifact::SweepEdge(source) => Artifact::SweepEdge(SweepEdge {
1385            id: remap_id_for_clone(source.id, entity_id_map),
1386            sub_type: source.sub_type,
1387            seg_id: remap_id_for_clone(source.seg_id, entity_id_map),
1388            cmd_id: clone_cmd_id,
1389            index: source.index,
1390            sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
1391            common_surface_ids: remap_ids_for_clone(&source.common_surface_ids, entity_id_map),
1392        }),
1393        Artifact::EdgeCut(source) => Artifact::EdgeCut(EdgeCut {
1394            id: remap_id_for_clone(source.id, entity_id_map),
1395            sub_type: source.sub_type,
1396            consumed_edge_id: remap_id_for_clone(source.consumed_edge_id, entity_id_map),
1397            edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
1398            surface_id: remap_opt_id_for_clone(source.surface_id, entity_id_map),
1399            code_ref: clone_code_ref.clone(),
1400        }),
1401        Artifact::EdgeCutEdge(source) => Artifact::EdgeCutEdge(EdgeCutEdge {
1402            id: remap_id_for_clone(source.id, entity_id_map),
1403            edge_cut_id: remap_id_for_clone(source.edge_cut_id, entity_id_map),
1404            surface_id: remap_id_for_clone(source.surface_id, entity_id_map),
1405        }),
1406        Artifact::Helix(source) => Artifact::Helix(Helix {
1407            id: remap_id_for_clone(source.id, entity_id_map),
1408            axis_id: remap_opt_id_for_clone(source.axis_id, entity_id_map),
1409            code_ref: clone_code_ref.clone(),
1410            trajectory_sweep_id: remap_opt_id_for_clone(source.trajectory_sweep_id, entity_id_map),
1411            consumed: if source.id == source_root_id {
1412                false
1413            } else {
1414                source.consumed
1415            },
1416        }),
1417        Artifact::GdtAnnotation(source) => Artifact::GdtAnnotation(GdtAnnotationArtifact {
1418            id: remap_id_for_clone(source.id, entity_id_map),
1419            code_ref: clone_code_ref.clone(),
1420        }),
1421        Artifact::Pattern(source) => Artifact::Pattern(Pattern {
1422            id: remap_id_for_clone(source.id, entity_id_map),
1423            sub_type: source.sub_type,
1424            source_id: remap_id_for_clone(source.source_id, entity_id_map),
1425            copy_ids: remap_ids_for_clone(&source.copy_ids, entity_id_map),
1426            copy_face_ids: remap_ids_for_clone(&source.copy_face_ids, entity_id_map),
1427            copy_edge_ids: remap_ids_for_clone(&source.copy_edge_ids, entity_id_map),
1428            code_ref: clone_code_ref.clone(),
1429        }),
1430    }
1431}
1432
1433fn pattern_source_ids(artifacts: &IndexMap<ArtifactId, Artifact>, source_id: ArtifactId) -> Vec<ArtifactId> {
1434    let mut source_ids = vec![source_id];
1435
1436    if let Some(Artifact::Path(path)) = artifacts.get(&source_id) {
1437        if let Some(sweep_id) = path.sweep_id {
1438            source_ids.push(sweep_id);
1439        }
1440        if let Some(composite_solid_id) = path.composite_solid_id {
1441            source_ids.push(composite_solid_id);
1442        }
1443    }
1444
1445    for artifact in artifacts.values() {
1446        match artifact {
1447            Artifact::Sweep(sweep) if sweep.path_id == source_id => source_ids.push(sweep.id),
1448            Artifact::CompositeSolid(composite)
1449                if composite.solid_ids.contains(&source_id) || composite.tool_ids.contains(&source_id) =>
1450            {
1451                source_ids.push(composite.id)
1452            }
1453            _ => {}
1454        }
1455    }
1456
1457    let mut unique = Vec::new();
1458    merge_ids(&mut unique, source_ids);
1459    unique
1460}
1461
1462fn pattern_artifact_updates(
1463    artifacts: &IndexMap<ArtifactId, Artifact>,
1464    pattern_id: ArtifactId,
1465    sub_type: PatternSubType,
1466    source_id: ArtifactId,
1467    face_edge_infos: &[kcmc::output::FaceEdgeInfo],
1468    code_ref: CodeRef,
1469) -> Vec<Artifact> {
1470    let copy_ids = face_edge_infos
1471        .iter()
1472        .map(|info| ArtifactId::new(info.object_id))
1473        .collect::<Vec<_>>();
1474    let copy_face_ids = face_edge_infos
1475        .iter()
1476        .flat_map(|info| info.faces.iter().copied().map(ArtifactId::new))
1477        .collect::<Vec<_>>();
1478    let copy_edge_ids = face_edge_infos
1479        .iter()
1480        .flat_map(|info| info.edges.iter().copied().map(ArtifactId::new))
1481        .collect::<Vec<_>>();
1482
1483    let source_ids = pattern_source_ids(artifacts, source_id);
1484    let mut return_arr = vec![Artifact::Pattern(Pattern {
1485        id: pattern_id,
1486        sub_type,
1487        source_id,
1488        copy_ids,
1489        copy_face_ids,
1490        copy_edge_ids,
1491        code_ref,
1492    })];
1493
1494    for source_id in source_ids {
1495        let Some(artifact) = artifacts.get(&source_id) else {
1496            continue;
1497        };
1498        match artifact {
1499            Artifact::Path(path) => {
1500                let mut new_path = path.clone();
1501                new_path.pattern_ids = vec![pattern_id];
1502                return_arr.push(Artifact::Path(new_path));
1503            }
1504            Artifact::Sweep(sweep) => {
1505                let mut new_sweep = sweep.clone();
1506                new_sweep.pattern_ids = vec![pattern_id];
1507                return_arr.push(Artifact::Sweep(new_sweep));
1508            }
1509            Artifact::CompositeSolid(composite) => {
1510                let mut new_composite = composite.clone();
1511                new_composite.pattern_ids = vec![pattern_id];
1512                return_arr.push(Artifact::CompositeSolid(new_composite));
1513            }
1514            _ => {}
1515        }
1516    }
1517
1518    return_arr
1519}
1520
1521fn is_single_target_self_subtract(target_ids: &[Uuid], tool_ids: &[Uuid]) -> bool {
1522    target_ids.len() == 1 && tool_ids.len() == 1 && target_ids[0] == tool_ids[0]
1523}
1524
1525fn boolean_subtract_output_artifact_ids(
1526    cmd_id: ArtifactId,
1527    target_ids: &[Uuid],
1528    tool_ids: &[Uuid],
1529    extra_solid_ids: &[Uuid],
1530) -> Vec<ArtifactId> {
1531    if is_single_target_self_subtract(target_ids, tool_ids) {
1532        return Vec::new();
1533    }
1534
1535    let mut output_ids = if target_ids.len() == 1 {
1536        vec![cmd_id]
1537    } else {
1538        Vec::new()
1539    };
1540
1541    for extra_solid_id in extra_solid_ids {
1542        let artifact_id = ArtifactId::new(*extra_solid_id);
1543        if !output_ids.contains(&artifact_id) {
1544            output_ids.push(artifact_id);
1545        }
1546    }
1547
1548    output_ids
1549}
1550
1551fn update_consumed_csg_sweep(
1552    return_arr: &mut Vec<Artifact>,
1553    artifacts: &IndexMap<ArtifactId, Artifact>,
1554    sweep_id: ArtifactId,
1555    consumed_sweep_ids: &mut FnvHashSet<ArtifactId>,
1556) {
1557    if consumed_sweep_ids.insert(sweep_id)
1558        && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
1559    {
1560        let mut new_sweep = sweep.clone();
1561        new_sweep.consumed = true;
1562        return_arr.push(Artifact::Sweep(new_sweep));
1563    }
1564}
1565
1566fn mark_artifact_consumed_by_id(
1567    return_arr: &mut Vec<Artifact>,
1568    artifacts: &IndexMap<ArtifactId, Artifact>,
1569    artifact_id: ArtifactId,
1570    consumed_ids: &mut FnvHashSet<ArtifactId>,
1571) {
1572    let already_marked_as_consumed = !consumed_ids.insert(artifact_id);
1573    if already_marked_as_consumed {
1574        return;
1575    }
1576
1577    let Some(artifact) = artifacts.get(&artifact_id) else {
1578        return;
1579    };
1580
1581    match artifact {
1582        Artifact::CompositeSolid(composite) => {
1583            let mut new_composite = composite.clone();
1584            new_composite.consumed = true;
1585            return_arr.push(Artifact::CompositeSolid(new_composite));
1586        }
1587        Artifact::Path(path) => {
1588            let mut new_path = path.clone();
1589            new_path.consumed = true;
1590            return_arr.push(Artifact::Path(new_path));
1591
1592            if let Some(sweep_id) = path.sweep_id {
1593                mark_artifact_consumed_by_id(return_arr, artifacts, sweep_id, consumed_ids);
1594            }
1595            if let Some(composite_solid_id) = path.composite_solid_id {
1596                mark_artifact_consumed_by_id(return_arr, artifacts, composite_solid_id, consumed_ids);
1597            }
1598        }
1599        Artifact::Sweep(sweep) => {
1600            let mut new_sweep = sweep.clone();
1601            new_sweep.consumed = true;
1602            return_arr.push(Artifact::Sweep(new_sweep));
1603        }
1604        Artifact::Helix(helix) => {
1605            let mut new_helix = helix.clone();
1606            new_helix.consumed = true;
1607            return_arr.push(Artifact::Helix(new_helix));
1608        }
1609        _ => {}
1610    }
1611}
1612
1613fn mark_deleted_artifacts_consumed(
1614    artifacts: &IndexMap<ArtifactId, Artifact>,
1615    object_ids: &std::collections::HashSet<Uuid>,
1616) -> Vec<Artifact> {
1617    let mut return_arr = Vec::new();
1618    let mut consumed_ids = FnvHashSet::default();
1619
1620    // The order of iteration doesn't matter here, as all artifacts get marked as consumed.
1621    // Also the set comes from the API crate which uses HashSet.
1622    #[allow(clippy::iter_over_hash_type)]
1623    for object_id in object_ids {
1624        let artifact_id = ArtifactId::new(*object_id);
1625        mark_artifact_consumed_by_id(&mut return_arr, artifacts, artifact_id, &mut consumed_ids);
1626    }
1627
1628    return_arr
1629}
1630
1631fn update_csg_input_artifacts(
1632    return_arr: &mut Vec<Artifact>,
1633    artifacts: &IndexMap<ArtifactId, Artifact>,
1634    input_ids: &[ArtifactId],
1635    composite_solid_id: Option<ArtifactId>,
1636    consumed_sweep_ids: &mut FnvHashSet<ArtifactId>,
1637) {
1638    for input_id in input_ids {
1639        if let Some(artifact) = artifacts.get(input_id) {
1640            match artifact {
1641                Artifact::CompositeSolid(comp) => {
1642                    let mut new_comp = comp.clone();
1643                    new_comp.composite_solid_id = composite_solid_id;
1644                    new_comp.consumed = true;
1645                    return_arr.push(Artifact::CompositeSolid(new_comp));
1646                }
1647                Artifact::Path(path) => {
1648                    let mut new_path = path.clone();
1649                    new_path.composite_solid_id = composite_solid_id;
1650
1651                    // We want to mark any sweeps of the path used in this operation
1652                    // as consumed. The path itself is already consumed by sweeping.
1653                    if let Some(sweep_id) = new_path.sweep_id {
1654                        update_consumed_csg_sweep(return_arr, artifacts, sweep_id, consumed_sweep_ids);
1655                    }
1656
1657                    return_arr.push(Artifact::Path(new_path));
1658                }
1659                Artifact::Sweep(sweep) => {
1660                    update_consumed_csg_sweep(return_arr, artifacts, sweep.id, consumed_sweep_ids);
1661                }
1662                _ => {}
1663            }
1664        }
1665    }
1666}
1667
1668fn mirror_3d_artifact_updates(
1669    artifacts: &IndexMap<ArtifactId, Artifact>,
1670    original_solid_ids: &[Uuid],
1671    face_edge_infos: &[kcmc::output::FaceEdgeInfo],
1672    code_ref: CodeRef,
1673    range: SourceRange,
1674    cmd: &ModelingCmd,
1675) -> Result<Vec<Artifact>, KclError> {
1676    if original_solid_ids.len() != face_edge_infos.len() {
1677        internal_error!(
1678            range,
1679            "EntityMirrorAcross response has different number face edge info than original mirrored solids: cmd={cmd:?}, face_edge_infos={face_edge_infos:?}"
1680        );
1681    }
1682
1683    let mut return_arr = Vec::new();
1684    for (face_edge_info, original_solid_id) in face_edge_infos.iter().zip(original_solid_ids) {
1685        let original_solid_id = ArtifactId::new(*original_solid_id);
1686        let mirrored_solid_id = ArtifactId::new(face_edge_info.object_id);
1687        let source_solid = match artifacts.get(&original_solid_id) {
1688            Some(Artifact::Path(path)) => path.sweep_id.and_then(|sweep_id| artifacts.get(&sweep_id)).or_else(|| {
1689                path.composite_solid_id
1690                    .and_then(|composite_id| artifacts.get(&composite_id))
1691            }),
1692            source => source,
1693        };
1694        match source_solid {
1695            Some(Artifact::Sweep(sweep)) => {
1696                let mut mirrored_sweep = sweep.clone();
1697                mirrored_sweep.id = mirrored_solid_id;
1698                mirrored_sweep.surface_ids = face_edge_info.faces.iter().copied().map(ArtifactId::new).collect();
1699                mirrored_sweep.edge_ids = face_edge_info.edges.iter().copied().map(ArtifactId::new).collect();
1700                mirrored_sweep.code_ref = code_ref.clone();
1701                mirrored_sweep.consumed = false;
1702                mirrored_sweep.pattern_ids = Vec::new();
1703                return_arr.push(Artifact::Sweep(mirrored_sweep));
1704            }
1705            Some(Artifact::CompositeSolid(composite)) => {
1706                let mut mirrored_composite = composite.clone();
1707                mirrored_composite.id = mirrored_solid_id;
1708                mirrored_composite.code_ref = code_ref.clone();
1709                mirrored_composite.consumed = false;
1710                mirrored_composite.composite_solid_id = None;
1711                mirrored_composite.pattern_ids = Vec::new();
1712                return_arr.push(Artifact::CompositeSolid(mirrored_composite));
1713            }
1714            Some(_) | None => continue,
1715        }
1716    }
1717
1718    Ok(return_arr)
1719}
1720
1721#[allow(clippy::too_many_arguments)]
1722fn artifacts_to_update(
1723    artifacts: &IndexMap<ArtifactId, Artifact>,
1724    artifact_command: &ArtifactCommand,
1725    responses: &FnvHashMap<Uuid, OkModelingCmdResponse>,
1726    entity_clone_id_maps: &FnvHashMap<Uuid, FnvHashMap<ArtifactId, ArtifactId>>,
1727    path_to_plane_id_map: &FnvHashMap<Uuid, Uuid>,
1728    programs: &crate::execution::ProgramLookup,
1729    cached_body_items: usize,
1730    exec_artifacts: &IndexMap<ArtifactId, Artifact>,
1731    import_code_refs: &FnvHashMap<ModuleId, ImportCodeRef>,
1732) -> Result<Vec<Artifact>, KclError> {
1733    let uuid = artifact_command.cmd_id;
1734    let response = responses.get(&uuid);
1735
1736    // TODO: Build path-to-node from artifact_command source range.  Right now,
1737    // we're serializing an empty array, and the TS wrapper fills it in with the
1738    // correct value based on NodePath.
1739    let path_to_node = Vec::new();
1740    let range = artifact_command.range;
1741    let (code_ref_range, node_path) = code_ref_for_range(programs, cached_body_items, range, import_code_refs);
1742    let code_ref = CodeRef {
1743        range: code_ref_range,
1744        node_path,
1745        path_to_node,
1746    };
1747
1748    let id = ArtifactId::new(uuid);
1749    let cmd = &artifact_command.command;
1750
1751    match cmd {
1752        ModelingCmd::MakePlane(_) => {
1753            if range.is_synthetic() {
1754                return Ok(Vec::new());
1755            }
1756            // If we're calling `make_plane` and the code range doesn't end at
1757            // `0` it's not a default plane, but a custom one from the
1758            // offsetPlane standard library function.
1759            return Ok(vec![Artifact::Plane(Plane {
1760                id,
1761                path_ids: Vec::new(),
1762                code_ref,
1763            })]);
1764        }
1765        ModelingCmd::FaceIsPlanar(FaceIsPlanar { object_id, .. }) => {
1766            return Ok(vec![Artifact::PlaneOfFace(PlaneOfFace {
1767                id,
1768                face_id: object_id.into(),
1769                code_ref,
1770            })]);
1771        }
1772        ModelingCmd::RemoveSceneObjects(remove) => {
1773            return Ok(mark_deleted_artifacts_consumed(artifacts, &remove.object_ids));
1774        }
1775        ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) => {
1776            let existing_plane = artifacts.get(&ArtifactId::new(*entity_id));
1777            match existing_plane {
1778                Some(Artifact::Wall(wall)) => {
1779                    return Ok(vec![Artifact::Wall(Wall {
1780                        id: entity_id.into(),
1781                        seg_id: wall.seg_id,
1782                        edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1783                        sweep_id: wall.sweep_id,
1784                        path_ids: wall.path_ids.clone(),
1785                        face_code_ref: wall.face_code_ref.clone(),
1786                        cmd_id: artifact_command.cmd_id,
1787                    })]);
1788                }
1789                Some(Artifact::Cap(cap)) => {
1790                    return Ok(vec![Artifact::Cap(Cap {
1791                        id: entity_id.into(),
1792                        sub_type: cap.sub_type,
1793                        edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1794                        sweep_id: cap.sweep_id,
1795                        path_ids: cap.path_ids.clone(),
1796                        face_code_ref: cap.face_code_ref.clone(),
1797                        cmd_id: artifact_command.cmd_id,
1798                    })]);
1799                }
1800                Some(_) | None => {
1801                    let path_ids = match existing_plane {
1802                        Some(Artifact::Plane(Plane { path_ids, .. })) => path_ids.clone(),
1803                        _ => Vec::new(),
1804                    };
1805                    // Create an entirely new plane
1806                    return Ok(vec![Artifact::Plane(Plane {
1807                        id: entity_id.into(),
1808                        path_ids,
1809                        code_ref,
1810                    })]);
1811                }
1812            }
1813        }
1814        ModelingCmd::StartPath(_) => {
1815            let mut return_arr = Vec::new();
1816            let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
1817                KclError::new_internal(KclErrorDetails::new(
1818                    format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"),
1819                    vec![range],
1820                ))
1821            })?;
1822            let sketch_block_id = exec_artifacts
1823                .values()
1824                .find(|a| {
1825                    if let Artifact::SketchBlock(s) = a {
1826                        if let Some(path_id) = s.path_id {
1827                            path_id == id
1828                        } else {
1829                            false
1830                        }
1831                    } else {
1832                        false
1833                    }
1834                })
1835                .map(|a| a.id());
1836            return_arr.push(Artifact::Path(Path {
1837                id,
1838                sub_type: PathSubType::Sketch,
1839                plane_id: (*current_plane_id).into(),
1840                seg_ids: Vec::new(),
1841                sweep_id: None,
1842                trajectory_sweep_id: None,
1843                solid2d_id: None,
1844                code_ref,
1845                composite_solid_id: None,
1846                sketch_block_id,
1847                origin_path_id: None,
1848                inner_path_id: None,
1849                outer_path_id: None,
1850                pattern_ids: Vec::new(),
1851                consumed: false,
1852            }));
1853            let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
1854            if let Some(Artifact::Plane(plane)) = plane {
1855                let plane_code_ref = plane.code_ref.clone();
1856                return_arr.push(Artifact::Plane(Plane {
1857                    id: (*current_plane_id).into(),
1858                    path_ids: vec![id],
1859                    code_ref: plane_code_ref,
1860                }));
1861            }
1862            if let Some(Artifact::Wall(wall)) = plane {
1863                return_arr.push(Artifact::Wall(Wall {
1864                    id: (*current_plane_id).into(),
1865                    seg_id: wall.seg_id,
1866                    edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1867                    sweep_id: wall.sweep_id,
1868                    path_ids: vec![id],
1869                    face_code_ref: wall.face_code_ref.clone(),
1870                    cmd_id: artifact_command.cmd_id,
1871                }));
1872            }
1873            if let Some(Artifact::Cap(cap)) = plane {
1874                return_arr.push(Artifact::Cap(Cap {
1875                    id: (*current_plane_id).into(),
1876                    sub_type: cap.sub_type,
1877                    edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1878                    sweep_id: cap.sweep_id,
1879                    path_ids: vec![id],
1880                    face_code_ref: cap.face_code_ref.clone(),
1881                    cmd_id: artifact_command.cmd_id,
1882                }));
1883            }
1884            return Ok(return_arr);
1885        }
1886        ModelingCmd::ClosePath(_) | ModelingCmd::ExtendPath(_) => {
1887            let path_id = ArtifactId::new(match cmd {
1888                ModelingCmd::ClosePath(c) => c.path_id,
1889                ModelingCmd::ExtendPath(e) => e.path.into(),
1890                _ => internal_error!(
1891                    range,
1892                    "Close or extend path command variant not handled: id={id:?}, cmd={cmd:?}"
1893                ),
1894            });
1895            let mut return_arr = Vec::new();
1896            return_arr.push(Artifact::Segment(Segment {
1897                id,
1898                path_id,
1899                original_seg_id: None,
1900                surface_id: None,
1901                edge_ids: Vec::new(),
1902                edge_cut_id: None,
1903                code_ref,
1904                common_surface_ids: Vec::new(),
1905            }));
1906            let path = artifacts.get(&path_id);
1907            if let Some(Artifact::Path(path)) = path {
1908                let mut new_path = path.clone();
1909                new_path.seg_ids = vec![id];
1910                return_arr.push(Artifact::Path(new_path));
1911            }
1912            if let Some(OkModelingCmdResponse::ClosePath(close_path)) = response {
1913                return_arr.push(Artifact::Solid2d(Solid2d {
1914                    id: close_path.face_id.into(),
1915                    path_id,
1916                }));
1917                if let Some(Artifact::Path(path)) = path {
1918                    let mut new_path = path.clone();
1919                    new_path.solid2d_id = Some(close_path.face_id.into());
1920                    return_arr.push(Artifact::Path(new_path));
1921                }
1922            }
1923            return Ok(return_arr);
1924        }
1925        ModelingCmd::CreateRegion(kcmc::CreateRegion {
1926            object_id: origin_path_id,
1927            ..
1928        })
1929        | ModelingCmd::CreateRegionFromQueryPoint(kcmc::CreateRegionFromQueryPoint {
1930            object_id: origin_path_id,
1931            ..
1932        }) => {
1933            let mut return_arr = Vec::new();
1934            let origin_path = artifacts.get(&ArtifactId::new(*origin_path_id));
1935            let Some(Artifact::Path(path)) = origin_path else {
1936                internal_error!(
1937                    range,
1938                    "Expected to find an existing path for the origin path of CreateRegion or CreateRegionFromQueryPoint command, but found none: origin_path={origin_path:?}, cmd={cmd:?}"
1939                );
1940            };
1941            // Create the path representing the region.
1942            return_arr.push(Artifact::Path(Path {
1943                id,
1944                sub_type: PathSubType::Region,
1945                plane_id: path.plane_id,
1946                seg_ids: Vec::new(),
1947                consumed: false,
1948                sweep_id: None,
1949                trajectory_sweep_id: None,
1950                solid2d_id: None,
1951                code_ref: code_ref.clone(),
1952                composite_solid_id: None,
1953                sketch_block_id: None,
1954                origin_path_id: Some(ArtifactId::new(*origin_path_id)),
1955                inner_path_id: None,
1956                outer_path_id: None,
1957                pattern_ids: Vec::new(),
1958            }));
1959            // If we have a response, we can also create the segments in the
1960            // region.
1961            let Some(
1962                OkModelingCmdResponse::CreateRegion(kcmc::output::CreateRegion { region_mapping, .. })
1963                | OkModelingCmdResponse::CreateRegionFromQueryPoint(kcmc::output::CreateRegionFromQueryPoint {
1964                    region_mapping,
1965                    ..
1966                }),
1967            ) = response
1968            else {
1969                return Ok(return_arr);
1970            };
1971            // Each key is a segment in the region. The value is the segment in
1972            // the original path. Build the reverse mapping.
1973            let original_segment_ids = path.seg_ids.iter().map(|p| p.0).collect::<Vec<_>>();
1974            let reverse = build_reverse_region_mapping(region_mapping, &original_segment_ids);
1975            for (original_segment_id, region_segment_ids) in reverse.iter() {
1976                for segment_id in region_segment_ids {
1977                    return_arr.push(Artifact::Segment(Segment {
1978                        id: ArtifactId::new(*segment_id),
1979                        path_id: id,
1980                        original_seg_id: Some(ArtifactId::new(*original_segment_id)),
1981                        surface_id: None,
1982                        edge_ids: Vec::new(),
1983                        edge_cut_id: None,
1984                        code_ref: code_ref.clone(),
1985                        common_surface_ids: Vec::new(),
1986                    }))
1987                }
1988            }
1989            return Ok(return_arr);
1990        }
1991        ModelingCmd::Solid3dGetFaceUuid(kcmc::Solid3dGetFaceUuid { object_id, .. }) => {
1992            let Some(OkModelingCmdResponse::Solid3dGetFaceUuid(face_uuid)) = response else {
1993                return Ok(Vec::new());
1994            };
1995
1996            return Ok(vec![Artifact::PrimitiveFace(PrimitiveFace {
1997                id: face_uuid.face_id.into(),
1998                solid_id: (*object_id).into(),
1999                code_ref,
2000            })]);
2001        }
2002        ModelingCmd::Solid3dGetEdgeUuid(kcmc::Solid3dGetEdgeUuid { object_id, .. }) => {
2003            let Some(OkModelingCmdResponse::Solid3dGetEdgeUuid(edge_uuid)) = response else {
2004                return Ok(Vec::new());
2005            };
2006
2007            return Ok(vec![Artifact::PrimitiveEdge(PrimitiveEdge {
2008                id: edge_uuid.edge_id.into(),
2009                solid_id: (*object_id).into(),
2010                code_ref,
2011            })]);
2012        }
2013        ModelingCmd::EntityLinearPatternTransform(pattern_cmd) => {
2014            let face_edge_infos = match response {
2015                Some(OkModelingCmdResponse::EntityLinearPatternTransform(resp)) => resp.entity_face_edge_ids.as_slice(),
2016                _ => &[],
2017            };
2018            return Ok(pattern_artifact_updates(
2019                artifacts,
2020                id,
2021                PatternSubType::Transform,
2022                ArtifactId::new(pattern_cmd.entity_id),
2023                face_edge_infos,
2024                code_ref,
2025            ));
2026        }
2027        ModelingCmd::EntityLinearPattern(pattern_cmd) => {
2028            let face_edge_infos = match response {
2029                Some(OkModelingCmdResponse::EntityLinearPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
2030                _ => &[],
2031            };
2032            return Ok(pattern_artifact_updates(
2033                artifacts,
2034                id,
2035                PatternSubType::Linear,
2036                ArtifactId::new(pattern_cmd.entity_id),
2037                face_edge_infos,
2038                code_ref,
2039            ));
2040        }
2041        ModelingCmd::EntityCircularPattern(pattern_cmd) => {
2042            let face_edge_infos = match response {
2043                Some(OkModelingCmdResponse::EntityCircularPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
2044                _ => &[],
2045            };
2046            return Ok(pattern_artifact_updates(
2047                artifacts,
2048                id,
2049                PatternSubType::Circular,
2050                ArtifactId::new(pattern_cmd.entity_id),
2051                face_edge_infos,
2052                code_ref,
2053            ));
2054        }
2055        ModelingCmd::EntityMirrorAcross(kcmc::EntityMirrorAcross {
2056            ids: original_solid_ids,
2057            ..
2058        }) => {
2059            let face_edge_infos = match response {
2060                Some(OkModelingCmdResponse::EntityMirrorAcross(resp)) => resp.entity_face_edge_ids.as_slice(),
2061                _ => internal_error!(
2062                    range,
2063                    "EntityMirrorAcross response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
2064                ),
2065            };
2066            return mirror_3d_artifact_updates(artifacts, original_solid_ids, face_edge_infos, code_ref, range, cmd);
2067        }
2068        ModelingCmd::EntityMirror(kcmc::EntityMirror {
2069            ids: original_path_ids, ..
2070        })
2071        | ModelingCmd::EntityMirrorAcrossEdge(kcmc::EntityMirrorAcrossEdge {
2072            ids: original_path_ids, ..
2073        }) => {
2074            let face_edge_infos = match response {
2075                Some(OkModelingCmdResponse::EntityMirror(resp)) => &resp.entity_face_edge_ids,
2076                Some(OkModelingCmdResponse::EntityMirrorAcrossEdge(resp)) => &resp.entity_face_edge_ids,
2077                _ => internal_error!(
2078                    range,
2079                    "Mirror response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
2080                ),
2081            };
2082            if original_path_ids.len() != face_edge_infos.len() {
2083                internal_error!(
2084                    range,
2085                    "EntityMirror or EntityMirrorAcrossEdge response has different number face edge info than original mirrored paths: id={id:?}, cmd={cmd:?}, response={response:?}"
2086                );
2087            }
2088            let mut return_arr = Vec::new();
2089            for (face_edge_info, original_path_id) in face_edge_infos.iter().zip(original_path_ids) {
2090                let original_path_id = ArtifactId::new(*original_path_id);
2091                let path_id = ArtifactId::new(face_edge_info.object_id);
2092                // The path may be an existing path that was extended or a new
2093                // path.
2094                let mut path = if let Some(Artifact::Path(path)) = artifacts.get(&path_id) {
2095                    // Existing path.
2096                    path.clone()
2097                } else {
2098                    // It's a new path.  We need the original path to get some
2099                    // of its info.
2100                    let Some(Artifact::Path(original_path)) = artifacts.get(&original_path_id) else {
2101                        // We couldn't find the original path. This is a bug.
2102                        internal_error!(
2103                            range,
2104                            "Couldn't find original path for mirror2d: original_path_id={original_path_id:?}, cmd={cmd:?}"
2105                        );
2106                    };
2107                    Path {
2108                        id: path_id,
2109                        sub_type: original_path.sub_type,
2110                        plane_id: original_path.plane_id,
2111                        seg_ids: Vec::new(),
2112                        sweep_id: None,
2113                        trajectory_sweep_id: None,
2114                        solid2d_id: None,
2115                        code_ref: code_ref.clone(),
2116                        composite_solid_id: None,
2117                        sketch_block_id: None,
2118                        origin_path_id: original_path.origin_path_id,
2119                        inner_path_id: None,
2120                        outer_path_id: None,
2121                        pattern_ids: Vec::new(),
2122                        consumed: false,
2123                    }
2124                };
2125
2126                face_edge_info.edges.iter().for_each(|edge_id| {
2127                    let edge_id = ArtifactId::new(*edge_id);
2128                    return_arr.push(Artifact::Segment(Segment {
2129                        id: edge_id,
2130                        path_id: path.id,
2131                        original_seg_id: None,
2132                        surface_id: None,
2133                        edge_ids: Vec::new(),
2134                        edge_cut_id: None,
2135                        code_ref: code_ref.clone(),
2136                        common_surface_ids: Vec::new(),
2137                    }));
2138                    // Add the edge ID to the path.
2139                    path.seg_ids.push(edge_id);
2140                });
2141
2142                return_arr.push(Artifact::Path(path));
2143            }
2144            return Ok(return_arr);
2145        }
2146        ModelingCmd::EntityClone(kcmc::EntityClone { entity_id, .. }) => {
2147            let source_id = ArtifactId::new(*entity_id);
2148
2149            let Some(source_artifact) = artifacts.get(&source_id) else {
2150                return Ok(Vec::new());
2151            };
2152
2153            let mut entity_id_map = entity_clone_id_maps.get(&uuid).cloned().unwrap_or_default();
2154            entity_id_map.insert(source_id, id);
2155
2156            let mut cloned_artifacts = Vec::new();
2157            cloned_artifacts.push(remap_artifact_for_clone(
2158                source_artifact,
2159                &entity_id_map,
2160                &code_ref,
2161                artifact_command.cmd_id,
2162                source_id,
2163            ));
2164
2165            for artifact in artifacts.values() {
2166                let artifact_id = artifact.id();
2167                if artifact_id == source_id || !entity_id_map.contains_key(&artifact_id) {
2168                    continue;
2169                }
2170                cloned_artifacts.push(remap_artifact_for_clone(
2171                    artifact,
2172                    &entity_id_map,
2173                    &code_ref,
2174                    artifact_command.cmd_id,
2175                    source_id,
2176                ));
2177            }
2178
2179            return Ok(cloned_artifacts);
2180        }
2181        ModelingCmd::Extrude(kcmc::Extrude { target, .. })
2182        | ModelingCmd::TwistExtrude(kcmc::TwistExtrude { target, .. })
2183        | ModelingCmd::Revolve(kcmc::Revolve { target, .. })
2184        | ModelingCmd::RevolveAboutEdge(kcmc::RevolveAboutEdge { target, .. })
2185        | ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { target, .. }) => {
2186            // Determine the resulting method from the specific command, if provided
2187            let method = match cmd {
2188                ModelingCmd::Extrude(kcmc::Extrude { extrude_method, .. }) => *extrude_method,
2189                ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { extrude_method, .. }) => *extrude_method,
2190                // TwistExtrude and Sweep don't carry method in the command; treat as Merge
2191                ModelingCmd::TwistExtrude(_) | ModelingCmd::Sweep(_) => {
2192                    kittycad_modeling_cmds::shared::ExtrudeMethod::Merge
2193                }
2194                // Revolve variants behave like New bodies in std layer
2195                ModelingCmd::Revolve(_) | ModelingCmd::RevolveAboutEdge(_) => {
2196                    kittycad_modeling_cmds::shared::ExtrudeMethod::New
2197                }
2198                _ => kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
2199            };
2200            let sub_type = match cmd {
2201                ModelingCmd::Extrude(_) => SweepSubType::Extrusion,
2202                ModelingCmd::ExtrudeToReference(_) => SweepSubType::Extrusion,
2203                ModelingCmd::TwistExtrude(_) => SweepSubType::ExtrusionTwist,
2204                ModelingCmd::Revolve(_) => SweepSubType::Revolve,
2205                ModelingCmd::RevolveAboutEdge(_) => SweepSubType::RevolveAboutEdge,
2206                _ => internal_error!(range, "Sweep-like command variant not handled: id={id:?}, cmd={cmd:?}",),
2207            };
2208            let mut return_arr = Vec::new();
2209            let target = ArtifactId::from(target);
2210            return_arr.push(Artifact::Sweep(Sweep {
2211                id,
2212                sub_type,
2213                path_id: target,
2214                surface_ids: Vec::new(),
2215                edge_ids: Vec::new(),
2216                code_ref,
2217                trajectory_id: None,
2218                method,
2219                consumed: false,
2220                pattern_ids: Vec::new(),
2221            }));
2222            let path = artifacts.get(&target);
2223            if let Some(Artifact::Path(path)) = path {
2224                let mut new_path = path.clone();
2225                new_path.sweep_id = Some(id);
2226                new_path.consumed = true;
2227                return_arr.push(Artifact::Path(new_path));
2228                if let Some(inner_path_id) = path.inner_path_id
2229                    && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
2230                    && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
2231                {
2232                    inner_path_artifact.sweep_id = Some(id);
2233                    inner_path_artifact.consumed = true;
2234                    return_arr.push(Artifact::Path(inner_path_artifact))
2235                }
2236            }
2237            return Ok(return_arr);
2238        }
2239        ModelingCmd::Sweep(kcmc::Sweep { target, trajectory, .. }) => {
2240            // Determine the resulting method from the specific command, if provided
2241            let method = kittycad_modeling_cmds::shared::ExtrudeMethod::Merge;
2242            let sub_type = SweepSubType::Sweep;
2243            let mut return_arr = Vec::new();
2244            let target = ArtifactId::from(target);
2245            let trajectory = ArtifactId::from(trajectory);
2246            return_arr.push(Artifact::Sweep(Sweep {
2247                id,
2248                sub_type,
2249                path_id: target,
2250                surface_ids: Vec::new(),
2251                edge_ids: Vec::new(),
2252                code_ref,
2253                trajectory_id: Some(trajectory),
2254                method,
2255                consumed: false,
2256                pattern_ids: Vec::new(),
2257            }));
2258            let path = artifacts.get(&target);
2259            if let Some(Artifact::Path(path)) = path {
2260                let mut new_path = path.clone();
2261                new_path.sweep_id = Some(id);
2262                new_path.consumed = true;
2263                return_arr.push(Artifact::Path(new_path));
2264                if let Some(inner_path_id) = path.inner_path_id
2265                    && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
2266                    && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
2267                {
2268                    inner_path_artifact.sweep_id = Some(id);
2269                    inner_path_artifact.consumed = true;
2270                    return_arr.push(Artifact::Path(inner_path_artifact))
2271                }
2272            }
2273            if let Some(trajectory_artifact) = artifacts.get(&trajectory) {
2274                match trajectory_artifact {
2275                    Artifact::Path(path) => {
2276                        let mut new_path = path.clone();
2277                        new_path.trajectory_sweep_id = Some(id);
2278                        new_path.consumed = true;
2279                        return_arr.push(Artifact::Path(new_path));
2280                    }
2281                    Artifact::Helix(helix) => {
2282                        let mut new_helix = helix.clone();
2283                        new_helix.trajectory_sweep_id = Some(id);
2284                        new_helix.consumed = true;
2285                        return_arr.push(Artifact::Helix(new_helix));
2286                    }
2287                    _ => {}
2288                }
2289            };
2290            return Ok(return_arr);
2291        }
2292        ModelingCmd::SurfaceBlend(surface_blend_cmd) => {
2293            let surface_id_to_path_id = |surface_id: ArtifactId| -> Option<ArtifactId> {
2294                match artifacts.get(&surface_id) {
2295                    Some(Artifact::Path(path)) => Some(path.id),
2296                    Some(Artifact::Segment(segment)) => Some(segment.path_id),
2297                    Some(Artifact::Sweep(sweep)) => Some(sweep.path_id),
2298                    Some(Artifact::Wall(wall)) => artifacts.get(&wall.sweep_id).and_then(|artifact| match artifact {
2299                        Artifact::Sweep(sweep) => Some(sweep.path_id),
2300                        _ => None,
2301                    }),
2302                    Some(Artifact::Cap(cap)) => artifacts.get(&cap.sweep_id).and_then(|artifact| match artifact {
2303                        Artifact::Sweep(sweep) => Some(sweep.path_id),
2304                        _ => None,
2305                    }),
2306                    _ => None,
2307                }
2308            };
2309            let Some(first_surface_ref) = surface_blend_cmd.surfaces.first() else {
2310                internal_error!(range, "SurfaceBlend command has no surfaces: id={id:?}, cmd={cmd:?}");
2311            };
2312            let first_surface_id = ArtifactId::new(first_surface_ref.object_id);
2313            let path_id = surface_id_to_path_id(first_surface_id).unwrap_or(first_surface_id);
2314            let trajectory_id = surface_blend_cmd
2315                .surfaces
2316                .get(1)
2317                .map(|surface| ArtifactId::new(surface.object_id))
2318                .and_then(surface_id_to_path_id);
2319            let return_arr = vec![Artifact::Sweep(Sweep {
2320                id,
2321                sub_type: SweepSubType::Blend,
2322                path_id,
2323                surface_ids: Vec::new(),
2324                edge_ids: Vec::new(),
2325                code_ref,
2326                trajectory_id,
2327                method: kittycad_modeling_cmds::shared::ExtrudeMethod::New,
2328                consumed: false,
2329                pattern_ids: Vec::new(),
2330            })];
2331            return Ok(return_arr);
2332        }
2333        ModelingCmd::Loft(loft_cmd) => {
2334            let Some(OkModelingCmdResponse::Loft(_)) = response else {
2335                return Ok(Vec::new());
2336            };
2337            let mut return_arr = Vec::new();
2338            return_arr.push(Artifact::Sweep(Sweep {
2339                id,
2340                sub_type: SweepSubType::Loft,
2341                // TODO: Using the first one.  Make sure to revisit this
2342                // choice, don't think it matters for now.
2343                path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
2344                    KclError::new_internal(KclErrorDetails::new(
2345                        format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
2346                        vec![range],
2347                    ))
2348                })?),
2349                surface_ids: Vec::new(),
2350                edge_ids: Vec::new(),
2351                code_ref,
2352                trajectory_id: None,
2353                method: kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
2354                consumed: false,
2355                pattern_ids: Vec::new(),
2356            }));
2357            for section_id in &loft_cmd.section_ids {
2358                let path = artifacts.get(&ArtifactId::new(*section_id));
2359                if let Some(Artifact::Path(path)) = path {
2360                    let mut new_path = path.clone();
2361                    new_path.consumed = true;
2362                    new_path.sweep_id = Some(id);
2363                    return_arr.push(Artifact::Path(new_path));
2364                }
2365            }
2366            return Ok(return_arr);
2367        }
2368        ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
2369            let Some(OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info)) = response else {
2370                return Ok(Vec::new());
2371            };
2372            let mut return_arr = Vec::new();
2373            let mut last_path = None;
2374            for face in &face_info.faces {
2375                if face.cap != ExtrusionFaceCapType::None {
2376                    continue;
2377                }
2378                let Some(curve_id) = face.curve_id.map(ArtifactId::new) else {
2379                    continue;
2380                };
2381                let Some(face_id) = face.face_id.map(ArtifactId::new) else {
2382                    continue;
2383                };
2384                let Some(Artifact::Segment(seg)) = artifacts.get(&curve_id) else {
2385                    continue;
2386                };
2387                let Some(Artifact::Path(path)) = artifacts.get(&seg.path_id) else {
2388                    continue;
2389                };
2390                last_path = Some(path);
2391                let Some(path_sweep_id) = path.sweep_id else {
2392                    // If the path doesn't have a sweep ID, check if it's a
2393                    // hole.
2394                    if path.outer_path_id.is_some() {
2395                        continue; // hole not handled
2396                    }
2397                    return Err(KclError::new_internal(KclErrorDetails::new(
2398                        format!(
2399                            "Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
2400                        ),
2401                        vec![range],
2402                    )));
2403                };
2404                let extra_artifact = exec_artifacts.values().find(|a| {
2405                    if let Artifact::StartSketchOnFace(s) = a {
2406                        s.face_id == face_id
2407                    } else if let Artifact::StartSketchOnPlane(s) = a {
2408                        s.plane_id == face_id
2409                    } else {
2410                        false
2411                    }
2412                });
2413                let sketch_on_face_code_ref = extra_artifact
2414                    .and_then(|a| match a {
2415                        Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
2416                        Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
2417                        _ => None,
2418                    })
2419                    // TODO: If we didn't find it, it's probably a bug.
2420                    .unwrap_or_default();
2421
2422                return_arr.push(Artifact::Wall(Wall {
2423                    id: face_id,
2424                    seg_id: curve_id,
2425                    edge_cut_edge_ids: Vec::new(),
2426                    sweep_id: path_sweep_id,
2427                    path_ids: Vec::new(),
2428                    face_code_ref: sketch_on_face_code_ref,
2429                    cmd_id: artifact_command.cmd_id,
2430                }));
2431                let mut new_seg = seg.clone();
2432                new_seg.surface_id = Some(face_id);
2433                return_arr.push(Artifact::Segment(new_seg));
2434                if let Some(Artifact::Sweep(sweep)) = path.sweep_id.and_then(|id| artifacts.get(&id)) {
2435                    let mut new_sweep = sweep.clone();
2436                    new_sweep.surface_ids = vec![face_id];
2437                    return_arr.push(Artifact::Sweep(new_sweep));
2438                }
2439            }
2440            if let Some(path) = last_path {
2441                for face in &face_info.faces {
2442                    let sub_type = match face.cap {
2443                        ExtrusionFaceCapType::Top => CapSubType::End,
2444                        ExtrusionFaceCapType::Bottom => CapSubType::Start,
2445                        ExtrusionFaceCapType::None | ExtrusionFaceCapType::Both => continue,
2446                        _other => {
2447                            // Modeling API has added something we're not aware of.
2448                            continue;
2449                        }
2450                    };
2451                    let Some(face_id) = face.face_id.map(ArtifactId::new) else {
2452                        continue;
2453                    };
2454                    let Some(path_sweep_id) = path.sweep_id else {
2455                        // If the path doesn't have a sweep ID, check if it's a
2456                        // hole.
2457                        if path.outer_path_id.is_some() {
2458                            continue; // hole not handled
2459                        }
2460                        return Err(KclError::new_internal(KclErrorDetails::new(
2461                            format!(
2462                                "Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
2463                            ),
2464                            vec![range],
2465                        )));
2466                    };
2467                    let extra_artifact = exec_artifacts.values().find(|a| {
2468                        if let Artifact::StartSketchOnFace(s) = a {
2469                            s.face_id == face_id
2470                        } else if let Artifact::StartSketchOnPlane(s) = a {
2471                            s.plane_id == face_id
2472                        } else {
2473                            false
2474                        }
2475                    });
2476                    let sketch_on_face_code_ref = extra_artifact
2477                        .and_then(|a| match a {
2478                            Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
2479                            Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
2480                            _ => None,
2481                        })
2482                        // TODO: If we didn't find it, it's probably a bug.
2483                        .unwrap_or_default();
2484                    return_arr.push(Artifact::Cap(Cap {
2485                        id: face_id,
2486                        sub_type,
2487                        edge_cut_edge_ids: Vec::new(),
2488                        sweep_id: path_sweep_id,
2489                        path_ids: Vec::new(),
2490                        face_code_ref: sketch_on_face_code_ref,
2491                        cmd_id: artifact_command.cmd_id,
2492                    }));
2493                    let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
2494                        continue;
2495                    };
2496                    let mut new_sweep = sweep.clone();
2497                    new_sweep.surface_ids = vec![face_id];
2498                    return_arr.push(Artifact::Sweep(new_sweep));
2499                }
2500            }
2501            return Ok(return_arr);
2502        }
2503        ModelingCmd::Solid3dGetAdjacencyInfo(kcmc::Solid3dGetAdjacencyInfo { .. }) => {
2504            let Some(OkModelingCmdResponse::Solid3dGetAdjacencyInfo(info)) = response else {
2505                return Ok(Vec::new());
2506            };
2507
2508            let mut return_arr = Vec::new();
2509            for (index, edge) in info.edges.iter().enumerate() {
2510                let Some(original_info) = &edge.original_info else {
2511                    continue;
2512                };
2513                let edge_id = ArtifactId::new(original_info.edge_id);
2514                let Some(artifact) = artifacts.get(&edge_id) else {
2515                    continue;
2516                };
2517                match artifact {
2518                    Artifact::Segment(segment) => {
2519                        let mut new_segment = segment.clone();
2520                        new_segment.common_surface_ids =
2521                            original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
2522                        return_arr.push(Artifact::Segment(new_segment));
2523                    }
2524                    Artifact::SweepEdge(sweep_edge) => {
2525                        let mut new_sweep_edge = sweep_edge.clone();
2526                        new_sweep_edge.common_surface_ids =
2527                            original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
2528                        return_arr.push(Artifact::SweepEdge(new_sweep_edge));
2529                    }
2530                    _ => {}
2531                };
2532
2533                let Some(Artifact::Segment(segment)) = artifacts.get(&edge_id) else {
2534                    continue;
2535                };
2536                let Some(surface_id) = segment.surface_id else {
2537                    continue;
2538                };
2539                let Some(Artifact::Wall(wall)) = artifacts.get(&surface_id) else {
2540                    continue;
2541                };
2542                let Some(Artifact::Sweep(sweep)) = artifacts.get(&wall.sweep_id) else {
2543                    continue;
2544                };
2545                let Some(Artifact::Path(_)) = artifacts.get(&sweep.path_id) else {
2546                    continue;
2547                };
2548
2549                if let Some(opposite_info) = &edge.opposite_info {
2550                    return_arr.push(Artifact::SweepEdge(SweepEdge {
2551                        id: opposite_info.edge_id.into(),
2552                        sub_type: SweepEdgeSubType::Opposite,
2553                        seg_id: edge_id,
2554                        cmd_id: artifact_command.cmd_id,
2555                        index,
2556                        sweep_id: sweep.id,
2557                        common_surface_ids: opposite_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
2558                    }));
2559                    let mut new_segment = segment.clone();
2560                    new_segment.edge_ids = vec![opposite_info.edge_id.into()];
2561                    return_arr.push(Artifact::Segment(new_segment));
2562                    let mut new_sweep = sweep.clone();
2563                    new_sweep.edge_ids = vec![opposite_info.edge_id.into()];
2564                    return_arr.push(Artifact::Sweep(new_sweep));
2565                    let mut new_wall = wall.clone();
2566                    new_wall.edge_cut_edge_ids = vec![opposite_info.edge_id.into()];
2567                    return_arr.push(Artifact::Wall(new_wall));
2568                }
2569                if let Some(adjacent_info) = &edge.adjacent_info {
2570                    return_arr.push(Artifact::SweepEdge(SweepEdge {
2571                        id: adjacent_info.edge_id.into(),
2572                        sub_type: SweepEdgeSubType::Adjacent,
2573                        seg_id: edge_id,
2574                        cmd_id: artifact_command.cmd_id,
2575                        index,
2576                        sweep_id: sweep.id,
2577                        common_surface_ids: adjacent_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
2578                    }));
2579                    let mut new_segment = segment.clone();
2580                    new_segment.edge_ids = vec![adjacent_info.edge_id.into()];
2581                    return_arr.push(Artifact::Segment(new_segment));
2582                    let mut new_sweep = sweep.clone();
2583                    new_sweep.edge_ids = vec![adjacent_info.edge_id.into()];
2584                    return_arr.push(Artifact::Sweep(new_sweep));
2585                    let mut new_wall = wall.clone();
2586                    new_wall.edge_cut_edge_ids = vec![adjacent_info.edge_id.into()];
2587                    return_arr.push(Artifact::Wall(new_wall));
2588                }
2589            }
2590            return Ok(return_arr);
2591        }
2592        ModelingCmd::Solid3dMultiJoin(cmd) => {
2593            let mut return_arr = Vec::new();
2594            return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2595                id,
2596                consumed: false,
2597                sub_type: CompositeSolidSubType::Union,
2598                output_index: None,
2599                solid_ids: cmd.object_ids.iter().map(|id| id.into()).collect(),
2600                tool_ids: vec![],
2601                code_ref,
2602                composite_solid_id: None,
2603                pattern_ids: Vec::new(),
2604            }));
2605
2606            let solid_ids = cmd.object_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2607
2608            for input_id in &solid_ids {
2609                if let Some(artifact) = artifacts.get(input_id)
2610                    && let Artifact::CompositeSolid(comp) = artifact
2611                {
2612                    let mut new_comp = comp.clone();
2613                    new_comp.composite_solid_id = Some(id);
2614                    new_comp.consumed = true;
2615                    return_arr.push(Artifact::CompositeSolid(new_comp));
2616                } else if let Some(Artifact::Sweep(sweep)) = artifacts.get(input_id) {
2617                    let mut new_sweep = sweep.clone();
2618                    new_sweep.consumed = true;
2619                    return_arr.push(Artifact::Sweep(new_sweep));
2620                }
2621            }
2622            return Ok(return_arr);
2623        }
2624        ModelingCmd::Solid3dFilletEdge(cmd) => {
2625            let mut return_arr = Vec::new();
2626            let edge_id = if let Some(edge_id) = cmd.edge_id {
2627                ArtifactId::new(edge_id)
2628            } else {
2629                let Some(edge_id) = cmd.edge_ids.first() else {
2630                    internal_error!(
2631                        range,
2632                        "Solid3dFilletEdge command has no edge ID: id={id:?}, cmd={cmd:?}"
2633                    );
2634                };
2635                edge_id.into()
2636            };
2637            return_arr.push(Artifact::EdgeCut(EdgeCut {
2638                id,
2639                sub_type: cmd.cut_type.into(),
2640                consumed_edge_id: edge_id,
2641                edge_ids: Vec::new(),
2642                surface_id: None,
2643                code_ref,
2644            }));
2645            let consumed_edge = artifacts.get(&edge_id);
2646            if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2647                let mut new_segment = consumed_edge.clone();
2648                new_segment.edge_cut_id = Some(id);
2649                return_arr.push(Artifact::Segment(new_segment));
2650            } else {
2651                // TODO: Handle other types like SweepEdge.
2652            }
2653            return Ok(return_arr);
2654        }
2655        ModelingCmd::Solid3dCutEdges(cmd) => {
2656            let mut return_arr = Vec::new();
2657            let edge_id = if let Some(edge_id) = cmd.edge_ids.first() {
2658                edge_id.into()
2659            } else {
2660                internal_error!(range, "Solid3dCutEdges command has no edge ID: id={id:?}, cmd={cmd:?}");
2661            };
2662            return_arr.push(Artifact::EdgeCut(EdgeCut {
2663                id,
2664                sub_type: cmd.cut_type.into(),
2665                consumed_edge_id: edge_id,
2666                edge_ids: Vec::new(),
2667                surface_id: None,
2668                code_ref,
2669            }));
2670            let consumed_edge = artifacts.get(&edge_id);
2671            if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2672                let mut new_segment = consumed_edge.clone();
2673                new_segment.edge_cut_id = Some(id);
2674                return_arr.push(Artifact::Segment(new_segment));
2675            } else {
2676                // TODO: Handle other types like SweepEdge.
2677            }
2678            return Ok(return_arr);
2679        }
2680        ModelingCmd::EntityMakeHelix(cmd) => {
2681            let cylinder_id = ArtifactId::new(cmd.cylinder_id);
2682            let return_arr = vec![Artifact::Helix(Helix {
2683                id,
2684                axis_id: Some(cylinder_id),
2685                code_ref,
2686                trajectory_sweep_id: None,
2687                consumed: false,
2688            })];
2689            return Ok(return_arr);
2690        }
2691        ModelingCmd::EntityMakeHelixFromParams(_) => {
2692            let return_arr = vec![Artifact::Helix(Helix {
2693                id,
2694                axis_id: None,
2695                code_ref,
2696                trajectory_sweep_id: None,
2697                consumed: false,
2698            })];
2699            return Ok(return_arr);
2700        }
2701        ModelingCmd::EntityMakeHelixFromEdge(helix) => {
2702            let return_arr = vec![Artifact::Helix(Helix {
2703                id,
2704                axis_id: helix.edge_id.map(ArtifactId::new),
2705                code_ref,
2706                trajectory_sweep_id: None,
2707                consumed: false,
2708            })];
2709            // We could add the reverse graph edge connecting from the edge to
2710            // the helix here, but it's not useful right now.
2711            return Ok(return_arr);
2712        }
2713        ModelingCmd::Solid2dAddHole(solid2d_add_hole) => {
2714            let mut return_arr = Vec::new();
2715            // Add the hole to the outer.
2716            let outer_path = artifacts.get(&ArtifactId::new(solid2d_add_hole.object_id));
2717            if let Some(Artifact::Path(path)) = outer_path {
2718                let mut new_path = path.clone();
2719                new_path.inner_path_id = Some(ArtifactId::new(solid2d_add_hole.hole_id));
2720                return_arr.push(Artifact::Path(new_path));
2721            }
2722            // Add the outer to the hole.
2723            let inner_solid2d = artifacts.get(&ArtifactId::new(solid2d_add_hole.hole_id));
2724            if let Some(Artifact::Path(path)) = inner_solid2d {
2725                let mut new_path = path.clone();
2726                new_path.consumed = true;
2727                new_path.outer_path_id = Some(ArtifactId::new(solid2d_add_hole.object_id));
2728                return_arr.push(Artifact::Path(new_path));
2729            }
2730            return Ok(return_arr);
2731        }
2732        ModelingCmd::BooleanIntersection(_) | ModelingCmd::BooleanSubtract(_) | ModelingCmd::BooleanUnion(_) => {
2733            let (sub_type, solid_ids, tool_ids) = match cmd {
2734                ModelingCmd::BooleanIntersection(intersection) => {
2735                    let solid_ids = intersection
2736                        .solid_ids
2737                        .iter()
2738                        .copied()
2739                        .map(ArtifactId::new)
2740                        .collect::<Vec<_>>();
2741                    (CompositeSolidSubType::Intersect, solid_ids, Vec::new())
2742                }
2743                ModelingCmd::BooleanSubtract(subtract) => {
2744                    let solid_ids = subtract
2745                        .target_ids
2746                        .iter()
2747                        .copied()
2748                        .map(ArtifactId::new)
2749                        .collect::<Vec<_>>();
2750                    let tool_ids = subtract
2751                        .tool_ids
2752                        .iter()
2753                        .copied()
2754                        .map(ArtifactId::new)
2755                        .collect::<Vec<_>>();
2756                    (CompositeSolidSubType::Subtract, solid_ids, tool_ids)
2757                }
2758                ModelingCmd::BooleanUnion(union) => {
2759                    let solid_ids = union.solid_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2760                    (CompositeSolidSubType::Union, solid_ids, Vec::new())
2761                }
2762                _ => internal_error!(
2763                    range,
2764                    "Boolean or composite command variant not handled: id={id:?}, cmd={cmd:?}"
2765                ),
2766            };
2767
2768            let mut new_solid_ids = vec![id];
2769
2770            // Make sure we don't ever create a duplicate ID since merge_ids
2771            // can't handle it.
2772            let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2773
2774            match (cmd, response) {
2775                (
2776                    ModelingCmd::BooleanSubtract(subtract_cmd),
2777                    Some(OkModelingCmdResponse::BooleanSubtract(subtract_resp)),
2778                ) => {
2779                    new_solid_ids = boolean_subtract_output_artifact_ids(
2780                        id,
2781                        &subtract_cmd.target_ids,
2782                        &subtract_cmd.tool_ids,
2783                        &subtract_resp.extra_solid_ids,
2784                    );
2785                }
2786                (_, Some(OkModelingCmdResponse::BooleanIntersection(intersection))) => intersection
2787                    .extra_solid_ids
2788                    .iter()
2789                    .copied()
2790                    .map(ArtifactId::new)
2791                    .filter(not_cmd_id)
2792                    .for_each(|id| new_solid_ids.push(id)),
2793                (_, Some(OkModelingCmdResponse::BooleanUnion(union))) => union
2794                    .extra_solid_ids
2795                    .iter()
2796                    .copied()
2797                    .map(ArtifactId::new)
2798                    .filter(not_cmd_id)
2799                    .for_each(|id| new_solid_ids.push(id)),
2800                _ => {}
2801            }
2802
2803            let mut return_arr = Vec::new();
2804            let mut consumed_sweep_ids = FnvHashSet::default();
2805            let mut input_ids = solid_ids.clone();
2806            merge_ids(&mut input_ids, tool_ids.clone());
2807
2808            if new_solid_ids.is_empty() {
2809                update_csg_input_artifacts(&mut return_arr, artifacts, &input_ids, None, &mut consumed_sweep_ids);
2810            }
2811
2812            // Create the new composite solids and update their linked artifacts
2813            for solid_id in &new_solid_ids {
2814                // Create the composite solid
2815                return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2816                    id: *solid_id,
2817                    consumed: false,
2818                    sub_type,
2819                    output_index: None,
2820                    solid_ids: solid_ids.clone(),
2821                    tool_ids: tool_ids.clone(),
2822                    code_ref: code_ref.clone(),
2823                    composite_solid_id: None,
2824                    pattern_ids: Vec::new(),
2825                }));
2826
2827                update_csg_input_artifacts(
2828                    &mut return_arr,
2829                    artifacts,
2830                    &input_ids,
2831                    Some(*solid_id),
2832                    &mut consumed_sweep_ids,
2833                );
2834            }
2835
2836            return Ok(return_arr);
2837        }
2838        ModelingCmd::BooleanImprint(imprint) => {
2839            let solid_ids = imprint
2840                .body_ids
2841                .iter()
2842                .copied()
2843                .map(ArtifactId::new)
2844                .collect::<Vec<_>>();
2845            let tool_ids = imprint
2846                .tool_ids
2847                .as_ref()
2848                .map(|ids| ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>())
2849                .unwrap_or_default();
2850
2851            let mut new_solid_ids = vec![id];
2852            let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2853            if let Some(OkModelingCmdResponse::BooleanImprint(imprint)) = response {
2854                imprint
2855                    .extra_solid_ids
2856                    .iter()
2857                    .copied()
2858                    .map(ArtifactId::new)
2859                    .filter(not_cmd_id)
2860                    .for_each(|id| new_solid_ids.push(id));
2861            }
2862
2863            let mut return_arr = Vec::new();
2864            let mut consumed_sweep_ids = FnvHashSet::default();
2865
2866            for input_id in solid_ids.iter().chain(tool_ids.iter()) {
2867                let sweep_id = match artifacts.get(input_id) {
2868                    Some(Artifact::Sweep(sweep)) => Some(sweep.id),
2869                    Some(Artifact::Path(path)) => path.sweep_id,
2870                    _ => None,
2871                };
2872
2873                if let Some(sweep_id) = sweep_id
2874                    && consumed_sweep_ids.insert(sweep_id)
2875                    && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
2876                {
2877                    let mut new_sweep = sweep.clone();
2878                    new_sweep.consumed = true;
2879                    return_arr.push(Artifact::Sweep(new_sweep));
2880                }
2881            }
2882
2883            for (output_index, solid_id) in new_solid_ids.iter().enumerate() {
2884                return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2885                    id: *solid_id,
2886                    consumed: false,
2887                    sub_type: CompositeSolidSubType::Split,
2888                    output_index: Some(output_index),
2889                    solid_ids: solid_ids.clone(),
2890                    tool_ids: tool_ids.clone(),
2891                    code_ref: code_ref.clone(),
2892                    composite_solid_id: None,
2893                    pattern_ids: Vec::new(),
2894                }));
2895
2896                for input_id in solid_ids.iter().chain(tool_ids.iter()) {
2897                    if let Some(artifact) = artifacts.get(input_id) {
2898                        match artifact {
2899                            Artifact::CompositeSolid(comp) => {
2900                                let mut new_comp = comp.clone();
2901                                new_comp.composite_solid_id = Some(*solid_id);
2902                                new_comp.consumed = true;
2903                                return_arr.push(Artifact::CompositeSolid(new_comp));
2904                            }
2905                            Artifact::Path(path) => {
2906                                let mut new_path = path.clone();
2907                                new_path.composite_solid_id = Some(*solid_id);
2908
2909                                return_arr.push(Artifact::Path(new_path));
2910                            }
2911                            _ => {}
2912                        }
2913                    }
2914                }
2915            }
2916
2917            return Ok(return_arr);
2918        }
2919        _ => {}
2920    }
2921
2922    Ok(Vec::new())
2923}