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