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