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