Skip to main content

kcl_lib/execution/
artifact.rs

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