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