Skip to main content

kcl_lib/execution/
artifact.rs

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