1use ahash::AHashMap;
2use ahash::AHashSet;
3use indexmap::IndexMap;
4use kittycad_modeling_cmds::EnableSketchMode;
5use kittycad_modeling_cmds::FaceIsPlanar;
6use kittycad_modeling_cmds::ModelingCmd;
7use kittycad_modeling_cmds::ok_response::OkModelingCmdResponse;
8use kittycad_modeling_cmds::shared::ExtrusionFaceCapType;
9use kittycad_modeling_cmds::websocket::BatchResponse;
10use kittycad_modeling_cmds::websocket::OkWebSocketResponseData;
11use kittycad_modeling_cmds::websocket::WebSocketResponse;
12use kittycad_modeling_cmds::{self as kcmc};
13use serde::Serialize;
14use serde::ser::SerializeSeq;
15use uuid::Uuid;
16
17use crate::KclError;
18use crate::ModuleId;
19use crate::NodePath;
20use crate::SourceRange;
21use crate::engine::PlaneName;
22use crate::errors::KclErrorDetails;
23use crate::execution::ArtifactId;
24use crate::execution::geometry::PlaneInfo;
25use crate::execution::state::ModuleInfoMap;
26use crate::front::Constraint;
27use crate::front::ObjectId;
28use crate::modules::ModulePath;
29use crate::parsing::ast::types::BodyItem;
30use crate::parsing::ast::types::ImportPath;
31use crate::parsing::ast::types::ImportSelector;
32use crate::parsing::ast::types::Node;
33use crate::parsing::ast::types::Program;
34use crate::std::sketch::build_reverse_region_mapping;
35
36#[cfg(test)]
37mod mermaid_tests;
38#[cfg(test)]
39mod tests;
40
41macro_rules! internal_error {
42 ($range:expr, $($rest:tt)*) => {{
43 let message = format!($($rest)*);
44 debug_assert!(false, "{}", &message);
45 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![$range])));
46 }};
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
53#[ts(export_to = "Artifact.ts")]
54#[serde(rename_all = "camelCase")]
55pub struct ArtifactCommand {
56 pub cmd_id: Uuid,
58 pub range: SourceRange,
61 pub command: ModelingCmd,
66}
67
68pub type DummyPathToNode = Vec<()>;
69
70fn serialize_dummy_path_to_node<S>(_path_to_node: &DummyPathToNode, serializer: S) -> Result<S::Ok, S::Error>
71where
72 S: serde::Serializer,
73{
74 let seq = serializer.serialize_seq(Some(0))?;
76 seq.end()
77}
78
79#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq, ts_rs::TS)]
80#[ts(export_to = "Artifact.ts")]
81#[serde(rename_all = "camelCase")]
82pub struct CodeRef {
83 pub range: SourceRange,
84 pub node_path: NodePath,
85 #[serde(default, serialize_with = "serialize_dummy_path_to_node")]
87 #[ts(type = "Array<[string | number, string]>")]
88 pub path_to_node: DummyPathToNode,
89}
90
91impl CodeRef {
92 pub fn placeholder(range: SourceRange) -> Self {
93 Self {
94 range,
95 node_path: Default::default(),
96 path_to_node: Vec::new(),
97 }
98 }
99}
100
101#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
102#[ts(export_to = "Artifact.ts")]
103#[serde(rename_all = "camelCase")]
104pub struct CompositeSolid {
105 pub id: ArtifactId,
106 pub consumed: bool,
108 pub sub_type: CompositeSolidSubType,
109 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub output_index: Option<usize>,
113 pub solid_ids: Vec<ArtifactId>,
115 pub tool_ids: Vec<ArtifactId>,
117 pub code_ref: CodeRef,
118 #[serde(default, skip_serializing_if = "Option::is_none")]
121 pub composite_solid_id: Option<ArtifactId>,
122 #[serde(default, skip_serializing_if = "Vec::is_empty")]
124 pub pattern_ids: Vec<ArtifactId>,
125}
126
127#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
128#[ts(export_to = "Artifact.ts")]
129#[serde(rename_all = "camelCase")]
130pub enum CompositeSolidSubType {
131 Intersect,
132 Subtract,
133 Split,
134 Union,
135}
136
137#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
138#[ts(export_to = "Artifact.ts")]
139#[serde(rename_all = "camelCase")]
140pub struct Plane {
141 pub id: ArtifactId,
142 pub path_ids: Vec<ArtifactId>,
143 pub code_ref: CodeRef,
144}
145
146#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
147#[ts(export_to = "Artifact.ts")]
148#[serde(rename_all = "camelCase")]
149pub struct Path {
150 pub id: ArtifactId,
151 pub sub_type: PathSubType,
152 pub plane_id: ArtifactId,
153 pub seg_ids: Vec<ArtifactId>,
154 pub consumed: bool,
156 #[serde(default, skip_serializing_if = "Option::is_none")]
157 pub sweep_id: Option<ArtifactId>,
160 pub trajectory_sweep_id: Option<ArtifactId>,
162 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub solid2d_id: Option<ArtifactId>,
164 pub code_ref: CodeRef,
165 #[serde(default, skip_serializing_if = "Option::is_none")]
168 pub composite_solid_id: Option<ArtifactId>,
169 #[serde(default, skip_serializing_if = "Option::is_none")]
172 pub sketch_block_id: Option<ArtifactId>,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
176 pub origin_path_id: Option<ArtifactId>,
177 #[serde(default, skip_serializing_if = "Option::is_none")]
179 pub inner_path_id: Option<ArtifactId>,
180 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub outer_path_id: Option<ArtifactId>,
184 #[serde(default, skip_serializing_if = "Vec::is_empty")]
186 pub pattern_ids: Vec<ArtifactId>,
187}
188
189#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
190#[ts(export_to = "Artifact.ts")]
191#[serde(rename_all = "camelCase")]
192pub enum PathSubType {
193 Sketch,
194 Region,
195}
196
197#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
198#[ts(export_to = "Artifact.ts")]
199#[serde(rename_all = "camelCase")]
200pub struct Segment {
201 pub id: ArtifactId,
202 pub path_id: ArtifactId,
203 #[serde(default, skip_serializing_if = "Option::is_none")]
206 pub original_seg_id: Option<ArtifactId>,
207 #[serde(default, skip_serializing_if = "Option::is_none")]
208 pub surface_id: Option<ArtifactId>,
209 pub edge_ids: Vec<ArtifactId>,
210 #[serde(default, skip_serializing_if = "Option::is_none")]
211 pub edge_cut_id: Option<ArtifactId>,
212 pub code_ref: CodeRef,
213 pub common_surface_ids: Vec<ArtifactId>,
214}
215
216#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
218#[ts(export_to = "Artifact.ts")]
219#[serde(rename_all = "camelCase")]
220pub struct Sweep {
221 pub id: ArtifactId,
222 pub sub_type: SweepSubType,
223 pub path_id: ArtifactId,
224 pub surface_ids: Vec<ArtifactId>,
225 pub edge_ids: Vec<ArtifactId>,
226 pub code_ref: CodeRef,
227 pub trajectory_id: Option<ArtifactId>,
231 pub method: kittycad_modeling_cmds::shared::ExtrudeMethod,
232 pub consumed: bool,
234 #[serde(default, skip_serializing_if = "Vec::is_empty")]
236 pub pattern_ids: Vec<ArtifactId>,
237}
238
239#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
240#[ts(export_to = "Artifact.ts")]
241#[serde(rename_all = "camelCase")]
242pub enum SweepSubType {
243 Extrusion,
244 ExtrusionTwist,
245 Revolve,
246 RevolveAboutEdge,
247 Loft,
248 Blend,
249 Sweep,
250}
251
252#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
253#[ts(export_to = "Artifact.ts")]
254#[serde(rename_all = "camelCase")]
255pub struct Solid2d {
256 pub id: ArtifactId,
257 pub path_id: ArtifactId,
258}
259
260#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
261#[ts(export_to = "Artifact.ts")]
262#[serde(rename_all = "camelCase")]
263pub struct PrimitiveFace {
264 pub id: ArtifactId,
265 pub solid_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 PrimitiveEdge {
273 pub id: ArtifactId,
274 pub solid_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 PlaneOfFace {
282 pub id: ArtifactId,
283 pub face_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 StartSketchOnFace {
291 pub id: ArtifactId,
292 pub face_id: ArtifactId,
293 pub code_ref: CodeRef,
294}
295
296#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
297#[ts(export_to = "Artifact.ts")]
298#[serde(rename_all = "camelCase")]
299pub struct StartSketchOnPlane {
300 pub id: ArtifactId,
301 pub plane_id: ArtifactId,
302 pub code_ref: CodeRef,
303}
304
305#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
306#[ts(export_to = "Artifact.ts")]
307#[serde(rename_all = "camelCase")]
308pub struct SketchBlock {
309 pub id: ArtifactId,
310 #[serde(default, skip_serializing_if = "Option::is_none")]
312 pub standard_plane: Option<PlaneName>,
313 #[serde(default, skip_serializing_if = "Option::is_none")]
315 pub plane_id: Option<ArtifactId>,
316 #[serde(default, skip_serializing_if = "Option::is_none")]
318 pub plane_info: Option<PlaneInfo>,
319 #[serde(default, skip_serializing_if = "Option::is_none")]
323 pub path_id: Option<ArtifactId>,
324 pub code_ref: CodeRef,
325 pub sketch_id: ObjectId,
327}
328
329#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
330#[ts(export_to = "Artifact.ts")]
331#[serde(rename_all = "camelCase")]
332pub enum SketchBlockConstraintType {
333 Angle,
334 Coincident,
335 Distance,
336 Diameter,
337 EqualRadius,
338 Fixed,
339 HorizontalDistance,
340 VerticalDistance,
341 Horizontal,
342 LinesEqualLength,
343 Midpoint,
344 Parallel,
345 Perpendicular,
346 Radius,
347 Symmetric,
348 Tangent,
349 Vertical,
350}
351
352impl From<&Constraint> for SketchBlockConstraintType {
353 fn from(constraint: &Constraint) -> Self {
354 match constraint {
355 Constraint::Coincident { .. } => SketchBlockConstraintType::Coincident,
356 Constraint::Distance { .. } => SketchBlockConstraintType::Distance,
357 Constraint::Diameter { .. } => SketchBlockConstraintType::Diameter,
358 Constraint::EqualRadius { .. } => SketchBlockConstraintType::EqualRadius,
359 Constraint::Fixed { .. } => SketchBlockConstraintType::Fixed,
360 Constraint::HorizontalDistance { .. } => SketchBlockConstraintType::HorizontalDistance,
361 Constraint::VerticalDistance { .. } => SketchBlockConstraintType::VerticalDistance,
362 Constraint::Horizontal { .. } => SketchBlockConstraintType::Horizontal,
363 Constraint::LinesEqualLength { .. } => SketchBlockConstraintType::LinesEqualLength,
364 Constraint::Midpoint(..) => SketchBlockConstraintType::Midpoint,
365 Constraint::Parallel { .. } => SketchBlockConstraintType::Parallel,
366 Constraint::Perpendicular { .. } => SketchBlockConstraintType::Perpendicular,
367 Constraint::Radius { .. } => SketchBlockConstraintType::Radius,
368 Constraint::Symmetric { .. } => SketchBlockConstraintType::Symmetric,
369 Constraint::Tangent { .. } => SketchBlockConstraintType::Tangent,
370 Constraint::Vertical { .. } => SketchBlockConstraintType::Vertical,
371 Constraint::Angle(..) => SketchBlockConstraintType::Angle,
372 }
373 }
374}
375
376#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
377#[ts(export_to = "Artifact.ts")]
378#[serde(rename_all = "camelCase")]
379pub struct SketchBlockConstraint {
380 pub id: ArtifactId,
381 pub sketch_id: ObjectId,
383 pub constraint_id: ObjectId,
385 pub constraint_type: SketchBlockConstraintType,
386 pub code_ref: CodeRef,
387}
388
389#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
390#[ts(export_to = "Artifact.ts")]
391#[serde(rename_all = "camelCase")]
392pub struct Wall {
393 pub id: ArtifactId,
394 pub seg_id: ArtifactId,
395 pub edge_cut_edge_ids: Vec<ArtifactId>,
396 pub sweep_id: ArtifactId,
397 pub path_ids: Vec<ArtifactId>,
398 pub face_code_ref: CodeRef,
401 pub cmd_id: uuid::Uuid,
403}
404
405#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
406#[ts(export_to = "Artifact.ts")]
407#[serde(rename_all = "camelCase")]
408pub struct Cap {
409 pub id: ArtifactId,
410 pub sub_type: CapSubType,
411 pub edge_cut_edge_ids: Vec<ArtifactId>,
412 pub sweep_id: ArtifactId,
413 pub path_ids: Vec<ArtifactId>,
414 pub face_code_ref: CodeRef,
417 pub cmd_id: uuid::Uuid,
419}
420
421#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
422#[ts(export_to = "Artifact.ts")]
423#[serde(rename_all = "camelCase")]
424pub enum CapSubType {
425 Start,
426 End,
427}
428
429#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
430#[ts(export_to = "Artifact.ts")]
431#[serde(rename_all = "camelCase")]
432pub struct SweepEdge {
433 pub id: ArtifactId,
434 pub sub_type: SweepEdgeSubType,
435 pub seg_id: ArtifactId,
436 pub cmd_id: uuid::Uuid,
437 #[serde(skip)]
439 pub index: usize,
440 pub sweep_id: ArtifactId,
441 pub common_surface_ids: Vec<ArtifactId>,
442}
443
444#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
445#[ts(export_to = "Artifact.ts")]
446#[serde(rename_all = "camelCase")]
447pub enum SweepEdgeSubType {
448 Opposite,
449 Adjacent,
450}
451
452#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
453#[ts(export_to = "Artifact.ts")]
454#[serde(rename_all = "camelCase")]
455pub struct EdgeCut {
456 pub id: ArtifactId,
457 pub sub_type: EdgeCutSubType,
458 pub consumed_edge_id: ArtifactId,
459 pub edge_ids: Vec<ArtifactId>,
460 #[serde(default, skip_serializing_if = "Option::is_none")]
461 pub surface_id: Option<ArtifactId>,
462 pub code_ref: CodeRef,
463}
464
465#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
466#[ts(export_to = "Artifact.ts")]
467#[serde(rename_all = "camelCase")]
468pub enum EdgeCutSubType {
469 Fillet,
470 Chamfer,
471 Custom,
472}
473
474impl From<kcmc::shared::CutType> for EdgeCutSubType {
475 fn from(cut_type: kcmc::shared::CutType) -> Self {
476 match cut_type {
477 kcmc::shared::CutType::Fillet => EdgeCutSubType::Fillet,
478 kcmc::shared::CutType::Chamfer => EdgeCutSubType::Chamfer,
479 }
480 }
481}
482
483impl From<kcmc::shared::CutTypeV2> for EdgeCutSubType {
484 fn from(cut_type: kcmc::shared::CutTypeV2) -> Self {
485 match cut_type {
486 kcmc::shared::CutTypeV2::Fillet { .. } => EdgeCutSubType::Fillet,
487 kcmc::shared::CutTypeV2::Chamfer { .. } => EdgeCutSubType::Chamfer,
488 kcmc::shared::CutTypeV2::Custom { .. } => EdgeCutSubType::Custom,
489 _other => EdgeCutSubType::Custom,
491 }
492 }
493}
494
495#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
496#[ts(export_to = "Artifact.ts")]
497#[serde(rename_all = "camelCase")]
498pub struct EdgeCutEdge {
499 pub id: ArtifactId,
500 pub edge_cut_id: ArtifactId,
501 pub surface_id: ArtifactId,
502}
503
504#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
505#[ts(export_to = "Artifact.ts")]
506#[serde(rename_all = "camelCase")]
507pub struct Helix {
508 pub id: ArtifactId,
509 pub axis_id: Option<ArtifactId>,
512 pub code_ref: CodeRef,
513 pub trajectory_sweep_id: Option<ArtifactId>,
515 pub consumed: bool,
517}
518
519#[derive(Debug, Clone, Serialize, PartialEq, Eq, ts_rs::TS)]
520#[ts(export_to = "Artifact.ts")]
521#[serde(rename_all = "camelCase")]
522pub struct GdtAnnotationArtifact {
523 pub id: ArtifactId,
524 pub code_ref: CodeRef,
525}
526
527#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
528#[ts(export_to = "Artifact.ts")]
529#[serde(rename_all = "camelCase")]
530pub struct Pattern {
531 pub id: ArtifactId,
532 pub sub_type: PatternSubType,
533 pub source_id: ArtifactId,
535 pub copy_ids: Vec<ArtifactId>,
537 pub copy_face_ids: Vec<ArtifactId>,
539 pub copy_edge_ids: Vec<ArtifactId>,
541 pub code_ref: CodeRef,
542}
543
544#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
545#[ts(export_to = "Artifact.ts")]
546#[serde(rename_all = "camelCase")]
547pub enum PatternSubType {
548 Circular,
549 Linear,
550 Transform,
551}
552
553#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
554#[ts(export_to = "Artifact.ts")]
555#[serde(tag = "type", rename_all = "camelCase")]
556pub enum Artifact {
557 CompositeSolid(CompositeSolid),
558 Plane(Plane),
559 Path(Path),
560 Segment(Segment),
561 Solid2d(Solid2d),
562 PrimitiveFace(PrimitiveFace),
563 PrimitiveEdge(PrimitiveEdge),
564 PlaneOfFace(PlaneOfFace),
565 StartSketchOnFace(StartSketchOnFace),
566 StartSketchOnPlane(StartSketchOnPlane),
567 SketchBlock(SketchBlock),
568 SketchBlockConstraint(SketchBlockConstraint),
569 Sweep(Sweep),
570 Wall(Wall),
571 Cap(Cap),
572 SweepEdge(SweepEdge),
573 EdgeCut(EdgeCut),
574 EdgeCutEdge(EdgeCutEdge),
575 Helix(Helix),
576 GdtAnnotation(GdtAnnotationArtifact),
577 Pattern(Pattern),
578}
579
580impl Artifact {
581 pub(crate) fn id(&self) -> ArtifactId {
582 match self {
583 Artifact::CompositeSolid(a) => a.id,
584 Artifact::Plane(a) => a.id,
585 Artifact::Path(a) => a.id,
586 Artifact::Segment(a) => a.id,
587 Artifact::Solid2d(a) => a.id,
588 Artifact::PrimitiveFace(a) => a.id,
589 Artifact::PrimitiveEdge(a) => a.id,
590 Artifact::StartSketchOnFace(a) => a.id,
591 Artifact::StartSketchOnPlane(a) => a.id,
592 Artifact::SketchBlock(a) => a.id,
593 Artifact::SketchBlockConstraint(a) => a.id,
594 Artifact::PlaneOfFace(a) => a.id,
595 Artifact::Sweep(a) => a.id,
596 Artifact::Wall(a) => a.id,
597 Artifact::Cap(a) => a.id,
598 Artifact::SweepEdge(a) => a.id,
599 Artifact::EdgeCut(a) => a.id,
600 Artifact::EdgeCutEdge(a) => a.id,
601 Artifact::Helix(a) => a.id,
602 Artifact::GdtAnnotation(a) => a.id,
603 Artifact::Pattern(a) => a.id,
604 }
605 }
606
607 pub fn code_ref(&self) -> Option<&CodeRef> {
610 match self {
611 Artifact::CompositeSolid(a) => Some(&a.code_ref),
612 Artifact::Plane(a) => Some(&a.code_ref),
613 Artifact::Path(a) => Some(&a.code_ref),
614 Artifact::Segment(a) => Some(&a.code_ref),
615 Artifact::Solid2d(_) => None,
616 Artifact::PrimitiveFace(a) => Some(&a.code_ref),
617 Artifact::PrimitiveEdge(a) => Some(&a.code_ref),
618 Artifact::StartSketchOnFace(a) => Some(&a.code_ref),
619 Artifact::StartSketchOnPlane(a) => Some(&a.code_ref),
620 Artifact::SketchBlock(a) => Some(&a.code_ref),
621 Artifact::SketchBlockConstraint(a) => Some(&a.code_ref),
622 Artifact::PlaneOfFace(a) => Some(&a.code_ref),
623 Artifact::Sweep(a) => Some(&a.code_ref),
624 Artifact::Wall(_) => None,
625 Artifact::Cap(_) => None,
626 Artifact::SweepEdge(_) => None,
627 Artifact::EdgeCut(a) => Some(&a.code_ref),
628 Artifact::EdgeCutEdge(_) => None,
629 Artifact::Helix(a) => Some(&a.code_ref),
630 Artifact::GdtAnnotation(a) => Some(&a.code_ref),
631 Artifact::Pattern(a) => Some(&a.code_ref),
632 }
633 }
634
635 pub fn face_code_ref(&self) -> Option<&CodeRef> {
638 match self {
639 Artifact::CompositeSolid(_)
640 | Artifact::Plane(_)
641 | Artifact::Path(_)
642 | Artifact::Segment(_)
643 | Artifact::Solid2d(_)
644 | Artifact::PrimitiveEdge(_)
645 | Artifact::StartSketchOnFace(_)
646 | Artifact::PlaneOfFace(_)
647 | Artifact::StartSketchOnPlane(_)
648 | Artifact::SketchBlock(_)
649 | Artifact::SketchBlockConstraint(_)
650 | Artifact::Sweep(_) => None,
651 Artifact::PrimitiveFace(a) => Some(&a.code_ref),
652 Artifact::Wall(a) => Some(&a.face_code_ref),
653 Artifact::Cap(a) => Some(&a.face_code_ref),
654 Artifact::SweepEdge(_)
655 | Artifact::EdgeCut(_)
656 | Artifact::EdgeCutEdge(_)
657 | Artifact::Helix(_)
658 | Artifact::GdtAnnotation(_)
659 | Artifact::Pattern(_) => None,
660 }
661 }
662
663 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
666 match self {
667 Artifact::CompositeSolid(a) => a.merge(new),
668 Artifact::Plane(a) => a.merge(new),
669 Artifact::Path(a) => a.merge(new),
670 Artifact::Segment(a) => a.merge(new),
671 Artifact::Solid2d(_) => Some(new),
672 Artifact::PrimitiveFace(_) => Some(new),
673 Artifact::PrimitiveEdge(_) => Some(new),
674 Artifact::StartSketchOnFace { .. } => Some(new),
675 Artifact::StartSketchOnPlane { .. } => Some(new),
676 Artifact::SketchBlock { .. } => Some(new),
677 Artifact::SketchBlockConstraint { .. } => Some(new),
678 Artifact::PlaneOfFace { .. } => Some(new),
679 Artifact::Sweep(a) => a.merge(new),
680 Artifact::Wall(a) => a.merge(new),
681 Artifact::Cap(a) => a.merge(new),
682 Artifact::SweepEdge(_) => Some(new),
683 Artifact::EdgeCut(a) => a.merge(new),
684 Artifact::EdgeCutEdge(_) => Some(new),
685 Artifact::Helix(a) => a.merge(new),
686 Artifact::GdtAnnotation(a) => a.merge(new),
687 Artifact::Pattern(a) => a.merge(new),
688 }
689 }
690}
691
692impl CompositeSolid {
693 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
694 let Artifact::CompositeSolid(new) = new else {
695 return Some(new);
696 };
697 merge_ids(&mut self.solid_ids, new.solid_ids);
698 merge_ids(&mut self.tool_ids, new.tool_ids);
699 merge_opt_id(&mut self.composite_solid_id, new.composite_solid_id);
700 merge_ids(&mut self.pattern_ids, new.pattern_ids);
701 self.output_index = new.output_index;
702 self.consumed = new.consumed;
703
704 None
705 }
706}
707
708impl Plane {
709 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
710 let Artifact::Plane(new) = new else {
711 return Some(new);
712 };
713 merge_ids(&mut self.path_ids, new.path_ids);
714
715 None
716 }
717}
718
719impl Path {
720 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
721 let Artifact::Path(new) = new else {
722 return Some(new);
723 };
724 merge_opt_id(&mut self.sweep_id, new.sweep_id);
725 merge_opt_id(&mut self.trajectory_sweep_id, new.trajectory_sweep_id);
726 merge_ids(&mut self.seg_ids, new.seg_ids);
727 merge_opt_id(&mut self.solid2d_id, new.solid2d_id);
728 merge_opt_id(&mut self.composite_solid_id, new.composite_solid_id);
729 merge_opt_id(&mut self.sketch_block_id, new.sketch_block_id);
730 merge_opt_id(&mut self.origin_path_id, new.origin_path_id);
731 merge_opt_id(&mut self.inner_path_id, new.inner_path_id);
732 merge_opt_id(&mut self.outer_path_id, new.outer_path_id);
733 merge_ids(&mut self.pattern_ids, new.pattern_ids);
734 self.consumed = new.consumed;
735
736 None
737 }
738}
739
740impl Segment {
741 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
742 let Artifact::Segment(new) = new else {
743 return Some(new);
744 };
745 merge_opt_id(&mut self.original_seg_id, new.original_seg_id);
746 merge_opt_id(&mut self.surface_id, new.surface_id);
747 merge_ids(&mut self.edge_ids, new.edge_ids);
748 merge_opt_id(&mut self.edge_cut_id, new.edge_cut_id);
749 merge_ids(&mut self.common_surface_ids, new.common_surface_ids);
750
751 None
752 }
753}
754
755impl Sweep {
756 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
757 let Artifact::Sweep(new) = new else {
758 return Some(new);
759 };
760 merge_ids(&mut self.surface_ids, new.surface_ids);
761 merge_ids(&mut self.edge_ids, new.edge_ids);
762 merge_opt_id(&mut self.trajectory_id, new.trajectory_id);
763 merge_ids(&mut self.pattern_ids, new.pattern_ids);
764 self.consumed = new.consumed;
765
766 None
767 }
768}
769
770impl Wall {
771 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
772 let Artifact::Wall(new) = new else {
773 return Some(new);
774 };
775 merge_ids(&mut self.edge_cut_edge_ids, new.edge_cut_edge_ids);
776 merge_ids(&mut self.path_ids, new.path_ids);
777
778 None
779 }
780}
781
782impl Cap {
783 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
784 let Artifact::Cap(new) = new else {
785 return Some(new);
786 };
787 merge_ids(&mut self.edge_cut_edge_ids, new.edge_cut_edge_ids);
788 merge_ids(&mut self.path_ids, new.path_ids);
789
790 None
791 }
792}
793
794impl EdgeCut {
795 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
796 let Artifact::EdgeCut(new) = new else {
797 return Some(new);
798 };
799 merge_opt_id(&mut self.surface_id, new.surface_id);
800 merge_ids(&mut self.edge_ids, new.edge_ids);
801
802 None
803 }
804}
805
806impl Helix {
807 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
808 let Artifact::Helix(new) = new else {
809 return Some(new);
810 };
811 merge_opt_id(&mut self.axis_id, new.axis_id);
812 merge_opt_id(&mut self.trajectory_sweep_id, new.trajectory_sweep_id);
813 self.consumed = new.consumed;
814
815 None
816 }
817}
818
819impl GdtAnnotationArtifact {
820 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
821 let Artifact::GdtAnnotation(new) = new else {
822 return Some(new);
823 };
824 self.code_ref = new.code_ref;
825
826 None
827 }
828}
829
830impl Pattern {
831 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
832 let Artifact::Pattern(new) = new else {
833 return Some(new);
834 };
835 merge_ids(&mut self.copy_ids, new.copy_ids);
836 merge_ids(&mut self.copy_face_ids, new.copy_face_ids);
837 merge_ids(&mut self.copy_edge_ids, new.copy_edge_ids);
838
839 None
840 }
841}
842
843#[derive(Debug, Clone, Default, PartialEq, Serialize, ts_rs::TS)]
844#[ts(export_to = "Artifact.ts")]
845#[serde(rename_all = "camelCase")]
846pub struct ArtifactGraph {
847 map: IndexMap<ArtifactId, Artifact>,
848 pub(super) item_count: usize,
849}
850
851impl ArtifactGraph {
852 pub fn get(&self, id: &ArtifactId) -> Option<&Artifact> {
853 self.map.get(id)
854 }
855
856 pub fn len(&self) -> usize {
857 self.map.len()
858 }
859
860 pub fn is_empty(&self) -> bool {
861 self.map.is_empty()
862 }
863
864 #[cfg(test)]
865 pub(crate) fn iter(&self) -> impl Iterator<Item = (&ArtifactId, &Artifact)> {
866 self.map.iter()
867 }
868
869 pub fn values(&self) -> impl Iterator<Item = &Artifact> {
870 self.map.values()
871 }
872
873 pub fn clear(&mut self) {
874 self.map.clear();
875 self.item_count = 0;
876 }
877
878 fn into_map(self) -> IndexMap<ArtifactId, Artifact> {
880 self.map
881 }
882}
883
884#[derive(Debug, Clone)]
885struct ImportCodeRef {
886 node_path: NodePath,
887 range: SourceRange,
888}
889
890fn import_statement_code_refs(
891 ast: &Node<Program>,
892 module_infos: &ModuleInfoMap,
893 programs: &crate::execution::ProgramLookup,
894 cached_body_items: usize,
895) -> AHashMap<ModuleId, ImportCodeRef> {
896 let mut code_refs = AHashMap::default();
897 for body_item in &ast.body {
898 let BodyItem::ImportStatement(import_stmt) = body_item else {
899 continue;
900 };
901 if !matches!(import_stmt.selector, ImportSelector::None { .. }) {
902 continue;
903 }
904 let Some(module_id) = module_id_for_import_path(module_infos, &import_stmt.path) else {
905 continue;
906 };
907 let range = SourceRange::from(import_stmt);
908 let node_path = NodePath::from_range(programs, cached_body_items, range).unwrap_or_default();
909 code_refs.entry(module_id).or_insert(ImportCodeRef { node_path, range });
910 }
911 code_refs
912}
913
914fn module_id_for_import_path(module_infos: &ModuleInfoMap, import_path: &ImportPath) -> Option<ModuleId> {
915 let import_path = match import_path {
916 ImportPath::Kcl { filename } => filename,
917 ImportPath::Foreign { path } => path,
918 ImportPath::Std { .. } => return None,
919 };
920
921 module_infos.iter().find_map(|(module_id, module_info)| {
922 if let ModulePath::Local {
923 original_import_path: Some(original_import_path),
924 ..
925 } = &module_info.path
926 && original_import_path == import_path
927 {
928 return Some(*module_id);
929 }
930 None
931 })
932}
933
934fn code_ref_for_range(
935 programs: &crate::execution::ProgramLookup,
936 cached_body_items: usize,
937 range: SourceRange,
938 import_code_refs: &AHashMap<ModuleId, ImportCodeRef>,
939) -> (SourceRange, NodePath) {
940 if let Some(code_ref) = import_code_refs.get(&range.module_id()) {
941 return (code_ref.range, code_ref.node_path.clone());
942 }
943
944 (
945 range,
946 NodePath::from_range(programs, cached_body_items, range).unwrap_or_default(),
947 )
948}
949
950pub(super) fn build_artifact_graph(
954 artifact_commands: &[ArtifactCommand],
955 responses: &IndexMap<Uuid, WebSocketResponse>,
956 ast: &Node<Program>,
957 exec_artifacts: &mut IndexMap<ArtifactId, Artifact>,
958 initial_graph: ArtifactGraph,
959 programs: &crate::execution::ProgramLookup,
960 module_infos: &ModuleInfoMap,
961) -> Result<ArtifactGraph, KclError> {
962 let item_count = initial_graph.item_count;
963 let mut map = initial_graph.into_map();
964
965 let mut path_to_plane_id_map = AHashMap::default();
966 let mut current_plane_id = None;
967 let import_code_refs = import_statement_code_refs(ast, module_infos, programs, item_count);
968 let flattened_responses = flatten_modeling_command_responses(responses);
969 let entity_clone_id_maps = build_entity_clone_id_maps(artifact_commands, &flattened_responses);
970
971 for exec_artifact in exec_artifacts.values_mut() {
974 fill_in_node_paths(exec_artifact, programs, item_count, &import_code_refs);
977 }
978
979 for artifact_command in artifact_commands {
980 if let ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) = artifact_command.command {
981 current_plane_id = Some(entity_id);
982 }
983 if let ModelingCmd::StartPath(_) = artifact_command.command
988 && let Some(plane_id) = current_plane_id
989 {
990 path_to_plane_id_map.insert(artifact_command.cmd_id, plane_id);
991 }
992 if let ModelingCmd::SketchModeDisable(_) = artifact_command.command {
993 current_plane_id = None;
994 }
995
996 let artifact_updates = artifacts_to_update(
997 &map,
998 artifact_command,
999 &flattened_responses,
1000 &entity_clone_id_maps,
1001 &path_to_plane_id_map,
1002 programs,
1003 item_count,
1004 exec_artifacts,
1005 &import_code_refs,
1006 )?;
1007 for artifact in artifact_updates {
1008 merge_artifact_into_map(&mut map, artifact);
1010 }
1011 }
1012
1013 for exec_artifact in exec_artifacts.values() {
1014 merge_artifact_into_map(&mut map, exec_artifact.clone());
1015 }
1016
1017 Ok(ArtifactGraph {
1018 map,
1019 item_count: item_count + ast.body.len(),
1020 })
1021}
1022
1023fn fill_in_node_paths(
1026 artifact: &mut Artifact,
1027 programs: &crate::execution::ProgramLookup,
1028 cached_body_items: usize,
1029 import_code_refs: &AHashMap<ModuleId, ImportCodeRef>,
1030) {
1031 match artifact {
1032 Artifact::StartSketchOnFace(face) if face.code_ref.node_path.is_empty() => {
1033 let (range, node_path) =
1034 code_ref_for_range(programs, cached_body_items, face.code_ref.range, import_code_refs);
1035 face.code_ref.range = range;
1036 face.code_ref.node_path = node_path;
1037 }
1038 Artifact::StartSketchOnPlane(plane) if plane.code_ref.node_path.is_empty() => {
1039 let (range, node_path) =
1040 code_ref_for_range(programs, cached_body_items, plane.code_ref.range, import_code_refs);
1041 plane.code_ref.range = range;
1042 plane.code_ref.node_path = node_path;
1043 }
1044 Artifact::SketchBlock(block) if block.code_ref.node_path.is_empty() => {
1045 let (range, node_path) =
1046 code_ref_for_range(programs, cached_body_items, block.code_ref.range, import_code_refs);
1047 block.code_ref.range = range;
1048 block.code_ref.node_path = node_path;
1049 }
1050 Artifact::SketchBlockConstraint(constraint) if constraint.code_ref.node_path.is_empty() => {
1051 constraint.code_ref.node_path =
1052 NodePath::from_range(programs, cached_body_items, constraint.code_ref.range).unwrap_or_default();
1053 }
1054 Artifact::GdtAnnotation(annotation) if annotation.code_ref.node_path.is_empty() => {
1055 let (range, node_path) =
1056 code_ref_for_range(programs, cached_body_items, annotation.code_ref.range, import_code_refs);
1057 annotation.code_ref.range = range;
1058 annotation.code_ref.node_path = node_path;
1059 }
1060 _ => {}
1061 }
1062}
1063
1064fn flatten_modeling_command_responses(
1067 responses: &IndexMap<Uuid, WebSocketResponse>,
1068) -> AHashMap<Uuid, OkModelingCmdResponse> {
1069 let mut map = AHashMap::default();
1070 for (cmd_id, ws_response) in responses {
1071 let WebSocketResponse::Success(response) = ws_response else {
1072 continue;
1074 };
1075 match &response.resp {
1076 OkWebSocketResponseData::Modeling { modeling_response } => {
1077 map.insert(*cmd_id, modeling_response.clone());
1078 }
1079 OkWebSocketResponseData::ModelingBatch { responses } =>
1080 {
1081 #[expect(
1082 clippy::iter_over_hash_type,
1083 reason = "Since we're moving entries to another unordered map, it's fine that the order is undefined"
1084 )]
1085 for (cmd_id, batch_response) in responses {
1086 if let BatchResponse::Success {
1087 response: modeling_response,
1088 } = batch_response
1089 {
1090 map.insert(*cmd_id.as_ref(), modeling_response.clone());
1091 }
1092 }
1093 }
1094 OkWebSocketResponseData::IceServerInfo { .. }
1095 | OkWebSocketResponseData::TrickleIce { .. }
1096 | OkWebSocketResponseData::SdpAnswer { .. }
1097 | OkWebSocketResponseData::Export { .. }
1098 | OkWebSocketResponseData::MetricsRequest { .. }
1099 | OkWebSocketResponseData::ModelingSessionData { .. }
1100 | OkWebSocketResponseData::Debug { .. }
1101 | OkWebSocketResponseData::Pong { .. } => {}
1102 _other => {}
1103 }
1104 }
1105
1106 map
1107}
1108
1109#[derive(Debug, Clone)]
1110struct PendingEntityCloneMapping {
1111 clone_cmd_id: Uuid,
1112 old_entity_id: Uuid,
1113 old_child_ids: Option<Vec<Uuid>>,
1114}
1115
1116fn build_entity_clone_id_maps(
1119 artifact_commands: &[ArtifactCommand],
1120 responses: &AHashMap<Uuid, OkModelingCmdResponse>,
1121) -> AHashMap<Uuid, AHashMap<ArtifactId, ArtifactId>> {
1122 let mut clone_id_maps = AHashMap::default();
1123 let mut pending = Vec::new();
1124
1125 for artifact_command in artifact_commands {
1126 match &artifact_command.command {
1127 ModelingCmd::EntityClone(kcmc::EntityClone { entity_id, .. }) => {
1128 pending.push(PendingEntityCloneMapping {
1129 clone_cmd_id: artifact_command.cmd_id,
1130 old_entity_id: *entity_id,
1131 old_child_ids: None,
1132 });
1133 }
1134 ModelingCmd::EntityGetAllChildUuids(kcmc::EntityGetAllChildUuids { entity_id, .. }) => {
1135 let Some(OkModelingCmdResponse::EntityGetAllChildUuids(child_ids_response)) =
1136 responses.get(&artifact_command.cmd_id)
1137 else {
1138 continue;
1139 };
1140 let child_ids = child_ids_response.entity_ids.clone();
1141
1142 let mut completed_index = None;
1143 for index in (0..pending.len()).rev() {
1144 let pending_map = &mut pending[index];
1145 if pending_map.old_child_ids.is_none() && *entity_id == pending_map.old_entity_id {
1146 pending_map.old_child_ids = Some(child_ids.clone());
1147 break;
1148 }
1149 if let Some(old_child_ids) = &pending_map.old_child_ids
1150 && *entity_id == pending_map.clone_cmd_id
1151 {
1152 let mut id_map = AHashMap::default();
1153 id_map.insert(
1154 ArtifactId::new(pending_map.old_entity_id),
1155 ArtifactId::new(pending_map.clone_cmd_id),
1156 );
1157 for (old_id, new_id) in old_child_ids.iter().zip(child_ids.iter()) {
1158 id_map.insert(ArtifactId::new(*old_id), ArtifactId::new(*new_id));
1159 }
1160 clone_id_maps.insert(pending_map.clone_cmd_id, id_map);
1161 completed_index = Some(index);
1162 break;
1163 }
1164 }
1165
1166 if let Some(index) = completed_index {
1167 pending.swap_remove(index);
1168 }
1169 }
1170 _ => {}
1171 }
1172 }
1173
1174 clone_id_maps
1175}
1176
1177fn merge_artifact_into_map(map: &mut IndexMap<ArtifactId, Artifact>, new_artifact: Artifact) {
1178 fn is_primitive_artifact(artifact: &Artifact) -> bool {
1179 matches!(artifact, Artifact::PrimitiveFace(_) | Artifact::PrimitiveEdge(_))
1180 }
1181
1182 let id = new_artifact.id();
1183 let Some(old_artifact) = map.get_mut(&id) else {
1184 map.insert(id, new_artifact);
1186 return;
1187 };
1188
1189 if is_primitive_artifact(&new_artifact) && !is_primitive_artifact(old_artifact) {
1193 return;
1194 }
1195
1196 if let Some(replacement) = old_artifact.merge(new_artifact) {
1197 *old_artifact = replacement;
1198 }
1199}
1200
1201fn merge_ids(base: &mut Vec<ArtifactId>, new: Vec<ArtifactId>) {
1205 let original_len = base.len();
1206 for id in new {
1207 let original_base = &base[..original_len];
1209 if !original_base.contains(&id) {
1210 base.push(id);
1211 }
1212 }
1213}
1214
1215fn merge_opt_id(base: &mut Option<ArtifactId>, new: Option<ArtifactId>) {
1217 *base = new;
1219}
1220
1221fn remap_id_for_clone(id: ArtifactId, entity_id_map: &AHashMap<ArtifactId, ArtifactId>) -> ArtifactId {
1222 entity_id_map.get(&id).copied().unwrap_or(id)
1223}
1224
1225fn remap_opt_id_for_clone(
1226 id: Option<ArtifactId>,
1227 entity_id_map: &AHashMap<ArtifactId, ArtifactId>,
1228) -> Option<ArtifactId> {
1229 id.map(|id| remap_id_for_clone(id, entity_id_map))
1230}
1231
1232fn remap_ids_for_clone(ids: &[ArtifactId], entity_id_map: &AHashMap<ArtifactId, ArtifactId>) -> Vec<ArtifactId> {
1233 ids.iter()
1234 .copied()
1235 .map(|id| remap_id_for_clone(id, entity_id_map))
1236 .collect()
1237}
1238
1239fn remap_mapped_ids_for_clone(ids: &[ArtifactId], entity_id_map: &AHashMap<ArtifactId, ArtifactId>) -> Vec<ArtifactId> {
1240 ids.iter().filter_map(|id| entity_id_map.get(id).copied()).collect()
1241}
1242
1243fn remap_artifact_for_clone(
1244 artifact: &Artifact,
1245 entity_id_map: &AHashMap<ArtifactId, ArtifactId>,
1246 clone_code_ref: &CodeRef,
1247 clone_cmd_id: Uuid,
1248 source_root_id: ArtifactId,
1249) -> Artifact {
1250 match artifact {
1251 Artifact::CompositeSolid(source) => Artifact::CompositeSolid(CompositeSolid {
1252 id: remap_id_for_clone(source.id, entity_id_map),
1253 consumed: if source.id == source_root_id {
1254 false
1255 } else {
1256 source.consumed
1257 },
1258 sub_type: source.sub_type,
1259 output_index: source.output_index,
1260 solid_ids: remap_ids_for_clone(&source.solid_ids, entity_id_map),
1261 tool_ids: remap_ids_for_clone(&source.tool_ids, entity_id_map),
1262 pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
1263 code_ref: clone_code_ref.clone(),
1264 composite_solid_id: remap_opt_id_for_clone(source.composite_solid_id, entity_id_map),
1265 }),
1266 Artifact::Plane(source) => Artifact::Plane(Plane {
1267 id: remap_id_for_clone(source.id, entity_id_map),
1268 path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
1269 code_ref: clone_code_ref.clone(),
1270 }),
1271 Artifact::Path(source) => Artifact::Path(Path {
1272 id: remap_id_for_clone(source.id, entity_id_map),
1273 sub_type: source.sub_type,
1274 plane_id: remap_id_for_clone(source.plane_id, entity_id_map),
1275 seg_ids: remap_ids_for_clone(&source.seg_ids, entity_id_map),
1276 consumed: if source.id == source_root_id {
1277 false
1278 } else {
1279 source.consumed
1280 },
1281 sweep_id: remap_opt_id_for_clone(source.sweep_id, entity_id_map),
1282 trajectory_sweep_id: remap_opt_id_for_clone(source.trajectory_sweep_id, entity_id_map),
1283 solid2d_id: remap_opt_id_for_clone(source.solid2d_id, entity_id_map),
1284 code_ref: clone_code_ref.clone(),
1285 composite_solid_id: remap_opt_id_for_clone(source.composite_solid_id, entity_id_map),
1286 sketch_block_id: remap_opt_id_for_clone(source.sketch_block_id, entity_id_map),
1287 origin_path_id: remap_opt_id_for_clone(source.origin_path_id, entity_id_map),
1288 inner_path_id: remap_opt_id_for_clone(source.inner_path_id, entity_id_map),
1289 outer_path_id: remap_opt_id_for_clone(source.outer_path_id, entity_id_map),
1290 pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
1291 }),
1292 Artifact::Segment(source) => Artifact::Segment(Segment {
1293 id: remap_id_for_clone(source.id, entity_id_map),
1294 path_id: remap_id_for_clone(source.path_id, entity_id_map),
1295 original_seg_id: remap_opt_id_for_clone(source.original_seg_id, entity_id_map),
1296 surface_id: remap_opt_id_for_clone(source.surface_id, entity_id_map),
1297 edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
1298 edge_cut_id: remap_opt_id_for_clone(source.edge_cut_id, entity_id_map),
1299 code_ref: clone_code_ref.clone(),
1300 common_surface_ids: remap_ids_for_clone(&source.common_surface_ids, entity_id_map),
1301 }),
1302 Artifact::Solid2d(source) => Artifact::Solid2d(Solid2d {
1303 id: remap_id_for_clone(source.id, entity_id_map),
1304 path_id: remap_id_for_clone(source.path_id, entity_id_map),
1305 }),
1306 Artifact::PrimitiveFace(source) => Artifact::PrimitiveFace(PrimitiveFace {
1307 id: remap_id_for_clone(source.id, entity_id_map),
1308 solid_id: remap_id_for_clone(source.solid_id, entity_id_map),
1309 code_ref: clone_code_ref.clone(),
1310 }),
1311 Artifact::PrimitiveEdge(source) => Artifact::PrimitiveEdge(PrimitiveEdge {
1312 id: remap_id_for_clone(source.id, entity_id_map),
1313 solid_id: remap_id_for_clone(source.solid_id, entity_id_map),
1314 code_ref: clone_code_ref.clone(),
1315 }),
1316 Artifact::PlaneOfFace(source) => Artifact::PlaneOfFace(PlaneOfFace {
1317 id: remap_id_for_clone(source.id, entity_id_map),
1318 face_id: remap_id_for_clone(source.face_id, entity_id_map),
1319 code_ref: clone_code_ref.clone(),
1320 }),
1321 Artifact::StartSketchOnFace(source) => Artifact::StartSketchOnFace(StartSketchOnFace {
1322 id: remap_id_for_clone(source.id, entity_id_map),
1323 face_id: remap_id_for_clone(source.face_id, entity_id_map),
1324 code_ref: clone_code_ref.clone(),
1325 }),
1326 Artifact::StartSketchOnPlane(source) => Artifact::StartSketchOnPlane(StartSketchOnPlane {
1327 id: remap_id_for_clone(source.id, entity_id_map),
1328 plane_id: remap_id_for_clone(source.plane_id, entity_id_map),
1329 code_ref: clone_code_ref.clone(),
1330 }),
1331 Artifact::SketchBlock(source) => Artifact::SketchBlock(SketchBlock {
1332 id: remap_id_for_clone(source.id, entity_id_map),
1333 standard_plane: source.standard_plane,
1334 plane_id: remap_opt_id_for_clone(source.plane_id, entity_id_map),
1335 plane_info: source.plane_info.clone(),
1336 path_id: remap_opt_id_for_clone(source.path_id, entity_id_map),
1337 code_ref: clone_code_ref.clone(),
1338 sketch_id: source.sketch_id,
1339 }),
1340 Artifact::SketchBlockConstraint(source) => Artifact::SketchBlockConstraint(SketchBlockConstraint {
1341 id: remap_id_for_clone(source.id, entity_id_map),
1342 sketch_id: source.sketch_id,
1343 constraint_id: source.constraint_id,
1344 constraint_type: source.constraint_type,
1345 code_ref: clone_code_ref.clone(),
1346 }),
1347 Artifact::Sweep(source) => Artifact::Sweep(Sweep {
1348 id: remap_id_for_clone(source.id, entity_id_map),
1349 sub_type: source.sub_type,
1350 path_id: remap_id_for_clone(source.path_id, entity_id_map),
1351 surface_ids: remap_ids_for_clone(&source.surface_ids, entity_id_map),
1352 edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
1353 code_ref: clone_code_ref.clone(),
1354 trajectory_id: remap_opt_id_for_clone(source.trajectory_id, entity_id_map),
1355 method: source.method,
1356 consumed: if source.id == source_root_id {
1357 false
1358 } else {
1359 source.consumed
1360 },
1361 pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
1362 }),
1363 Artifact::Wall(source) => Artifact::Wall(Wall {
1364 id: remap_id_for_clone(source.id, entity_id_map),
1365 seg_id: remap_id_for_clone(source.seg_id, entity_id_map),
1366 edge_cut_edge_ids: remap_ids_for_clone(&source.edge_cut_edge_ids, entity_id_map),
1367 sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
1368 path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
1369 face_code_ref: source.face_code_ref.clone(),
1370 cmd_id: clone_cmd_id,
1371 }),
1372 Artifact::Cap(source) => Artifact::Cap(Cap {
1373 id: remap_id_for_clone(source.id, entity_id_map),
1374 sub_type: source.sub_type,
1375 edge_cut_edge_ids: remap_ids_for_clone(&source.edge_cut_edge_ids, entity_id_map),
1376 sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
1377 path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
1378 face_code_ref: source.face_code_ref.clone(),
1379 cmd_id: clone_cmd_id,
1380 }),
1381 Artifact::SweepEdge(source) => Artifact::SweepEdge(SweepEdge {
1382 id: remap_id_for_clone(source.id, entity_id_map),
1383 sub_type: source.sub_type,
1384 seg_id: remap_id_for_clone(source.seg_id, entity_id_map),
1385 cmd_id: clone_cmd_id,
1386 index: source.index,
1387 sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
1388 common_surface_ids: remap_ids_for_clone(&source.common_surface_ids, entity_id_map),
1389 }),
1390 Artifact::EdgeCut(source) => Artifact::EdgeCut(EdgeCut {
1391 id: remap_id_for_clone(source.id, entity_id_map),
1392 sub_type: source.sub_type,
1393 consumed_edge_id: remap_id_for_clone(source.consumed_edge_id, entity_id_map),
1394 edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
1395 surface_id: remap_opt_id_for_clone(source.surface_id, entity_id_map),
1396 code_ref: clone_code_ref.clone(),
1397 }),
1398 Artifact::EdgeCutEdge(source) => Artifact::EdgeCutEdge(EdgeCutEdge {
1399 id: remap_id_for_clone(source.id, entity_id_map),
1400 edge_cut_id: remap_id_for_clone(source.edge_cut_id, entity_id_map),
1401 surface_id: remap_id_for_clone(source.surface_id, entity_id_map),
1402 }),
1403 Artifact::Helix(source) => Artifact::Helix(Helix {
1404 id: remap_id_for_clone(source.id, entity_id_map),
1405 axis_id: remap_opt_id_for_clone(source.axis_id, entity_id_map),
1406 code_ref: clone_code_ref.clone(),
1407 trajectory_sweep_id: remap_opt_id_for_clone(source.trajectory_sweep_id, entity_id_map),
1408 consumed: if source.id == source_root_id {
1409 false
1410 } else {
1411 source.consumed
1412 },
1413 }),
1414 Artifact::GdtAnnotation(source) => Artifact::GdtAnnotation(GdtAnnotationArtifact {
1415 id: remap_id_for_clone(source.id, entity_id_map),
1416 code_ref: clone_code_ref.clone(),
1417 }),
1418 Artifact::Pattern(source) => Artifact::Pattern(Pattern {
1419 id: remap_id_for_clone(source.id, entity_id_map),
1420 sub_type: source.sub_type,
1421 source_id: remap_id_for_clone(source.source_id, entity_id_map),
1422 copy_ids: remap_ids_for_clone(&source.copy_ids, entity_id_map),
1423 copy_face_ids: remap_ids_for_clone(&source.copy_face_ids, entity_id_map),
1424 copy_edge_ids: remap_ids_for_clone(&source.copy_edge_ids, entity_id_map),
1425 code_ref: clone_code_ref.clone(),
1426 }),
1427 }
1428}
1429
1430fn pattern_source_ids(artifacts: &IndexMap<ArtifactId, Artifact>, source_id: ArtifactId) -> Vec<ArtifactId> {
1431 let mut source_ids = vec![source_id];
1432
1433 if let Some(Artifact::Path(path)) = artifacts.get(&source_id) {
1434 if let Some(sweep_id) = path.sweep_id {
1435 source_ids.push(sweep_id);
1436 }
1437 if let Some(composite_solid_id) = path.composite_solid_id {
1438 source_ids.push(composite_solid_id);
1439 }
1440 }
1441
1442 for artifact in artifacts.values() {
1443 match artifact {
1444 Artifact::Sweep(sweep) if sweep.path_id == source_id => source_ids.push(sweep.id),
1445 Artifact::CompositeSolid(composite)
1446 if composite.solid_ids.contains(&source_id) || composite.tool_ids.contains(&source_id) =>
1447 {
1448 source_ids.push(composite.id)
1449 }
1450 _ => {}
1451 }
1452 }
1453
1454 let mut unique = Vec::new();
1455 merge_ids(&mut unique, source_ids);
1456 unique
1457}
1458
1459fn pattern_artifact_updates(
1460 artifacts: &IndexMap<ArtifactId, Artifact>,
1461 pattern_id: ArtifactId,
1462 sub_type: PatternSubType,
1463 source_id: ArtifactId,
1464 face_edge_infos: &[kcmc::output::FaceEdgeInfo],
1465 code_ref: CodeRef,
1466) -> Vec<Artifact> {
1467 let copy_ids = face_edge_infos
1468 .iter()
1469 .map(|info| ArtifactId::new(info.object_id))
1470 .collect::<Vec<_>>();
1471 let copy_face_ids = face_edge_infos
1472 .iter()
1473 .flat_map(|info| info.faces.iter().copied().map(ArtifactId::new))
1474 .collect::<Vec<_>>();
1475 let copy_edge_ids = face_edge_infos
1476 .iter()
1477 .flat_map(|info| info.edges.iter().copied().map(ArtifactId::new))
1478 .collect::<Vec<_>>();
1479
1480 let source_ids = pattern_source_ids(artifacts, source_id);
1481 let mut return_arr = vec![Artifact::Pattern(Pattern {
1482 id: pattern_id,
1483 sub_type,
1484 source_id,
1485 copy_ids,
1486 copy_face_ids,
1487 copy_edge_ids,
1488 code_ref,
1489 })];
1490
1491 for source_id in source_ids {
1492 let Some(artifact) = artifacts.get(&source_id) else {
1493 continue;
1494 };
1495 match artifact {
1496 Artifact::Path(path) => {
1497 let mut new_path = path.clone();
1498 new_path.pattern_ids = vec![pattern_id];
1499 return_arr.push(Artifact::Path(new_path));
1500 }
1501 Artifact::Sweep(sweep) => {
1502 let mut new_sweep = sweep.clone();
1503 new_sweep.pattern_ids = vec![pattern_id];
1504 return_arr.push(Artifact::Sweep(new_sweep));
1505 }
1506 Artifact::CompositeSolid(composite) => {
1507 let mut new_composite = composite.clone();
1508 new_composite.pattern_ids = vec![pattern_id];
1509 return_arr.push(Artifact::CompositeSolid(new_composite));
1510 }
1511 _ => {}
1512 }
1513 }
1514
1515 return_arr
1516}
1517
1518fn is_single_target_self_subtract(target_ids: &[Uuid], tool_ids: &[Uuid]) -> bool {
1519 target_ids.len() == 1 && tool_ids.len() == 1 && target_ids[0] == tool_ids[0]
1520}
1521
1522fn boolean_subtract_output_artifact_ids(
1523 cmd_id: ArtifactId,
1524 target_ids: &[Uuid],
1525 tool_ids: &[Uuid],
1526 extra_solid_ids: &[Uuid],
1527) -> Vec<ArtifactId> {
1528 if is_single_target_self_subtract(target_ids, tool_ids) {
1529 return Vec::new();
1530 }
1531
1532 let mut output_ids = if target_ids.len() == 1 {
1533 vec![cmd_id]
1534 } else {
1535 Vec::new()
1536 };
1537
1538 for extra_solid_id in extra_solid_ids {
1539 let artifact_id = ArtifactId::new(*extra_solid_id);
1540 if !output_ids.contains(&artifact_id) {
1541 output_ids.push(artifact_id);
1542 }
1543 }
1544
1545 output_ids
1546}
1547
1548fn update_consumed_csg_sweep(
1549 return_arr: &mut Vec<Artifact>,
1550 artifacts: &IndexMap<ArtifactId, Artifact>,
1551 sweep_id: ArtifactId,
1552 consumed_sweep_ids: &mut AHashSet<ArtifactId>,
1553) {
1554 if consumed_sweep_ids.insert(sweep_id)
1555 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
1556 {
1557 let mut new_sweep = sweep.clone();
1558 new_sweep.consumed = true;
1559 return_arr.push(Artifact::Sweep(new_sweep));
1560 }
1561}
1562
1563fn mark_artifact_consumed_by_id(
1564 return_arr: &mut Vec<Artifact>,
1565 artifacts: &IndexMap<ArtifactId, Artifact>,
1566 artifact_id: ArtifactId,
1567 consumed_ids: &mut AHashSet<ArtifactId>,
1568) {
1569 let already_marked_as_consumed = !consumed_ids.insert(artifact_id);
1570 if already_marked_as_consumed {
1571 return;
1572 }
1573
1574 let Some(artifact) = artifacts.get(&artifact_id) else {
1575 return;
1576 };
1577
1578 match artifact {
1579 Artifact::CompositeSolid(composite) => {
1580 let mut new_composite = composite.clone();
1581 new_composite.consumed = true;
1582 return_arr.push(Artifact::CompositeSolid(new_composite));
1583 }
1584 Artifact::Path(path) => {
1585 let mut new_path = path.clone();
1586 new_path.consumed = true;
1587 return_arr.push(Artifact::Path(new_path));
1588
1589 if let Some(sweep_id) = path.sweep_id {
1590 mark_artifact_consumed_by_id(return_arr, artifacts, sweep_id, consumed_ids);
1591 }
1592 if let Some(composite_solid_id) = path.composite_solid_id {
1593 mark_artifact_consumed_by_id(return_arr, artifacts, composite_solid_id, consumed_ids);
1594 }
1595 }
1596 Artifact::Sweep(sweep) => {
1597 let mut new_sweep = sweep.clone();
1598 new_sweep.consumed = true;
1599 return_arr.push(Artifact::Sweep(new_sweep));
1600 }
1601 Artifact::Helix(helix) => {
1602 let mut new_helix = helix.clone();
1603 new_helix.consumed = true;
1604 return_arr.push(Artifact::Helix(new_helix));
1605 }
1606 _ => {}
1607 }
1608}
1609
1610fn mark_deleted_artifacts_consumed(
1611 artifacts: &IndexMap<ArtifactId, Artifact>,
1612 object_ids: &std::collections::HashSet<Uuid>,
1613) -> Vec<Artifact> {
1614 let mut return_arr = Vec::new();
1615 let mut consumed_ids = AHashSet::default();
1616
1617 #[allow(clippy::iter_over_hash_type)]
1620 for object_id in object_ids {
1621 let artifact_id = ArtifactId::new(*object_id);
1622 mark_artifact_consumed_by_id(&mut return_arr, artifacts, artifact_id, &mut consumed_ids);
1623 }
1624
1625 return_arr
1626}
1627
1628fn update_csg_input_artifacts(
1629 return_arr: &mut Vec<Artifact>,
1630 artifacts: &IndexMap<ArtifactId, Artifact>,
1631 input_ids: &[ArtifactId],
1632 composite_solid_id: Option<ArtifactId>,
1633 consumed_sweep_ids: &mut AHashSet<ArtifactId>,
1634) {
1635 for input_id in input_ids {
1636 if let Some(artifact) = artifacts.get(input_id) {
1637 match artifact {
1638 Artifact::CompositeSolid(comp) => {
1639 let mut new_comp = comp.clone();
1640 new_comp.composite_solid_id = composite_solid_id;
1641 new_comp.consumed = true;
1642 return_arr.push(Artifact::CompositeSolid(new_comp));
1643 }
1644 Artifact::Path(path) => {
1645 let mut new_path = path.clone();
1646 new_path.composite_solid_id = composite_solid_id;
1647
1648 if let Some(sweep_id) = new_path.sweep_id {
1651 update_consumed_csg_sweep(return_arr, artifacts, sweep_id, consumed_sweep_ids);
1652 }
1653
1654 return_arr.push(Artifact::Path(new_path));
1655 }
1656 Artifact::Sweep(sweep) => {
1657 update_consumed_csg_sweep(return_arr, artifacts, sweep.id, consumed_sweep_ids);
1658 }
1659 _ => {}
1660 }
1661 }
1662 }
1663}
1664
1665fn mirror_3d_artifact_updates(
1666 artifacts: &IndexMap<ArtifactId, Artifact>,
1667 original_solid_ids: &[Uuid],
1668 face_edge_infos: &[kcmc::output::FaceEdgeInfo],
1669 code_ref: CodeRef,
1670 range: SourceRange,
1671 cmd: &ModelingCmd,
1672) -> Result<Vec<Artifact>, KclError> {
1673 if original_solid_ids.len() != face_edge_infos.len() {
1674 internal_error!(
1675 range,
1676 "EntityMirrorAcross response has different number face edge info than original mirrored solids: cmd={cmd:?}, face_edge_infos={face_edge_infos:?}"
1677 );
1678 }
1679
1680 let mut return_arr = Vec::new();
1681 for (face_edge_info, original_solid_id) in face_edge_infos.iter().zip(original_solid_ids) {
1682 let original_solid_id = ArtifactId::new(*original_solid_id);
1683 let mirrored_solid_id = ArtifactId::new(face_edge_info.object_id);
1684 let source_solid = match artifacts.get(&original_solid_id) {
1685 Some(Artifact::Path(path)) => path.sweep_id.and_then(|sweep_id| artifacts.get(&sweep_id)).or_else(|| {
1686 path.composite_solid_id
1687 .and_then(|composite_id| artifacts.get(&composite_id))
1688 }),
1689 source => source,
1690 };
1691 match source_solid {
1692 Some(Artifact::Sweep(sweep)) => {
1693 let mut mirrored_sweep = sweep.clone();
1694 mirrored_sweep.id = mirrored_solid_id;
1695 mirrored_sweep.surface_ids = face_edge_info.faces.iter().copied().map(ArtifactId::new).collect();
1696 mirrored_sweep.edge_ids = face_edge_info.edges.iter().copied().map(ArtifactId::new).collect();
1697 mirrored_sweep.code_ref = code_ref.clone();
1698 mirrored_sweep.consumed = false;
1699 mirrored_sweep.pattern_ids = Vec::new();
1700 return_arr.push(Artifact::Sweep(mirrored_sweep));
1701 }
1702 Some(Artifact::CompositeSolid(composite)) => {
1703 let mut mirrored_composite = composite.clone();
1704 mirrored_composite.id = mirrored_solid_id;
1705 mirrored_composite.code_ref = code_ref.clone();
1706 mirrored_composite.consumed = false;
1707 mirrored_composite.composite_solid_id = None;
1708 mirrored_composite.pattern_ids = Vec::new();
1709 return_arr.push(Artifact::CompositeSolid(mirrored_composite));
1710 }
1711 Some(_) | None => continue,
1712 }
1713 }
1714
1715 Ok(return_arr)
1716}
1717
1718#[allow(clippy::too_many_arguments)]
1719fn artifacts_to_update(
1720 artifacts: &IndexMap<ArtifactId, Artifact>,
1721 artifact_command: &ArtifactCommand,
1722 responses: &AHashMap<Uuid, OkModelingCmdResponse>,
1723 entity_clone_id_maps: &AHashMap<Uuid, AHashMap<ArtifactId, ArtifactId>>,
1724 path_to_plane_id_map: &AHashMap<Uuid, Uuid>,
1725 programs: &crate::execution::ProgramLookup,
1726 cached_body_items: usize,
1727 exec_artifacts: &IndexMap<ArtifactId, Artifact>,
1728 import_code_refs: &AHashMap<ModuleId, ImportCodeRef>,
1729) -> Result<Vec<Artifact>, KclError> {
1730 let uuid = artifact_command.cmd_id;
1731 let response = responses.get(&uuid);
1732
1733 let path_to_node = Vec::new();
1737 let range = artifact_command.range;
1738 let (code_ref_range, node_path) = code_ref_for_range(programs, cached_body_items, range, import_code_refs);
1739 let code_ref = CodeRef {
1740 range: code_ref_range,
1741 node_path,
1742 path_to_node,
1743 };
1744
1745 let id = ArtifactId::new(uuid);
1746 let cmd = &artifact_command.command;
1747
1748 match cmd {
1749 ModelingCmd::MakePlane(_) => {
1750 if range.is_synthetic() {
1751 return Ok(Vec::new());
1752 }
1753 return Ok(vec![Artifact::Plane(Plane {
1757 id,
1758 path_ids: Vec::new(),
1759 code_ref,
1760 })]);
1761 }
1762 ModelingCmd::FaceIsPlanar(FaceIsPlanar { object_id, .. }) => {
1763 return Ok(vec![Artifact::PlaneOfFace(PlaneOfFace {
1764 id,
1765 face_id: object_id.into(),
1766 code_ref,
1767 })]);
1768 }
1769 ModelingCmd::RemoveSceneObjects(remove) => {
1770 return Ok(mark_deleted_artifacts_consumed(artifacts, &remove.object_ids));
1771 }
1772 ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) => {
1773 let existing_plane = artifacts.get(&ArtifactId::new(*entity_id));
1774 match existing_plane {
1775 Some(Artifact::Wall(wall)) => {
1776 return Ok(vec![Artifact::Wall(Wall {
1777 id: entity_id.into(),
1778 seg_id: wall.seg_id,
1779 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1780 sweep_id: wall.sweep_id,
1781 path_ids: wall.path_ids.clone(),
1782 face_code_ref: wall.face_code_ref.clone(),
1783 cmd_id: artifact_command.cmd_id,
1784 })]);
1785 }
1786 Some(Artifact::Cap(cap)) => {
1787 return Ok(vec![Artifact::Cap(Cap {
1788 id: entity_id.into(),
1789 sub_type: cap.sub_type,
1790 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1791 sweep_id: cap.sweep_id,
1792 path_ids: cap.path_ids.clone(),
1793 face_code_ref: cap.face_code_ref.clone(),
1794 cmd_id: artifact_command.cmd_id,
1795 })]);
1796 }
1797 Some(_) | None => {
1798 let path_ids = match existing_plane {
1799 Some(Artifact::Plane(Plane { path_ids, .. })) => path_ids.clone(),
1800 _ => Vec::new(),
1801 };
1802 return Ok(vec![Artifact::Plane(Plane {
1804 id: entity_id.into(),
1805 path_ids,
1806 code_ref,
1807 })]);
1808 }
1809 }
1810 }
1811 ModelingCmd::StartPath(_) => {
1812 let mut return_arr = Vec::new();
1813 let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
1814 KclError::new_internal(KclErrorDetails::new(
1815 format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"),
1816 vec![range],
1817 ))
1818 })?;
1819 let sketch_block_id = exec_artifacts
1820 .values()
1821 .find(|a| {
1822 if let Artifact::SketchBlock(s) = a {
1823 if let Some(path_id) = s.path_id {
1824 path_id == id
1825 } else {
1826 false
1827 }
1828 } else {
1829 false
1830 }
1831 })
1832 .map(|a| a.id());
1833 return_arr.push(Artifact::Path(Path {
1834 id,
1835 sub_type: PathSubType::Sketch,
1836 plane_id: (*current_plane_id).into(),
1837 seg_ids: Vec::new(),
1838 sweep_id: None,
1839 trajectory_sweep_id: None,
1840 solid2d_id: None,
1841 code_ref,
1842 composite_solid_id: None,
1843 sketch_block_id,
1844 origin_path_id: None,
1845 inner_path_id: None,
1846 outer_path_id: None,
1847 pattern_ids: Vec::new(),
1848 consumed: false,
1849 }));
1850 let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
1851 if let Some(Artifact::Plane(plane)) = plane {
1852 let plane_code_ref = plane.code_ref.clone();
1853 return_arr.push(Artifact::Plane(Plane {
1854 id: (*current_plane_id).into(),
1855 path_ids: vec![id],
1856 code_ref: plane_code_ref,
1857 }));
1858 }
1859 if let Some(Artifact::Wall(wall)) = plane {
1860 return_arr.push(Artifact::Wall(Wall {
1861 id: (*current_plane_id).into(),
1862 seg_id: wall.seg_id,
1863 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1864 sweep_id: wall.sweep_id,
1865 path_ids: vec![id],
1866 face_code_ref: wall.face_code_ref.clone(),
1867 cmd_id: artifact_command.cmd_id,
1868 }));
1869 }
1870 if let Some(Artifact::Cap(cap)) = plane {
1871 return_arr.push(Artifact::Cap(Cap {
1872 id: (*current_plane_id).into(),
1873 sub_type: cap.sub_type,
1874 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1875 sweep_id: cap.sweep_id,
1876 path_ids: vec![id],
1877 face_code_ref: cap.face_code_ref.clone(),
1878 cmd_id: artifact_command.cmd_id,
1879 }));
1880 }
1881 return Ok(return_arr);
1882 }
1883 ModelingCmd::ClosePath(_) | ModelingCmd::ExtendPath(_) => {
1884 let path_id = ArtifactId::new(match cmd {
1885 ModelingCmd::ClosePath(c) => c.path_id,
1886 ModelingCmd::ExtendPath(e) => e.path.into(),
1887 _ => internal_error!(
1888 range,
1889 "Close or extend path command variant not handled: id={id:?}, cmd={cmd:?}"
1890 ),
1891 });
1892 let mut return_arr = Vec::new();
1893 return_arr.push(Artifact::Segment(Segment {
1894 id,
1895 path_id,
1896 original_seg_id: None,
1897 surface_id: None,
1898 edge_ids: Vec::new(),
1899 edge_cut_id: None,
1900 code_ref,
1901 common_surface_ids: Vec::new(),
1902 }));
1903 let path = artifacts.get(&path_id);
1904 if let Some(Artifact::Path(path)) = path {
1905 let mut new_path = path.clone();
1906 new_path.seg_ids = vec![id];
1907 return_arr.push(Artifact::Path(new_path));
1908 }
1909 if let Some(OkModelingCmdResponse::ClosePath(close_path)) = response {
1910 return_arr.push(Artifact::Solid2d(Solid2d {
1911 id: close_path.face_id.into(),
1912 path_id,
1913 }));
1914 if let Some(Artifact::Path(path)) = path {
1915 let mut new_path = path.clone();
1916 new_path.solid2d_id = Some(close_path.face_id.into());
1917 return_arr.push(Artifact::Path(new_path));
1918 }
1919 }
1920 return Ok(return_arr);
1921 }
1922 ModelingCmd::CreateRegion(kcmc::CreateRegion {
1923 object_id: origin_path_id,
1924 ..
1925 })
1926 | ModelingCmd::CreateRegionFromQueryPoint(kcmc::CreateRegionFromQueryPoint {
1927 object_id: origin_path_id,
1928 ..
1929 }) => {
1930 let mut return_arr = Vec::new();
1931 let origin_path = artifacts.get(&ArtifactId::new(*origin_path_id));
1932 let Some(Artifact::Path(path)) = origin_path else {
1933 internal_error!(
1934 range,
1935 "Expected to find an existing path for the origin path of CreateRegion or CreateRegionFromQueryPoint command, but found none: origin_path={origin_path:?}, cmd={cmd:?}"
1936 );
1937 };
1938 return_arr.push(Artifact::Path(Path {
1940 id,
1941 sub_type: PathSubType::Region,
1942 plane_id: path.plane_id,
1943 seg_ids: Vec::new(),
1944 consumed: false,
1945 sweep_id: None,
1946 trajectory_sweep_id: None,
1947 solid2d_id: None,
1948 code_ref: code_ref.clone(),
1949 composite_solid_id: None,
1950 sketch_block_id: None,
1951 origin_path_id: Some(ArtifactId::new(*origin_path_id)),
1952 inner_path_id: None,
1953 outer_path_id: None,
1954 pattern_ids: Vec::new(),
1955 }));
1956 let Some(
1959 OkModelingCmdResponse::CreateRegion(kcmc::output::CreateRegion { region_mapping, .. })
1960 | OkModelingCmdResponse::CreateRegionFromQueryPoint(kcmc::output::CreateRegionFromQueryPoint {
1961 region_mapping,
1962 ..
1963 }),
1964 ) = response
1965 else {
1966 return Ok(return_arr);
1967 };
1968 let original_segment_ids = path.seg_ids.iter().map(|p| p.0).collect::<Vec<_>>();
1971 let reverse = build_reverse_region_mapping(region_mapping, &original_segment_ids);
1972 for (original_segment_id, region_segment_ids) in reverse.iter() {
1973 for segment_id in region_segment_ids {
1974 return_arr.push(Artifact::Segment(Segment {
1975 id: ArtifactId::new(*segment_id),
1976 path_id: id,
1977 original_seg_id: Some(ArtifactId::new(*original_segment_id)),
1978 surface_id: None,
1979 edge_ids: Vec::new(),
1980 edge_cut_id: None,
1981 code_ref: code_ref.clone(),
1982 common_surface_ids: Vec::new(),
1983 }))
1984 }
1985 }
1986 return Ok(return_arr);
1987 }
1988 ModelingCmd::Solid3dGetFaceUuid(kcmc::Solid3dGetFaceUuid { object_id, .. }) => {
1989 let Some(OkModelingCmdResponse::Solid3dGetFaceUuid(face_uuid)) = response else {
1990 return Ok(Vec::new());
1991 };
1992
1993 return Ok(vec![Artifact::PrimitiveFace(PrimitiveFace {
1994 id: face_uuid.face_id.into(),
1995 solid_id: (*object_id).into(),
1996 code_ref,
1997 })]);
1998 }
1999 ModelingCmd::Solid3dGetEdgeUuid(kcmc::Solid3dGetEdgeUuid { object_id, .. }) => {
2000 let Some(OkModelingCmdResponse::Solid3dGetEdgeUuid(edge_uuid)) = response else {
2001 return Ok(Vec::new());
2002 };
2003
2004 return Ok(vec![Artifact::PrimitiveEdge(PrimitiveEdge {
2005 id: edge_uuid.edge_id.into(),
2006 solid_id: (*object_id).into(),
2007 code_ref,
2008 })]);
2009 }
2010 ModelingCmd::EntityLinearPatternTransform(pattern_cmd) => {
2011 let face_edge_infos = match response {
2012 Some(OkModelingCmdResponse::EntityLinearPatternTransform(resp)) => resp.entity_face_edge_ids.as_slice(),
2013 _ => &[],
2014 };
2015 return Ok(pattern_artifact_updates(
2016 artifacts,
2017 id,
2018 PatternSubType::Transform,
2019 ArtifactId::new(pattern_cmd.entity_id),
2020 face_edge_infos,
2021 code_ref,
2022 ));
2023 }
2024 ModelingCmd::EntityLinearPattern(pattern_cmd) => {
2025 let face_edge_infos = match response {
2026 Some(OkModelingCmdResponse::EntityLinearPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
2027 _ => &[],
2028 };
2029 return Ok(pattern_artifact_updates(
2030 artifacts,
2031 id,
2032 PatternSubType::Linear,
2033 ArtifactId::new(pattern_cmd.entity_id),
2034 face_edge_infos,
2035 code_ref,
2036 ));
2037 }
2038 ModelingCmd::EntityCircularPattern(pattern_cmd) => {
2039 let face_edge_infos = match response {
2040 Some(OkModelingCmdResponse::EntityCircularPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
2041 _ => &[],
2042 };
2043 return Ok(pattern_artifact_updates(
2044 artifacts,
2045 id,
2046 PatternSubType::Circular,
2047 ArtifactId::new(pattern_cmd.entity_id),
2048 face_edge_infos,
2049 code_ref,
2050 ));
2051 }
2052 ModelingCmd::EntityMirrorAcross(kcmc::EntityMirrorAcross {
2053 ids: original_solid_ids,
2054 ..
2055 }) => {
2056 let face_edge_infos = match response {
2057 Some(OkModelingCmdResponse::EntityMirrorAcross(resp)) => resp.entity_face_edge_ids.as_slice(),
2058 _ => internal_error!(
2059 range,
2060 "EntityMirrorAcross response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
2061 ),
2062 };
2063 return mirror_3d_artifact_updates(artifacts, original_solid_ids, face_edge_infos, code_ref, range, cmd);
2064 }
2065 ModelingCmd::EntityMirror(kcmc::EntityMirror {
2066 ids: original_path_ids, ..
2067 })
2068 | ModelingCmd::EntityMirrorAcrossEdge(kcmc::EntityMirrorAcrossEdge {
2069 ids: original_path_ids, ..
2070 }) => {
2071 let face_edge_infos = match response {
2072 Some(OkModelingCmdResponse::EntityMirror(resp)) => &resp.entity_face_edge_ids,
2073 Some(OkModelingCmdResponse::EntityMirrorAcrossEdge(resp)) => &resp.entity_face_edge_ids,
2074 _ => internal_error!(
2075 range,
2076 "Mirror response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
2077 ),
2078 };
2079 if original_path_ids.len() != face_edge_infos.len() {
2080 internal_error!(
2081 range,
2082 "EntityMirror or EntityMirrorAcrossEdge response has different number face edge info than original mirrored paths: id={id:?}, cmd={cmd:?}, response={response:?}"
2083 );
2084 }
2085 let mut return_arr = Vec::new();
2086 for (face_edge_info, original_path_id) in face_edge_infos.iter().zip(original_path_ids) {
2087 let original_path_id = ArtifactId::new(*original_path_id);
2088 let path_id = ArtifactId::new(face_edge_info.object_id);
2089 let mut path = if let Some(Artifact::Path(path)) = artifacts.get(&path_id) {
2092 path.clone()
2094 } else {
2095 let Some(Artifact::Path(original_path)) = artifacts.get(&original_path_id) else {
2098 internal_error!(
2100 range,
2101 "Couldn't find original path for mirror2d: original_path_id={original_path_id:?}, cmd={cmd:?}"
2102 );
2103 };
2104 Path {
2105 id: path_id,
2106 sub_type: original_path.sub_type,
2107 plane_id: original_path.plane_id,
2108 seg_ids: Vec::new(),
2109 sweep_id: None,
2110 trajectory_sweep_id: None,
2111 solid2d_id: None,
2112 code_ref: code_ref.clone(),
2113 composite_solid_id: None,
2114 sketch_block_id: None,
2115 origin_path_id: original_path.origin_path_id,
2116 inner_path_id: None,
2117 outer_path_id: None,
2118 pattern_ids: Vec::new(),
2119 consumed: false,
2120 }
2121 };
2122
2123 face_edge_info.edges.iter().for_each(|edge_id| {
2124 let edge_id = ArtifactId::new(*edge_id);
2125 return_arr.push(Artifact::Segment(Segment {
2126 id: edge_id,
2127 path_id: path.id,
2128 original_seg_id: None,
2129 surface_id: None,
2130 edge_ids: Vec::new(),
2131 edge_cut_id: None,
2132 code_ref: code_ref.clone(),
2133 common_surface_ids: Vec::new(),
2134 }));
2135 path.seg_ids.push(edge_id);
2137 });
2138
2139 return_arr.push(Artifact::Path(path));
2140 }
2141 return Ok(return_arr);
2142 }
2143 ModelingCmd::EntityClone(kcmc::EntityClone { entity_id, .. }) => {
2144 let source_id = ArtifactId::new(*entity_id);
2145
2146 let Some(source_artifact) = artifacts.get(&source_id) else {
2147 return Ok(Vec::new());
2148 };
2149
2150 let mut entity_id_map = entity_clone_id_maps.get(&uuid).cloned().unwrap_or_default();
2151 entity_id_map.insert(source_id, id);
2152
2153 let mut cloned_artifacts = Vec::new();
2154 cloned_artifacts.push(remap_artifact_for_clone(
2155 source_artifact,
2156 &entity_id_map,
2157 &code_ref,
2158 artifact_command.cmd_id,
2159 source_id,
2160 ));
2161
2162 for artifact in artifacts.values() {
2163 let artifact_id = artifact.id();
2164 if artifact_id == source_id || !entity_id_map.contains_key(&artifact_id) {
2165 continue;
2166 }
2167 cloned_artifacts.push(remap_artifact_for_clone(
2168 artifact,
2169 &entity_id_map,
2170 &code_ref,
2171 artifact_command.cmd_id,
2172 source_id,
2173 ));
2174 }
2175
2176 return Ok(cloned_artifacts);
2177 }
2178 ModelingCmd::Extrude(kcmc::Extrude { target, .. })
2179 | ModelingCmd::TwistExtrude(kcmc::TwistExtrude { target, .. })
2180 | ModelingCmd::Revolve(kcmc::Revolve { target, .. })
2181 | ModelingCmd::RevolveAboutEdge(kcmc::RevolveAboutEdge { target, .. })
2182 | ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { target, .. }) => {
2183 let method = match cmd {
2185 ModelingCmd::Extrude(kcmc::Extrude { extrude_method, .. }) => *extrude_method,
2186 ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { extrude_method, .. }) => *extrude_method,
2187 ModelingCmd::TwistExtrude(_) | ModelingCmd::Sweep(_) => {
2189 kittycad_modeling_cmds::shared::ExtrudeMethod::Merge
2190 }
2191 ModelingCmd::Revolve(_) | ModelingCmd::RevolveAboutEdge(_) => {
2193 kittycad_modeling_cmds::shared::ExtrudeMethod::New
2194 }
2195 _ => kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
2196 };
2197 let sub_type = match cmd {
2198 ModelingCmd::Extrude(_) => SweepSubType::Extrusion,
2199 ModelingCmd::ExtrudeToReference(_) => SweepSubType::Extrusion,
2200 ModelingCmd::TwistExtrude(_) => SweepSubType::ExtrusionTwist,
2201 ModelingCmd::Revolve(_) => SweepSubType::Revolve,
2202 ModelingCmd::RevolveAboutEdge(_) => SweepSubType::RevolveAboutEdge,
2203 _ => internal_error!(range, "Sweep-like command variant not handled: id={id:?}, cmd={cmd:?}",),
2204 };
2205 let mut return_arr = Vec::new();
2206 let target = ArtifactId::from(target);
2207 return_arr.push(Artifact::Sweep(Sweep {
2208 id,
2209 sub_type,
2210 path_id: target,
2211 surface_ids: Vec::new(),
2212 edge_ids: Vec::new(),
2213 code_ref,
2214 trajectory_id: None,
2215 method,
2216 consumed: false,
2217 pattern_ids: Vec::new(),
2218 }));
2219 let path = artifacts.get(&target);
2220 if let Some(Artifact::Path(path)) = path {
2221 let mut new_path = path.clone();
2222 new_path.sweep_id = Some(id);
2223 new_path.consumed = true;
2224 return_arr.push(Artifact::Path(new_path));
2225 if let Some(inner_path_id) = path.inner_path_id
2226 && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
2227 && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
2228 {
2229 inner_path_artifact.sweep_id = Some(id);
2230 inner_path_artifact.consumed = true;
2231 return_arr.push(Artifact::Path(inner_path_artifact))
2232 }
2233 }
2234 return Ok(return_arr);
2235 }
2236 ModelingCmd::Sweep(kcmc::Sweep { target, trajectory, .. }) => {
2237 let method = kittycad_modeling_cmds::shared::ExtrudeMethod::Merge;
2239 let sub_type = SweepSubType::Sweep;
2240 let mut return_arr = Vec::new();
2241 let target = ArtifactId::from(target);
2242 let trajectory = ArtifactId::from(trajectory);
2243 return_arr.push(Artifact::Sweep(Sweep {
2244 id,
2245 sub_type,
2246 path_id: target,
2247 surface_ids: Vec::new(),
2248 edge_ids: Vec::new(),
2249 code_ref,
2250 trajectory_id: Some(trajectory),
2251 method,
2252 consumed: false,
2253 pattern_ids: Vec::new(),
2254 }));
2255 let path = artifacts.get(&target);
2256 if let Some(Artifact::Path(path)) = path {
2257 let mut new_path = path.clone();
2258 new_path.sweep_id = Some(id);
2259 new_path.consumed = true;
2260 return_arr.push(Artifact::Path(new_path));
2261 if let Some(inner_path_id) = path.inner_path_id
2262 && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
2263 && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
2264 {
2265 inner_path_artifact.sweep_id = Some(id);
2266 inner_path_artifact.consumed = true;
2267 return_arr.push(Artifact::Path(inner_path_artifact))
2268 }
2269 }
2270 if let Some(trajectory_artifact) = artifacts.get(&trajectory) {
2271 match trajectory_artifact {
2272 Artifact::Path(path) => {
2273 let mut new_path = path.clone();
2274 new_path.trajectory_sweep_id = Some(id);
2275 new_path.consumed = true;
2276 return_arr.push(Artifact::Path(new_path));
2277 }
2278 Artifact::Helix(helix) => {
2279 let mut new_helix = helix.clone();
2280 new_helix.trajectory_sweep_id = Some(id);
2281 new_helix.consumed = true;
2282 return_arr.push(Artifact::Helix(new_helix));
2283 }
2284 _ => {}
2285 }
2286 };
2287 return Ok(return_arr);
2288 }
2289 ModelingCmd::SurfaceBlend(surface_blend_cmd) => {
2290 let surface_id_to_path_id = |surface_id: ArtifactId| -> Option<ArtifactId> {
2291 match artifacts.get(&surface_id) {
2292 Some(Artifact::Path(path)) => Some(path.id),
2293 Some(Artifact::Segment(segment)) => Some(segment.path_id),
2294 Some(Artifact::Sweep(sweep)) => Some(sweep.path_id),
2295 Some(Artifact::Wall(wall)) => artifacts.get(&wall.sweep_id).and_then(|artifact| match artifact {
2296 Artifact::Sweep(sweep) => Some(sweep.path_id),
2297 _ => None,
2298 }),
2299 Some(Artifact::Cap(cap)) => artifacts.get(&cap.sweep_id).and_then(|artifact| match artifact {
2300 Artifact::Sweep(sweep) => Some(sweep.path_id),
2301 _ => None,
2302 }),
2303 _ => None,
2304 }
2305 };
2306 let Some(first_surface_ref) = surface_blend_cmd.surfaces.first() else {
2307 internal_error!(range, "SurfaceBlend command has no surfaces: id={id:?}, cmd={cmd:?}");
2308 };
2309 let first_surface_id = ArtifactId::new(first_surface_ref.object_id);
2310 let path_id = surface_id_to_path_id(first_surface_id).unwrap_or(first_surface_id);
2311 let trajectory_id = surface_blend_cmd
2312 .surfaces
2313 .get(1)
2314 .map(|surface| ArtifactId::new(surface.object_id))
2315 .and_then(surface_id_to_path_id);
2316 let return_arr = vec![Artifact::Sweep(Sweep {
2317 id,
2318 sub_type: SweepSubType::Blend,
2319 path_id,
2320 surface_ids: Vec::new(),
2321 edge_ids: Vec::new(),
2322 code_ref,
2323 trajectory_id,
2324 method: kittycad_modeling_cmds::shared::ExtrudeMethod::New,
2325 consumed: false,
2326 pattern_ids: Vec::new(),
2327 })];
2328 return Ok(return_arr);
2329 }
2330 ModelingCmd::Loft(loft_cmd) => {
2331 let Some(OkModelingCmdResponse::Loft(_)) = response else {
2332 return Ok(Vec::new());
2333 };
2334 let mut return_arr = Vec::new();
2335 return_arr.push(Artifact::Sweep(Sweep {
2336 id,
2337 sub_type: SweepSubType::Loft,
2338 path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
2341 KclError::new_internal(KclErrorDetails::new(
2342 format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
2343 vec![range],
2344 ))
2345 })?),
2346 surface_ids: Vec::new(),
2347 edge_ids: Vec::new(),
2348 code_ref,
2349 trajectory_id: None,
2350 method: kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
2351 consumed: false,
2352 pattern_ids: Vec::new(),
2353 }));
2354 for section_id in &loft_cmd.section_ids {
2355 let path = artifacts.get(&ArtifactId::new(*section_id));
2356 if let Some(Artifact::Path(path)) = path {
2357 let mut new_path = path.clone();
2358 new_path.consumed = true;
2359 new_path.sweep_id = Some(id);
2360 return_arr.push(Artifact::Path(new_path));
2361 }
2362 }
2363 return Ok(return_arr);
2364 }
2365 ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
2366 let Some(OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info)) = response else {
2367 return Ok(Vec::new());
2368 };
2369 let mut return_arr = Vec::new();
2370 let mut last_path = None;
2371 for face in &face_info.faces {
2372 if face.cap != ExtrusionFaceCapType::None {
2373 continue;
2374 }
2375 let Some(curve_id) = face.curve_id.map(ArtifactId::new) else {
2376 continue;
2377 };
2378 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
2379 continue;
2380 };
2381 let Some(Artifact::Segment(seg)) = artifacts.get(&curve_id) else {
2382 continue;
2383 };
2384 let Some(Artifact::Path(path)) = artifacts.get(&seg.path_id) else {
2385 continue;
2386 };
2387 last_path = Some(path);
2388 let Some(path_sweep_id) = path.sweep_id else {
2389 if path.outer_path_id.is_some() {
2392 continue; }
2394 return Err(KclError::new_internal(KclErrorDetails::new(
2395 format!(
2396 "Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
2397 ),
2398 vec![range],
2399 )));
2400 };
2401 let extra_artifact = exec_artifacts.values().find(|a| {
2402 if let Artifact::StartSketchOnFace(s) = a {
2403 s.face_id == face_id
2404 } else if let Artifact::StartSketchOnPlane(s) = a {
2405 s.plane_id == face_id
2406 } else {
2407 false
2408 }
2409 });
2410 let sketch_on_face_code_ref = extra_artifact
2411 .and_then(|a| match a {
2412 Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
2413 Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
2414 _ => None,
2415 })
2416 .unwrap_or_default();
2418
2419 return_arr.push(Artifact::Wall(Wall {
2420 id: face_id,
2421 seg_id: curve_id,
2422 edge_cut_edge_ids: Vec::new(),
2423 sweep_id: path_sweep_id,
2424 path_ids: Vec::new(),
2425 face_code_ref: sketch_on_face_code_ref,
2426 cmd_id: artifact_command.cmd_id,
2427 }));
2428 let mut new_seg = seg.clone();
2429 new_seg.surface_id = Some(face_id);
2430 return_arr.push(Artifact::Segment(new_seg));
2431 if let Some(Artifact::Sweep(sweep)) = path.sweep_id.and_then(|id| artifacts.get(&id)) {
2432 let mut new_sweep = sweep.clone();
2433 new_sweep.surface_ids = vec![face_id];
2434 return_arr.push(Artifact::Sweep(new_sweep));
2435 }
2436 }
2437 if let Some(path) = last_path {
2438 for face in &face_info.faces {
2439 let sub_type = match face.cap {
2440 ExtrusionFaceCapType::Top => CapSubType::End,
2441 ExtrusionFaceCapType::Bottom => CapSubType::Start,
2442 ExtrusionFaceCapType::None | ExtrusionFaceCapType::Both => continue,
2443 _other => {
2444 continue;
2446 }
2447 };
2448 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
2449 continue;
2450 };
2451 let Some(path_sweep_id) = path.sweep_id else {
2452 if path.outer_path_id.is_some() {
2455 continue; }
2457 return Err(KclError::new_internal(KclErrorDetails::new(
2458 format!(
2459 "Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
2460 ),
2461 vec![range],
2462 )));
2463 };
2464 let extra_artifact = exec_artifacts.values().find(|a| {
2465 if let Artifact::StartSketchOnFace(s) = a {
2466 s.face_id == face_id
2467 } else if let Artifact::StartSketchOnPlane(s) = a {
2468 s.plane_id == face_id
2469 } else {
2470 false
2471 }
2472 });
2473 let sketch_on_face_code_ref = extra_artifact
2474 .and_then(|a| match a {
2475 Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
2476 Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
2477 _ => None,
2478 })
2479 .unwrap_or_default();
2481 return_arr.push(Artifact::Cap(Cap {
2482 id: face_id,
2483 sub_type,
2484 edge_cut_edge_ids: Vec::new(),
2485 sweep_id: path_sweep_id,
2486 path_ids: Vec::new(),
2487 face_code_ref: sketch_on_face_code_ref,
2488 cmd_id: artifact_command.cmd_id,
2489 }));
2490 let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
2491 continue;
2492 };
2493 let mut new_sweep = sweep.clone();
2494 new_sweep.surface_ids = vec![face_id];
2495 return_arr.push(Artifact::Sweep(new_sweep));
2496 }
2497 }
2498 return Ok(return_arr);
2499 }
2500 ModelingCmd::Solid3dGetAdjacencyInfo(kcmc::Solid3dGetAdjacencyInfo { .. }) => {
2501 let Some(OkModelingCmdResponse::Solid3dGetAdjacencyInfo(info)) = response else {
2502 return Ok(Vec::new());
2503 };
2504
2505 let mut return_arr = Vec::new();
2506 for (index, edge) in info.edges.iter().enumerate() {
2507 let Some(original_info) = &edge.original_info else {
2508 continue;
2509 };
2510 let edge_id = ArtifactId::new(original_info.edge_id);
2511 let Some(artifact) = artifacts.get(&edge_id) else {
2512 continue;
2513 };
2514 match artifact {
2515 Artifact::Segment(segment) => {
2516 let mut new_segment = segment.clone();
2517 new_segment.common_surface_ids =
2518 original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
2519 return_arr.push(Artifact::Segment(new_segment));
2520 }
2521 Artifact::SweepEdge(sweep_edge) => {
2522 let mut new_sweep_edge = sweep_edge.clone();
2523 new_sweep_edge.common_surface_ids =
2524 original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
2525 return_arr.push(Artifact::SweepEdge(new_sweep_edge));
2526 }
2527 _ => {}
2528 };
2529
2530 let Some(Artifact::Segment(segment)) = artifacts.get(&edge_id) else {
2531 continue;
2532 };
2533 let Some(surface_id) = segment.surface_id else {
2534 continue;
2535 };
2536 let Some(Artifact::Wall(wall)) = artifacts.get(&surface_id) else {
2537 continue;
2538 };
2539 let Some(Artifact::Sweep(sweep)) = artifacts.get(&wall.sweep_id) else {
2540 continue;
2541 };
2542 let Some(Artifact::Path(_)) = artifacts.get(&sweep.path_id) else {
2543 continue;
2544 };
2545
2546 if let Some(opposite_info) = &edge.opposite_info {
2547 return_arr.push(Artifact::SweepEdge(SweepEdge {
2548 id: opposite_info.edge_id.into(),
2549 sub_type: SweepEdgeSubType::Opposite,
2550 seg_id: edge_id,
2551 cmd_id: artifact_command.cmd_id,
2552 index,
2553 sweep_id: sweep.id,
2554 common_surface_ids: opposite_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
2555 }));
2556 let mut new_segment = segment.clone();
2557 new_segment.edge_ids = vec![opposite_info.edge_id.into()];
2558 return_arr.push(Artifact::Segment(new_segment));
2559 let mut new_sweep = sweep.clone();
2560 new_sweep.edge_ids = vec![opposite_info.edge_id.into()];
2561 return_arr.push(Artifact::Sweep(new_sweep));
2562 let mut new_wall = wall.clone();
2563 new_wall.edge_cut_edge_ids = vec![opposite_info.edge_id.into()];
2564 return_arr.push(Artifact::Wall(new_wall));
2565 }
2566 if let Some(adjacent_info) = &edge.adjacent_info {
2567 return_arr.push(Artifact::SweepEdge(SweepEdge {
2568 id: adjacent_info.edge_id.into(),
2569 sub_type: SweepEdgeSubType::Adjacent,
2570 seg_id: edge_id,
2571 cmd_id: artifact_command.cmd_id,
2572 index,
2573 sweep_id: sweep.id,
2574 common_surface_ids: adjacent_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
2575 }));
2576 let mut new_segment = segment.clone();
2577 new_segment.edge_ids = vec![adjacent_info.edge_id.into()];
2578 return_arr.push(Artifact::Segment(new_segment));
2579 let mut new_sweep = sweep.clone();
2580 new_sweep.edge_ids = vec![adjacent_info.edge_id.into()];
2581 return_arr.push(Artifact::Sweep(new_sweep));
2582 let mut new_wall = wall.clone();
2583 new_wall.edge_cut_edge_ids = vec![adjacent_info.edge_id.into()];
2584 return_arr.push(Artifact::Wall(new_wall));
2585 }
2586 }
2587 return Ok(return_arr);
2588 }
2589 ModelingCmd::Solid3dMultiJoin(cmd) => {
2590 let mut return_arr = Vec::new();
2591 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2592 id,
2593 consumed: false,
2594 sub_type: CompositeSolidSubType::Union,
2595 output_index: None,
2596 solid_ids: cmd.object_ids.iter().map(|id| id.into()).collect(),
2597 tool_ids: vec![],
2598 code_ref,
2599 composite_solid_id: None,
2600 pattern_ids: Vec::new(),
2601 }));
2602
2603 let solid_ids = cmd.object_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2604
2605 for input_id in &solid_ids {
2606 if let Some(artifact) = artifacts.get(input_id)
2607 && let Artifact::CompositeSolid(comp) = artifact
2608 {
2609 let mut new_comp = comp.clone();
2610 new_comp.composite_solid_id = Some(id);
2611 new_comp.consumed = true;
2612 return_arr.push(Artifact::CompositeSolid(new_comp));
2613 } else if let Some(Artifact::Sweep(sweep)) = artifacts.get(input_id) {
2614 let mut new_sweep = sweep.clone();
2615 new_sweep.consumed = true;
2616 return_arr.push(Artifact::Sweep(new_sweep));
2617 }
2618 }
2619 return Ok(return_arr);
2620 }
2621 ModelingCmd::Solid3dFilletEdge(cmd) => {
2622 let mut return_arr = Vec::new();
2623 let edge_id = if let Some(edge_id) = cmd.edge_id {
2624 ArtifactId::new(edge_id)
2625 } else {
2626 let Some(edge_id) = cmd.edge_ids.first() else {
2627 internal_error!(
2628 range,
2629 "Solid3dFilletEdge command has no edge ID: id={id:?}, cmd={cmd:?}"
2630 );
2631 };
2632 edge_id.into()
2633 };
2634 return_arr.push(Artifact::EdgeCut(EdgeCut {
2635 id,
2636 sub_type: cmd.cut_type.into(),
2637 consumed_edge_id: edge_id,
2638 edge_ids: Vec::new(),
2639 surface_id: None,
2640 code_ref,
2641 }));
2642 let consumed_edge = artifacts.get(&edge_id);
2643 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2644 let mut new_segment = consumed_edge.clone();
2645 new_segment.edge_cut_id = Some(id);
2646 return_arr.push(Artifact::Segment(new_segment));
2647 } else {
2648 }
2650 return Ok(return_arr);
2651 }
2652 ModelingCmd::Solid3dCutEdges(cmd) => {
2653 let mut return_arr = Vec::new();
2654 let edge_id = if let Some(edge_id) = cmd.edge_ids.first() {
2655 edge_id.into()
2656 } else {
2657 internal_error!(range, "Solid3dCutEdges command has no edge ID: id={id:?}, cmd={cmd:?}");
2658 };
2659 return_arr.push(Artifact::EdgeCut(EdgeCut {
2660 id,
2661 sub_type: cmd.cut_type.into(),
2662 consumed_edge_id: edge_id,
2663 edge_ids: Vec::new(),
2664 surface_id: None,
2665 code_ref,
2666 }));
2667 let consumed_edge = artifacts.get(&edge_id);
2668 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2669 let mut new_segment = consumed_edge.clone();
2670 new_segment.edge_cut_id = Some(id);
2671 return_arr.push(Artifact::Segment(new_segment));
2672 } else {
2673 }
2675 return Ok(return_arr);
2676 }
2677 ModelingCmd::EntityMakeHelix(cmd) => {
2678 let cylinder_id = ArtifactId::new(cmd.cylinder_id);
2679 let return_arr = vec![Artifact::Helix(Helix {
2680 id,
2681 axis_id: Some(cylinder_id),
2682 code_ref,
2683 trajectory_sweep_id: None,
2684 consumed: false,
2685 })];
2686 return Ok(return_arr);
2687 }
2688 ModelingCmd::EntityMakeHelixFromParams(_) => {
2689 let return_arr = vec![Artifact::Helix(Helix {
2690 id,
2691 axis_id: None,
2692 code_ref,
2693 trajectory_sweep_id: None,
2694 consumed: false,
2695 })];
2696 return Ok(return_arr);
2697 }
2698 ModelingCmd::EntityMakeHelixFromEdge(helix) => {
2699 let return_arr = vec![Artifact::Helix(Helix {
2700 id,
2701 axis_id: helix.edge_id.map(ArtifactId::new),
2702 code_ref,
2703 trajectory_sweep_id: None,
2704 consumed: false,
2705 })];
2706 return Ok(return_arr);
2709 }
2710 ModelingCmd::Solid2dAddHole(solid2d_add_hole) => {
2711 let mut return_arr = Vec::new();
2712 let outer_path = artifacts.get(&ArtifactId::new(solid2d_add_hole.object_id));
2714 if let Some(Artifact::Path(path)) = outer_path {
2715 let mut new_path = path.clone();
2716 new_path.inner_path_id = Some(ArtifactId::new(solid2d_add_hole.hole_id));
2717 return_arr.push(Artifact::Path(new_path));
2718 }
2719 let inner_solid2d = artifacts.get(&ArtifactId::new(solid2d_add_hole.hole_id));
2721 if let Some(Artifact::Path(path)) = inner_solid2d {
2722 let mut new_path = path.clone();
2723 new_path.consumed = true;
2724 new_path.outer_path_id = Some(ArtifactId::new(solid2d_add_hole.object_id));
2725 return_arr.push(Artifact::Path(new_path));
2726 }
2727 return Ok(return_arr);
2728 }
2729 ModelingCmd::BooleanIntersection(_) | ModelingCmd::BooleanSubtract(_) | ModelingCmd::BooleanUnion(_) => {
2730 let (sub_type, solid_ids, tool_ids) = match cmd {
2731 ModelingCmd::BooleanIntersection(intersection) => {
2732 let solid_ids = intersection
2733 .solid_ids
2734 .iter()
2735 .copied()
2736 .map(ArtifactId::new)
2737 .collect::<Vec<_>>();
2738 (CompositeSolidSubType::Intersect, solid_ids, Vec::new())
2739 }
2740 ModelingCmd::BooleanSubtract(subtract) => {
2741 let solid_ids = subtract
2742 .target_ids
2743 .iter()
2744 .copied()
2745 .map(ArtifactId::new)
2746 .collect::<Vec<_>>();
2747 let tool_ids = subtract
2748 .tool_ids
2749 .iter()
2750 .copied()
2751 .map(ArtifactId::new)
2752 .collect::<Vec<_>>();
2753 (CompositeSolidSubType::Subtract, solid_ids, tool_ids)
2754 }
2755 ModelingCmd::BooleanUnion(union) => {
2756 let solid_ids = union.solid_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2757 (CompositeSolidSubType::Union, solid_ids, Vec::new())
2758 }
2759 _ => internal_error!(
2760 range,
2761 "Boolean or composite command variant not handled: id={id:?}, cmd={cmd:?}"
2762 ),
2763 };
2764
2765 let mut new_solid_ids = vec![id];
2766
2767 let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2770
2771 match (cmd, response) {
2772 (
2773 ModelingCmd::BooleanSubtract(subtract_cmd),
2774 Some(OkModelingCmdResponse::BooleanSubtract(subtract_resp)),
2775 ) => {
2776 new_solid_ids = boolean_subtract_output_artifact_ids(
2777 id,
2778 &subtract_cmd.target_ids,
2779 &subtract_cmd.tool_ids,
2780 &subtract_resp.extra_solid_ids,
2781 );
2782 }
2783 (_, Some(OkModelingCmdResponse::BooleanIntersection(intersection))) => intersection
2784 .extra_solid_ids
2785 .iter()
2786 .copied()
2787 .map(ArtifactId::new)
2788 .filter(not_cmd_id)
2789 .for_each(|id| new_solid_ids.push(id)),
2790 (_, Some(OkModelingCmdResponse::BooleanUnion(union))) => union
2791 .extra_solid_ids
2792 .iter()
2793 .copied()
2794 .map(ArtifactId::new)
2795 .filter(not_cmd_id)
2796 .for_each(|id| new_solid_ids.push(id)),
2797 _ => {}
2798 }
2799
2800 let mut return_arr = Vec::new();
2801 let mut consumed_sweep_ids = AHashSet::default();
2802 let mut input_ids = solid_ids.clone();
2803 merge_ids(&mut input_ids, tool_ids.clone());
2804
2805 if new_solid_ids.is_empty() {
2806 update_csg_input_artifacts(&mut return_arr, artifacts, &input_ids, None, &mut consumed_sweep_ids);
2807 }
2808
2809 for solid_id in &new_solid_ids {
2811 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2813 id: *solid_id,
2814 consumed: false,
2815 sub_type,
2816 output_index: None,
2817 solid_ids: solid_ids.clone(),
2818 tool_ids: tool_ids.clone(),
2819 code_ref: code_ref.clone(),
2820 composite_solid_id: None,
2821 pattern_ids: Vec::new(),
2822 }));
2823
2824 update_csg_input_artifacts(
2825 &mut return_arr,
2826 artifacts,
2827 &input_ids,
2828 Some(*solid_id),
2829 &mut consumed_sweep_ids,
2830 );
2831 }
2832
2833 return Ok(return_arr);
2834 }
2835 ModelingCmd::BooleanImprint(imprint) => {
2836 let solid_ids = imprint
2837 .body_ids
2838 .iter()
2839 .copied()
2840 .map(ArtifactId::new)
2841 .collect::<Vec<_>>();
2842 let tool_ids = imprint
2843 .tool_ids
2844 .as_ref()
2845 .map(|ids| ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>())
2846 .unwrap_or_default();
2847
2848 let mut new_solid_ids = vec![id];
2849 let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2850 if let Some(OkModelingCmdResponse::BooleanImprint(imprint)) = response {
2851 imprint
2852 .extra_solid_ids
2853 .iter()
2854 .copied()
2855 .map(ArtifactId::new)
2856 .filter(not_cmd_id)
2857 .for_each(|id| new_solid_ids.push(id));
2858 }
2859
2860 let mut return_arr = Vec::new();
2861 let mut consumed_sweep_ids = AHashSet::default();
2862
2863 for input_id in solid_ids.iter().chain(tool_ids.iter()) {
2864 let sweep_id = match artifacts.get(input_id) {
2865 Some(Artifact::Sweep(sweep)) => Some(sweep.id),
2866 Some(Artifact::Path(path)) => path.sweep_id,
2867 _ => None,
2868 };
2869
2870 if let Some(sweep_id) = sweep_id
2871 && consumed_sweep_ids.insert(sweep_id)
2872 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
2873 {
2874 let mut new_sweep = sweep.clone();
2875 new_sweep.consumed = true;
2876 return_arr.push(Artifact::Sweep(new_sweep));
2877 }
2878 }
2879
2880 for (output_index, solid_id) in new_solid_ids.iter().enumerate() {
2881 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2882 id: *solid_id,
2883 consumed: false,
2884 sub_type: CompositeSolidSubType::Split,
2885 output_index: Some(output_index),
2886 solid_ids: solid_ids.clone(),
2887 tool_ids: tool_ids.clone(),
2888 code_ref: code_ref.clone(),
2889 composite_solid_id: None,
2890 pattern_ids: Vec::new(),
2891 }));
2892
2893 for input_id in solid_ids.iter().chain(tool_ids.iter()) {
2894 if let Some(artifact) = artifacts.get(input_id) {
2895 match artifact {
2896 Artifact::CompositeSolid(comp) => {
2897 let mut new_comp = comp.clone();
2898 new_comp.composite_solid_id = Some(*solid_id);
2899 new_comp.consumed = true;
2900 return_arr.push(Artifact::CompositeSolid(new_comp));
2901 }
2902 Artifact::Path(path) => {
2903 let mut new_path = path.clone();
2904 new_path.composite_solid_id = Some(*solid_id);
2905
2906 return_arr.push(Artifact::Path(new_path));
2907 }
2908 _ => {}
2909 }
2910 }
2911 }
2912 }
2913
2914 return Ok(return_arr);
2915 }
2916 _ => {}
2917 }
2918
2919 Ok(Vec::new())
2920}