Skip to main content

kcl_lib/execution/
artifact.rs

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