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