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, ts_rs::TS)]
516#[ts(export_to = "Artifact.ts")]
517#[serde(rename_all = "camelCase")]
518pub struct Pattern {
519 pub id: ArtifactId,
520 pub sub_type: PatternSubType,
521 pub source_id: ArtifactId,
523 pub copy_ids: Vec<ArtifactId>,
525 pub copy_face_ids: Vec<ArtifactId>,
527 pub copy_edge_ids: Vec<ArtifactId>,
529 pub code_ref: CodeRef,
530}
531
532#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
533#[ts(export_to = "Artifact.ts")]
534#[serde(rename_all = "camelCase")]
535pub enum PatternSubType {
536 Circular,
537 Linear,
538 Transform,
539}
540
541#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
542#[ts(export_to = "Artifact.ts")]
543#[serde(tag = "type", rename_all = "camelCase")]
544#[expect(clippy::large_enum_variant)]
545pub enum Artifact {
546 CompositeSolid(CompositeSolid),
547 Plane(Plane),
548 Path(Path),
549 Segment(Segment),
550 Solid2d(Solid2d),
551 PrimitiveFace(PrimitiveFace),
552 PrimitiveEdge(PrimitiveEdge),
553 PlaneOfFace(PlaneOfFace),
554 StartSketchOnFace(StartSketchOnFace),
555 StartSketchOnPlane(StartSketchOnPlane),
556 SketchBlock(SketchBlock),
557 SketchBlockConstraint(SketchBlockConstraint),
558 Sweep(Sweep),
559 Wall(Wall),
560 Cap(Cap),
561 SweepEdge(SweepEdge),
562 EdgeCut(EdgeCut),
563 EdgeCutEdge(EdgeCutEdge),
564 Helix(Helix),
565 Pattern(Pattern),
566}
567
568impl Artifact {
569 pub(crate) fn id(&self) -> ArtifactId {
570 match self {
571 Artifact::CompositeSolid(a) => a.id,
572 Artifact::Plane(a) => a.id,
573 Artifact::Path(a) => a.id,
574 Artifact::Segment(a) => a.id,
575 Artifact::Solid2d(a) => a.id,
576 Artifact::PrimitiveFace(a) => a.id,
577 Artifact::PrimitiveEdge(a) => a.id,
578 Artifact::StartSketchOnFace(a) => a.id,
579 Artifact::StartSketchOnPlane(a) => a.id,
580 Artifact::SketchBlock(a) => a.id,
581 Artifact::SketchBlockConstraint(a) => a.id,
582 Artifact::PlaneOfFace(a) => a.id,
583 Artifact::Sweep(a) => a.id,
584 Artifact::Wall(a) => a.id,
585 Artifact::Cap(a) => a.id,
586 Artifact::SweepEdge(a) => a.id,
587 Artifact::EdgeCut(a) => a.id,
588 Artifact::EdgeCutEdge(a) => a.id,
589 Artifact::Helix(a) => a.id,
590 Artifact::Pattern(a) => a.id,
591 }
592 }
593
594 pub fn code_ref(&self) -> Option<&CodeRef> {
597 match self {
598 Artifact::CompositeSolid(a) => Some(&a.code_ref),
599 Artifact::Plane(a) => Some(&a.code_ref),
600 Artifact::Path(a) => Some(&a.code_ref),
601 Artifact::Segment(a) => Some(&a.code_ref),
602 Artifact::Solid2d(_) => None,
603 Artifact::PrimitiveFace(a) => Some(&a.code_ref),
604 Artifact::PrimitiveEdge(a) => Some(&a.code_ref),
605 Artifact::StartSketchOnFace(a) => Some(&a.code_ref),
606 Artifact::StartSketchOnPlane(a) => Some(&a.code_ref),
607 Artifact::SketchBlock(a) => Some(&a.code_ref),
608 Artifact::SketchBlockConstraint(a) => Some(&a.code_ref),
609 Artifact::PlaneOfFace(a) => Some(&a.code_ref),
610 Artifact::Sweep(a) => Some(&a.code_ref),
611 Artifact::Wall(_) => None,
612 Artifact::Cap(_) => None,
613 Artifact::SweepEdge(_) => None,
614 Artifact::EdgeCut(a) => Some(&a.code_ref),
615 Artifact::EdgeCutEdge(_) => None,
616 Artifact::Helix(a) => Some(&a.code_ref),
617 Artifact::Pattern(a) => Some(&a.code_ref),
618 }
619 }
620
621 pub fn face_code_ref(&self) -> Option<&CodeRef> {
624 match self {
625 Artifact::CompositeSolid(_)
626 | Artifact::Plane(_)
627 | Artifact::Path(_)
628 | Artifact::Segment(_)
629 | Artifact::Solid2d(_)
630 | Artifact::PrimitiveEdge(_)
631 | Artifact::StartSketchOnFace(_)
632 | Artifact::PlaneOfFace(_)
633 | Artifact::StartSketchOnPlane(_)
634 | Artifact::SketchBlock(_)
635 | Artifact::SketchBlockConstraint(_)
636 | Artifact::Sweep(_) => None,
637 Artifact::PrimitiveFace(a) => Some(&a.code_ref),
638 Artifact::Wall(a) => Some(&a.face_code_ref),
639 Artifact::Cap(a) => Some(&a.face_code_ref),
640 Artifact::SweepEdge(_)
641 | Artifact::EdgeCut(_)
642 | Artifact::EdgeCutEdge(_)
643 | Artifact::Helix(_)
644 | Artifact::Pattern(_) => None,
645 }
646 }
647
648 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
651 match self {
652 Artifact::CompositeSolid(a) => a.merge(new),
653 Artifact::Plane(a) => a.merge(new),
654 Artifact::Path(a) => a.merge(new),
655 Artifact::Segment(a) => a.merge(new),
656 Artifact::Solid2d(_) => Some(new),
657 Artifact::PrimitiveFace(_) => Some(new),
658 Artifact::PrimitiveEdge(_) => Some(new),
659 Artifact::StartSketchOnFace { .. } => Some(new),
660 Artifact::StartSketchOnPlane { .. } => Some(new),
661 Artifact::SketchBlock { .. } => Some(new),
662 Artifact::SketchBlockConstraint { .. } => Some(new),
663 Artifact::PlaneOfFace { .. } => Some(new),
664 Artifact::Sweep(a) => a.merge(new),
665 Artifact::Wall(a) => a.merge(new),
666 Artifact::Cap(a) => a.merge(new),
667 Artifact::SweepEdge(_) => Some(new),
668 Artifact::EdgeCut(a) => a.merge(new),
669 Artifact::EdgeCutEdge(_) => Some(new),
670 Artifact::Helix(a) => a.merge(new),
671 Artifact::Pattern(a) => a.merge(new),
672 }
673 }
674}
675
676impl CompositeSolid {
677 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
678 let Artifact::CompositeSolid(new) = new else {
679 return Some(new);
680 };
681 merge_ids(&mut self.solid_ids, new.solid_ids);
682 merge_ids(&mut self.tool_ids, new.tool_ids);
683 merge_opt_id(&mut self.composite_solid_id, new.composite_solid_id);
684 merge_ids(&mut self.pattern_ids, new.pattern_ids);
685 self.output_index = new.output_index;
686 self.consumed = new.consumed;
687
688 None
689 }
690}
691
692impl Plane {
693 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
694 let Artifact::Plane(new) = new else {
695 return Some(new);
696 };
697 merge_ids(&mut self.path_ids, new.path_ids);
698
699 None
700 }
701}
702
703impl Path {
704 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
705 let Artifact::Path(new) = new else {
706 return Some(new);
707 };
708 merge_opt_id(&mut self.sweep_id, new.sweep_id);
709 merge_opt_id(&mut self.trajectory_sweep_id, new.trajectory_sweep_id);
710 merge_ids(&mut self.seg_ids, new.seg_ids);
711 merge_opt_id(&mut self.solid2d_id, new.solid2d_id);
712 merge_opt_id(&mut self.composite_solid_id, new.composite_solid_id);
713 merge_opt_id(&mut self.sketch_block_id, new.sketch_block_id);
714 merge_opt_id(&mut self.origin_path_id, new.origin_path_id);
715 merge_opt_id(&mut self.inner_path_id, new.inner_path_id);
716 merge_opt_id(&mut self.outer_path_id, new.outer_path_id);
717 merge_ids(&mut self.pattern_ids, new.pattern_ids);
718 self.consumed = new.consumed;
719
720 None
721 }
722}
723
724impl Segment {
725 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
726 let Artifact::Segment(new) = new else {
727 return Some(new);
728 };
729 merge_opt_id(&mut self.original_seg_id, new.original_seg_id);
730 merge_opt_id(&mut self.surface_id, new.surface_id);
731 merge_ids(&mut self.edge_ids, new.edge_ids);
732 merge_opt_id(&mut self.edge_cut_id, new.edge_cut_id);
733 merge_ids(&mut self.common_surface_ids, new.common_surface_ids);
734
735 None
736 }
737}
738
739impl Sweep {
740 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
741 let Artifact::Sweep(new) = new else {
742 return Some(new);
743 };
744 merge_ids(&mut self.surface_ids, new.surface_ids);
745 merge_ids(&mut self.edge_ids, new.edge_ids);
746 merge_opt_id(&mut self.trajectory_id, new.trajectory_id);
747 merge_ids(&mut self.pattern_ids, new.pattern_ids);
748 self.consumed = new.consumed;
749
750 None
751 }
752}
753
754impl Wall {
755 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
756 let Artifact::Wall(new) = new else {
757 return Some(new);
758 };
759 merge_ids(&mut self.edge_cut_edge_ids, new.edge_cut_edge_ids);
760 merge_ids(&mut self.path_ids, new.path_ids);
761
762 None
763 }
764}
765
766impl Cap {
767 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
768 let Artifact::Cap(new) = new else {
769 return Some(new);
770 };
771 merge_ids(&mut self.edge_cut_edge_ids, new.edge_cut_edge_ids);
772 merge_ids(&mut self.path_ids, new.path_ids);
773
774 None
775 }
776}
777
778impl EdgeCut {
779 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
780 let Artifact::EdgeCut(new) = new else {
781 return Some(new);
782 };
783 merge_opt_id(&mut self.surface_id, new.surface_id);
784 merge_ids(&mut self.edge_ids, new.edge_ids);
785
786 None
787 }
788}
789
790impl Helix {
791 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
792 let Artifact::Helix(new) = new else {
793 return Some(new);
794 };
795 merge_opt_id(&mut self.axis_id, new.axis_id);
796 merge_opt_id(&mut self.trajectory_sweep_id, new.trajectory_sweep_id);
797 self.consumed = new.consumed;
798
799 None
800 }
801}
802
803impl Pattern {
804 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
805 let Artifact::Pattern(new) = new else {
806 return Some(new);
807 };
808 merge_ids(&mut self.copy_ids, new.copy_ids);
809 merge_ids(&mut self.copy_face_ids, new.copy_face_ids);
810 merge_ids(&mut self.copy_edge_ids, new.copy_edge_ids);
811
812 None
813 }
814}
815
816#[derive(Debug, Clone, Default, PartialEq, Serialize, ts_rs::TS)]
817#[ts(export_to = "Artifact.ts")]
818#[serde(rename_all = "camelCase")]
819pub struct ArtifactGraph {
820 map: IndexMap<ArtifactId, Artifact>,
821 pub(super) item_count: usize,
822}
823
824impl ArtifactGraph {
825 pub fn get(&self, id: &ArtifactId) -> Option<&Artifact> {
826 self.map.get(id)
827 }
828
829 pub fn len(&self) -> usize {
830 self.map.len()
831 }
832
833 pub fn is_empty(&self) -> bool {
834 self.map.is_empty()
835 }
836
837 #[cfg(test)]
838 pub(crate) fn iter(&self) -> impl Iterator<Item = (&ArtifactId, &Artifact)> {
839 self.map.iter()
840 }
841
842 pub fn values(&self) -> impl Iterator<Item = &Artifact> {
843 self.map.values()
844 }
845
846 pub fn clear(&mut self) {
847 self.map.clear();
848 self.item_count = 0;
849 }
850
851 fn into_map(self) -> IndexMap<ArtifactId, Artifact> {
853 self.map
854 }
855}
856
857#[derive(Debug, Clone)]
858struct ImportCodeRef {
859 node_path: NodePath,
860 range: SourceRange,
861}
862
863fn import_statement_code_refs(
864 ast: &Node<Program>,
865 module_infos: &ModuleInfoMap,
866 programs: &crate::execution::ProgramLookup,
867 cached_body_items: usize,
868) -> FnvHashMap<ModuleId, ImportCodeRef> {
869 let mut code_refs = FnvHashMap::default();
870 for body_item in &ast.body {
871 let BodyItem::ImportStatement(import_stmt) = body_item else {
872 continue;
873 };
874 if !matches!(import_stmt.selector, ImportSelector::None { .. }) {
875 continue;
876 }
877 let Some(module_id) = module_id_for_import_path(module_infos, &import_stmt.path) else {
878 continue;
879 };
880 let range = SourceRange::from(import_stmt);
881 let node_path = NodePath::from_range(programs, cached_body_items, range).unwrap_or_default();
882 code_refs.entry(module_id).or_insert(ImportCodeRef { node_path, range });
883 }
884 code_refs
885}
886
887fn module_id_for_import_path(module_infos: &ModuleInfoMap, import_path: &ImportPath) -> Option<ModuleId> {
888 let import_path = match import_path {
889 ImportPath::Kcl { filename } => filename,
890 ImportPath::Foreign { path } => path,
891 ImportPath::Std { .. } => return None,
892 };
893
894 module_infos.iter().find_map(|(module_id, module_info)| {
895 if let ModulePath::Local {
896 original_import_path: Some(original_import_path),
897 ..
898 } = &module_info.path
899 && original_import_path == import_path
900 {
901 return Some(*module_id);
902 }
903 None
904 })
905}
906
907fn code_ref_for_range(
908 programs: &crate::execution::ProgramLookup,
909 cached_body_items: usize,
910 range: SourceRange,
911 import_code_refs: &FnvHashMap<ModuleId, ImportCodeRef>,
912) -> (SourceRange, NodePath) {
913 if let Some(code_ref) = import_code_refs.get(&range.module_id()) {
914 return (code_ref.range, code_ref.node_path.clone());
915 }
916
917 (
918 range,
919 NodePath::from_range(programs, cached_body_items, range).unwrap_or_default(),
920 )
921}
922
923pub(super) fn build_artifact_graph(
927 artifact_commands: &[ArtifactCommand],
928 responses: &IndexMap<Uuid, WebSocketResponse>,
929 ast: &Node<Program>,
930 exec_artifacts: &mut IndexMap<ArtifactId, Artifact>,
931 initial_graph: ArtifactGraph,
932 programs: &crate::execution::ProgramLookup,
933 module_infos: &ModuleInfoMap,
934) -> Result<ArtifactGraph, KclError> {
935 let item_count = initial_graph.item_count;
936 let mut map = initial_graph.into_map();
937
938 let mut path_to_plane_id_map = FnvHashMap::default();
939 let mut current_plane_id = None;
940 let import_code_refs = import_statement_code_refs(ast, module_infos, programs, item_count);
941 let flattened_responses = flatten_modeling_command_responses(responses);
942 let entity_clone_id_maps = build_entity_clone_id_maps(artifact_commands, &flattened_responses);
943
944 for exec_artifact in exec_artifacts.values_mut() {
947 fill_in_node_paths(exec_artifact, programs, item_count, &import_code_refs);
950 }
951
952 for artifact_command in artifact_commands {
953 if let ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) = artifact_command.command {
954 current_plane_id = Some(entity_id);
955 }
956 if let ModelingCmd::StartPath(_) = artifact_command.command
961 && let Some(plane_id) = current_plane_id
962 {
963 path_to_plane_id_map.insert(artifact_command.cmd_id, plane_id);
964 }
965 if let ModelingCmd::SketchModeDisable(_) = artifact_command.command {
966 current_plane_id = None;
967 }
968
969 let artifact_updates = artifacts_to_update(
970 &map,
971 artifact_command,
972 &flattened_responses,
973 &entity_clone_id_maps,
974 &path_to_plane_id_map,
975 programs,
976 item_count,
977 exec_artifacts,
978 &import_code_refs,
979 )?;
980 for artifact in artifact_updates {
981 merge_artifact_into_map(&mut map, artifact);
983 }
984 }
985
986 for exec_artifact in exec_artifacts.values() {
987 merge_artifact_into_map(&mut map, exec_artifact.clone());
988 }
989
990 Ok(ArtifactGraph {
991 map,
992 item_count: item_count + ast.body.len(),
993 })
994}
995
996fn fill_in_node_paths(
999 artifact: &mut Artifact,
1000 programs: &crate::execution::ProgramLookup,
1001 cached_body_items: usize,
1002 import_code_refs: &FnvHashMap<ModuleId, ImportCodeRef>,
1003) {
1004 match artifact {
1005 Artifact::StartSketchOnFace(face) if face.code_ref.node_path.is_empty() => {
1006 let (range, node_path) =
1007 code_ref_for_range(programs, cached_body_items, face.code_ref.range, import_code_refs);
1008 face.code_ref.range = range;
1009 face.code_ref.node_path = node_path;
1010 }
1011 Artifact::StartSketchOnPlane(plane) if plane.code_ref.node_path.is_empty() => {
1012 let (range, node_path) =
1013 code_ref_for_range(programs, cached_body_items, plane.code_ref.range, import_code_refs);
1014 plane.code_ref.range = range;
1015 plane.code_ref.node_path = node_path;
1016 }
1017 Artifact::SketchBlock(block) if block.code_ref.node_path.is_empty() => {
1018 let (range, node_path) =
1019 code_ref_for_range(programs, cached_body_items, block.code_ref.range, import_code_refs);
1020 block.code_ref.range = range;
1021 block.code_ref.node_path = node_path;
1022 }
1023 Artifact::SketchBlockConstraint(constraint) if constraint.code_ref.node_path.is_empty() => {
1024 constraint.code_ref.node_path =
1025 NodePath::from_range(programs, cached_body_items, constraint.code_ref.range).unwrap_or_default();
1026 }
1027 _ => {}
1028 }
1029}
1030
1031fn flatten_modeling_command_responses(
1034 responses: &IndexMap<Uuid, WebSocketResponse>,
1035) -> FnvHashMap<Uuid, OkModelingCmdResponse> {
1036 let mut map = FnvHashMap::default();
1037 for (cmd_id, ws_response) in responses {
1038 let WebSocketResponse::Success(response) = ws_response else {
1039 continue;
1041 };
1042 match &response.resp {
1043 OkWebSocketResponseData::Modeling { modeling_response } => {
1044 map.insert(*cmd_id, modeling_response.clone());
1045 }
1046 OkWebSocketResponseData::ModelingBatch { responses } =>
1047 {
1048 #[expect(
1049 clippy::iter_over_hash_type,
1050 reason = "Since we're moving entries to another unordered map, it's fine that the order is undefined"
1051 )]
1052 for (cmd_id, batch_response) in responses {
1053 if let BatchResponse::Success {
1054 response: modeling_response,
1055 } = batch_response
1056 {
1057 map.insert(*cmd_id.as_ref(), modeling_response.clone());
1058 }
1059 }
1060 }
1061 OkWebSocketResponseData::IceServerInfo { .. }
1062 | OkWebSocketResponseData::TrickleIce { .. }
1063 | OkWebSocketResponseData::SdpAnswer { .. }
1064 | OkWebSocketResponseData::Export { .. }
1065 | OkWebSocketResponseData::MetricsRequest { .. }
1066 | OkWebSocketResponseData::ModelingSessionData { .. }
1067 | OkWebSocketResponseData::Debug { .. }
1068 | OkWebSocketResponseData::Pong { .. } => {}
1069 _other => {}
1070 }
1071 }
1072
1073 map
1074}
1075
1076#[derive(Debug, Clone)]
1077struct PendingEntityCloneMapping {
1078 clone_cmd_id: Uuid,
1079 old_entity_id: Uuid,
1080 old_child_ids: Option<Vec<Uuid>>,
1081}
1082
1083fn build_entity_clone_id_maps(
1086 artifact_commands: &[ArtifactCommand],
1087 responses: &FnvHashMap<Uuid, OkModelingCmdResponse>,
1088) -> FnvHashMap<Uuid, FnvHashMap<ArtifactId, ArtifactId>> {
1089 let mut clone_id_maps = FnvHashMap::default();
1090 let mut pending = Vec::new();
1091
1092 for artifact_command in artifact_commands {
1093 match &artifact_command.command {
1094 ModelingCmd::EntityClone(kcmc::EntityClone { entity_id, .. }) => {
1095 pending.push(PendingEntityCloneMapping {
1096 clone_cmd_id: artifact_command.cmd_id,
1097 old_entity_id: *entity_id,
1098 old_child_ids: None,
1099 });
1100 }
1101 ModelingCmd::EntityGetAllChildUuids(kcmc::EntityGetAllChildUuids { entity_id, .. }) => {
1102 let Some(OkModelingCmdResponse::EntityGetAllChildUuids(child_ids_response)) =
1103 responses.get(&artifact_command.cmd_id)
1104 else {
1105 continue;
1106 };
1107 let child_ids = child_ids_response.entity_ids.clone();
1108
1109 let mut completed_index = None;
1110 for index in (0..pending.len()).rev() {
1111 let pending_map = &mut pending[index];
1112 if pending_map.old_child_ids.is_none() && *entity_id == pending_map.old_entity_id {
1113 pending_map.old_child_ids = Some(child_ids.clone());
1114 break;
1115 }
1116 if let Some(old_child_ids) = &pending_map.old_child_ids
1117 && *entity_id == pending_map.clone_cmd_id
1118 {
1119 let mut id_map = FnvHashMap::default();
1120 id_map.insert(
1121 ArtifactId::new(pending_map.old_entity_id),
1122 ArtifactId::new(pending_map.clone_cmd_id),
1123 );
1124 for (old_id, new_id) in old_child_ids.iter().zip(child_ids.iter()) {
1125 id_map.insert(ArtifactId::new(*old_id), ArtifactId::new(*new_id));
1126 }
1127 clone_id_maps.insert(pending_map.clone_cmd_id, id_map);
1128 completed_index = Some(index);
1129 break;
1130 }
1131 }
1132
1133 if let Some(index) = completed_index {
1134 pending.swap_remove(index);
1135 }
1136 }
1137 _ => {}
1138 }
1139 }
1140
1141 clone_id_maps
1142}
1143
1144fn merge_artifact_into_map(map: &mut IndexMap<ArtifactId, Artifact>, new_artifact: Artifact) {
1145 fn is_primitive_artifact(artifact: &Artifact) -> bool {
1146 matches!(artifact, Artifact::PrimitiveFace(_) | Artifact::PrimitiveEdge(_))
1147 }
1148
1149 let id = new_artifact.id();
1150 let Some(old_artifact) = map.get_mut(&id) else {
1151 map.insert(id, new_artifact);
1153 return;
1154 };
1155
1156 if is_primitive_artifact(&new_artifact) && !is_primitive_artifact(old_artifact) {
1160 return;
1161 }
1162
1163 if let Some(replacement) = old_artifact.merge(new_artifact) {
1164 *old_artifact = replacement;
1165 }
1166}
1167
1168fn merge_ids(base: &mut Vec<ArtifactId>, new: Vec<ArtifactId>) {
1172 let original_len = base.len();
1173 for id in new {
1174 let original_base = &base[..original_len];
1176 if !original_base.contains(&id) {
1177 base.push(id);
1178 }
1179 }
1180}
1181
1182fn merge_opt_id(base: &mut Option<ArtifactId>, new: Option<ArtifactId>) {
1184 *base = new;
1186}
1187
1188fn remap_id_for_clone(id: ArtifactId, entity_id_map: &FnvHashMap<ArtifactId, ArtifactId>) -> ArtifactId {
1189 entity_id_map.get(&id).copied().unwrap_or(id)
1190}
1191
1192fn remap_opt_id_for_clone(
1193 id: Option<ArtifactId>,
1194 entity_id_map: &FnvHashMap<ArtifactId, ArtifactId>,
1195) -> Option<ArtifactId> {
1196 id.map(|id| remap_id_for_clone(id, entity_id_map))
1197}
1198
1199fn remap_ids_for_clone(ids: &[ArtifactId], entity_id_map: &FnvHashMap<ArtifactId, ArtifactId>) -> Vec<ArtifactId> {
1200 ids.iter()
1201 .copied()
1202 .map(|id| remap_id_for_clone(id, entity_id_map))
1203 .collect()
1204}
1205
1206fn remap_mapped_ids_for_clone(
1207 ids: &[ArtifactId],
1208 entity_id_map: &FnvHashMap<ArtifactId, ArtifactId>,
1209) -> Vec<ArtifactId> {
1210 ids.iter().filter_map(|id| entity_id_map.get(id).copied()).collect()
1211}
1212
1213fn remap_artifact_for_clone(
1214 artifact: &Artifact,
1215 entity_id_map: &FnvHashMap<ArtifactId, ArtifactId>,
1216 clone_code_ref: &CodeRef,
1217 clone_cmd_id: Uuid,
1218 source_root_id: ArtifactId,
1219) -> Artifact {
1220 match artifact {
1221 Artifact::CompositeSolid(source) => Artifact::CompositeSolid(CompositeSolid {
1222 id: remap_id_for_clone(source.id, entity_id_map),
1223 consumed: if source.id == source_root_id {
1224 false
1225 } else {
1226 source.consumed
1227 },
1228 sub_type: source.sub_type,
1229 output_index: source.output_index,
1230 solid_ids: remap_ids_for_clone(&source.solid_ids, entity_id_map),
1231 tool_ids: remap_ids_for_clone(&source.tool_ids, entity_id_map),
1232 pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
1233 code_ref: clone_code_ref.clone(),
1234 composite_solid_id: remap_opt_id_for_clone(source.composite_solid_id, entity_id_map),
1235 }),
1236 Artifact::Plane(source) => Artifact::Plane(Plane {
1237 id: remap_id_for_clone(source.id, entity_id_map),
1238 path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
1239 code_ref: clone_code_ref.clone(),
1240 }),
1241 Artifact::Path(source) => Artifact::Path(Path {
1242 id: remap_id_for_clone(source.id, entity_id_map),
1243 sub_type: source.sub_type,
1244 plane_id: remap_id_for_clone(source.plane_id, entity_id_map),
1245 seg_ids: remap_ids_for_clone(&source.seg_ids, entity_id_map),
1246 consumed: if source.id == source_root_id {
1247 false
1248 } else {
1249 source.consumed
1250 },
1251 sweep_id: remap_opt_id_for_clone(source.sweep_id, entity_id_map),
1252 trajectory_sweep_id: remap_opt_id_for_clone(source.trajectory_sweep_id, entity_id_map),
1253 solid2d_id: remap_opt_id_for_clone(source.solid2d_id, entity_id_map),
1254 code_ref: clone_code_ref.clone(),
1255 composite_solid_id: remap_opt_id_for_clone(source.composite_solid_id, entity_id_map),
1256 sketch_block_id: remap_opt_id_for_clone(source.sketch_block_id, entity_id_map),
1257 origin_path_id: remap_opt_id_for_clone(source.origin_path_id, entity_id_map),
1258 inner_path_id: remap_opt_id_for_clone(source.inner_path_id, entity_id_map),
1259 outer_path_id: remap_opt_id_for_clone(source.outer_path_id, entity_id_map),
1260 pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
1261 }),
1262 Artifact::Segment(source) => Artifact::Segment(Segment {
1263 id: remap_id_for_clone(source.id, entity_id_map),
1264 path_id: remap_id_for_clone(source.path_id, entity_id_map),
1265 original_seg_id: remap_opt_id_for_clone(source.original_seg_id, entity_id_map),
1266 surface_id: remap_opt_id_for_clone(source.surface_id, entity_id_map),
1267 edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
1268 edge_cut_id: remap_opt_id_for_clone(source.edge_cut_id, entity_id_map),
1269 code_ref: clone_code_ref.clone(),
1270 common_surface_ids: remap_ids_for_clone(&source.common_surface_ids, entity_id_map),
1271 }),
1272 Artifact::Solid2d(source) => Artifact::Solid2d(Solid2d {
1273 id: remap_id_for_clone(source.id, entity_id_map),
1274 path_id: remap_id_for_clone(source.path_id, entity_id_map),
1275 }),
1276 Artifact::PrimitiveFace(source) => Artifact::PrimitiveFace(PrimitiveFace {
1277 id: remap_id_for_clone(source.id, entity_id_map),
1278 solid_id: remap_id_for_clone(source.solid_id, entity_id_map),
1279 code_ref: clone_code_ref.clone(),
1280 }),
1281 Artifact::PrimitiveEdge(source) => Artifact::PrimitiveEdge(PrimitiveEdge {
1282 id: remap_id_for_clone(source.id, entity_id_map),
1283 solid_id: remap_id_for_clone(source.solid_id, entity_id_map),
1284 code_ref: clone_code_ref.clone(),
1285 }),
1286 Artifact::PlaneOfFace(source) => Artifact::PlaneOfFace(PlaneOfFace {
1287 id: remap_id_for_clone(source.id, entity_id_map),
1288 face_id: remap_id_for_clone(source.face_id, entity_id_map),
1289 code_ref: clone_code_ref.clone(),
1290 }),
1291 Artifact::StartSketchOnFace(source) => Artifact::StartSketchOnFace(StartSketchOnFace {
1292 id: remap_id_for_clone(source.id, entity_id_map),
1293 face_id: remap_id_for_clone(source.face_id, entity_id_map),
1294 code_ref: clone_code_ref.clone(),
1295 }),
1296 Artifact::StartSketchOnPlane(source) => Artifact::StartSketchOnPlane(StartSketchOnPlane {
1297 id: remap_id_for_clone(source.id, entity_id_map),
1298 plane_id: remap_id_for_clone(source.plane_id, entity_id_map),
1299 code_ref: clone_code_ref.clone(),
1300 }),
1301 Artifact::SketchBlock(source) => Artifact::SketchBlock(SketchBlock {
1302 id: remap_id_for_clone(source.id, entity_id_map),
1303 standard_plane: source.standard_plane,
1304 plane_id: remap_opt_id_for_clone(source.plane_id, entity_id_map),
1305 path_id: remap_opt_id_for_clone(source.path_id, entity_id_map),
1306 code_ref: clone_code_ref.clone(),
1307 sketch_id: source.sketch_id,
1308 }),
1309 Artifact::SketchBlockConstraint(source) => Artifact::SketchBlockConstraint(SketchBlockConstraint {
1310 id: remap_id_for_clone(source.id, entity_id_map),
1311 sketch_id: source.sketch_id,
1312 constraint_id: source.constraint_id,
1313 constraint_type: source.constraint_type,
1314 code_ref: clone_code_ref.clone(),
1315 }),
1316 Artifact::Sweep(source) => Artifact::Sweep(Sweep {
1317 id: remap_id_for_clone(source.id, entity_id_map),
1318 sub_type: source.sub_type,
1319 path_id: remap_id_for_clone(source.path_id, entity_id_map),
1320 surface_ids: remap_ids_for_clone(&source.surface_ids, entity_id_map),
1321 edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
1322 code_ref: clone_code_ref.clone(),
1323 trajectory_id: remap_opt_id_for_clone(source.trajectory_id, entity_id_map),
1324 method: source.method,
1325 consumed: if source.id == source_root_id {
1326 false
1327 } else {
1328 source.consumed
1329 },
1330 pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
1331 }),
1332 Artifact::Wall(source) => Artifact::Wall(Wall {
1333 id: remap_id_for_clone(source.id, entity_id_map),
1334 seg_id: remap_id_for_clone(source.seg_id, entity_id_map),
1335 edge_cut_edge_ids: remap_ids_for_clone(&source.edge_cut_edge_ids, entity_id_map),
1336 sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
1337 path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
1338 face_code_ref: source.face_code_ref.clone(),
1339 cmd_id: clone_cmd_id,
1340 }),
1341 Artifact::Cap(source) => Artifact::Cap(Cap {
1342 id: remap_id_for_clone(source.id, entity_id_map),
1343 sub_type: source.sub_type,
1344 edge_cut_edge_ids: remap_ids_for_clone(&source.edge_cut_edge_ids, entity_id_map),
1345 sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
1346 path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
1347 face_code_ref: source.face_code_ref.clone(),
1348 cmd_id: clone_cmd_id,
1349 }),
1350 Artifact::SweepEdge(source) => Artifact::SweepEdge(SweepEdge {
1351 id: remap_id_for_clone(source.id, entity_id_map),
1352 sub_type: source.sub_type,
1353 seg_id: remap_id_for_clone(source.seg_id, entity_id_map),
1354 cmd_id: clone_cmd_id,
1355 index: source.index,
1356 sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
1357 common_surface_ids: remap_ids_for_clone(&source.common_surface_ids, entity_id_map),
1358 }),
1359 Artifact::EdgeCut(source) => Artifact::EdgeCut(EdgeCut {
1360 id: remap_id_for_clone(source.id, entity_id_map),
1361 sub_type: source.sub_type,
1362 consumed_edge_id: remap_id_for_clone(source.consumed_edge_id, entity_id_map),
1363 edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
1364 surface_id: remap_opt_id_for_clone(source.surface_id, entity_id_map),
1365 code_ref: clone_code_ref.clone(),
1366 }),
1367 Artifact::EdgeCutEdge(source) => Artifact::EdgeCutEdge(EdgeCutEdge {
1368 id: remap_id_for_clone(source.id, entity_id_map),
1369 edge_cut_id: remap_id_for_clone(source.edge_cut_id, entity_id_map),
1370 surface_id: remap_id_for_clone(source.surface_id, entity_id_map),
1371 }),
1372 Artifact::Helix(source) => Artifact::Helix(Helix {
1373 id: remap_id_for_clone(source.id, entity_id_map),
1374 axis_id: remap_opt_id_for_clone(source.axis_id, entity_id_map),
1375 code_ref: clone_code_ref.clone(),
1376 trajectory_sweep_id: remap_opt_id_for_clone(source.trajectory_sweep_id, entity_id_map),
1377 consumed: if source.id == source_root_id {
1378 false
1379 } else {
1380 source.consumed
1381 },
1382 }),
1383 Artifact::Pattern(source) => Artifact::Pattern(Pattern {
1384 id: remap_id_for_clone(source.id, entity_id_map),
1385 sub_type: source.sub_type,
1386 source_id: remap_id_for_clone(source.source_id, entity_id_map),
1387 copy_ids: remap_ids_for_clone(&source.copy_ids, entity_id_map),
1388 copy_face_ids: remap_ids_for_clone(&source.copy_face_ids, entity_id_map),
1389 copy_edge_ids: remap_ids_for_clone(&source.copy_edge_ids, entity_id_map),
1390 code_ref: clone_code_ref.clone(),
1391 }),
1392 }
1393}
1394
1395fn pattern_source_ids(artifacts: &IndexMap<ArtifactId, Artifact>, source_id: ArtifactId) -> Vec<ArtifactId> {
1396 let mut source_ids = vec![source_id];
1397
1398 if let Some(Artifact::Path(path)) = artifacts.get(&source_id) {
1399 if let Some(sweep_id) = path.sweep_id {
1400 source_ids.push(sweep_id);
1401 }
1402 if let Some(composite_solid_id) = path.composite_solid_id {
1403 source_ids.push(composite_solid_id);
1404 }
1405 }
1406
1407 for artifact in artifacts.values() {
1408 match artifact {
1409 Artifact::Sweep(sweep) if sweep.path_id == source_id => source_ids.push(sweep.id),
1410 Artifact::CompositeSolid(composite)
1411 if composite.solid_ids.contains(&source_id) || composite.tool_ids.contains(&source_id) =>
1412 {
1413 source_ids.push(composite.id)
1414 }
1415 _ => {}
1416 }
1417 }
1418
1419 let mut unique = Vec::new();
1420 merge_ids(&mut unique, source_ids);
1421 unique
1422}
1423
1424fn pattern_artifact_updates(
1425 artifacts: &IndexMap<ArtifactId, Artifact>,
1426 pattern_id: ArtifactId,
1427 sub_type: PatternSubType,
1428 source_id: ArtifactId,
1429 face_edge_infos: &[kcmc::output::FaceEdgeInfo],
1430 code_ref: CodeRef,
1431) -> Vec<Artifact> {
1432 let copy_ids = face_edge_infos
1433 .iter()
1434 .map(|info| ArtifactId::new(info.object_id))
1435 .collect::<Vec<_>>();
1436 let copy_face_ids = face_edge_infos
1437 .iter()
1438 .flat_map(|info| info.faces.iter().copied().map(ArtifactId::new))
1439 .collect::<Vec<_>>();
1440 let copy_edge_ids = face_edge_infos
1441 .iter()
1442 .flat_map(|info| info.edges.iter().copied().map(ArtifactId::new))
1443 .collect::<Vec<_>>();
1444
1445 let source_ids = pattern_source_ids(artifacts, source_id);
1446 let mut return_arr = vec![Artifact::Pattern(Pattern {
1447 id: pattern_id,
1448 sub_type,
1449 source_id,
1450 copy_ids,
1451 copy_face_ids,
1452 copy_edge_ids,
1453 code_ref,
1454 })];
1455
1456 for source_id in source_ids {
1457 let Some(artifact) = artifacts.get(&source_id) else {
1458 continue;
1459 };
1460 match artifact {
1461 Artifact::Path(path) => {
1462 let mut new_path = path.clone();
1463 new_path.pattern_ids = vec![pattern_id];
1464 return_arr.push(Artifact::Path(new_path));
1465 }
1466 Artifact::Sweep(sweep) => {
1467 let mut new_sweep = sweep.clone();
1468 new_sweep.pattern_ids = vec![pattern_id];
1469 return_arr.push(Artifact::Sweep(new_sweep));
1470 }
1471 Artifact::CompositeSolid(composite) => {
1472 let mut new_composite = composite.clone();
1473 new_composite.pattern_ids = vec![pattern_id];
1474 return_arr.push(Artifact::CompositeSolid(new_composite));
1475 }
1476 _ => {}
1477 }
1478 }
1479
1480 return_arr
1481}
1482
1483fn is_single_target_self_subtract(target_ids: &[Uuid], tool_ids: &[Uuid]) -> bool {
1484 target_ids.len() == 1 && tool_ids.len() == 1 && target_ids[0] == tool_ids[0]
1485}
1486
1487fn boolean_subtract_output_artifact_ids(
1488 cmd_id: ArtifactId,
1489 target_ids: &[Uuid],
1490 tool_ids: &[Uuid],
1491 extra_solid_ids: &[Uuid],
1492) -> Vec<ArtifactId> {
1493 if is_single_target_self_subtract(target_ids, tool_ids) {
1494 return Vec::new();
1495 }
1496
1497 let mut output_ids = if target_ids.len() == 1 {
1498 vec![cmd_id]
1499 } else {
1500 Vec::new()
1501 };
1502
1503 for extra_solid_id in extra_solid_ids {
1504 let artifact_id = ArtifactId::new(*extra_solid_id);
1505 if !output_ids.contains(&artifact_id) {
1506 output_ids.push(artifact_id);
1507 }
1508 }
1509
1510 output_ids
1511}
1512
1513fn update_consumed_csg_sweep(
1514 return_arr: &mut Vec<Artifact>,
1515 artifacts: &IndexMap<ArtifactId, Artifact>,
1516 sweep_id: ArtifactId,
1517 consumed_sweep_ids: &mut FnvHashSet<ArtifactId>,
1518) {
1519 if consumed_sweep_ids.insert(sweep_id)
1520 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
1521 {
1522 let mut new_sweep = sweep.clone();
1523 new_sweep.consumed = true;
1524 return_arr.push(Artifact::Sweep(new_sweep));
1525 }
1526}
1527
1528fn update_csg_input_artifacts(
1529 return_arr: &mut Vec<Artifact>,
1530 artifacts: &IndexMap<ArtifactId, Artifact>,
1531 input_ids: &[ArtifactId],
1532 composite_solid_id: Option<ArtifactId>,
1533 consumed_sweep_ids: &mut FnvHashSet<ArtifactId>,
1534) {
1535 for input_id in input_ids {
1536 if let Some(artifact) = artifacts.get(input_id) {
1537 match artifact {
1538 Artifact::CompositeSolid(comp) => {
1539 let mut new_comp = comp.clone();
1540 new_comp.composite_solid_id = composite_solid_id;
1541 new_comp.consumed = true;
1542 return_arr.push(Artifact::CompositeSolid(new_comp));
1543 }
1544 Artifact::Path(path) => {
1545 let mut new_path = path.clone();
1546 new_path.composite_solid_id = composite_solid_id;
1547
1548 if let Some(sweep_id) = new_path.sweep_id {
1551 update_consumed_csg_sweep(return_arr, artifacts, sweep_id, consumed_sweep_ids);
1552 }
1553
1554 return_arr.push(Artifact::Path(new_path));
1555 }
1556 Artifact::Sweep(sweep) => {
1557 update_consumed_csg_sweep(return_arr, artifacts, sweep.id, consumed_sweep_ids);
1558 }
1559 _ => {}
1560 }
1561 }
1562 }
1563}
1564
1565fn mirror_3d_artifact_updates(
1566 artifacts: &IndexMap<ArtifactId, Artifact>,
1567 original_solid_ids: &[Uuid],
1568 face_edge_infos: &[kcmc::output::FaceEdgeInfo],
1569 code_ref: CodeRef,
1570 range: SourceRange,
1571 cmd: &ModelingCmd,
1572) -> Result<Vec<Artifact>, KclError> {
1573 if original_solid_ids.len() != face_edge_infos.len() {
1574 internal_error!(
1575 range,
1576 "EntityMirrorAcross response has different number face edge info than original mirrored solids: cmd={cmd:?}, face_edge_infos={face_edge_infos:?}"
1577 );
1578 }
1579
1580 let mut return_arr = Vec::new();
1581 for (face_edge_info, original_solid_id) in face_edge_infos.iter().zip(original_solid_ids) {
1582 let original_solid_id = ArtifactId::new(*original_solid_id);
1583 let mirrored_solid_id = ArtifactId::new(face_edge_info.object_id);
1584 let source_solid = match artifacts.get(&original_solid_id) {
1585 Some(Artifact::Path(path)) => path.sweep_id.and_then(|sweep_id| artifacts.get(&sweep_id)).or_else(|| {
1586 path.composite_solid_id
1587 .and_then(|composite_id| artifacts.get(&composite_id))
1588 }),
1589 source => source,
1590 };
1591 match source_solid {
1592 Some(Artifact::Sweep(sweep)) => {
1593 let mut mirrored_sweep = sweep.clone();
1594 mirrored_sweep.id = mirrored_solid_id;
1595 mirrored_sweep.surface_ids = face_edge_info.faces.iter().copied().map(ArtifactId::new).collect();
1596 mirrored_sweep.edge_ids = face_edge_info.edges.iter().copied().map(ArtifactId::new).collect();
1597 mirrored_sweep.code_ref = code_ref.clone();
1598 mirrored_sweep.consumed = false;
1599 mirrored_sweep.pattern_ids = Vec::new();
1600 return_arr.push(Artifact::Sweep(mirrored_sweep));
1601 }
1602 Some(Artifact::CompositeSolid(composite)) => {
1603 let mut mirrored_composite = composite.clone();
1604 mirrored_composite.id = mirrored_solid_id;
1605 mirrored_composite.code_ref = code_ref.clone();
1606 mirrored_composite.consumed = false;
1607 mirrored_composite.composite_solid_id = None;
1608 mirrored_composite.pattern_ids = Vec::new();
1609 return_arr.push(Artifact::CompositeSolid(mirrored_composite));
1610 }
1611 Some(_) | None => continue,
1612 }
1613 }
1614
1615 Ok(return_arr)
1616}
1617
1618#[allow(clippy::too_many_arguments)]
1619fn artifacts_to_update(
1620 artifacts: &IndexMap<ArtifactId, Artifact>,
1621 artifact_command: &ArtifactCommand,
1622 responses: &FnvHashMap<Uuid, OkModelingCmdResponse>,
1623 entity_clone_id_maps: &FnvHashMap<Uuid, FnvHashMap<ArtifactId, ArtifactId>>,
1624 path_to_plane_id_map: &FnvHashMap<Uuid, Uuid>,
1625 programs: &crate::execution::ProgramLookup,
1626 cached_body_items: usize,
1627 exec_artifacts: &IndexMap<ArtifactId, Artifact>,
1628 import_code_refs: &FnvHashMap<ModuleId, ImportCodeRef>,
1629) -> Result<Vec<Artifact>, KclError> {
1630 let uuid = artifact_command.cmd_id;
1631 let response = responses.get(&uuid);
1632
1633 let path_to_node = Vec::new();
1637 let range = artifact_command.range;
1638 let (code_ref_range, node_path) = code_ref_for_range(programs, cached_body_items, range, import_code_refs);
1639 let code_ref = CodeRef {
1640 range: code_ref_range,
1641 node_path,
1642 path_to_node,
1643 };
1644
1645 let id = ArtifactId::new(uuid);
1646 let cmd = &artifact_command.command;
1647
1648 match cmd {
1649 ModelingCmd::MakePlane(_) => {
1650 if range.is_synthetic() {
1651 return Ok(Vec::new());
1652 }
1653 return Ok(vec![Artifact::Plane(Plane {
1657 id,
1658 path_ids: Vec::new(),
1659 code_ref,
1660 })]);
1661 }
1662 ModelingCmd::FaceIsPlanar(FaceIsPlanar { object_id, .. }) => {
1663 return Ok(vec![Artifact::PlaneOfFace(PlaneOfFace {
1664 id,
1665 face_id: object_id.into(),
1666 code_ref,
1667 })]);
1668 }
1669 ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) => {
1670 let existing_plane = artifacts.get(&ArtifactId::new(*entity_id));
1671 match existing_plane {
1672 Some(Artifact::Wall(wall)) => {
1673 return Ok(vec![Artifact::Wall(Wall {
1674 id: entity_id.into(),
1675 seg_id: wall.seg_id,
1676 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1677 sweep_id: wall.sweep_id,
1678 path_ids: wall.path_ids.clone(),
1679 face_code_ref: wall.face_code_ref.clone(),
1680 cmd_id: artifact_command.cmd_id,
1681 })]);
1682 }
1683 Some(Artifact::Cap(cap)) => {
1684 return Ok(vec![Artifact::Cap(Cap {
1685 id: entity_id.into(),
1686 sub_type: cap.sub_type,
1687 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1688 sweep_id: cap.sweep_id,
1689 path_ids: cap.path_ids.clone(),
1690 face_code_ref: cap.face_code_ref.clone(),
1691 cmd_id: artifact_command.cmd_id,
1692 })]);
1693 }
1694 Some(_) | None => {
1695 let path_ids = match existing_plane {
1696 Some(Artifact::Plane(Plane { path_ids, .. })) => path_ids.clone(),
1697 _ => Vec::new(),
1698 };
1699 return Ok(vec![Artifact::Plane(Plane {
1701 id: entity_id.into(),
1702 path_ids,
1703 code_ref,
1704 })]);
1705 }
1706 }
1707 }
1708 ModelingCmd::StartPath(_) => {
1709 let mut return_arr = Vec::new();
1710 let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
1711 KclError::new_internal(KclErrorDetails::new(
1712 format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"),
1713 vec![range],
1714 ))
1715 })?;
1716 let sketch_block_id = exec_artifacts
1717 .values()
1718 .find(|a| {
1719 if let Artifact::SketchBlock(s) = a {
1720 if let Some(path_id) = s.path_id {
1721 path_id == id
1722 } else {
1723 false
1724 }
1725 } else {
1726 false
1727 }
1728 })
1729 .map(|a| a.id());
1730 return_arr.push(Artifact::Path(Path {
1731 id,
1732 sub_type: PathSubType::Sketch,
1733 plane_id: (*current_plane_id).into(),
1734 seg_ids: Vec::new(),
1735 sweep_id: None,
1736 trajectory_sweep_id: None,
1737 solid2d_id: None,
1738 code_ref,
1739 composite_solid_id: None,
1740 sketch_block_id,
1741 origin_path_id: None,
1742 inner_path_id: None,
1743 outer_path_id: None,
1744 pattern_ids: Vec::new(),
1745 consumed: false,
1746 }));
1747 let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
1748 if let Some(Artifact::Plane(plane)) = plane {
1749 let plane_code_ref = plane.code_ref.clone();
1750 return_arr.push(Artifact::Plane(Plane {
1751 id: (*current_plane_id).into(),
1752 path_ids: vec![id],
1753 code_ref: plane_code_ref,
1754 }));
1755 }
1756 if let Some(Artifact::Wall(wall)) = plane {
1757 return_arr.push(Artifact::Wall(Wall {
1758 id: (*current_plane_id).into(),
1759 seg_id: wall.seg_id,
1760 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1761 sweep_id: wall.sweep_id,
1762 path_ids: vec![id],
1763 face_code_ref: wall.face_code_ref.clone(),
1764 cmd_id: artifact_command.cmd_id,
1765 }));
1766 }
1767 if let Some(Artifact::Cap(cap)) = plane {
1768 return_arr.push(Artifact::Cap(Cap {
1769 id: (*current_plane_id).into(),
1770 sub_type: cap.sub_type,
1771 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1772 sweep_id: cap.sweep_id,
1773 path_ids: vec![id],
1774 face_code_ref: cap.face_code_ref.clone(),
1775 cmd_id: artifact_command.cmd_id,
1776 }));
1777 }
1778 return Ok(return_arr);
1779 }
1780 ModelingCmd::ClosePath(_) | ModelingCmd::ExtendPath(_) => {
1781 let path_id = ArtifactId::new(match cmd {
1782 ModelingCmd::ClosePath(c) => c.path_id,
1783 ModelingCmd::ExtendPath(e) => e.path.into(),
1784 _ => internal_error!(
1785 range,
1786 "Close or extend path command variant not handled: id={id:?}, cmd={cmd:?}"
1787 ),
1788 });
1789 let mut return_arr = Vec::new();
1790 return_arr.push(Artifact::Segment(Segment {
1791 id,
1792 path_id,
1793 original_seg_id: None,
1794 surface_id: None,
1795 edge_ids: Vec::new(),
1796 edge_cut_id: None,
1797 code_ref,
1798 common_surface_ids: Vec::new(),
1799 }));
1800 let path = artifacts.get(&path_id);
1801 if let Some(Artifact::Path(path)) = path {
1802 let mut new_path = path.clone();
1803 new_path.seg_ids = vec![id];
1804 return_arr.push(Artifact::Path(new_path));
1805 }
1806 if let Some(OkModelingCmdResponse::ClosePath(close_path)) = response {
1807 return_arr.push(Artifact::Solid2d(Solid2d {
1808 id: close_path.face_id.into(),
1809 path_id,
1810 }));
1811 if let Some(Artifact::Path(path)) = path {
1812 let mut new_path = path.clone();
1813 new_path.solid2d_id = Some(close_path.face_id.into());
1814 return_arr.push(Artifact::Path(new_path));
1815 }
1816 }
1817 return Ok(return_arr);
1818 }
1819 ModelingCmd::CreateRegion(kcmc::CreateRegion {
1820 object_id: origin_path_id,
1821 ..
1822 })
1823 | ModelingCmd::CreateRegionFromQueryPoint(kcmc::CreateRegionFromQueryPoint {
1824 object_id: origin_path_id,
1825 ..
1826 }) => {
1827 let mut return_arr = Vec::new();
1828 let origin_path = artifacts.get(&ArtifactId::new(*origin_path_id));
1829 let Some(Artifact::Path(path)) = origin_path else {
1830 internal_error!(
1831 range,
1832 "Expected to find an existing path for the origin path of CreateRegion or CreateRegionFromQueryPoint command, but found none: origin_path={origin_path:?}, cmd={cmd:?}"
1833 );
1834 };
1835 return_arr.push(Artifact::Path(Path {
1837 id,
1838 sub_type: PathSubType::Region,
1839 plane_id: path.plane_id,
1840 seg_ids: Vec::new(),
1841 consumed: false,
1842 sweep_id: None,
1843 trajectory_sweep_id: None,
1844 solid2d_id: None,
1845 code_ref: code_ref.clone(),
1846 composite_solid_id: None,
1847 sketch_block_id: None,
1848 origin_path_id: Some(ArtifactId::new(*origin_path_id)),
1849 inner_path_id: None,
1850 outer_path_id: None,
1851 pattern_ids: Vec::new(),
1852 }));
1853 let Some(
1856 OkModelingCmdResponse::CreateRegion(kcmc::output::CreateRegion { region_mapping, .. })
1857 | OkModelingCmdResponse::CreateRegionFromQueryPoint(kcmc::output::CreateRegionFromQueryPoint {
1858 region_mapping,
1859 ..
1860 }),
1861 ) = response
1862 else {
1863 return Ok(return_arr);
1864 };
1865 let original_segment_ids = path.seg_ids.iter().map(|p| p.0).collect::<Vec<_>>();
1868 let reverse = build_reverse_region_mapping(region_mapping, &original_segment_ids);
1869 for (original_segment_id, region_segment_ids) in reverse.iter() {
1870 for segment_id in region_segment_ids {
1871 return_arr.push(Artifact::Segment(Segment {
1872 id: ArtifactId::new(*segment_id),
1873 path_id: id,
1874 original_seg_id: Some(ArtifactId::new(*original_segment_id)),
1875 surface_id: None,
1876 edge_ids: Vec::new(),
1877 edge_cut_id: None,
1878 code_ref: code_ref.clone(),
1879 common_surface_ids: Vec::new(),
1880 }))
1881 }
1882 }
1883 return Ok(return_arr);
1884 }
1885 ModelingCmd::Solid3dGetFaceUuid(kcmc::Solid3dGetFaceUuid { object_id, .. }) => {
1886 let Some(OkModelingCmdResponse::Solid3dGetFaceUuid(face_uuid)) = response else {
1887 return Ok(Vec::new());
1888 };
1889
1890 return Ok(vec![Artifact::PrimitiveFace(PrimitiveFace {
1891 id: face_uuid.face_id.into(),
1892 solid_id: (*object_id).into(),
1893 code_ref,
1894 })]);
1895 }
1896 ModelingCmd::Solid3dGetEdgeUuid(kcmc::Solid3dGetEdgeUuid { object_id, .. }) => {
1897 let Some(OkModelingCmdResponse::Solid3dGetEdgeUuid(edge_uuid)) = response else {
1898 return Ok(Vec::new());
1899 };
1900
1901 return Ok(vec![Artifact::PrimitiveEdge(PrimitiveEdge {
1902 id: edge_uuid.edge_id.into(),
1903 solid_id: (*object_id).into(),
1904 code_ref,
1905 })]);
1906 }
1907 ModelingCmd::EntityLinearPatternTransform(pattern_cmd) => {
1908 let face_edge_infos = match response {
1909 Some(OkModelingCmdResponse::EntityLinearPatternTransform(resp)) => resp.entity_face_edge_ids.as_slice(),
1910 _ => &[],
1911 };
1912 return Ok(pattern_artifact_updates(
1913 artifacts,
1914 id,
1915 PatternSubType::Transform,
1916 ArtifactId::new(pattern_cmd.entity_id),
1917 face_edge_infos,
1918 code_ref,
1919 ));
1920 }
1921 ModelingCmd::EntityLinearPattern(pattern_cmd) => {
1922 let face_edge_infos = match response {
1923 Some(OkModelingCmdResponse::EntityLinearPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
1924 _ => &[],
1925 };
1926 return Ok(pattern_artifact_updates(
1927 artifacts,
1928 id,
1929 PatternSubType::Linear,
1930 ArtifactId::new(pattern_cmd.entity_id),
1931 face_edge_infos,
1932 code_ref,
1933 ));
1934 }
1935 ModelingCmd::EntityCircularPattern(pattern_cmd) => {
1936 let face_edge_infos = match response {
1937 Some(OkModelingCmdResponse::EntityCircularPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
1938 _ => &[],
1939 };
1940 return Ok(pattern_artifact_updates(
1941 artifacts,
1942 id,
1943 PatternSubType::Circular,
1944 ArtifactId::new(pattern_cmd.entity_id),
1945 face_edge_infos,
1946 code_ref,
1947 ));
1948 }
1949 ModelingCmd::EntityMirrorAcross(kcmc::EntityMirrorAcross {
1950 ids: original_solid_ids,
1951 ..
1952 }) => {
1953 let face_edge_infos = match response {
1954 Some(OkModelingCmdResponse::EntityMirrorAcross(resp)) => resp.entity_face_edge_ids.as_slice(),
1955 _ => internal_error!(
1956 range,
1957 "EntityMirrorAcross response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
1958 ),
1959 };
1960 return mirror_3d_artifact_updates(artifacts, original_solid_ids, face_edge_infos, code_ref, range, cmd);
1961 }
1962 ModelingCmd::EntityMirror(kcmc::EntityMirror {
1963 ids: original_path_ids, ..
1964 })
1965 | ModelingCmd::EntityMirrorAcrossEdge(kcmc::EntityMirrorAcrossEdge {
1966 ids: original_path_ids, ..
1967 }) => {
1968 let face_edge_infos = match response {
1969 Some(OkModelingCmdResponse::EntityMirror(resp)) => &resp.entity_face_edge_ids,
1970 Some(OkModelingCmdResponse::EntityMirrorAcrossEdge(resp)) => &resp.entity_face_edge_ids,
1971 _ => internal_error!(
1972 range,
1973 "Mirror response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
1974 ),
1975 };
1976 if original_path_ids.len() != face_edge_infos.len() {
1977 internal_error!(
1978 range,
1979 "EntityMirror or EntityMirrorAcrossEdge response has different number face edge info than original mirrored paths: id={id:?}, cmd={cmd:?}, response={response:?}"
1980 );
1981 }
1982 let mut return_arr = Vec::new();
1983 for (face_edge_info, original_path_id) in face_edge_infos.iter().zip(original_path_ids) {
1984 let original_path_id = ArtifactId::new(*original_path_id);
1985 let path_id = ArtifactId::new(face_edge_info.object_id);
1986 let mut path = if let Some(Artifact::Path(path)) = artifacts.get(&path_id) {
1989 path.clone()
1991 } else {
1992 let Some(Artifact::Path(original_path)) = artifacts.get(&original_path_id) else {
1995 internal_error!(
1997 range,
1998 "Couldn't find original path for mirror2d: original_path_id={original_path_id:?}, cmd={cmd:?}"
1999 );
2000 };
2001 Path {
2002 id: path_id,
2003 sub_type: original_path.sub_type,
2004 plane_id: original_path.plane_id,
2005 seg_ids: Vec::new(),
2006 sweep_id: None,
2007 trajectory_sweep_id: None,
2008 solid2d_id: None,
2009 code_ref: code_ref.clone(),
2010 composite_solid_id: None,
2011 sketch_block_id: None,
2012 origin_path_id: original_path.origin_path_id,
2013 inner_path_id: None,
2014 outer_path_id: None,
2015 pattern_ids: Vec::new(),
2016 consumed: false,
2017 }
2018 };
2019
2020 face_edge_info.edges.iter().for_each(|edge_id| {
2021 let edge_id = ArtifactId::new(*edge_id);
2022 return_arr.push(Artifact::Segment(Segment {
2023 id: edge_id,
2024 path_id: path.id,
2025 original_seg_id: None,
2026 surface_id: None,
2027 edge_ids: Vec::new(),
2028 edge_cut_id: None,
2029 code_ref: code_ref.clone(),
2030 common_surface_ids: Vec::new(),
2031 }));
2032 path.seg_ids.push(edge_id);
2034 });
2035
2036 return_arr.push(Artifact::Path(path));
2037 }
2038 return Ok(return_arr);
2039 }
2040 ModelingCmd::EntityClone(kcmc::EntityClone { entity_id, .. }) => {
2041 let source_id = ArtifactId::new(*entity_id);
2042
2043 let Some(source_artifact) = artifacts.get(&source_id) else {
2044 return Ok(Vec::new());
2045 };
2046
2047 let mut entity_id_map = entity_clone_id_maps.get(&uuid).cloned().unwrap_or_default();
2048 entity_id_map.insert(source_id, id);
2049
2050 let mut cloned_artifacts = Vec::new();
2051 cloned_artifacts.push(remap_artifact_for_clone(
2052 source_artifact,
2053 &entity_id_map,
2054 &code_ref,
2055 artifact_command.cmd_id,
2056 source_id,
2057 ));
2058
2059 for artifact in artifacts.values() {
2060 let artifact_id = artifact.id();
2061 if artifact_id == source_id || !entity_id_map.contains_key(&artifact_id) {
2062 continue;
2063 }
2064 cloned_artifacts.push(remap_artifact_for_clone(
2065 artifact,
2066 &entity_id_map,
2067 &code_ref,
2068 artifact_command.cmd_id,
2069 source_id,
2070 ));
2071 }
2072
2073 return Ok(cloned_artifacts);
2074 }
2075 ModelingCmd::Extrude(kcmc::Extrude { target, .. })
2076 | ModelingCmd::TwistExtrude(kcmc::TwistExtrude { target, .. })
2077 | ModelingCmd::Revolve(kcmc::Revolve { target, .. })
2078 | ModelingCmd::RevolveAboutEdge(kcmc::RevolveAboutEdge { target, .. })
2079 | ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { target, .. }) => {
2080 let method = match cmd {
2082 ModelingCmd::Extrude(kcmc::Extrude { extrude_method, .. }) => *extrude_method,
2083 ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { extrude_method, .. }) => *extrude_method,
2084 ModelingCmd::TwistExtrude(_) | ModelingCmd::Sweep(_) => {
2086 kittycad_modeling_cmds::shared::ExtrudeMethod::Merge
2087 }
2088 ModelingCmd::Revolve(_) | ModelingCmd::RevolveAboutEdge(_) => {
2090 kittycad_modeling_cmds::shared::ExtrudeMethod::New
2091 }
2092 _ => kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
2093 };
2094 let sub_type = match cmd {
2095 ModelingCmd::Extrude(_) => SweepSubType::Extrusion,
2096 ModelingCmd::ExtrudeToReference(_) => SweepSubType::Extrusion,
2097 ModelingCmd::TwistExtrude(_) => SweepSubType::ExtrusionTwist,
2098 ModelingCmd::Revolve(_) => SweepSubType::Revolve,
2099 ModelingCmd::RevolveAboutEdge(_) => SweepSubType::RevolveAboutEdge,
2100 _ => internal_error!(range, "Sweep-like command variant not handled: id={id:?}, cmd={cmd:?}",),
2101 };
2102 let mut return_arr = Vec::new();
2103 let target = ArtifactId::from(target);
2104 return_arr.push(Artifact::Sweep(Sweep {
2105 id,
2106 sub_type,
2107 path_id: target,
2108 surface_ids: Vec::new(),
2109 edge_ids: Vec::new(),
2110 code_ref,
2111 trajectory_id: None,
2112 method,
2113 consumed: false,
2114 pattern_ids: Vec::new(),
2115 }));
2116 let path = artifacts.get(&target);
2117 if let Some(Artifact::Path(path)) = path {
2118 let mut new_path = path.clone();
2119 new_path.sweep_id = Some(id);
2120 new_path.consumed = true;
2121 return_arr.push(Artifact::Path(new_path));
2122 if let Some(inner_path_id) = path.inner_path_id
2123 && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
2124 && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
2125 {
2126 inner_path_artifact.sweep_id = Some(id);
2127 inner_path_artifact.consumed = true;
2128 return_arr.push(Artifact::Path(inner_path_artifact))
2129 }
2130 }
2131 return Ok(return_arr);
2132 }
2133 ModelingCmd::Sweep(kcmc::Sweep { target, trajectory, .. }) => {
2134 let method = kittycad_modeling_cmds::shared::ExtrudeMethod::Merge;
2136 let sub_type = SweepSubType::Sweep;
2137 let mut return_arr = Vec::new();
2138 let target = ArtifactId::from(target);
2139 let trajectory = ArtifactId::from(trajectory);
2140 return_arr.push(Artifact::Sweep(Sweep {
2141 id,
2142 sub_type,
2143 path_id: target,
2144 surface_ids: Vec::new(),
2145 edge_ids: Vec::new(),
2146 code_ref,
2147 trajectory_id: Some(trajectory),
2148 method,
2149 consumed: false,
2150 pattern_ids: Vec::new(),
2151 }));
2152 let path = artifacts.get(&target);
2153 if let Some(Artifact::Path(path)) = path {
2154 let mut new_path = path.clone();
2155 new_path.sweep_id = Some(id);
2156 new_path.consumed = true;
2157 return_arr.push(Artifact::Path(new_path));
2158 if let Some(inner_path_id) = path.inner_path_id
2159 && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
2160 && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
2161 {
2162 inner_path_artifact.sweep_id = Some(id);
2163 inner_path_artifact.consumed = true;
2164 return_arr.push(Artifact::Path(inner_path_artifact))
2165 }
2166 }
2167 if let Some(trajectory_artifact) = artifacts.get(&trajectory) {
2168 match trajectory_artifact {
2169 Artifact::Path(path) => {
2170 let mut new_path = path.clone();
2171 new_path.trajectory_sweep_id = Some(id);
2172 new_path.consumed = true;
2173 return_arr.push(Artifact::Path(new_path));
2174 }
2175 Artifact::Helix(helix) => {
2176 let mut new_helix = helix.clone();
2177 new_helix.trajectory_sweep_id = Some(id);
2178 new_helix.consumed = true;
2179 return_arr.push(Artifact::Helix(new_helix));
2180 }
2181 _ => {}
2182 }
2183 };
2184 return Ok(return_arr);
2185 }
2186 ModelingCmd::SurfaceBlend(surface_blend_cmd) => {
2187 let surface_id_to_path_id = |surface_id: ArtifactId| -> Option<ArtifactId> {
2188 match artifacts.get(&surface_id) {
2189 Some(Artifact::Path(path)) => Some(path.id),
2190 Some(Artifact::Segment(segment)) => Some(segment.path_id),
2191 Some(Artifact::Sweep(sweep)) => Some(sweep.path_id),
2192 Some(Artifact::Wall(wall)) => artifacts.get(&wall.sweep_id).and_then(|artifact| match artifact {
2193 Artifact::Sweep(sweep) => Some(sweep.path_id),
2194 _ => None,
2195 }),
2196 Some(Artifact::Cap(cap)) => artifacts.get(&cap.sweep_id).and_then(|artifact| match artifact {
2197 Artifact::Sweep(sweep) => Some(sweep.path_id),
2198 _ => None,
2199 }),
2200 _ => None,
2201 }
2202 };
2203 let Some(first_surface_ref) = surface_blend_cmd.surfaces.first() else {
2204 internal_error!(range, "SurfaceBlend command has no surfaces: id={id:?}, cmd={cmd:?}");
2205 };
2206 let first_surface_id = ArtifactId::new(first_surface_ref.object_id);
2207 let path_id = surface_id_to_path_id(first_surface_id).unwrap_or(first_surface_id);
2208 let trajectory_id = surface_blend_cmd
2209 .surfaces
2210 .get(1)
2211 .map(|surface| ArtifactId::new(surface.object_id))
2212 .and_then(surface_id_to_path_id);
2213 let return_arr = vec![Artifact::Sweep(Sweep {
2214 id,
2215 sub_type: SweepSubType::Blend,
2216 path_id,
2217 surface_ids: Vec::new(),
2218 edge_ids: Vec::new(),
2219 code_ref,
2220 trajectory_id,
2221 method: kittycad_modeling_cmds::shared::ExtrudeMethod::New,
2222 consumed: false,
2223 pattern_ids: Vec::new(),
2224 })];
2225 return Ok(return_arr);
2226 }
2227 ModelingCmd::Loft(loft_cmd) => {
2228 let Some(OkModelingCmdResponse::Loft(_)) = response else {
2229 return Ok(Vec::new());
2230 };
2231 let mut return_arr = Vec::new();
2232 return_arr.push(Artifact::Sweep(Sweep {
2233 id,
2234 sub_type: SweepSubType::Loft,
2235 path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
2238 KclError::new_internal(KclErrorDetails::new(
2239 format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
2240 vec![range],
2241 ))
2242 })?),
2243 surface_ids: Vec::new(),
2244 edge_ids: Vec::new(),
2245 code_ref,
2246 trajectory_id: None,
2247 method: kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
2248 consumed: false,
2249 pattern_ids: Vec::new(),
2250 }));
2251 for section_id in &loft_cmd.section_ids {
2252 let path = artifacts.get(&ArtifactId::new(*section_id));
2253 if let Some(Artifact::Path(path)) = path {
2254 let mut new_path = path.clone();
2255 new_path.consumed = true;
2256 new_path.sweep_id = Some(id);
2257 return_arr.push(Artifact::Path(new_path));
2258 }
2259 }
2260 return Ok(return_arr);
2261 }
2262 ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
2263 let Some(OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info)) = response else {
2264 return Ok(Vec::new());
2265 };
2266 let mut return_arr = Vec::new();
2267 let mut last_path = None;
2268 for face in &face_info.faces {
2269 if face.cap != ExtrusionFaceCapType::None {
2270 continue;
2271 }
2272 let Some(curve_id) = face.curve_id.map(ArtifactId::new) else {
2273 continue;
2274 };
2275 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
2276 continue;
2277 };
2278 let Some(Artifact::Segment(seg)) = artifacts.get(&curve_id) else {
2279 continue;
2280 };
2281 let Some(Artifact::Path(path)) = artifacts.get(&seg.path_id) else {
2282 continue;
2283 };
2284 last_path = Some(path);
2285 let Some(path_sweep_id) = path.sweep_id else {
2286 if path.outer_path_id.is_some() {
2289 continue; }
2291 return Err(KclError::new_internal(KclErrorDetails::new(
2292 format!(
2293 "Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
2294 ),
2295 vec![range],
2296 )));
2297 };
2298 let extra_artifact = exec_artifacts.values().find(|a| {
2299 if let Artifact::StartSketchOnFace(s) = a {
2300 s.face_id == face_id
2301 } else if let Artifact::StartSketchOnPlane(s) = a {
2302 s.plane_id == face_id
2303 } else {
2304 false
2305 }
2306 });
2307 let sketch_on_face_code_ref = extra_artifact
2308 .and_then(|a| match a {
2309 Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
2310 Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
2311 _ => None,
2312 })
2313 .unwrap_or_default();
2315
2316 return_arr.push(Artifact::Wall(Wall {
2317 id: face_id,
2318 seg_id: curve_id,
2319 edge_cut_edge_ids: Vec::new(),
2320 sweep_id: path_sweep_id,
2321 path_ids: Vec::new(),
2322 face_code_ref: sketch_on_face_code_ref,
2323 cmd_id: artifact_command.cmd_id,
2324 }));
2325 let mut new_seg = seg.clone();
2326 new_seg.surface_id = Some(face_id);
2327 return_arr.push(Artifact::Segment(new_seg));
2328 if let Some(Artifact::Sweep(sweep)) = path.sweep_id.and_then(|id| artifacts.get(&id)) {
2329 let mut new_sweep = sweep.clone();
2330 new_sweep.surface_ids = vec![face_id];
2331 return_arr.push(Artifact::Sweep(new_sweep));
2332 }
2333 }
2334 if let Some(path) = last_path {
2335 for face in &face_info.faces {
2336 let sub_type = match face.cap {
2337 ExtrusionFaceCapType::Top => CapSubType::End,
2338 ExtrusionFaceCapType::Bottom => CapSubType::Start,
2339 ExtrusionFaceCapType::None | ExtrusionFaceCapType::Both => continue,
2340 _other => {
2341 continue;
2343 }
2344 };
2345 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
2346 continue;
2347 };
2348 let Some(path_sweep_id) = path.sweep_id else {
2349 if path.outer_path_id.is_some() {
2352 continue; }
2354 return Err(KclError::new_internal(KclErrorDetails::new(
2355 format!(
2356 "Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
2357 ),
2358 vec![range],
2359 )));
2360 };
2361 let extra_artifact = exec_artifacts.values().find(|a| {
2362 if let Artifact::StartSketchOnFace(s) = a {
2363 s.face_id == face_id
2364 } else if let Artifact::StartSketchOnPlane(s) = a {
2365 s.plane_id == face_id
2366 } else {
2367 false
2368 }
2369 });
2370 let sketch_on_face_code_ref = extra_artifact
2371 .and_then(|a| match a {
2372 Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
2373 Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
2374 _ => None,
2375 })
2376 .unwrap_or_default();
2378 return_arr.push(Artifact::Cap(Cap {
2379 id: face_id,
2380 sub_type,
2381 edge_cut_edge_ids: Vec::new(),
2382 sweep_id: path_sweep_id,
2383 path_ids: Vec::new(),
2384 face_code_ref: sketch_on_face_code_ref,
2385 cmd_id: artifact_command.cmd_id,
2386 }));
2387 let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
2388 continue;
2389 };
2390 let mut new_sweep = sweep.clone();
2391 new_sweep.surface_ids = vec![face_id];
2392 return_arr.push(Artifact::Sweep(new_sweep));
2393 }
2394 }
2395 return Ok(return_arr);
2396 }
2397 ModelingCmd::Solid3dGetAdjacencyInfo(kcmc::Solid3dGetAdjacencyInfo { .. }) => {
2398 let Some(OkModelingCmdResponse::Solid3dGetAdjacencyInfo(info)) = response else {
2399 return Ok(Vec::new());
2400 };
2401
2402 let mut return_arr = Vec::new();
2403 for (index, edge) in info.edges.iter().enumerate() {
2404 let Some(original_info) = &edge.original_info else {
2405 continue;
2406 };
2407 let edge_id = ArtifactId::new(original_info.edge_id);
2408 let Some(artifact) = artifacts.get(&edge_id) else {
2409 continue;
2410 };
2411 match artifact {
2412 Artifact::Segment(segment) => {
2413 let mut new_segment = segment.clone();
2414 new_segment.common_surface_ids =
2415 original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
2416 return_arr.push(Artifact::Segment(new_segment));
2417 }
2418 Artifact::SweepEdge(sweep_edge) => {
2419 let mut new_sweep_edge = sweep_edge.clone();
2420 new_sweep_edge.common_surface_ids =
2421 original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
2422 return_arr.push(Artifact::SweepEdge(new_sweep_edge));
2423 }
2424 _ => {}
2425 };
2426
2427 let Some(Artifact::Segment(segment)) = artifacts.get(&edge_id) else {
2428 continue;
2429 };
2430 let Some(surface_id) = segment.surface_id else {
2431 continue;
2432 };
2433 let Some(Artifact::Wall(wall)) = artifacts.get(&surface_id) else {
2434 continue;
2435 };
2436 let Some(Artifact::Sweep(sweep)) = artifacts.get(&wall.sweep_id) else {
2437 continue;
2438 };
2439 let Some(Artifact::Path(_)) = artifacts.get(&sweep.path_id) else {
2440 continue;
2441 };
2442
2443 if let Some(opposite_info) = &edge.opposite_info {
2444 return_arr.push(Artifact::SweepEdge(SweepEdge {
2445 id: opposite_info.edge_id.into(),
2446 sub_type: SweepEdgeSubType::Opposite,
2447 seg_id: edge_id,
2448 cmd_id: artifact_command.cmd_id,
2449 index,
2450 sweep_id: sweep.id,
2451 common_surface_ids: opposite_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
2452 }));
2453 let mut new_segment = segment.clone();
2454 new_segment.edge_ids = vec![opposite_info.edge_id.into()];
2455 return_arr.push(Artifact::Segment(new_segment));
2456 let mut new_sweep = sweep.clone();
2457 new_sweep.edge_ids = vec![opposite_info.edge_id.into()];
2458 return_arr.push(Artifact::Sweep(new_sweep));
2459 let mut new_wall = wall.clone();
2460 new_wall.edge_cut_edge_ids = vec![opposite_info.edge_id.into()];
2461 return_arr.push(Artifact::Wall(new_wall));
2462 }
2463 if let Some(adjacent_info) = &edge.adjacent_info {
2464 return_arr.push(Artifact::SweepEdge(SweepEdge {
2465 id: adjacent_info.edge_id.into(),
2466 sub_type: SweepEdgeSubType::Adjacent,
2467 seg_id: edge_id,
2468 cmd_id: artifact_command.cmd_id,
2469 index,
2470 sweep_id: sweep.id,
2471 common_surface_ids: adjacent_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
2472 }));
2473 let mut new_segment = segment.clone();
2474 new_segment.edge_ids = vec![adjacent_info.edge_id.into()];
2475 return_arr.push(Artifact::Segment(new_segment));
2476 let mut new_sweep = sweep.clone();
2477 new_sweep.edge_ids = vec![adjacent_info.edge_id.into()];
2478 return_arr.push(Artifact::Sweep(new_sweep));
2479 let mut new_wall = wall.clone();
2480 new_wall.edge_cut_edge_ids = vec![adjacent_info.edge_id.into()];
2481 return_arr.push(Artifact::Wall(new_wall));
2482 }
2483 }
2484 return Ok(return_arr);
2485 }
2486 ModelingCmd::Solid3dMultiJoin(cmd) => {
2487 let mut return_arr = Vec::new();
2488 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2489 id,
2490 consumed: false,
2491 sub_type: CompositeSolidSubType::Union,
2492 output_index: None,
2493 solid_ids: cmd.object_ids.iter().map(|id| id.into()).collect(),
2494 tool_ids: vec![],
2495 code_ref,
2496 composite_solid_id: None,
2497 pattern_ids: Vec::new(),
2498 }));
2499
2500 let solid_ids = cmd.object_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2501
2502 for input_id in &solid_ids {
2503 if let Some(artifact) = artifacts.get(input_id)
2504 && let Artifact::CompositeSolid(comp) = artifact
2505 {
2506 let mut new_comp = comp.clone();
2507 new_comp.composite_solid_id = Some(id);
2508 new_comp.consumed = true;
2509 return_arr.push(Artifact::CompositeSolid(new_comp));
2510 } else if let Some(Artifact::Sweep(sweep)) = artifacts.get(input_id) {
2511 let mut new_sweep = sweep.clone();
2512 new_sweep.consumed = true;
2513 return_arr.push(Artifact::Sweep(new_sweep));
2514 }
2515 }
2516 return Ok(return_arr);
2517 }
2518 ModelingCmd::Solid3dFilletEdge(cmd) => {
2519 let mut return_arr = Vec::new();
2520 let edge_id = if let Some(edge_id) = cmd.edge_id {
2521 ArtifactId::new(edge_id)
2522 } else {
2523 let Some(edge_id) = cmd.edge_ids.first() else {
2524 internal_error!(
2525 range,
2526 "Solid3dFilletEdge command has no edge ID: id={id:?}, cmd={cmd:?}"
2527 );
2528 };
2529 edge_id.into()
2530 };
2531 return_arr.push(Artifact::EdgeCut(EdgeCut {
2532 id,
2533 sub_type: cmd.cut_type.into(),
2534 consumed_edge_id: edge_id,
2535 edge_ids: Vec::new(),
2536 surface_id: None,
2537 code_ref,
2538 }));
2539 let consumed_edge = artifacts.get(&edge_id);
2540 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2541 let mut new_segment = consumed_edge.clone();
2542 new_segment.edge_cut_id = Some(id);
2543 return_arr.push(Artifact::Segment(new_segment));
2544 } else {
2545 }
2547 return Ok(return_arr);
2548 }
2549 ModelingCmd::Solid3dCutEdges(cmd) => {
2550 let mut return_arr = Vec::new();
2551 let edge_id = if let Some(edge_id) = cmd.edge_ids.first() {
2552 edge_id.into()
2553 } else {
2554 internal_error!(range, "Solid3dCutEdges command has no edge ID: id={id:?}, cmd={cmd:?}");
2555 };
2556 return_arr.push(Artifact::EdgeCut(EdgeCut {
2557 id,
2558 sub_type: cmd.cut_type.into(),
2559 consumed_edge_id: edge_id,
2560 edge_ids: Vec::new(),
2561 surface_id: None,
2562 code_ref,
2563 }));
2564 let consumed_edge = artifacts.get(&edge_id);
2565 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2566 let mut new_segment = consumed_edge.clone();
2567 new_segment.edge_cut_id = Some(id);
2568 return_arr.push(Artifact::Segment(new_segment));
2569 } else {
2570 }
2572 return Ok(return_arr);
2573 }
2574 ModelingCmd::EntityMakeHelix(cmd) => {
2575 let cylinder_id = ArtifactId::new(cmd.cylinder_id);
2576 let return_arr = vec![Artifact::Helix(Helix {
2577 id,
2578 axis_id: Some(cylinder_id),
2579 code_ref,
2580 trajectory_sweep_id: None,
2581 consumed: false,
2582 })];
2583 return Ok(return_arr);
2584 }
2585 ModelingCmd::EntityMakeHelixFromParams(_) => {
2586 let return_arr = vec![Artifact::Helix(Helix {
2587 id,
2588 axis_id: None,
2589 code_ref,
2590 trajectory_sweep_id: None,
2591 consumed: false,
2592 })];
2593 return Ok(return_arr);
2594 }
2595 ModelingCmd::EntityMakeHelixFromEdge(helix) => {
2596 let return_arr = vec![Artifact::Helix(Helix {
2597 id,
2598 axis_id: helix.edge_id.map(ArtifactId::new),
2599 code_ref,
2600 trajectory_sweep_id: None,
2601 consumed: false,
2602 })];
2603 return Ok(return_arr);
2606 }
2607 ModelingCmd::Solid2dAddHole(solid2d_add_hole) => {
2608 let mut return_arr = Vec::new();
2609 let outer_path = artifacts.get(&ArtifactId::new(solid2d_add_hole.object_id));
2611 if let Some(Artifact::Path(path)) = outer_path {
2612 let mut new_path = path.clone();
2613 new_path.inner_path_id = Some(ArtifactId::new(solid2d_add_hole.hole_id));
2614 return_arr.push(Artifact::Path(new_path));
2615 }
2616 let inner_solid2d = artifacts.get(&ArtifactId::new(solid2d_add_hole.hole_id));
2618 if let Some(Artifact::Path(path)) = inner_solid2d {
2619 let mut new_path = path.clone();
2620 new_path.consumed = true;
2621 new_path.outer_path_id = Some(ArtifactId::new(solid2d_add_hole.object_id));
2622 return_arr.push(Artifact::Path(new_path));
2623 }
2624 return Ok(return_arr);
2625 }
2626 ModelingCmd::BooleanIntersection(_) | ModelingCmd::BooleanSubtract(_) | ModelingCmd::BooleanUnion(_) => {
2627 let (sub_type, solid_ids, tool_ids) = match cmd {
2628 ModelingCmd::BooleanIntersection(intersection) => {
2629 let solid_ids = intersection
2630 .solid_ids
2631 .iter()
2632 .copied()
2633 .map(ArtifactId::new)
2634 .collect::<Vec<_>>();
2635 (CompositeSolidSubType::Intersect, solid_ids, Vec::new())
2636 }
2637 ModelingCmd::BooleanSubtract(subtract) => {
2638 let solid_ids = subtract
2639 .target_ids
2640 .iter()
2641 .copied()
2642 .map(ArtifactId::new)
2643 .collect::<Vec<_>>();
2644 let tool_ids = subtract
2645 .tool_ids
2646 .iter()
2647 .copied()
2648 .map(ArtifactId::new)
2649 .collect::<Vec<_>>();
2650 (CompositeSolidSubType::Subtract, solid_ids, tool_ids)
2651 }
2652 ModelingCmd::BooleanUnion(union) => {
2653 let solid_ids = union.solid_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2654 (CompositeSolidSubType::Union, solid_ids, Vec::new())
2655 }
2656 _ => internal_error!(
2657 range,
2658 "Boolean or composite command variant not handled: id={id:?}, cmd={cmd:?}"
2659 ),
2660 };
2661
2662 let mut new_solid_ids = vec![id];
2663
2664 let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2667
2668 match (cmd, response) {
2669 (
2670 ModelingCmd::BooleanSubtract(subtract_cmd),
2671 Some(OkModelingCmdResponse::BooleanSubtract(subtract_resp)),
2672 ) => {
2673 new_solid_ids = boolean_subtract_output_artifact_ids(
2674 id,
2675 &subtract_cmd.target_ids,
2676 &subtract_cmd.tool_ids,
2677 &subtract_resp.extra_solid_ids,
2678 );
2679 }
2680 (_, Some(OkModelingCmdResponse::BooleanIntersection(intersection))) => intersection
2681 .extra_solid_ids
2682 .iter()
2683 .copied()
2684 .map(ArtifactId::new)
2685 .filter(not_cmd_id)
2686 .for_each(|id| new_solid_ids.push(id)),
2687 (_, Some(OkModelingCmdResponse::BooleanUnion(union))) => union
2688 .extra_solid_ids
2689 .iter()
2690 .copied()
2691 .map(ArtifactId::new)
2692 .filter(not_cmd_id)
2693 .for_each(|id| new_solid_ids.push(id)),
2694 _ => {}
2695 }
2696
2697 let mut return_arr = Vec::new();
2698 let mut consumed_sweep_ids = FnvHashSet::default();
2699 let mut input_ids = solid_ids.clone();
2700 merge_ids(&mut input_ids, tool_ids.clone());
2701
2702 if new_solid_ids.is_empty() {
2703 update_csg_input_artifacts(&mut return_arr, artifacts, &input_ids, None, &mut consumed_sweep_ids);
2704 }
2705
2706 for solid_id in &new_solid_ids {
2708 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2710 id: *solid_id,
2711 consumed: false,
2712 sub_type,
2713 output_index: None,
2714 solid_ids: solid_ids.clone(),
2715 tool_ids: tool_ids.clone(),
2716 code_ref: code_ref.clone(),
2717 composite_solid_id: None,
2718 pattern_ids: Vec::new(),
2719 }));
2720
2721 update_csg_input_artifacts(
2722 &mut return_arr,
2723 artifacts,
2724 &input_ids,
2725 Some(*solid_id),
2726 &mut consumed_sweep_ids,
2727 );
2728 }
2729
2730 return Ok(return_arr);
2731 }
2732 ModelingCmd::BooleanImprint(imprint) => {
2733 let solid_ids = imprint
2734 .body_ids
2735 .iter()
2736 .copied()
2737 .map(ArtifactId::new)
2738 .collect::<Vec<_>>();
2739 let tool_ids = imprint
2740 .tool_ids
2741 .as_ref()
2742 .map(|ids| ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>())
2743 .unwrap_or_default();
2744
2745 let mut new_solid_ids = vec![id];
2746 let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2747 if let Some(OkModelingCmdResponse::BooleanImprint(imprint)) = response {
2748 imprint
2749 .extra_solid_ids
2750 .iter()
2751 .copied()
2752 .map(ArtifactId::new)
2753 .filter(not_cmd_id)
2754 .for_each(|id| new_solid_ids.push(id));
2755 }
2756
2757 let mut return_arr = Vec::new();
2758 let mut consumed_sweep_ids = FnvHashSet::default();
2759
2760 for input_id in solid_ids.iter().chain(tool_ids.iter()) {
2761 let sweep_id = match artifacts.get(input_id) {
2762 Some(Artifact::Sweep(sweep)) => Some(sweep.id),
2763 Some(Artifact::Path(path)) => path.sweep_id,
2764 _ => None,
2765 };
2766
2767 if let Some(sweep_id) = sweep_id
2768 && consumed_sweep_ids.insert(sweep_id)
2769 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
2770 {
2771 let mut new_sweep = sweep.clone();
2772 new_sweep.consumed = true;
2773 return_arr.push(Artifact::Sweep(new_sweep));
2774 }
2775 }
2776
2777 for (output_index, solid_id) in new_solid_ids.iter().enumerate() {
2778 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2779 id: *solid_id,
2780 consumed: false,
2781 sub_type: CompositeSolidSubType::Split,
2782 output_index: Some(output_index),
2783 solid_ids: solid_ids.clone(),
2784 tool_ids: tool_ids.clone(),
2785 code_ref: code_ref.clone(),
2786 composite_solid_id: None,
2787 pattern_ids: Vec::new(),
2788 }));
2789
2790 for input_id in solid_ids.iter().chain(tool_ids.iter()) {
2791 if let Some(artifact) = artifacts.get(input_id) {
2792 match artifact {
2793 Artifact::CompositeSolid(comp) => {
2794 let mut new_comp = comp.clone();
2795 new_comp.composite_solid_id = Some(*solid_id);
2796 new_comp.consumed = true;
2797 return_arr.push(Artifact::CompositeSolid(new_comp));
2798 }
2799 Artifact::Path(path) => {
2800 let mut new_path = path.clone();
2801 new_path.composite_solid_id = Some(*solid_id);
2802
2803 return_arr.push(Artifact::Path(new_path));
2804 }
2805 _ => {}
2806 }
2807 }
2808 }
2809 }
2810
2811 return Ok(return_arr);
2812 }
2813 _ => {}
2814 }
2815
2816 Ok(Vec::new())
2817}