Skip to main content

kcl_lib/execution/
artifact.rs

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