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) => {
932 if face.code_ref.node_path.is_empty() {
933 let (range, node_path) =
934 code_ref_for_range(programs, cached_body_items, face.code_ref.range, import_code_refs);
935 face.code_ref.range = range;
936 face.code_ref.node_path = node_path;
937 }
938 }
939 Artifact::StartSketchOnPlane(plane) => {
940 if plane.code_ref.node_path.is_empty() {
941 let (range, node_path) =
942 code_ref_for_range(programs, cached_body_items, plane.code_ref.range, import_code_refs);
943 plane.code_ref.range = range;
944 plane.code_ref.node_path = node_path;
945 }
946 }
947 Artifact::SketchBlock(block) => {
948 if block.code_ref.node_path.is_empty() {
949 let (range, node_path) =
950 code_ref_for_range(programs, cached_body_items, block.code_ref.range, import_code_refs);
951 block.code_ref.range = range;
952 block.code_ref.node_path = node_path;
953 }
954 }
955 Artifact::SketchBlockConstraint(constraint) => {
956 if constraint.code_ref.node_path.is_empty() {
957 constraint.code_ref.node_path =
958 NodePath::from_range(programs, cached_body_items, constraint.code_ref.range).unwrap_or_default();
959 }
960 }
961 _ => {}
962 }
963}
964
965fn flatten_modeling_command_responses(
968 responses: &IndexMap<Uuid, WebSocketResponse>,
969) -> FnvHashMap<Uuid, OkModelingCmdResponse> {
970 let mut map = FnvHashMap::default();
971 for (cmd_id, ws_response) in responses {
972 let WebSocketResponse::Success(response) = ws_response else {
973 continue;
975 };
976 match &response.resp {
977 OkWebSocketResponseData::Modeling { modeling_response } => {
978 map.insert(*cmd_id, modeling_response.clone());
979 }
980 OkWebSocketResponseData::ModelingBatch { responses } =>
981 {
982 #[expect(
983 clippy::iter_over_hash_type,
984 reason = "Since we're moving entries to another unordered map, it's fine that the order is undefined"
985 )]
986 for (cmd_id, batch_response) in responses {
987 if let BatchResponse::Success {
988 response: modeling_response,
989 } = batch_response
990 {
991 map.insert(*cmd_id.as_ref(), modeling_response.clone());
992 }
993 }
994 }
995 OkWebSocketResponseData::IceServerInfo { .. }
996 | OkWebSocketResponseData::TrickleIce { .. }
997 | OkWebSocketResponseData::SdpAnswer { .. }
998 | OkWebSocketResponseData::Export { .. }
999 | OkWebSocketResponseData::MetricsRequest { .. }
1000 | OkWebSocketResponseData::ModelingSessionData { .. }
1001 | OkWebSocketResponseData::Debug { .. }
1002 | OkWebSocketResponseData::Pong { .. } => {}
1003 _other => {}
1004 }
1005 }
1006
1007 map
1008}
1009
1010fn merge_artifact_into_map(map: &mut IndexMap<ArtifactId, Artifact>, new_artifact: Artifact) {
1011 fn is_primitive_artifact(artifact: &Artifact) -> bool {
1012 matches!(artifact, Artifact::PrimitiveFace(_) | Artifact::PrimitiveEdge(_))
1013 }
1014
1015 let id = new_artifact.id();
1016 let Some(old_artifact) = map.get_mut(&id) else {
1017 map.insert(id, new_artifact);
1019 return;
1020 };
1021
1022 if is_primitive_artifact(&new_artifact) && !is_primitive_artifact(old_artifact) {
1026 return;
1027 }
1028
1029 if let Some(replacement) = old_artifact.merge(new_artifact) {
1030 *old_artifact = replacement;
1031 }
1032}
1033
1034fn merge_ids(base: &mut Vec<ArtifactId>, new: Vec<ArtifactId>) {
1038 let original_len = base.len();
1039 for id in new {
1040 let original_base = &base[..original_len];
1042 if !original_base.contains(&id) {
1043 base.push(id);
1044 }
1045 }
1046}
1047
1048fn merge_opt_id(base: &mut Option<ArtifactId>, new: Option<ArtifactId>) {
1050 *base = new;
1052}
1053
1054#[allow(clippy::too_many_arguments)]
1055fn artifacts_to_update(
1056 artifacts: &IndexMap<ArtifactId, Artifact>,
1057 artifact_command: &ArtifactCommand,
1058 responses: &FnvHashMap<Uuid, OkModelingCmdResponse>,
1059 path_to_plane_id_map: &FnvHashMap<Uuid, Uuid>,
1060 programs: &crate::execution::ProgramLookup,
1061 cached_body_items: usize,
1062 exec_artifacts: &IndexMap<ArtifactId, Artifact>,
1063 import_code_refs: &FnvHashMap<ModuleId, ImportCodeRef>,
1064) -> Result<Vec<Artifact>, KclError> {
1065 let uuid = artifact_command.cmd_id;
1066 let response = responses.get(&uuid);
1067
1068 let path_to_node = Vec::new();
1072 let range = artifact_command.range;
1073 let (code_ref_range, node_path) = code_ref_for_range(programs, cached_body_items, range, import_code_refs);
1074 let code_ref = CodeRef {
1075 range: code_ref_range,
1076 node_path,
1077 path_to_node,
1078 };
1079
1080 let id = ArtifactId::new(uuid);
1081 let cmd = &artifact_command.command;
1082
1083 match cmd {
1084 ModelingCmd::MakePlane(_) => {
1085 if range.is_synthetic() {
1086 return Ok(Vec::new());
1087 }
1088 return Ok(vec![Artifact::Plane(Plane {
1092 id,
1093 path_ids: Vec::new(),
1094 code_ref,
1095 })]);
1096 }
1097 ModelingCmd::FaceIsPlanar(FaceIsPlanar { object_id, .. }) => {
1098 return Ok(vec![Artifact::PlaneOfFace(PlaneOfFace {
1099 id,
1100 face_id: object_id.into(),
1101 code_ref,
1102 })]);
1103 }
1104 ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) => {
1105 let existing_plane = artifacts.get(&ArtifactId::new(*entity_id));
1106 match existing_plane {
1107 Some(Artifact::Wall(wall)) => {
1108 return Ok(vec![Artifact::Wall(Wall {
1109 id: entity_id.into(),
1110 seg_id: wall.seg_id,
1111 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1112 sweep_id: wall.sweep_id,
1113 path_ids: wall.path_ids.clone(),
1114 face_code_ref: wall.face_code_ref.clone(),
1115 cmd_id: artifact_command.cmd_id,
1116 })]);
1117 }
1118 Some(Artifact::Cap(cap)) => {
1119 return Ok(vec![Artifact::Cap(Cap {
1120 id: entity_id.into(),
1121 sub_type: cap.sub_type,
1122 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1123 sweep_id: cap.sweep_id,
1124 path_ids: cap.path_ids.clone(),
1125 face_code_ref: cap.face_code_ref.clone(),
1126 cmd_id: artifact_command.cmd_id,
1127 })]);
1128 }
1129 Some(_) | None => {
1130 let path_ids = match existing_plane {
1131 Some(Artifact::Plane(Plane { path_ids, .. })) => path_ids.clone(),
1132 _ => Vec::new(),
1133 };
1134 return Ok(vec![Artifact::Plane(Plane {
1136 id: entity_id.into(),
1137 path_ids,
1138 code_ref,
1139 })]);
1140 }
1141 }
1142 }
1143 ModelingCmd::StartPath(_) => {
1144 let mut return_arr = Vec::new();
1145 let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
1146 KclError::new_internal(KclErrorDetails::new(
1147 format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"),
1148 vec![range],
1149 ))
1150 })?;
1151 let sketch_block_id = exec_artifacts
1152 .values()
1153 .find(|a| {
1154 if let Artifact::SketchBlock(s) = a {
1155 if let Some(path_id) = s.path_id {
1156 path_id == id
1157 } else {
1158 false
1159 }
1160 } else {
1161 false
1162 }
1163 })
1164 .map(|a| a.id());
1165 return_arr.push(Artifact::Path(Path {
1166 id,
1167 sub_type: PathSubType::Sketch,
1168 plane_id: (*current_plane_id).into(),
1169 seg_ids: Vec::new(),
1170 sweep_id: None,
1171 trajectory_sweep_id: None,
1172 solid2d_id: None,
1173 code_ref,
1174 composite_solid_id: None,
1175 sketch_block_id,
1176 origin_path_id: None,
1177 inner_path_id: None,
1178 outer_path_id: None,
1179 consumed: false,
1180 }));
1181 let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
1182 if let Some(Artifact::Plane(plane)) = plane {
1183 let plane_code_ref = plane.code_ref.clone();
1184 return_arr.push(Artifact::Plane(Plane {
1185 id: (*current_plane_id).into(),
1186 path_ids: vec![id],
1187 code_ref: plane_code_ref,
1188 }));
1189 }
1190 if let Some(Artifact::Wall(wall)) = plane {
1191 return_arr.push(Artifact::Wall(Wall {
1192 id: (*current_plane_id).into(),
1193 seg_id: wall.seg_id,
1194 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1195 sweep_id: wall.sweep_id,
1196 path_ids: vec![id],
1197 face_code_ref: wall.face_code_ref.clone(),
1198 cmd_id: artifact_command.cmd_id,
1199 }));
1200 }
1201 if let Some(Artifact::Cap(cap)) = plane {
1202 return_arr.push(Artifact::Cap(Cap {
1203 id: (*current_plane_id).into(),
1204 sub_type: cap.sub_type,
1205 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1206 sweep_id: cap.sweep_id,
1207 path_ids: vec![id],
1208 face_code_ref: cap.face_code_ref.clone(),
1209 cmd_id: artifact_command.cmd_id,
1210 }));
1211 }
1212 return Ok(return_arr);
1213 }
1214 ModelingCmd::ClosePath(_) | ModelingCmd::ExtendPath(_) => {
1215 let path_id = ArtifactId::new(match cmd {
1216 ModelingCmd::ClosePath(c) => c.path_id,
1217 ModelingCmd::ExtendPath(e) => e.path.into(),
1218 _ => internal_error!(
1219 range,
1220 "Close or extend path command variant not handled: id={id:?}, cmd={cmd:?}"
1221 ),
1222 });
1223 let mut return_arr = Vec::new();
1224 return_arr.push(Artifact::Segment(Segment {
1225 id,
1226 path_id,
1227 original_seg_id: None,
1228 surface_id: None,
1229 edge_ids: Vec::new(),
1230 edge_cut_id: None,
1231 code_ref,
1232 common_surface_ids: Vec::new(),
1233 }));
1234 let path = artifacts.get(&path_id);
1235 if let Some(Artifact::Path(path)) = path {
1236 let mut new_path = path.clone();
1237 new_path.seg_ids = vec![id];
1238 return_arr.push(Artifact::Path(new_path));
1239 }
1240 if let Some(OkModelingCmdResponse::ClosePath(close_path)) = response {
1241 return_arr.push(Artifact::Solid2d(Solid2d {
1242 id: close_path.face_id.into(),
1243 path_id,
1244 }));
1245 if let Some(Artifact::Path(path)) = path {
1246 let mut new_path = path.clone();
1247 new_path.solid2d_id = Some(close_path.face_id.into());
1248 return_arr.push(Artifact::Path(new_path));
1249 }
1250 }
1251 return Ok(return_arr);
1252 }
1253 ModelingCmd::CreateRegion(kcmc::CreateRegion {
1254 object_id: origin_path_id,
1255 ..
1256 })
1257 | ModelingCmd::CreateRegionFromQueryPoint(kcmc::CreateRegionFromQueryPoint {
1258 object_id: origin_path_id,
1259 ..
1260 }) => {
1261 let mut return_arr = Vec::new();
1262 let origin_path = artifacts.get(&ArtifactId::new(*origin_path_id));
1263 let Some(Artifact::Path(path)) = origin_path else {
1264 internal_error!(
1265 range,
1266 "Expected to find an existing path for the origin path of CreateRegion or CreateRegionFromQueryPoint command, but found none: origin_path={origin_path:?}, cmd={cmd:?}"
1267 );
1268 };
1269 return_arr.push(Artifact::Path(Path {
1271 id,
1272 sub_type: PathSubType::Region,
1273 plane_id: path.plane_id,
1274 seg_ids: Vec::new(),
1275 consumed: false,
1276 sweep_id: None,
1277 trajectory_sweep_id: None,
1278 solid2d_id: None,
1279 code_ref: code_ref.clone(),
1280 composite_solid_id: None,
1281 sketch_block_id: None,
1282 origin_path_id: Some(ArtifactId::new(*origin_path_id)),
1283 inner_path_id: None,
1284 outer_path_id: None,
1285 }));
1286 let Some(
1289 OkModelingCmdResponse::CreateRegion(kcmc::output::CreateRegion { region_mapping, .. })
1290 | OkModelingCmdResponse::CreateRegionFromQueryPoint(kcmc::output::CreateRegionFromQueryPoint {
1291 region_mapping,
1292 ..
1293 }),
1294 ) = response
1295 else {
1296 return Ok(return_arr);
1297 };
1298 let original_segment_ids = path.seg_ids.iter().map(|p| p.0).collect::<Vec<_>>();
1301 let reverse = build_reverse_region_mapping(region_mapping, &original_segment_ids);
1302 for (original_segment_id, region_segment_ids) in reverse.iter() {
1303 for segment_id in region_segment_ids {
1304 return_arr.push(Artifact::Segment(Segment {
1305 id: ArtifactId::new(*segment_id),
1306 path_id: id,
1307 original_seg_id: Some(ArtifactId::new(*original_segment_id)),
1308 surface_id: None,
1309 edge_ids: Vec::new(),
1310 edge_cut_id: None,
1311 code_ref: code_ref.clone(),
1312 common_surface_ids: Vec::new(),
1313 }))
1314 }
1315 }
1316 return Ok(return_arr);
1317 }
1318 ModelingCmd::Solid3dGetFaceUuid(kcmc::Solid3dGetFaceUuid { object_id, .. }) => {
1319 let Some(OkModelingCmdResponse::Solid3dGetFaceUuid(face_uuid)) = response else {
1320 return Ok(Vec::new());
1321 };
1322
1323 return Ok(vec![Artifact::PrimitiveFace(PrimitiveFace {
1324 id: face_uuid.face_id.into(),
1325 solid_id: (*object_id).into(),
1326 code_ref,
1327 })]);
1328 }
1329 ModelingCmd::Solid3dGetEdgeUuid(kcmc::Solid3dGetEdgeUuid { object_id, .. }) => {
1330 let Some(OkModelingCmdResponse::Solid3dGetEdgeUuid(edge_uuid)) = response else {
1331 return Ok(Vec::new());
1332 };
1333
1334 return Ok(vec![Artifact::PrimitiveEdge(PrimitiveEdge {
1335 id: edge_uuid.edge_id.into(),
1336 solid_id: (*object_id).into(),
1337 code_ref,
1338 })]);
1339 }
1340 ModelingCmd::EntityMirror(kcmc::EntityMirror {
1341 ids: original_path_ids, ..
1342 })
1343 | ModelingCmd::EntityMirrorAcrossEdge(kcmc::EntityMirrorAcrossEdge {
1344 ids: original_path_ids, ..
1345 }) => {
1346 let face_edge_infos = match response {
1347 Some(OkModelingCmdResponse::EntityMirror(resp)) => &resp.entity_face_edge_ids,
1348 Some(OkModelingCmdResponse::EntityMirrorAcrossEdge(resp)) => &resp.entity_face_edge_ids,
1349 _ => internal_error!(
1350 range,
1351 "Mirror response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
1352 ),
1353 };
1354 if original_path_ids.len() != face_edge_infos.len() {
1355 internal_error!(
1356 range,
1357 "EntityMirror or EntityMirrorAcrossEdge response has different number face edge info than original mirrored paths: id={id:?}, cmd={cmd:?}, response={response:?}"
1358 );
1359 }
1360 let mut return_arr = Vec::new();
1361 for (face_edge_info, original_path_id) in face_edge_infos.iter().zip(original_path_ids) {
1362 let original_path_id = ArtifactId::new(*original_path_id);
1363 let path_id = ArtifactId::new(face_edge_info.object_id);
1364 let mut path = if let Some(Artifact::Path(path)) = artifacts.get(&path_id) {
1367 path.clone()
1369 } else {
1370 let Some(Artifact::Path(original_path)) = artifacts.get(&original_path_id) else {
1373 internal_error!(
1375 range,
1376 "Couldn't find original path for mirror2d: original_path_id={original_path_id:?}, cmd={cmd:?}"
1377 );
1378 };
1379 Path {
1380 id: path_id,
1381 sub_type: original_path.sub_type,
1382 plane_id: original_path.plane_id,
1383 seg_ids: Vec::new(),
1384 sweep_id: None,
1385 trajectory_sweep_id: None,
1386 solid2d_id: None,
1387 code_ref: code_ref.clone(),
1388 composite_solid_id: None,
1389 sketch_block_id: None,
1390 origin_path_id: original_path.origin_path_id,
1391 inner_path_id: None,
1392 outer_path_id: None,
1393 consumed: false,
1394 }
1395 };
1396
1397 face_edge_info.edges.iter().for_each(|edge_id| {
1398 let edge_id = ArtifactId::new(*edge_id);
1399 return_arr.push(Artifact::Segment(Segment {
1400 id: edge_id,
1401 path_id: path.id,
1402 original_seg_id: None,
1403 surface_id: None,
1404 edge_ids: Vec::new(),
1405 edge_cut_id: None,
1406 code_ref: code_ref.clone(),
1407 common_surface_ids: Vec::new(),
1408 }));
1409 path.seg_ids.push(edge_id);
1411 });
1412
1413 return_arr.push(Artifact::Path(path));
1414 }
1415 return Ok(return_arr);
1416 }
1417 ModelingCmd::Extrude(kcmc::Extrude { target, .. })
1418 | ModelingCmd::TwistExtrude(kcmc::TwistExtrude { target, .. })
1419 | ModelingCmd::Revolve(kcmc::Revolve { target, .. })
1420 | ModelingCmd::RevolveAboutEdge(kcmc::RevolveAboutEdge { target, .. })
1421 | ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { target, .. }) => {
1422 let method = match cmd {
1424 ModelingCmd::Extrude(kcmc::Extrude { extrude_method, .. }) => *extrude_method,
1425 ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { extrude_method, .. }) => *extrude_method,
1426 ModelingCmd::TwistExtrude(_) | ModelingCmd::Sweep(_) => {
1428 kittycad_modeling_cmds::shared::ExtrudeMethod::Merge
1429 }
1430 ModelingCmd::Revolve(_) | ModelingCmd::RevolveAboutEdge(_) => {
1432 kittycad_modeling_cmds::shared::ExtrudeMethod::New
1433 }
1434 _ => kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
1435 };
1436 let sub_type = match cmd {
1437 ModelingCmd::Extrude(_) => SweepSubType::Extrusion,
1438 ModelingCmd::ExtrudeToReference(_) => SweepSubType::Extrusion,
1439 ModelingCmd::TwistExtrude(_) => SweepSubType::ExtrusionTwist,
1440 ModelingCmd::Revolve(_) => SweepSubType::Revolve,
1441 ModelingCmd::RevolveAboutEdge(_) => SweepSubType::RevolveAboutEdge,
1442 _ => internal_error!(range, "Sweep-like command variant not handled: id={id:?}, cmd={cmd:?}",),
1443 };
1444 let mut return_arr = Vec::new();
1445 let target = ArtifactId::from(target);
1446 return_arr.push(Artifact::Sweep(Sweep {
1447 id,
1448 sub_type,
1449 path_id: target,
1450 surface_ids: Vec::new(),
1451 edge_ids: Vec::new(),
1452 code_ref,
1453 trajectory_id: None,
1454 method,
1455 consumed: false,
1456 }));
1457 let path = artifacts.get(&target);
1458 if let Some(Artifact::Path(path)) = path {
1459 let mut new_path = path.clone();
1460 new_path.sweep_id = Some(id);
1461 new_path.consumed = true;
1462 return_arr.push(Artifact::Path(new_path));
1463 if let Some(inner_path_id) = path.inner_path_id
1464 && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
1465 && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
1466 {
1467 inner_path_artifact.sweep_id = Some(id);
1468 inner_path_artifact.consumed = true;
1469 return_arr.push(Artifact::Path(inner_path_artifact))
1470 }
1471 }
1472 return Ok(return_arr);
1473 }
1474 ModelingCmd::Sweep(kcmc::Sweep { target, trajectory, .. }) => {
1475 let method = kittycad_modeling_cmds::shared::ExtrudeMethod::Merge;
1477 let sub_type = SweepSubType::Sweep;
1478 let mut return_arr = Vec::new();
1479 let target = ArtifactId::from(target);
1480 let trajectory = ArtifactId::from(trajectory);
1481 return_arr.push(Artifact::Sweep(Sweep {
1482 id,
1483 sub_type,
1484 path_id: target,
1485 surface_ids: Vec::new(),
1486 edge_ids: Vec::new(),
1487 code_ref,
1488 trajectory_id: Some(trajectory),
1489 method,
1490 consumed: false,
1491 }));
1492 let path = artifacts.get(&target);
1493 if let Some(Artifact::Path(path)) = path {
1494 let mut new_path = path.clone();
1495 new_path.sweep_id = Some(id);
1496 new_path.consumed = true;
1497 return_arr.push(Artifact::Path(new_path));
1498 if let Some(inner_path_id) = path.inner_path_id
1499 && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
1500 && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
1501 {
1502 inner_path_artifact.sweep_id = Some(id);
1503 inner_path_artifact.consumed = true;
1504 return_arr.push(Artifact::Path(inner_path_artifact))
1505 }
1506 }
1507 if let Some(trajectory_artifact) = artifacts.get(&trajectory) {
1508 match trajectory_artifact {
1509 Artifact::Path(path) => {
1510 let mut new_path = path.clone();
1511 new_path.trajectory_sweep_id = Some(id);
1512 new_path.consumed = true;
1513 return_arr.push(Artifact::Path(new_path));
1514 }
1515 Artifact::Helix(helix) => {
1516 let mut new_helix = helix.clone();
1517 new_helix.trajectory_sweep_id = Some(id);
1518 new_helix.consumed = true;
1519 return_arr.push(Artifact::Helix(new_helix));
1520 }
1521 _ => {}
1522 }
1523 };
1524 return Ok(return_arr);
1525 }
1526 ModelingCmd::SurfaceBlend(surface_blend_cmd) => {
1527 let surface_id_to_path_id = |surface_id: ArtifactId| -> Option<ArtifactId> {
1528 match artifacts.get(&surface_id) {
1529 Some(Artifact::Path(path)) => Some(path.id),
1530 Some(Artifact::Segment(segment)) => Some(segment.path_id),
1531 Some(Artifact::Sweep(sweep)) => Some(sweep.path_id),
1532 Some(Artifact::Wall(wall)) => artifacts.get(&wall.sweep_id).and_then(|artifact| match artifact {
1533 Artifact::Sweep(sweep) => Some(sweep.path_id),
1534 _ => None,
1535 }),
1536 Some(Artifact::Cap(cap)) => artifacts.get(&cap.sweep_id).and_then(|artifact| match artifact {
1537 Artifact::Sweep(sweep) => Some(sweep.path_id),
1538 _ => None,
1539 }),
1540 _ => None,
1541 }
1542 };
1543 let Some(first_surface_ref) = surface_blend_cmd.surfaces.first() else {
1544 internal_error!(range, "SurfaceBlend command has no surfaces: id={id:?}, cmd={cmd:?}");
1545 };
1546 let first_surface_id = ArtifactId::new(first_surface_ref.object_id);
1547 let path_id = surface_id_to_path_id(first_surface_id).unwrap_or(first_surface_id);
1548 let trajectory_id = surface_blend_cmd
1549 .surfaces
1550 .get(1)
1551 .map(|surface| ArtifactId::new(surface.object_id))
1552 .and_then(surface_id_to_path_id);
1553 let return_arr = vec![Artifact::Sweep(Sweep {
1554 id,
1555 sub_type: SweepSubType::Blend,
1556 path_id,
1557 surface_ids: Vec::new(),
1558 edge_ids: Vec::new(),
1559 code_ref,
1560 trajectory_id,
1561 method: kittycad_modeling_cmds::shared::ExtrudeMethod::New,
1562 consumed: false,
1563 })];
1564 return Ok(return_arr);
1565 }
1566 ModelingCmd::Loft(loft_cmd) => {
1567 let Some(OkModelingCmdResponse::Loft(_)) = response else {
1568 return Ok(Vec::new());
1569 };
1570 let mut return_arr = Vec::new();
1571 return_arr.push(Artifact::Sweep(Sweep {
1572 id,
1573 sub_type: SweepSubType::Loft,
1574 path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
1577 KclError::new_internal(KclErrorDetails::new(
1578 format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
1579 vec![range],
1580 ))
1581 })?),
1582 surface_ids: Vec::new(),
1583 edge_ids: Vec::new(),
1584 code_ref,
1585 trajectory_id: None,
1586 method: kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
1587 consumed: false,
1588 }));
1589 for section_id in &loft_cmd.section_ids {
1590 let path = artifacts.get(&ArtifactId::new(*section_id));
1591 if let Some(Artifact::Path(path)) = path {
1592 let mut new_path = path.clone();
1593 new_path.consumed = true;
1594 new_path.sweep_id = Some(id);
1595 return_arr.push(Artifact::Path(new_path));
1596 }
1597 }
1598 return Ok(return_arr);
1599 }
1600 ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
1601 let Some(OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info)) = response else {
1602 return Ok(Vec::new());
1603 };
1604 let mut return_arr = Vec::new();
1605 let mut last_path = None;
1606 for face in &face_info.faces {
1607 if face.cap != ExtrusionFaceCapType::None {
1608 continue;
1609 }
1610 let Some(curve_id) = face.curve_id.map(ArtifactId::new) else {
1611 continue;
1612 };
1613 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
1614 continue;
1615 };
1616 let Some(Artifact::Segment(seg)) = artifacts.get(&curve_id) else {
1617 continue;
1618 };
1619 let Some(Artifact::Path(path)) = artifacts.get(&seg.path_id) else {
1620 continue;
1621 };
1622 last_path = Some(path);
1623 let Some(path_sweep_id) = path.sweep_id else {
1624 if path.outer_path_id.is_some() {
1627 continue; }
1629 return Err(KclError::new_internal(KclErrorDetails::new(
1630 format!(
1631 "Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
1632 ),
1633 vec![range],
1634 )));
1635 };
1636 let extra_artifact = exec_artifacts.values().find(|a| {
1637 if let Artifact::StartSketchOnFace(s) = a {
1638 s.face_id == face_id
1639 } else if let Artifact::StartSketchOnPlane(s) = a {
1640 s.plane_id == face_id
1641 } else {
1642 false
1643 }
1644 });
1645 let sketch_on_face_code_ref = extra_artifact
1646 .and_then(|a| match a {
1647 Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
1648 Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
1649 _ => None,
1650 })
1651 .unwrap_or_default();
1653
1654 return_arr.push(Artifact::Wall(Wall {
1655 id: face_id,
1656 seg_id: curve_id,
1657 edge_cut_edge_ids: Vec::new(),
1658 sweep_id: path_sweep_id,
1659 path_ids: Vec::new(),
1660 face_code_ref: sketch_on_face_code_ref,
1661 cmd_id: artifact_command.cmd_id,
1662 }));
1663 let mut new_seg = seg.clone();
1664 new_seg.surface_id = Some(face_id);
1665 return_arr.push(Artifact::Segment(new_seg));
1666 if let Some(Artifact::Sweep(sweep)) = path.sweep_id.and_then(|id| artifacts.get(&id)) {
1667 let mut new_sweep = sweep.clone();
1668 new_sweep.surface_ids = vec![face_id];
1669 return_arr.push(Artifact::Sweep(new_sweep));
1670 }
1671 }
1672 if let Some(path) = last_path {
1673 for face in &face_info.faces {
1674 let sub_type = match face.cap {
1675 ExtrusionFaceCapType::Top => CapSubType::End,
1676 ExtrusionFaceCapType::Bottom => CapSubType::Start,
1677 ExtrusionFaceCapType::None | ExtrusionFaceCapType::Both => continue,
1678 _other => {
1679 continue;
1681 }
1682 };
1683 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
1684 continue;
1685 };
1686 let Some(path_sweep_id) = path.sweep_id else {
1687 if path.outer_path_id.is_some() {
1690 continue; }
1692 return Err(KclError::new_internal(KclErrorDetails::new(
1693 format!(
1694 "Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
1695 ),
1696 vec![range],
1697 )));
1698 };
1699 let extra_artifact = exec_artifacts.values().find(|a| {
1700 if let Artifact::StartSketchOnFace(s) = a {
1701 s.face_id == face_id
1702 } else if let Artifact::StartSketchOnPlane(s) = a {
1703 s.plane_id == face_id
1704 } else {
1705 false
1706 }
1707 });
1708 let sketch_on_face_code_ref = extra_artifact
1709 .and_then(|a| match a {
1710 Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
1711 Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
1712 _ => None,
1713 })
1714 .unwrap_or_default();
1716 return_arr.push(Artifact::Cap(Cap {
1717 id: face_id,
1718 sub_type,
1719 edge_cut_edge_ids: Vec::new(),
1720 sweep_id: path_sweep_id,
1721 path_ids: Vec::new(),
1722 face_code_ref: sketch_on_face_code_ref,
1723 cmd_id: artifact_command.cmd_id,
1724 }));
1725 let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
1726 continue;
1727 };
1728 let mut new_sweep = sweep.clone();
1729 new_sweep.surface_ids = vec![face_id];
1730 return_arr.push(Artifact::Sweep(new_sweep));
1731 }
1732 }
1733 return Ok(return_arr);
1734 }
1735 ModelingCmd::Solid3dGetAdjacencyInfo(kcmc::Solid3dGetAdjacencyInfo { .. }) => {
1736 let Some(OkModelingCmdResponse::Solid3dGetAdjacencyInfo(info)) = response else {
1737 return Ok(Vec::new());
1738 };
1739
1740 let mut return_arr = Vec::new();
1741 for (index, edge) in info.edges.iter().enumerate() {
1742 let Some(original_info) = &edge.original_info else {
1743 continue;
1744 };
1745 let edge_id = ArtifactId::new(original_info.edge_id);
1746 let Some(artifact) = artifacts.get(&edge_id) else {
1747 continue;
1748 };
1749 match artifact {
1750 Artifact::Segment(segment) => {
1751 let mut new_segment = segment.clone();
1752 new_segment.common_surface_ids =
1753 original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
1754 return_arr.push(Artifact::Segment(new_segment));
1755 }
1756 Artifact::SweepEdge(sweep_edge) => {
1757 let mut new_sweep_edge = sweep_edge.clone();
1758 new_sweep_edge.common_surface_ids =
1759 original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
1760 return_arr.push(Artifact::SweepEdge(new_sweep_edge));
1761 }
1762 _ => {}
1763 };
1764
1765 let Some(Artifact::Segment(segment)) = artifacts.get(&edge_id) else {
1766 continue;
1767 };
1768 let Some(surface_id) = segment.surface_id else {
1769 continue;
1770 };
1771 let Some(Artifact::Wall(wall)) = artifacts.get(&surface_id) else {
1772 continue;
1773 };
1774 let Some(Artifact::Sweep(sweep)) = artifacts.get(&wall.sweep_id) else {
1775 continue;
1776 };
1777 let Some(Artifact::Path(_)) = artifacts.get(&sweep.path_id) else {
1778 continue;
1779 };
1780
1781 if let Some(opposite_info) = &edge.opposite_info {
1782 return_arr.push(Artifact::SweepEdge(SweepEdge {
1783 id: opposite_info.edge_id.into(),
1784 sub_type: SweepEdgeSubType::Opposite,
1785 seg_id: edge_id,
1786 cmd_id: artifact_command.cmd_id,
1787 index,
1788 sweep_id: sweep.id,
1789 common_surface_ids: opposite_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
1790 }));
1791 let mut new_segment = segment.clone();
1792 new_segment.edge_ids = vec![opposite_info.edge_id.into()];
1793 return_arr.push(Artifact::Segment(new_segment));
1794 let mut new_sweep = sweep.clone();
1795 new_sweep.edge_ids = vec![opposite_info.edge_id.into()];
1796 return_arr.push(Artifact::Sweep(new_sweep));
1797 let mut new_wall = wall.clone();
1798 new_wall.edge_cut_edge_ids = vec![opposite_info.edge_id.into()];
1799 return_arr.push(Artifact::Wall(new_wall));
1800 }
1801 if let Some(adjacent_info) = &edge.adjacent_info {
1802 return_arr.push(Artifact::SweepEdge(SweepEdge {
1803 id: adjacent_info.edge_id.into(),
1804 sub_type: SweepEdgeSubType::Adjacent,
1805 seg_id: edge_id,
1806 cmd_id: artifact_command.cmd_id,
1807 index,
1808 sweep_id: sweep.id,
1809 common_surface_ids: adjacent_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
1810 }));
1811 let mut new_segment = segment.clone();
1812 new_segment.edge_ids = vec![adjacent_info.edge_id.into()];
1813 return_arr.push(Artifact::Segment(new_segment));
1814 let mut new_sweep = sweep.clone();
1815 new_sweep.edge_ids = vec![adjacent_info.edge_id.into()];
1816 return_arr.push(Artifact::Sweep(new_sweep));
1817 let mut new_wall = wall.clone();
1818 new_wall.edge_cut_edge_ids = vec![adjacent_info.edge_id.into()];
1819 return_arr.push(Artifact::Wall(new_wall));
1820 }
1821 }
1822 return Ok(return_arr);
1823 }
1824 ModelingCmd::Solid3dMultiJoin(cmd) => {
1825 let mut return_arr = Vec::new();
1826 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
1827 id,
1828 consumed: false,
1829 sub_type: CompositeSolidSubType::Union,
1830 solid_ids: cmd.object_ids.iter().map(|id| id.into()).collect(),
1831 tool_ids: vec![],
1832 code_ref,
1833 composite_solid_id: None,
1834 }));
1835
1836 let solid_ids = cmd.object_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
1837
1838 for input_id in &solid_ids {
1839 if let Some(artifact) = artifacts.get(input_id)
1840 && let Artifact::CompositeSolid(comp) = artifact
1841 {
1842 let mut new_comp = comp.clone();
1843 new_comp.composite_solid_id = Some(id);
1844 new_comp.consumed = true;
1845 return_arr.push(Artifact::CompositeSolid(new_comp));
1846 }
1847 }
1848 return Ok(return_arr);
1849 }
1850 ModelingCmd::Solid3dFilletEdge(cmd) => {
1851 let mut return_arr = Vec::new();
1852 let edge_id = if let Some(edge_id) = cmd.edge_id {
1853 ArtifactId::new(edge_id)
1854 } else {
1855 let Some(edge_id) = cmd.edge_ids.first() else {
1856 internal_error!(
1857 range,
1858 "Solid3dFilletEdge command has no edge ID: id={id:?}, cmd={cmd:?}"
1859 );
1860 };
1861 edge_id.into()
1862 };
1863 return_arr.push(Artifact::EdgeCut(EdgeCut {
1864 id,
1865 sub_type: cmd.cut_type.into(),
1866 consumed_edge_id: edge_id,
1867 edge_ids: Vec::new(),
1868 surface_id: None,
1869 code_ref,
1870 }));
1871 let consumed_edge = artifacts.get(&edge_id);
1872 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
1873 let mut new_segment = consumed_edge.clone();
1874 new_segment.edge_cut_id = Some(id);
1875 return_arr.push(Artifact::Segment(new_segment));
1876 } else {
1877 }
1879 return Ok(return_arr);
1880 }
1881 ModelingCmd::Solid3dCutEdges(cmd) => {
1882 let mut return_arr = Vec::new();
1883 let edge_id = if let Some(edge_id) = cmd.edge_ids.first() {
1884 edge_id.into()
1885 } else {
1886 internal_error!(range, "Solid3dCutEdges command has no edge ID: id={id:?}, cmd={cmd:?}");
1887 };
1888 return_arr.push(Artifact::EdgeCut(EdgeCut {
1889 id,
1890 sub_type: cmd.cut_type.into(),
1891 consumed_edge_id: edge_id,
1892 edge_ids: Vec::new(),
1893 surface_id: None,
1894 code_ref,
1895 }));
1896 let consumed_edge = artifacts.get(&edge_id);
1897 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
1898 let mut new_segment = consumed_edge.clone();
1899 new_segment.edge_cut_id = Some(id);
1900 return_arr.push(Artifact::Segment(new_segment));
1901 } else {
1902 }
1904 return Ok(return_arr);
1905 }
1906 ModelingCmd::EntityMakeHelix(cmd) => {
1907 let cylinder_id = ArtifactId::new(cmd.cylinder_id);
1908 let return_arr = vec![Artifact::Helix(Helix {
1909 id,
1910 axis_id: Some(cylinder_id),
1911 code_ref,
1912 trajectory_sweep_id: None,
1913 consumed: false,
1914 })];
1915 return Ok(return_arr);
1916 }
1917 ModelingCmd::EntityMakeHelixFromParams(_) => {
1918 let return_arr = vec![Artifact::Helix(Helix {
1919 id,
1920 axis_id: None,
1921 code_ref,
1922 trajectory_sweep_id: None,
1923 consumed: false,
1924 })];
1925 return Ok(return_arr);
1926 }
1927 ModelingCmd::EntityMakeHelixFromEdge(helix) => {
1928 let edge_id = ArtifactId::new(helix.edge_id);
1929 let return_arr = vec![Artifact::Helix(Helix {
1930 id,
1931 axis_id: Some(edge_id),
1932 code_ref,
1933 trajectory_sweep_id: None,
1934 consumed: false,
1935 })];
1936 return Ok(return_arr);
1939 }
1940 ModelingCmd::Solid2dAddHole(solid2d_add_hole) => {
1941 let mut return_arr = Vec::new();
1942 let outer_path = artifacts.get(&ArtifactId::new(solid2d_add_hole.object_id));
1944 if let Some(Artifact::Path(path)) = outer_path {
1945 let mut new_path = path.clone();
1946 new_path.inner_path_id = Some(ArtifactId::new(solid2d_add_hole.hole_id));
1947 return_arr.push(Artifact::Path(new_path));
1948 }
1949 let inner_solid2d = artifacts.get(&ArtifactId::new(solid2d_add_hole.hole_id));
1951 if let Some(Artifact::Path(path)) = inner_solid2d {
1952 let mut new_path = path.clone();
1953 new_path.consumed = true;
1954 new_path.outer_path_id = Some(ArtifactId::new(solid2d_add_hole.object_id));
1955 return_arr.push(Artifact::Path(new_path));
1956 }
1957 return Ok(return_arr);
1958 }
1959 ModelingCmd::BooleanIntersection(_) | ModelingCmd::BooleanSubtract(_) | ModelingCmd::BooleanUnion(_) => {
1960 let (sub_type, solid_ids, tool_ids) = match cmd {
1961 ModelingCmd::BooleanIntersection(intersection) => {
1962 let solid_ids = intersection
1963 .solid_ids
1964 .iter()
1965 .copied()
1966 .map(ArtifactId::new)
1967 .collect::<Vec<_>>();
1968 (CompositeSolidSubType::Intersect, solid_ids, Vec::new())
1969 }
1970 ModelingCmd::BooleanSubtract(subtract) => {
1971 let solid_ids = subtract
1972 .target_ids
1973 .iter()
1974 .copied()
1975 .map(ArtifactId::new)
1976 .collect::<Vec<_>>();
1977 let tool_ids = subtract
1978 .tool_ids
1979 .iter()
1980 .copied()
1981 .map(ArtifactId::new)
1982 .collect::<Vec<_>>();
1983 (CompositeSolidSubType::Subtract, solid_ids, tool_ids)
1984 }
1985 ModelingCmd::BooleanUnion(union) => {
1986 let solid_ids = union.solid_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
1987 (CompositeSolidSubType::Union, solid_ids, Vec::new())
1988 }
1989 _ => internal_error!(
1990 range,
1991 "Boolean or composite command variant not handled: id={id:?}, cmd={cmd:?}"
1992 ),
1993 };
1994
1995 let mut new_solid_ids = vec![id];
1996
1997 let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2000
2001 match response {
2002 Some(OkModelingCmdResponse::BooleanIntersection(intersection)) => intersection
2003 .extra_solid_ids
2004 .iter()
2005 .copied()
2006 .map(ArtifactId::new)
2007 .filter(not_cmd_id)
2008 .for_each(|id| new_solid_ids.push(id)),
2009 Some(OkModelingCmdResponse::BooleanSubtract(subtract)) => subtract
2010 .extra_solid_ids
2011 .iter()
2012 .copied()
2013 .map(ArtifactId::new)
2014 .filter(not_cmd_id)
2015 .for_each(|id| new_solid_ids.push(id)),
2016 Some(OkModelingCmdResponse::BooleanUnion(union)) => union
2017 .extra_solid_ids
2018 .iter()
2019 .copied()
2020 .map(ArtifactId::new)
2021 .filter(not_cmd_id)
2022 .for_each(|id| new_solid_ids.push(id)),
2023 _ => {}
2024 }
2025
2026 let mut return_arr = Vec::new();
2027
2028 for solid_id in &new_solid_ids {
2030 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2032 id: *solid_id,
2033 consumed: false,
2034 sub_type,
2035 solid_ids: solid_ids.clone(),
2036 tool_ids: tool_ids.clone(),
2037 code_ref: code_ref.clone(),
2038 composite_solid_id: None,
2039 }));
2040
2041 for input_id in &solid_ids {
2043 if let Some(artifact) = artifacts.get(input_id) {
2044 match artifact {
2045 Artifact::CompositeSolid(comp) => {
2046 let mut new_comp = comp.clone();
2047 new_comp.composite_solid_id = Some(*solid_id);
2048 new_comp.consumed = true;
2049 return_arr.push(Artifact::CompositeSolid(new_comp));
2050 }
2051 Artifact::Path(path) => {
2052 let mut new_path = path.clone();
2053 new_path.composite_solid_id = Some(*solid_id);
2054
2055 if let Some(sweep_id) = new_path.sweep_id
2058 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
2059 {
2060 let mut new_sweep = sweep.clone();
2061 new_sweep.consumed = true;
2062 return_arr.push(Artifact::Sweep(new_sweep));
2063 }
2064
2065 return_arr.push(Artifact::Path(new_path));
2066 }
2067 _ => {}
2068 }
2069 }
2070 }
2071
2072 for tool_id in &tool_ids {
2074 if let Some(artifact) = artifacts.get(tool_id) {
2075 match artifact {
2076 Artifact::CompositeSolid(comp) => {
2077 let mut new_comp = comp.clone();
2078 new_comp.composite_solid_id = Some(*solid_id);
2079 new_comp.consumed = true;
2080 return_arr.push(Artifact::CompositeSolid(new_comp));
2081 }
2082 Artifact::Path(path) => {
2083 let mut new_path = path.clone();
2084 new_path.composite_solid_id = Some(*solid_id);
2085
2086 if let Some(sweep_id) = new_path.sweep_id
2089 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
2090 {
2091 let mut new_sweep = sweep.clone();
2092 new_sweep.consumed = true;
2093 return_arr.push(Artifact::Sweep(new_sweep));
2094 }
2095
2096 return_arr.push(Artifact::Path(new_path));
2097 }
2098 _ => {}
2099 }
2100 }
2101 }
2102 }
2103
2104 return Ok(return_arr);
2105 }
2106 _ => {}
2107 }
2108
2109 Ok(Vec::new())
2110}