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