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#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
49#[ts(export_to = "Artifact.ts")]
50#[serde(rename_all = "camelCase")]
51pub struct ArtifactCommand {
52 pub cmd_id: Uuid,
54 pub range: SourceRange,
57 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 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 #[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 pub consumed: bool,
104 pub sub_type: CompositeSolidSubType,
105 pub solid_ids: Vec<ArtifactId>,
107 pub tool_ids: Vec<ArtifactId>,
109 pub code_ref: CodeRef,
110 #[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 pub consumed: bool,
144 #[serde(default, skip_serializing_if = "Option::is_none")]
145 pub sweep_id: Option<ArtifactId>,
148 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 #[serde(default, skip_serializing_if = "Option::is_none")]
156 pub composite_solid_id: Option<ArtifactId>,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub sketch_block_id: Option<ArtifactId>,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
164 pub origin_path_id: Option<ArtifactId>,
165 #[serde(default, skip_serializing_if = "Option::is_none")]
167 pub inner_path_id: Option<ArtifactId>,
168 #[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 #[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#[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 pub trajectory_id: Option<ArtifactId>,
216 pub method: kittycad_modeling_cmds::shared::ExtrudeMethod,
217 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 #[serde(default, skip_serializing_if = "Option::is_none")]
294 pub standard_plane: Option<PlaneName>,
295 #[serde(default, skip_serializing_if = "Option::is_none")]
297 pub plane_id: Option<ArtifactId>,
298 #[serde(default, skip_serializing_if = "Option::is_none")]
302 pub path_id: Option<ArtifactId>,
303 pub code_ref: CodeRef,
304 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 pub sketch_id: ObjectId,
360 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 pub face_code_ref: CodeRef,
378 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 pub face_code_ref: CodeRef,
394 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 #[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 _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 pub axis_id: Option<ArtifactId>,
489 pub code_ref: CodeRef,
490 pub trajectory_sweep_id: Option<ArtifactId>,
492 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 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 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 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 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
853pub(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 for exec_artifact in exec_artifacts.values_mut() {
875 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 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_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
924fn 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
959fn 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 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 map.insert(id, new_artifact);
1013 return;
1014 };
1015
1016 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
1028fn merge_ids(base: &mut Vec<ArtifactId>, new: Vec<ArtifactId>) {
1032 let original_len = base.len();
1033 for id in new {
1034 let original_base = &base[..original_len];
1036 if !original_base.contains(&id) {
1037 base.push(id);
1038 }
1039 }
1040}
1041
1042fn merge_opt_id(base: &mut Option<ArtifactId>, new: Option<ArtifactId>) {
1044 *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 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 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 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 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 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 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 let mut path = if let Some(Artifact::Path(path)) = artifacts.get(&path_id) {
1361 path.clone()
1363 } else {
1364 let Some(Artifact::Path(original_path)) = artifacts.get(&original_path_id) else {
1367 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 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 let method = match cmd {
1418 ModelingCmd::Extrude(kcmc::Extrude { extrude_method, .. }) => *extrude_method,
1419 ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { extrude_method, .. }) => *extrude_method,
1420 ModelingCmd::TwistExtrude(_) | ModelingCmd::Sweep(_) => {
1422 kittycad_modeling_cmds::shared::ExtrudeMethod::Merge
1423 }
1424 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 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 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 path.outer_path_id.is_some() {
1621 continue; }
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 .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 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 path.outer_path_id.is_some() {
1684 continue; }
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 .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 }
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 }
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 return Ok(return_arr);
1933 }
1934 ModelingCmd::Solid2dAddHole(solid2d_add_hole) => {
1935 let mut return_arr = Vec::new();
1936 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 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 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 for solid_id in &new_solid_ids {
2024 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 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 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 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 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}