1use fnv::FnvHashMap;
2use indexmap::IndexMap;
3use kittycad_modeling_cmds::EnableSketchMode;
4use kittycad_modeling_cmds::FaceIsPlanar;
5use kittycad_modeling_cmds::ModelingCmd;
6use kittycad_modeling_cmds::ok_response::OkModelingCmdResponse;
7use kittycad_modeling_cmds::shared::ExtrusionFaceCapType;
8use kittycad_modeling_cmds::websocket::BatchResponse;
9use kittycad_modeling_cmds::websocket::OkWebSocketResponseData;
10use kittycad_modeling_cmds::websocket::WebSocketResponse;
11use kittycad_modeling_cmds::{self as kcmc};
12use serde::Serialize;
13use serde::ser::SerializeSeq;
14use uuid::Uuid;
15
16use crate::KclError;
17use crate::ModuleId;
18use crate::NodePath;
19use crate::SourceRange;
20use crate::engine::PlaneName;
21use crate::errors::KclErrorDetails;
22use crate::execution::ArtifactId;
23use crate::execution::state::ModuleInfoMap;
24use crate::front::Constraint;
25use crate::front::ObjectId;
26use crate::modules::ModulePath;
27use crate::parsing::ast::types::BodyItem;
28use crate::parsing::ast::types::ImportPath;
29use crate::parsing::ast::types::ImportSelector;
30use crate::parsing::ast::types::Node;
31use crate::parsing::ast::types::Program;
32use crate::std::sketch::build_reverse_region_mapping;
33
34#[cfg(test)]
35mod mermaid_tests;
36
37macro_rules! internal_error {
38 ($range:expr, $($rest:tt)*) => {{
39 let message = format!($($rest)*);
40 debug_assert!(false, "{}", &message);
41 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![$range])));
42 }};
43}
44
45#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
49#[ts(export_to = "Artifact.ts")]
50#[serde(rename_all = "camelCase")]
51pub struct ArtifactCommand {
52 pub cmd_id: Uuid,
54 pub range: SourceRange,
57 pub command: ModelingCmd,
62}
63
64pub type DummyPathToNode = Vec<()>;
65
66fn serialize_dummy_path_to_node<S>(_path_to_node: &DummyPathToNode, serializer: S) -> Result<S::Ok, S::Error>
67where
68 S: serde::Serializer,
69{
70 let seq = serializer.serialize_seq(Some(0))?;
72 seq.end()
73}
74
75#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq, ts_rs::TS)]
76#[ts(export_to = "Artifact.ts")]
77#[serde(rename_all = "camelCase")]
78pub struct CodeRef {
79 pub range: SourceRange,
80 pub node_path: NodePath,
81 #[serde(default, serialize_with = "serialize_dummy_path_to_node")]
83 #[ts(type = "Array<[string | number, string]>")]
84 pub path_to_node: DummyPathToNode,
85}
86
87impl CodeRef {
88 pub fn placeholder(range: SourceRange) -> Self {
89 Self {
90 range,
91 node_path: Default::default(),
92 path_to_node: Vec::new(),
93 }
94 }
95}
96
97#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
98#[ts(export_to = "Artifact.ts")]
99#[serde(rename_all = "camelCase")]
100pub struct CompositeSolid {
101 pub id: ArtifactId,
102 pub consumed: bool,
104 pub sub_type: CompositeSolidSubType,
105 #[serde(default, skip_serializing_if = "Option::is_none")]
108 pub output_index: Option<usize>,
109 pub solid_ids: Vec<ArtifactId>,
111 pub tool_ids: Vec<ArtifactId>,
113 pub code_ref: CodeRef,
114 #[serde(default, skip_serializing_if = "Option::is_none")]
117 pub composite_solid_id: Option<ArtifactId>,
118 #[serde(default, skip_serializing_if = "Vec::is_empty")]
120 pub pattern_ids: Vec<ArtifactId>,
121}
122
123#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
124#[ts(export_to = "Artifact.ts")]
125#[serde(rename_all = "camelCase")]
126pub enum CompositeSolidSubType {
127 Intersect,
128 Subtract,
129 Split,
130 Union,
131}
132
133#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
134#[ts(export_to = "Artifact.ts")]
135#[serde(rename_all = "camelCase")]
136pub struct Plane {
137 pub id: ArtifactId,
138 pub path_ids: Vec<ArtifactId>,
139 pub code_ref: CodeRef,
140}
141
142#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
143#[ts(export_to = "Artifact.ts")]
144#[serde(rename_all = "camelCase")]
145pub struct Path {
146 pub id: ArtifactId,
147 pub sub_type: PathSubType,
148 pub plane_id: ArtifactId,
149 pub seg_ids: Vec<ArtifactId>,
150 pub consumed: bool,
152 #[serde(default, skip_serializing_if = "Option::is_none")]
153 pub sweep_id: Option<ArtifactId>,
156 pub trajectory_sweep_id: Option<ArtifactId>,
158 #[serde(default, skip_serializing_if = "Option::is_none")]
159 pub solid2d_id: Option<ArtifactId>,
160 pub code_ref: CodeRef,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
164 pub composite_solid_id: Option<ArtifactId>,
165 #[serde(default, skip_serializing_if = "Option::is_none")]
168 pub sketch_block_id: Option<ArtifactId>,
169 #[serde(default, skip_serializing_if = "Option::is_none")]
172 pub origin_path_id: Option<ArtifactId>,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
175 pub inner_path_id: Option<ArtifactId>,
176 #[serde(default, skip_serializing_if = "Option::is_none")]
179 pub outer_path_id: Option<ArtifactId>,
180 #[serde(default, skip_serializing_if = "Vec::is_empty")]
182 pub pattern_ids: Vec<ArtifactId>,
183}
184
185#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
186#[ts(export_to = "Artifact.ts")]
187#[serde(rename_all = "camelCase")]
188pub enum PathSubType {
189 Sketch,
190 Region,
191}
192
193#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
194#[ts(export_to = "Artifact.ts")]
195#[serde(rename_all = "camelCase")]
196pub struct Segment {
197 pub id: ArtifactId,
198 pub path_id: ArtifactId,
199 #[serde(default, skip_serializing_if = "Option::is_none")]
202 pub original_seg_id: Option<ArtifactId>,
203 #[serde(default, skip_serializing_if = "Option::is_none")]
204 pub surface_id: Option<ArtifactId>,
205 pub edge_ids: Vec<ArtifactId>,
206 #[serde(default, skip_serializing_if = "Option::is_none")]
207 pub edge_cut_id: Option<ArtifactId>,
208 pub code_ref: CodeRef,
209 pub common_surface_ids: Vec<ArtifactId>,
210}
211
212#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
214#[ts(export_to = "Artifact.ts")]
215#[serde(rename_all = "camelCase")]
216pub struct Sweep {
217 pub id: ArtifactId,
218 pub sub_type: SweepSubType,
219 pub path_id: ArtifactId,
220 pub surface_ids: Vec<ArtifactId>,
221 pub edge_ids: Vec<ArtifactId>,
222 pub code_ref: CodeRef,
223 pub trajectory_id: Option<ArtifactId>,
227 pub method: kittycad_modeling_cmds::shared::ExtrudeMethod,
228 pub consumed: bool,
230 #[serde(default, skip_serializing_if = "Vec::is_empty")]
232 pub pattern_ids: Vec<ArtifactId>,
233}
234
235#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
236#[ts(export_to = "Artifact.ts")]
237#[serde(rename_all = "camelCase")]
238pub enum SweepSubType {
239 Extrusion,
240 ExtrusionTwist,
241 Revolve,
242 RevolveAboutEdge,
243 Loft,
244 Blend,
245 Sweep,
246}
247
248#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
249#[ts(export_to = "Artifact.ts")]
250#[serde(rename_all = "camelCase")]
251pub struct Solid2d {
252 pub id: ArtifactId,
253 pub path_id: ArtifactId,
254}
255
256#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
257#[ts(export_to = "Artifact.ts")]
258#[serde(rename_all = "camelCase")]
259pub struct PrimitiveFace {
260 pub id: ArtifactId,
261 pub solid_id: ArtifactId,
262 pub code_ref: CodeRef,
263}
264
265#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
266#[ts(export_to = "Artifact.ts")]
267#[serde(rename_all = "camelCase")]
268pub struct PrimitiveEdge {
269 pub id: ArtifactId,
270 pub solid_id: ArtifactId,
271 pub code_ref: CodeRef,
272}
273
274#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
275#[ts(export_to = "Artifact.ts")]
276#[serde(rename_all = "camelCase")]
277pub struct PlaneOfFace {
278 pub id: ArtifactId,
279 pub face_id: ArtifactId,
280 pub code_ref: CodeRef,
281}
282
283#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
284#[ts(export_to = "Artifact.ts")]
285#[serde(rename_all = "camelCase")]
286pub struct StartSketchOnFace {
287 pub id: ArtifactId,
288 pub face_id: ArtifactId,
289 pub code_ref: CodeRef,
290}
291
292#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
293#[ts(export_to = "Artifact.ts")]
294#[serde(rename_all = "camelCase")]
295pub struct StartSketchOnPlane {
296 pub id: ArtifactId,
297 pub plane_id: ArtifactId,
298 pub code_ref: CodeRef,
299}
300
301#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
302#[ts(export_to = "Artifact.ts")]
303#[serde(rename_all = "camelCase")]
304pub struct SketchBlock {
305 pub id: ArtifactId,
306 #[serde(default, skip_serializing_if = "Option::is_none")]
308 pub standard_plane: Option<PlaneName>,
309 #[serde(default, skip_serializing_if = "Option::is_none")]
311 pub plane_id: Option<ArtifactId>,
312 #[serde(default, skip_serializing_if = "Option::is_none")]
316 pub path_id: Option<ArtifactId>,
317 pub code_ref: CodeRef,
318 pub sketch_id: ObjectId,
320}
321
322#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
323#[ts(export_to = "Artifact.ts")]
324#[serde(rename_all = "camelCase")]
325pub enum SketchBlockConstraintType {
326 Angle,
327 Coincident,
328 Distance,
329 Diameter,
330 EqualRadius,
331 Fixed,
332 HorizontalDistance,
333 VerticalDistance,
334 Horizontal,
335 LinesEqualLength,
336 Midpoint,
337 Parallel,
338 Perpendicular,
339 Radius,
340 Symmetric,
341 Tangent,
342 Vertical,
343}
344
345impl From<&Constraint> for SketchBlockConstraintType {
346 fn from(constraint: &Constraint) -> Self {
347 match constraint {
348 Constraint::Coincident { .. } => SketchBlockConstraintType::Coincident,
349 Constraint::Distance { .. } => SketchBlockConstraintType::Distance,
350 Constraint::Diameter { .. } => SketchBlockConstraintType::Diameter,
351 Constraint::EqualRadius { .. } => SketchBlockConstraintType::EqualRadius,
352 Constraint::Fixed { .. } => SketchBlockConstraintType::Fixed,
353 Constraint::HorizontalDistance { .. } => SketchBlockConstraintType::HorizontalDistance,
354 Constraint::VerticalDistance { .. } => SketchBlockConstraintType::VerticalDistance,
355 Constraint::Horizontal { .. } => SketchBlockConstraintType::Horizontal,
356 Constraint::LinesEqualLength { .. } => SketchBlockConstraintType::LinesEqualLength,
357 Constraint::Midpoint(..) => SketchBlockConstraintType::Midpoint,
358 Constraint::Parallel { .. } => SketchBlockConstraintType::Parallel,
359 Constraint::Perpendicular { .. } => SketchBlockConstraintType::Perpendicular,
360 Constraint::Radius { .. } => SketchBlockConstraintType::Radius,
361 Constraint::Symmetric { .. } => SketchBlockConstraintType::Symmetric,
362 Constraint::Tangent { .. } => SketchBlockConstraintType::Tangent,
363 Constraint::Vertical { .. } => SketchBlockConstraintType::Vertical,
364 Constraint::Angle(..) => SketchBlockConstraintType::Angle,
365 }
366 }
367}
368
369#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
370#[ts(export_to = "Artifact.ts")]
371#[serde(rename_all = "camelCase")]
372pub struct SketchBlockConstraint {
373 pub id: ArtifactId,
374 pub sketch_id: ObjectId,
376 pub constraint_id: ObjectId,
378 pub constraint_type: SketchBlockConstraintType,
379 pub code_ref: CodeRef,
380}
381
382#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
383#[ts(export_to = "Artifact.ts")]
384#[serde(rename_all = "camelCase")]
385pub struct Wall {
386 pub id: ArtifactId,
387 pub seg_id: ArtifactId,
388 pub edge_cut_edge_ids: Vec<ArtifactId>,
389 pub sweep_id: ArtifactId,
390 pub path_ids: Vec<ArtifactId>,
391 pub face_code_ref: CodeRef,
394 pub cmd_id: uuid::Uuid,
396}
397
398#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
399#[ts(export_to = "Artifact.ts")]
400#[serde(rename_all = "camelCase")]
401pub struct Cap {
402 pub id: ArtifactId,
403 pub sub_type: CapSubType,
404 pub edge_cut_edge_ids: Vec<ArtifactId>,
405 pub sweep_id: ArtifactId,
406 pub path_ids: Vec<ArtifactId>,
407 pub face_code_ref: CodeRef,
410 pub cmd_id: uuid::Uuid,
412}
413
414#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
415#[ts(export_to = "Artifact.ts")]
416#[serde(rename_all = "camelCase")]
417pub enum CapSubType {
418 Start,
419 End,
420}
421
422#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
423#[ts(export_to = "Artifact.ts")]
424#[serde(rename_all = "camelCase")]
425pub struct SweepEdge {
426 pub id: ArtifactId,
427 pub sub_type: SweepEdgeSubType,
428 pub seg_id: ArtifactId,
429 pub cmd_id: uuid::Uuid,
430 #[serde(skip)]
432 pub index: usize,
433 pub sweep_id: ArtifactId,
434 pub common_surface_ids: Vec<ArtifactId>,
435}
436
437#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
438#[ts(export_to = "Artifact.ts")]
439#[serde(rename_all = "camelCase")]
440pub enum SweepEdgeSubType {
441 Opposite,
442 Adjacent,
443}
444
445#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
446#[ts(export_to = "Artifact.ts")]
447#[serde(rename_all = "camelCase")]
448pub struct EdgeCut {
449 pub id: ArtifactId,
450 pub sub_type: EdgeCutSubType,
451 pub consumed_edge_id: ArtifactId,
452 pub edge_ids: Vec<ArtifactId>,
453 #[serde(default, skip_serializing_if = "Option::is_none")]
454 pub surface_id: Option<ArtifactId>,
455 pub code_ref: CodeRef,
456}
457
458#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
459#[ts(export_to = "Artifact.ts")]
460#[serde(rename_all = "camelCase")]
461pub enum EdgeCutSubType {
462 Fillet,
463 Chamfer,
464 Custom,
465}
466
467impl From<kcmc::shared::CutType> for EdgeCutSubType {
468 fn from(cut_type: kcmc::shared::CutType) -> Self {
469 match cut_type {
470 kcmc::shared::CutType::Fillet => EdgeCutSubType::Fillet,
471 kcmc::shared::CutType::Chamfer => EdgeCutSubType::Chamfer,
472 }
473 }
474}
475
476impl From<kcmc::shared::CutTypeV2> for EdgeCutSubType {
477 fn from(cut_type: kcmc::shared::CutTypeV2) -> Self {
478 match cut_type {
479 kcmc::shared::CutTypeV2::Fillet { .. } => EdgeCutSubType::Fillet,
480 kcmc::shared::CutTypeV2::Chamfer { .. } => EdgeCutSubType::Chamfer,
481 kcmc::shared::CutTypeV2::Custom { .. } => EdgeCutSubType::Custom,
482 _other => EdgeCutSubType::Custom,
484 }
485 }
486}
487
488#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
489#[ts(export_to = "Artifact.ts")]
490#[serde(rename_all = "camelCase")]
491pub struct EdgeCutEdge {
492 pub id: ArtifactId,
493 pub edge_cut_id: ArtifactId,
494 pub surface_id: ArtifactId,
495}
496
497#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
498#[ts(export_to = "Artifact.ts")]
499#[serde(rename_all = "camelCase")]
500pub struct Helix {
501 pub id: ArtifactId,
502 pub axis_id: Option<ArtifactId>,
505 pub code_ref: CodeRef,
506 pub trajectory_sweep_id: Option<ArtifactId>,
508 pub consumed: bool,
510}
511
512#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
513#[ts(export_to = "Artifact.ts")]
514#[serde(rename_all = "camelCase")]
515pub struct Pattern {
516 pub id: ArtifactId,
517 pub sub_type: PatternSubType,
518 pub source_id: ArtifactId,
520 pub copy_ids: Vec<ArtifactId>,
522 pub copy_face_ids: Vec<ArtifactId>,
524 pub copy_edge_ids: Vec<ArtifactId>,
526 pub code_ref: CodeRef,
527}
528
529#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
530#[ts(export_to = "Artifact.ts")]
531#[serde(rename_all = "camelCase")]
532pub enum PatternSubType {
533 Circular,
534 Linear,
535 Transform,
536}
537
538#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
539#[ts(export_to = "Artifact.ts")]
540#[serde(tag = "type", rename_all = "camelCase")]
541#[expect(clippy::large_enum_variant)]
542pub enum Artifact {
543 CompositeSolid(CompositeSolid),
544 Plane(Plane),
545 Path(Path),
546 Segment(Segment),
547 Solid2d(Solid2d),
548 PrimitiveFace(PrimitiveFace),
549 PrimitiveEdge(PrimitiveEdge),
550 PlaneOfFace(PlaneOfFace),
551 StartSketchOnFace(StartSketchOnFace),
552 StartSketchOnPlane(StartSketchOnPlane),
553 SketchBlock(SketchBlock),
554 SketchBlockConstraint(SketchBlockConstraint),
555 Sweep(Sweep),
556 Wall(Wall),
557 Cap(Cap),
558 SweepEdge(SweepEdge),
559 EdgeCut(EdgeCut),
560 EdgeCutEdge(EdgeCutEdge),
561 Helix(Helix),
562 Pattern(Pattern),
563}
564
565impl Artifact {
566 pub(crate) fn id(&self) -> ArtifactId {
567 match self {
568 Artifact::CompositeSolid(a) => a.id,
569 Artifact::Plane(a) => a.id,
570 Artifact::Path(a) => a.id,
571 Artifact::Segment(a) => a.id,
572 Artifact::Solid2d(a) => a.id,
573 Artifact::PrimitiveFace(a) => a.id,
574 Artifact::PrimitiveEdge(a) => a.id,
575 Artifact::StartSketchOnFace(a) => a.id,
576 Artifact::StartSketchOnPlane(a) => a.id,
577 Artifact::SketchBlock(a) => a.id,
578 Artifact::SketchBlockConstraint(a) => a.id,
579 Artifact::PlaneOfFace(a) => a.id,
580 Artifact::Sweep(a) => a.id,
581 Artifact::Wall(a) => a.id,
582 Artifact::Cap(a) => a.id,
583 Artifact::SweepEdge(a) => a.id,
584 Artifact::EdgeCut(a) => a.id,
585 Artifact::EdgeCutEdge(a) => a.id,
586 Artifact::Helix(a) => a.id,
587 Artifact::Pattern(a) => a.id,
588 }
589 }
590
591 pub fn code_ref(&self) -> Option<&CodeRef> {
594 match self {
595 Artifact::CompositeSolid(a) => Some(&a.code_ref),
596 Artifact::Plane(a) => Some(&a.code_ref),
597 Artifact::Path(a) => Some(&a.code_ref),
598 Artifact::Segment(a) => Some(&a.code_ref),
599 Artifact::Solid2d(_) => None,
600 Artifact::PrimitiveFace(a) => Some(&a.code_ref),
601 Artifact::PrimitiveEdge(a) => Some(&a.code_ref),
602 Artifact::StartSketchOnFace(a) => Some(&a.code_ref),
603 Artifact::StartSketchOnPlane(a) => Some(&a.code_ref),
604 Artifact::SketchBlock(a) => Some(&a.code_ref),
605 Artifact::SketchBlockConstraint(a) => Some(&a.code_ref),
606 Artifact::PlaneOfFace(a) => Some(&a.code_ref),
607 Artifact::Sweep(a) => Some(&a.code_ref),
608 Artifact::Wall(_) => None,
609 Artifact::Cap(_) => None,
610 Artifact::SweepEdge(_) => None,
611 Artifact::EdgeCut(a) => Some(&a.code_ref),
612 Artifact::EdgeCutEdge(_) => None,
613 Artifact::Helix(a) => Some(&a.code_ref),
614 Artifact::Pattern(a) => Some(&a.code_ref),
615 }
616 }
617
618 pub fn face_code_ref(&self) -> Option<&CodeRef> {
621 match self {
622 Artifact::CompositeSolid(_)
623 | Artifact::Plane(_)
624 | Artifact::Path(_)
625 | Artifact::Segment(_)
626 | Artifact::Solid2d(_)
627 | Artifact::PrimitiveEdge(_)
628 | Artifact::StartSketchOnFace(_)
629 | Artifact::PlaneOfFace(_)
630 | Artifact::StartSketchOnPlane(_)
631 | Artifact::SketchBlock(_)
632 | Artifact::SketchBlockConstraint(_)
633 | Artifact::Sweep(_) => None,
634 Artifact::PrimitiveFace(a) => Some(&a.code_ref),
635 Artifact::Wall(a) => Some(&a.face_code_ref),
636 Artifact::Cap(a) => Some(&a.face_code_ref),
637 Artifact::SweepEdge(_)
638 | Artifact::EdgeCut(_)
639 | Artifact::EdgeCutEdge(_)
640 | Artifact::Helix(_)
641 | Artifact::Pattern(_) => None,
642 }
643 }
644
645 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
648 match self {
649 Artifact::CompositeSolid(a) => a.merge(new),
650 Artifact::Plane(a) => a.merge(new),
651 Artifact::Path(a) => a.merge(new),
652 Artifact::Segment(a) => a.merge(new),
653 Artifact::Solid2d(_) => Some(new),
654 Artifact::PrimitiveFace(_) => Some(new),
655 Artifact::PrimitiveEdge(_) => Some(new),
656 Artifact::StartSketchOnFace { .. } => Some(new),
657 Artifact::StartSketchOnPlane { .. } => Some(new),
658 Artifact::SketchBlock { .. } => Some(new),
659 Artifact::SketchBlockConstraint { .. } => Some(new),
660 Artifact::PlaneOfFace { .. } => Some(new),
661 Artifact::Sweep(a) => a.merge(new),
662 Artifact::Wall(a) => a.merge(new),
663 Artifact::Cap(a) => a.merge(new),
664 Artifact::SweepEdge(_) => Some(new),
665 Artifact::EdgeCut(a) => a.merge(new),
666 Artifact::EdgeCutEdge(_) => Some(new),
667 Artifact::Helix(a) => a.merge(new),
668 Artifact::Pattern(a) => a.merge(new),
669 }
670 }
671}
672
673impl CompositeSolid {
674 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
675 let Artifact::CompositeSolid(new) = new else {
676 return Some(new);
677 };
678 merge_ids(&mut self.solid_ids, new.solid_ids);
679 merge_ids(&mut self.tool_ids, new.tool_ids);
680 merge_opt_id(&mut self.composite_solid_id, new.composite_solid_id);
681 merge_ids(&mut self.pattern_ids, new.pattern_ids);
682 self.output_index = new.output_index;
683 self.consumed = new.consumed;
684
685 None
686 }
687}
688
689impl Plane {
690 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
691 let Artifact::Plane(new) = new else {
692 return Some(new);
693 };
694 merge_ids(&mut self.path_ids, new.path_ids);
695
696 None
697 }
698}
699
700impl Path {
701 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
702 let Artifact::Path(new) = new else {
703 return Some(new);
704 };
705 merge_opt_id(&mut self.sweep_id, new.sweep_id);
706 merge_opt_id(&mut self.trajectory_sweep_id, new.trajectory_sweep_id);
707 merge_ids(&mut self.seg_ids, new.seg_ids);
708 merge_opt_id(&mut self.solid2d_id, new.solid2d_id);
709 merge_opt_id(&mut self.composite_solid_id, new.composite_solid_id);
710 merge_opt_id(&mut self.sketch_block_id, new.sketch_block_id);
711 merge_opt_id(&mut self.origin_path_id, new.origin_path_id);
712 merge_opt_id(&mut self.inner_path_id, new.inner_path_id);
713 merge_opt_id(&mut self.outer_path_id, new.outer_path_id);
714 merge_ids(&mut self.pattern_ids, new.pattern_ids);
715 self.consumed = new.consumed;
716
717 None
718 }
719}
720
721impl Segment {
722 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
723 let Artifact::Segment(new) = new else {
724 return Some(new);
725 };
726 merge_opt_id(&mut self.original_seg_id, new.original_seg_id);
727 merge_opt_id(&mut self.surface_id, new.surface_id);
728 merge_ids(&mut self.edge_ids, new.edge_ids);
729 merge_opt_id(&mut self.edge_cut_id, new.edge_cut_id);
730 merge_ids(&mut self.common_surface_ids, new.common_surface_ids);
731
732 None
733 }
734}
735
736impl Sweep {
737 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
738 let Artifact::Sweep(new) = new else {
739 return Some(new);
740 };
741 merge_ids(&mut self.surface_ids, new.surface_ids);
742 merge_ids(&mut self.edge_ids, new.edge_ids);
743 merge_opt_id(&mut self.trajectory_id, new.trajectory_id);
744 merge_ids(&mut self.pattern_ids, new.pattern_ids);
745 self.consumed = new.consumed;
746
747 None
748 }
749}
750
751impl Wall {
752 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
753 let Artifact::Wall(new) = new else {
754 return Some(new);
755 };
756 merge_ids(&mut self.edge_cut_edge_ids, new.edge_cut_edge_ids);
757 merge_ids(&mut self.path_ids, new.path_ids);
758
759 None
760 }
761}
762
763impl Cap {
764 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
765 let Artifact::Cap(new) = new else {
766 return Some(new);
767 };
768 merge_ids(&mut self.edge_cut_edge_ids, new.edge_cut_edge_ids);
769 merge_ids(&mut self.path_ids, new.path_ids);
770
771 None
772 }
773}
774
775impl EdgeCut {
776 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
777 let Artifact::EdgeCut(new) = new else {
778 return Some(new);
779 };
780 merge_opt_id(&mut self.surface_id, new.surface_id);
781 merge_ids(&mut self.edge_ids, new.edge_ids);
782
783 None
784 }
785}
786
787impl Helix {
788 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
789 let Artifact::Helix(new) = new else {
790 return Some(new);
791 };
792 merge_opt_id(&mut self.axis_id, new.axis_id);
793 merge_opt_id(&mut self.trajectory_sweep_id, new.trajectory_sweep_id);
794 self.consumed = new.consumed;
795
796 None
797 }
798}
799
800impl Pattern {
801 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
802 let Artifact::Pattern(new) = new else {
803 return Some(new);
804 };
805 merge_ids(&mut self.copy_ids, new.copy_ids);
806 merge_ids(&mut self.copy_face_ids, new.copy_face_ids);
807 merge_ids(&mut self.copy_edge_ids, new.copy_edge_ids);
808
809 None
810 }
811}
812
813#[derive(Debug, Clone, Default, PartialEq, Serialize, ts_rs::TS)]
814#[ts(export_to = "Artifact.ts")]
815#[serde(rename_all = "camelCase")]
816pub struct ArtifactGraph {
817 map: IndexMap<ArtifactId, Artifact>,
818 pub(super) item_count: usize,
819}
820
821impl ArtifactGraph {
822 pub fn get(&self, id: &ArtifactId) -> Option<&Artifact> {
823 self.map.get(id)
824 }
825
826 pub fn len(&self) -> usize {
827 self.map.len()
828 }
829
830 pub fn is_empty(&self) -> bool {
831 self.map.is_empty()
832 }
833
834 #[cfg(test)]
835 pub(crate) fn iter(&self) -> impl Iterator<Item = (&ArtifactId, &Artifact)> {
836 self.map.iter()
837 }
838
839 pub fn values(&self) -> impl Iterator<Item = &Artifact> {
840 self.map.values()
841 }
842
843 pub fn clear(&mut self) {
844 self.map.clear();
845 self.item_count = 0;
846 }
847
848 fn into_map(self) -> IndexMap<ArtifactId, Artifact> {
850 self.map
851 }
852}
853
854#[derive(Debug, Clone)]
855struct ImportCodeRef {
856 node_path: NodePath,
857 range: SourceRange,
858}
859
860fn import_statement_code_refs(
861 ast: &Node<Program>,
862 module_infos: &ModuleInfoMap,
863 programs: &crate::execution::ProgramLookup,
864 cached_body_items: usize,
865) -> FnvHashMap<ModuleId, ImportCodeRef> {
866 let mut code_refs = FnvHashMap::default();
867 for body_item in &ast.body {
868 let BodyItem::ImportStatement(import_stmt) = body_item else {
869 continue;
870 };
871 if !matches!(import_stmt.selector, ImportSelector::None { .. }) {
872 continue;
873 }
874 let Some(module_id) = module_id_for_import_path(module_infos, &import_stmt.path) else {
875 continue;
876 };
877 let range = SourceRange::from(import_stmt);
878 let node_path = NodePath::from_range(programs, cached_body_items, range).unwrap_or_default();
879 code_refs.entry(module_id).or_insert(ImportCodeRef { node_path, range });
880 }
881 code_refs
882}
883
884fn module_id_for_import_path(module_infos: &ModuleInfoMap, import_path: &ImportPath) -> Option<ModuleId> {
885 let import_path = match import_path {
886 ImportPath::Kcl { filename } => filename,
887 ImportPath::Foreign { path } => path,
888 ImportPath::Std { .. } => return None,
889 };
890
891 module_infos.iter().find_map(|(module_id, module_info)| {
892 if let ModulePath::Local {
893 original_import_path: Some(original_import_path),
894 ..
895 } = &module_info.path
896 && original_import_path == import_path
897 {
898 return Some(*module_id);
899 }
900 None
901 })
902}
903
904fn code_ref_for_range(
905 programs: &crate::execution::ProgramLookup,
906 cached_body_items: usize,
907 range: SourceRange,
908 import_code_refs: &FnvHashMap<ModuleId, ImportCodeRef>,
909) -> (SourceRange, NodePath) {
910 if let Some(code_ref) = import_code_refs.get(&range.module_id()) {
911 return (code_ref.range, code_ref.node_path.clone());
912 }
913
914 (
915 range,
916 NodePath::from_range(programs, cached_body_items, range).unwrap_or_default(),
917 )
918}
919
920pub(super) fn build_artifact_graph(
924 artifact_commands: &[ArtifactCommand],
925 responses: &IndexMap<Uuid, WebSocketResponse>,
926 ast: &Node<Program>,
927 exec_artifacts: &mut IndexMap<ArtifactId, Artifact>,
928 initial_graph: ArtifactGraph,
929 programs: &crate::execution::ProgramLookup,
930 module_infos: &ModuleInfoMap,
931) -> Result<ArtifactGraph, KclError> {
932 let item_count = initial_graph.item_count;
933 let mut map = initial_graph.into_map();
934
935 let mut path_to_plane_id_map = FnvHashMap::default();
936 let mut current_plane_id = None;
937 let import_code_refs = import_statement_code_refs(ast, module_infos, programs, item_count);
938
939 for exec_artifact in exec_artifacts.values_mut() {
942 fill_in_node_paths(exec_artifact, programs, item_count, &import_code_refs);
945 }
946
947 for artifact_command in artifact_commands {
948 if let ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) = artifact_command.command {
949 current_plane_id = Some(entity_id);
950 }
951 if let ModelingCmd::StartPath(_) = artifact_command.command
956 && let Some(plane_id) = current_plane_id
957 {
958 path_to_plane_id_map.insert(artifact_command.cmd_id, plane_id);
959 }
960 if let ModelingCmd::SketchModeDisable(_) = artifact_command.command {
961 current_plane_id = None;
962 }
963
964 let flattened_responses = flatten_modeling_command_responses(responses);
965 let artifact_updates = artifacts_to_update(
966 &map,
967 artifact_command,
968 &flattened_responses,
969 &path_to_plane_id_map,
970 programs,
971 item_count,
972 exec_artifacts,
973 &import_code_refs,
974 )?;
975 for artifact in artifact_updates {
976 merge_artifact_into_map(&mut map, artifact);
978 }
979 }
980
981 for exec_artifact in exec_artifacts.values() {
982 merge_artifact_into_map(&mut map, exec_artifact.clone());
983 }
984
985 Ok(ArtifactGraph {
986 map,
987 item_count: item_count + ast.body.len(),
988 })
989}
990
991fn fill_in_node_paths(
994 artifact: &mut Artifact,
995 programs: &crate::execution::ProgramLookup,
996 cached_body_items: usize,
997 import_code_refs: &FnvHashMap<ModuleId, ImportCodeRef>,
998) {
999 match artifact {
1000 Artifact::StartSketchOnFace(face) if face.code_ref.node_path.is_empty() => {
1001 let (range, node_path) =
1002 code_ref_for_range(programs, cached_body_items, face.code_ref.range, import_code_refs);
1003 face.code_ref.range = range;
1004 face.code_ref.node_path = node_path;
1005 }
1006 Artifact::StartSketchOnPlane(plane) if plane.code_ref.node_path.is_empty() => {
1007 let (range, node_path) =
1008 code_ref_for_range(programs, cached_body_items, plane.code_ref.range, import_code_refs);
1009 plane.code_ref.range = range;
1010 plane.code_ref.node_path = node_path;
1011 }
1012 Artifact::SketchBlock(block) if block.code_ref.node_path.is_empty() => {
1013 let (range, node_path) =
1014 code_ref_for_range(programs, cached_body_items, block.code_ref.range, import_code_refs);
1015 block.code_ref.range = range;
1016 block.code_ref.node_path = node_path;
1017 }
1018 Artifact::SketchBlockConstraint(constraint) if constraint.code_ref.node_path.is_empty() => {
1019 constraint.code_ref.node_path =
1020 NodePath::from_range(programs, cached_body_items, constraint.code_ref.range).unwrap_or_default();
1021 }
1022 _ => {}
1023 }
1024}
1025
1026fn flatten_modeling_command_responses(
1029 responses: &IndexMap<Uuid, WebSocketResponse>,
1030) -> FnvHashMap<Uuid, OkModelingCmdResponse> {
1031 let mut map = FnvHashMap::default();
1032 for (cmd_id, ws_response) in responses {
1033 let WebSocketResponse::Success(response) = ws_response else {
1034 continue;
1036 };
1037 match &response.resp {
1038 OkWebSocketResponseData::Modeling { modeling_response } => {
1039 map.insert(*cmd_id, modeling_response.clone());
1040 }
1041 OkWebSocketResponseData::ModelingBatch { responses } =>
1042 {
1043 #[expect(
1044 clippy::iter_over_hash_type,
1045 reason = "Since we're moving entries to another unordered map, it's fine that the order is undefined"
1046 )]
1047 for (cmd_id, batch_response) in responses {
1048 if let BatchResponse::Success {
1049 response: modeling_response,
1050 } = batch_response
1051 {
1052 map.insert(*cmd_id.as_ref(), modeling_response.clone());
1053 }
1054 }
1055 }
1056 OkWebSocketResponseData::IceServerInfo { .. }
1057 | OkWebSocketResponseData::TrickleIce { .. }
1058 | OkWebSocketResponseData::SdpAnswer { .. }
1059 | OkWebSocketResponseData::Export { .. }
1060 | OkWebSocketResponseData::MetricsRequest { .. }
1061 | OkWebSocketResponseData::ModelingSessionData { .. }
1062 | OkWebSocketResponseData::Debug { .. }
1063 | OkWebSocketResponseData::Pong { .. } => {}
1064 _other => {}
1065 }
1066 }
1067
1068 map
1069}
1070
1071fn merge_artifact_into_map(map: &mut IndexMap<ArtifactId, Artifact>, new_artifact: Artifact) {
1072 fn is_primitive_artifact(artifact: &Artifact) -> bool {
1073 matches!(artifact, Artifact::PrimitiveFace(_) | Artifact::PrimitiveEdge(_))
1074 }
1075
1076 let id = new_artifact.id();
1077 let Some(old_artifact) = map.get_mut(&id) else {
1078 map.insert(id, new_artifact);
1080 return;
1081 };
1082
1083 if is_primitive_artifact(&new_artifact) && !is_primitive_artifact(old_artifact) {
1087 return;
1088 }
1089
1090 if let Some(replacement) = old_artifact.merge(new_artifact) {
1091 *old_artifact = replacement;
1092 }
1093}
1094
1095fn merge_ids(base: &mut Vec<ArtifactId>, new: Vec<ArtifactId>) {
1099 let original_len = base.len();
1100 for id in new {
1101 let original_base = &base[..original_len];
1103 if !original_base.contains(&id) {
1104 base.push(id);
1105 }
1106 }
1107}
1108
1109fn merge_opt_id(base: &mut Option<ArtifactId>, new: Option<ArtifactId>) {
1111 *base = new;
1113}
1114
1115fn pattern_source_ids(artifacts: &IndexMap<ArtifactId, Artifact>, source_id: ArtifactId) -> Vec<ArtifactId> {
1116 let mut source_ids = vec![source_id];
1117
1118 if let Some(Artifact::Path(path)) = artifacts.get(&source_id) {
1119 if let Some(sweep_id) = path.sweep_id {
1120 source_ids.push(sweep_id);
1121 }
1122 if let Some(composite_solid_id) = path.composite_solid_id {
1123 source_ids.push(composite_solid_id);
1124 }
1125 }
1126
1127 for artifact in artifacts.values() {
1128 match artifact {
1129 Artifact::Sweep(sweep) if sweep.path_id == source_id => source_ids.push(sweep.id),
1130 Artifact::CompositeSolid(composite)
1131 if composite.solid_ids.contains(&source_id) || composite.tool_ids.contains(&source_id) =>
1132 {
1133 source_ids.push(composite.id)
1134 }
1135 _ => {}
1136 }
1137 }
1138
1139 let mut unique = Vec::new();
1140 merge_ids(&mut unique, source_ids);
1141 unique
1142}
1143
1144fn pattern_artifact_updates(
1145 artifacts: &IndexMap<ArtifactId, Artifact>,
1146 pattern_id: ArtifactId,
1147 sub_type: PatternSubType,
1148 source_id: ArtifactId,
1149 face_edge_infos: &[kcmc::output::FaceEdgeInfo],
1150 code_ref: CodeRef,
1151) -> Vec<Artifact> {
1152 let copy_ids = face_edge_infos
1153 .iter()
1154 .map(|info| ArtifactId::new(info.object_id))
1155 .collect::<Vec<_>>();
1156 let copy_face_ids = face_edge_infos
1157 .iter()
1158 .flat_map(|info| info.faces.iter().copied().map(ArtifactId::new))
1159 .collect::<Vec<_>>();
1160 let copy_edge_ids = face_edge_infos
1161 .iter()
1162 .flat_map(|info| info.edges.iter().copied().map(ArtifactId::new))
1163 .collect::<Vec<_>>();
1164
1165 let source_ids = pattern_source_ids(artifacts, source_id);
1166 let mut return_arr = vec![Artifact::Pattern(Pattern {
1167 id: pattern_id,
1168 sub_type,
1169 source_id,
1170 copy_ids,
1171 copy_face_ids,
1172 copy_edge_ids,
1173 code_ref,
1174 })];
1175
1176 for source_id in source_ids {
1177 let Some(artifact) = artifacts.get(&source_id) else {
1178 continue;
1179 };
1180 match artifact {
1181 Artifact::Path(path) => {
1182 let mut new_path = path.clone();
1183 new_path.pattern_ids = vec![pattern_id];
1184 return_arr.push(Artifact::Path(new_path));
1185 }
1186 Artifact::Sweep(sweep) => {
1187 let mut new_sweep = sweep.clone();
1188 new_sweep.pattern_ids = vec![pattern_id];
1189 return_arr.push(Artifact::Sweep(new_sweep));
1190 }
1191 Artifact::CompositeSolid(composite) => {
1192 let mut new_composite = composite.clone();
1193 new_composite.pattern_ids = vec![pattern_id];
1194 return_arr.push(Artifact::CompositeSolid(new_composite));
1195 }
1196 _ => {}
1197 }
1198 }
1199
1200 return_arr
1201}
1202
1203#[allow(clippy::too_many_arguments)]
1204fn artifacts_to_update(
1205 artifacts: &IndexMap<ArtifactId, Artifact>,
1206 artifact_command: &ArtifactCommand,
1207 responses: &FnvHashMap<Uuid, OkModelingCmdResponse>,
1208 path_to_plane_id_map: &FnvHashMap<Uuid, Uuid>,
1209 programs: &crate::execution::ProgramLookup,
1210 cached_body_items: usize,
1211 exec_artifacts: &IndexMap<ArtifactId, Artifact>,
1212 import_code_refs: &FnvHashMap<ModuleId, ImportCodeRef>,
1213) -> Result<Vec<Artifact>, KclError> {
1214 let uuid = artifact_command.cmd_id;
1215 let response = responses.get(&uuid);
1216
1217 let path_to_node = Vec::new();
1221 let range = artifact_command.range;
1222 let (code_ref_range, node_path) = code_ref_for_range(programs, cached_body_items, range, import_code_refs);
1223 let code_ref = CodeRef {
1224 range: code_ref_range,
1225 node_path,
1226 path_to_node,
1227 };
1228
1229 let id = ArtifactId::new(uuid);
1230 let cmd = &artifact_command.command;
1231
1232 match cmd {
1233 ModelingCmd::MakePlane(_) => {
1234 if range.is_synthetic() {
1235 return Ok(Vec::new());
1236 }
1237 return Ok(vec![Artifact::Plane(Plane {
1241 id,
1242 path_ids: Vec::new(),
1243 code_ref,
1244 })]);
1245 }
1246 ModelingCmd::FaceIsPlanar(FaceIsPlanar { object_id, .. }) => {
1247 return Ok(vec![Artifact::PlaneOfFace(PlaneOfFace {
1248 id,
1249 face_id: object_id.into(),
1250 code_ref,
1251 })]);
1252 }
1253 ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) => {
1254 let existing_plane = artifacts.get(&ArtifactId::new(*entity_id));
1255 match existing_plane {
1256 Some(Artifact::Wall(wall)) => {
1257 return Ok(vec![Artifact::Wall(Wall {
1258 id: entity_id.into(),
1259 seg_id: wall.seg_id,
1260 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1261 sweep_id: wall.sweep_id,
1262 path_ids: wall.path_ids.clone(),
1263 face_code_ref: wall.face_code_ref.clone(),
1264 cmd_id: artifact_command.cmd_id,
1265 })]);
1266 }
1267 Some(Artifact::Cap(cap)) => {
1268 return Ok(vec![Artifact::Cap(Cap {
1269 id: entity_id.into(),
1270 sub_type: cap.sub_type,
1271 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1272 sweep_id: cap.sweep_id,
1273 path_ids: cap.path_ids.clone(),
1274 face_code_ref: cap.face_code_ref.clone(),
1275 cmd_id: artifact_command.cmd_id,
1276 })]);
1277 }
1278 Some(_) | None => {
1279 let path_ids = match existing_plane {
1280 Some(Artifact::Plane(Plane { path_ids, .. })) => path_ids.clone(),
1281 _ => Vec::new(),
1282 };
1283 return Ok(vec![Artifact::Plane(Plane {
1285 id: entity_id.into(),
1286 path_ids,
1287 code_ref,
1288 })]);
1289 }
1290 }
1291 }
1292 ModelingCmd::StartPath(_) => {
1293 let mut return_arr = Vec::new();
1294 let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
1295 KclError::new_internal(KclErrorDetails::new(
1296 format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"),
1297 vec![range],
1298 ))
1299 })?;
1300 let sketch_block_id = exec_artifacts
1301 .values()
1302 .find(|a| {
1303 if let Artifact::SketchBlock(s) = a {
1304 if let Some(path_id) = s.path_id {
1305 path_id == id
1306 } else {
1307 false
1308 }
1309 } else {
1310 false
1311 }
1312 })
1313 .map(|a| a.id());
1314 return_arr.push(Artifact::Path(Path {
1315 id,
1316 sub_type: PathSubType::Sketch,
1317 plane_id: (*current_plane_id).into(),
1318 seg_ids: Vec::new(),
1319 sweep_id: None,
1320 trajectory_sweep_id: None,
1321 solid2d_id: None,
1322 code_ref,
1323 composite_solid_id: None,
1324 sketch_block_id,
1325 origin_path_id: None,
1326 inner_path_id: None,
1327 outer_path_id: None,
1328 pattern_ids: Vec::new(),
1329 consumed: false,
1330 }));
1331 let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
1332 if let Some(Artifact::Plane(plane)) = plane {
1333 let plane_code_ref = plane.code_ref.clone();
1334 return_arr.push(Artifact::Plane(Plane {
1335 id: (*current_plane_id).into(),
1336 path_ids: vec![id],
1337 code_ref: plane_code_ref,
1338 }));
1339 }
1340 if let Some(Artifact::Wall(wall)) = plane {
1341 return_arr.push(Artifact::Wall(Wall {
1342 id: (*current_plane_id).into(),
1343 seg_id: wall.seg_id,
1344 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1345 sweep_id: wall.sweep_id,
1346 path_ids: vec![id],
1347 face_code_ref: wall.face_code_ref.clone(),
1348 cmd_id: artifact_command.cmd_id,
1349 }));
1350 }
1351 if let Some(Artifact::Cap(cap)) = plane {
1352 return_arr.push(Artifact::Cap(Cap {
1353 id: (*current_plane_id).into(),
1354 sub_type: cap.sub_type,
1355 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1356 sweep_id: cap.sweep_id,
1357 path_ids: vec![id],
1358 face_code_ref: cap.face_code_ref.clone(),
1359 cmd_id: artifact_command.cmd_id,
1360 }));
1361 }
1362 return Ok(return_arr);
1363 }
1364 ModelingCmd::ClosePath(_) | ModelingCmd::ExtendPath(_) => {
1365 let path_id = ArtifactId::new(match cmd {
1366 ModelingCmd::ClosePath(c) => c.path_id,
1367 ModelingCmd::ExtendPath(e) => e.path.into(),
1368 _ => internal_error!(
1369 range,
1370 "Close or extend path command variant not handled: id={id:?}, cmd={cmd:?}"
1371 ),
1372 });
1373 let mut return_arr = Vec::new();
1374 return_arr.push(Artifact::Segment(Segment {
1375 id,
1376 path_id,
1377 original_seg_id: None,
1378 surface_id: None,
1379 edge_ids: Vec::new(),
1380 edge_cut_id: None,
1381 code_ref,
1382 common_surface_ids: Vec::new(),
1383 }));
1384 let path = artifacts.get(&path_id);
1385 if let Some(Artifact::Path(path)) = path {
1386 let mut new_path = path.clone();
1387 new_path.seg_ids = vec![id];
1388 return_arr.push(Artifact::Path(new_path));
1389 }
1390 if let Some(OkModelingCmdResponse::ClosePath(close_path)) = response {
1391 return_arr.push(Artifact::Solid2d(Solid2d {
1392 id: close_path.face_id.into(),
1393 path_id,
1394 }));
1395 if let Some(Artifact::Path(path)) = path {
1396 let mut new_path = path.clone();
1397 new_path.solid2d_id = Some(close_path.face_id.into());
1398 return_arr.push(Artifact::Path(new_path));
1399 }
1400 }
1401 return Ok(return_arr);
1402 }
1403 ModelingCmd::CreateRegion(kcmc::CreateRegion {
1404 object_id: origin_path_id,
1405 ..
1406 })
1407 | ModelingCmd::CreateRegionFromQueryPoint(kcmc::CreateRegionFromQueryPoint {
1408 object_id: origin_path_id,
1409 ..
1410 }) => {
1411 let mut return_arr = Vec::new();
1412 let origin_path = artifacts.get(&ArtifactId::new(*origin_path_id));
1413 let Some(Artifact::Path(path)) = origin_path else {
1414 internal_error!(
1415 range,
1416 "Expected to find an existing path for the origin path of CreateRegion or CreateRegionFromQueryPoint command, but found none: origin_path={origin_path:?}, cmd={cmd:?}"
1417 );
1418 };
1419 return_arr.push(Artifact::Path(Path {
1421 id,
1422 sub_type: PathSubType::Region,
1423 plane_id: path.plane_id,
1424 seg_ids: Vec::new(),
1425 consumed: false,
1426 sweep_id: None,
1427 trajectory_sweep_id: None,
1428 solid2d_id: None,
1429 code_ref: code_ref.clone(),
1430 composite_solid_id: None,
1431 sketch_block_id: None,
1432 origin_path_id: Some(ArtifactId::new(*origin_path_id)),
1433 inner_path_id: None,
1434 outer_path_id: None,
1435 pattern_ids: Vec::new(),
1436 }));
1437 let Some(
1440 OkModelingCmdResponse::CreateRegion(kcmc::output::CreateRegion { region_mapping, .. })
1441 | OkModelingCmdResponse::CreateRegionFromQueryPoint(kcmc::output::CreateRegionFromQueryPoint {
1442 region_mapping,
1443 ..
1444 }),
1445 ) = response
1446 else {
1447 return Ok(return_arr);
1448 };
1449 let original_segment_ids = path.seg_ids.iter().map(|p| p.0).collect::<Vec<_>>();
1452 let reverse = build_reverse_region_mapping(region_mapping, &original_segment_ids);
1453 for (original_segment_id, region_segment_ids) in reverse.iter() {
1454 for segment_id in region_segment_ids {
1455 return_arr.push(Artifact::Segment(Segment {
1456 id: ArtifactId::new(*segment_id),
1457 path_id: id,
1458 original_seg_id: Some(ArtifactId::new(*original_segment_id)),
1459 surface_id: None,
1460 edge_ids: Vec::new(),
1461 edge_cut_id: None,
1462 code_ref: code_ref.clone(),
1463 common_surface_ids: Vec::new(),
1464 }))
1465 }
1466 }
1467 return Ok(return_arr);
1468 }
1469 ModelingCmd::Solid3dGetFaceUuid(kcmc::Solid3dGetFaceUuid { object_id, .. }) => {
1470 let Some(OkModelingCmdResponse::Solid3dGetFaceUuid(face_uuid)) = response else {
1471 return Ok(Vec::new());
1472 };
1473
1474 return Ok(vec![Artifact::PrimitiveFace(PrimitiveFace {
1475 id: face_uuid.face_id.into(),
1476 solid_id: (*object_id).into(),
1477 code_ref,
1478 })]);
1479 }
1480 ModelingCmd::Solid3dGetEdgeUuid(kcmc::Solid3dGetEdgeUuid { object_id, .. }) => {
1481 let Some(OkModelingCmdResponse::Solid3dGetEdgeUuid(edge_uuid)) = response else {
1482 return Ok(Vec::new());
1483 };
1484
1485 return Ok(vec![Artifact::PrimitiveEdge(PrimitiveEdge {
1486 id: edge_uuid.edge_id.into(),
1487 solid_id: (*object_id).into(),
1488 code_ref,
1489 })]);
1490 }
1491 ModelingCmd::EntityLinearPatternTransform(pattern_cmd) => {
1492 let face_edge_infos = match response {
1493 Some(OkModelingCmdResponse::EntityLinearPatternTransform(resp)) => resp.entity_face_edge_ids.as_slice(),
1494 _ => &[],
1495 };
1496 return Ok(pattern_artifact_updates(
1497 artifacts,
1498 id,
1499 PatternSubType::Transform,
1500 ArtifactId::new(pattern_cmd.entity_id),
1501 face_edge_infos,
1502 code_ref,
1503 ));
1504 }
1505 ModelingCmd::EntityLinearPattern(pattern_cmd) => {
1506 let face_edge_infos = match response {
1507 Some(OkModelingCmdResponse::EntityLinearPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
1508 _ => &[],
1509 };
1510 return Ok(pattern_artifact_updates(
1511 artifacts,
1512 id,
1513 PatternSubType::Linear,
1514 ArtifactId::new(pattern_cmd.entity_id),
1515 face_edge_infos,
1516 code_ref,
1517 ));
1518 }
1519 ModelingCmd::EntityCircularPattern(pattern_cmd) => {
1520 let face_edge_infos = match response {
1521 Some(OkModelingCmdResponse::EntityCircularPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
1522 _ => &[],
1523 };
1524 return Ok(pattern_artifact_updates(
1525 artifacts,
1526 id,
1527 PatternSubType::Circular,
1528 ArtifactId::new(pattern_cmd.entity_id),
1529 face_edge_infos,
1530 code_ref,
1531 ));
1532 }
1533 ModelingCmd::EntityMirror(kcmc::EntityMirror {
1534 ids: original_path_ids, ..
1535 })
1536 | ModelingCmd::EntityMirrorAcrossEdge(kcmc::EntityMirrorAcrossEdge {
1537 ids: original_path_ids, ..
1538 }) => {
1539 let face_edge_infos = match response {
1540 Some(OkModelingCmdResponse::EntityMirror(resp)) => &resp.entity_face_edge_ids,
1541 Some(OkModelingCmdResponse::EntityMirrorAcrossEdge(resp)) => &resp.entity_face_edge_ids,
1542 _ => internal_error!(
1543 range,
1544 "Mirror response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
1545 ),
1546 };
1547 if original_path_ids.len() != face_edge_infos.len() {
1548 internal_error!(
1549 range,
1550 "EntityMirror or EntityMirrorAcrossEdge response has different number face edge info than original mirrored paths: id={id:?}, cmd={cmd:?}, response={response:?}"
1551 );
1552 }
1553 let mut return_arr = Vec::new();
1554 for (face_edge_info, original_path_id) in face_edge_infos.iter().zip(original_path_ids) {
1555 let original_path_id = ArtifactId::new(*original_path_id);
1556 let path_id = ArtifactId::new(face_edge_info.object_id);
1557 let mut path = if let Some(Artifact::Path(path)) = artifacts.get(&path_id) {
1560 path.clone()
1562 } else {
1563 let Some(Artifact::Path(original_path)) = artifacts.get(&original_path_id) else {
1566 internal_error!(
1568 range,
1569 "Couldn't find original path for mirror2d: original_path_id={original_path_id:?}, cmd={cmd:?}"
1570 );
1571 };
1572 Path {
1573 id: path_id,
1574 sub_type: original_path.sub_type,
1575 plane_id: original_path.plane_id,
1576 seg_ids: Vec::new(),
1577 sweep_id: None,
1578 trajectory_sweep_id: None,
1579 solid2d_id: None,
1580 code_ref: code_ref.clone(),
1581 composite_solid_id: None,
1582 sketch_block_id: None,
1583 origin_path_id: original_path.origin_path_id,
1584 inner_path_id: None,
1585 outer_path_id: None,
1586 pattern_ids: Vec::new(),
1587 consumed: false,
1588 }
1589 };
1590
1591 face_edge_info.edges.iter().for_each(|edge_id| {
1592 let edge_id = ArtifactId::new(*edge_id);
1593 return_arr.push(Artifact::Segment(Segment {
1594 id: edge_id,
1595 path_id: path.id,
1596 original_seg_id: None,
1597 surface_id: None,
1598 edge_ids: Vec::new(),
1599 edge_cut_id: None,
1600 code_ref: code_ref.clone(),
1601 common_surface_ids: Vec::new(),
1602 }));
1603 path.seg_ids.push(edge_id);
1605 });
1606
1607 return_arr.push(Artifact::Path(path));
1608 }
1609 return Ok(return_arr);
1610 }
1611 ModelingCmd::Extrude(kcmc::Extrude { target, .. })
1612 | ModelingCmd::TwistExtrude(kcmc::TwistExtrude { target, .. })
1613 | ModelingCmd::Revolve(kcmc::Revolve { target, .. })
1614 | ModelingCmd::RevolveAboutEdge(kcmc::RevolveAboutEdge { target, .. })
1615 | ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { target, .. }) => {
1616 let method = match cmd {
1618 ModelingCmd::Extrude(kcmc::Extrude { extrude_method, .. }) => *extrude_method,
1619 ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { extrude_method, .. }) => *extrude_method,
1620 ModelingCmd::TwistExtrude(_) | ModelingCmd::Sweep(_) => {
1622 kittycad_modeling_cmds::shared::ExtrudeMethod::Merge
1623 }
1624 ModelingCmd::Revolve(_) | ModelingCmd::RevolveAboutEdge(_) => {
1626 kittycad_modeling_cmds::shared::ExtrudeMethod::New
1627 }
1628 _ => kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
1629 };
1630 let sub_type = match cmd {
1631 ModelingCmd::Extrude(_) => SweepSubType::Extrusion,
1632 ModelingCmd::ExtrudeToReference(_) => SweepSubType::Extrusion,
1633 ModelingCmd::TwistExtrude(_) => SweepSubType::ExtrusionTwist,
1634 ModelingCmd::Revolve(_) => SweepSubType::Revolve,
1635 ModelingCmd::RevolveAboutEdge(_) => SweepSubType::RevolveAboutEdge,
1636 _ => internal_error!(range, "Sweep-like command variant not handled: id={id:?}, cmd={cmd:?}",),
1637 };
1638 let mut return_arr = Vec::new();
1639 let target = ArtifactId::from(target);
1640 return_arr.push(Artifact::Sweep(Sweep {
1641 id,
1642 sub_type,
1643 path_id: target,
1644 surface_ids: Vec::new(),
1645 edge_ids: Vec::new(),
1646 code_ref,
1647 trajectory_id: None,
1648 method,
1649 consumed: false,
1650 pattern_ids: Vec::new(),
1651 }));
1652 let path = artifacts.get(&target);
1653 if let Some(Artifact::Path(path)) = path {
1654 let mut new_path = path.clone();
1655 new_path.sweep_id = Some(id);
1656 new_path.consumed = true;
1657 return_arr.push(Artifact::Path(new_path));
1658 if let Some(inner_path_id) = path.inner_path_id
1659 && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
1660 && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
1661 {
1662 inner_path_artifact.sweep_id = Some(id);
1663 inner_path_artifact.consumed = true;
1664 return_arr.push(Artifact::Path(inner_path_artifact))
1665 }
1666 }
1667 return Ok(return_arr);
1668 }
1669 ModelingCmd::Sweep(kcmc::Sweep { target, trajectory, .. }) => {
1670 let method = kittycad_modeling_cmds::shared::ExtrudeMethod::Merge;
1672 let sub_type = SweepSubType::Sweep;
1673 let mut return_arr = Vec::new();
1674 let target = ArtifactId::from(target);
1675 let trajectory = ArtifactId::from(trajectory);
1676 return_arr.push(Artifact::Sweep(Sweep {
1677 id,
1678 sub_type,
1679 path_id: target,
1680 surface_ids: Vec::new(),
1681 edge_ids: Vec::new(),
1682 code_ref,
1683 trajectory_id: Some(trajectory),
1684 method,
1685 consumed: false,
1686 pattern_ids: Vec::new(),
1687 }));
1688 let path = artifacts.get(&target);
1689 if let Some(Artifact::Path(path)) = path {
1690 let mut new_path = path.clone();
1691 new_path.sweep_id = Some(id);
1692 new_path.consumed = true;
1693 return_arr.push(Artifact::Path(new_path));
1694 if let Some(inner_path_id) = path.inner_path_id
1695 && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
1696 && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
1697 {
1698 inner_path_artifact.sweep_id = Some(id);
1699 inner_path_artifact.consumed = true;
1700 return_arr.push(Artifact::Path(inner_path_artifact))
1701 }
1702 }
1703 if let Some(trajectory_artifact) = artifacts.get(&trajectory) {
1704 match trajectory_artifact {
1705 Artifact::Path(path) => {
1706 let mut new_path = path.clone();
1707 new_path.trajectory_sweep_id = Some(id);
1708 new_path.consumed = true;
1709 return_arr.push(Artifact::Path(new_path));
1710 }
1711 Artifact::Helix(helix) => {
1712 let mut new_helix = helix.clone();
1713 new_helix.trajectory_sweep_id = Some(id);
1714 new_helix.consumed = true;
1715 return_arr.push(Artifact::Helix(new_helix));
1716 }
1717 _ => {}
1718 }
1719 };
1720 return Ok(return_arr);
1721 }
1722 ModelingCmd::SurfaceBlend(surface_blend_cmd) => {
1723 let surface_id_to_path_id = |surface_id: ArtifactId| -> Option<ArtifactId> {
1724 match artifacts.get(&surface_id) {
1725 Some(Artifact::Path(path)) => Some(path.id),
1726 Some(Artifact::Segment(segment)) => Some(segment.path_id),
1727 Some(Artifact::Sweep(sweep)) => Some(sweep.path_id),
1728 Some(Artifact::Wall(wall)) => artifacts.get(&wall.sweep_id).and_then(|artifact| match artifact {
1729 Artifact::Sweep(sweep) => Some(sweep.path_id),
1730 _ => None,
1731 }),
1732 Some(Artifact::Cap(cap)) => artifacts.get(&cap.sweep_id).and_then(|artifact| match artifact {
1733 Artifact::Sweep(sweep) => Some(sweep.path_id),
1734 _ => None,
1735 }),
1736 _ => None,
1737 }
1738 };
1739 let Some(first_surface_ref) = surface_blend_cmd.surfaces.first() else {
1740 internal_error!(range, "SurfaceBlend command has no surfaces: id={id:?}, cmd={cmd:?}");
1741 };
1742 let first_surface_id = ArtifactId::new(first_surface_ref.object_id);
1743 let path_id = surface_id_to_path_id(first_surface_id).unwrap_or(first_surface_id);
1744 let trajectory_id = surface_blend_cmd
1745 .surfaces
1746 .get(1)
1747 .map(|surface| ArtifactId::new(surface.object_id))
1748 .and_then(surface_id_to_path_id);
1749 let return_arr = vec![Artifact::Sweep(Sweep {
1750 id,
1751 sub_type: SweepSubType::Blend,
1752 path_id,
1753 surface_ids: Vec::new(),
1754 edge_ids: Vec::new(),
1755 code_ref,
1756 trajectory_id,
1757 method: kittycad_modeling_cmds::shared::ExtrudeMethod::New,
1758 consumed: false,
1759 pattern_ids: Vec::new(),
1760 })];
1761 return Ok(return_arr);
1762 }
1763 ModelingCmd::Loft(loft_cmd) => {
1764 let Some(OkModelingCmdResponse::Loft(_)) = response else {
1765 return Ok(Vec::new());
1766 };
1767 let mut return_arr = Vec::new();
1768 return_arr.push(Artifact::Sweep(Sweep {
1769 id,
1770 sub_type: SweepSubType::Loft,
1771 path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
1774 KclError::new_internal(KclErrorDetails::new(
1775 format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
1776 vec![range],
1777 ))
1778 })?),
1779 surface_ids: Vec::new(),
1780 edge_ids: Vec::new(),
1781 code_ref,
1782 trajectory_id: None,
1783 method: kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
1784 consumed: false,
1785 pattern_ids: Vec::new(),
1786 }));
1787 for section_id in &loft_cmd.section_ids {
1788 let path = artifacts.get(&ArtifactId::new(*section_id));
1789 if let Some(Artifact::Path(path)) = path {
1790 let mut new_path = path.clone();
1791 new_path.consumed = true;
1792 new_path.sweep_id = Some(id);
1793 return_arr.push(Artifact::Path(new_path));
1794 }
1795 }
1796 return Ok(return_arr);
1797 }
1798 ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
1799 let Some(OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info)) = response else {
1800 return Ok(Vec::new());
1801 };
1802 let mut return_arr = Vec::new();
1803 let mut last_path = None;
1804 for face in &face_info.faces {
1805 if face.cap != ExtrusionFaceCapType::None {
1806 continue;
1807 }
1808 let Some(curve_id) = face.curve_id.map(ArtifactId::new) else {
1809 continue;
1810 };
1811 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
1812 continue;
1813 };
1814 let Some(Artifact::Segment(seg)) = artifacts.get(&curve_id) else {
1815 continue;
1816 };
1817 let Some(Artifact::Path(path)) = artifacts.get(&seg.path_id) else {
1818 continue;
1819 };
1820 last_path = Some(path);
1821 let Some(path_sweep_id) = path.sweep_id else {
1822 if path.outer_path_id.is_some() {
1825 continue; }
1827 return Err(KclError::new_internal(KclErrorDetails::new(
1828 format!(
1829 "Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
1830 ),
1831 vec![range],
1832 )));
1833 };
1834 let extra_artifact = exec_artifacts.values().find(|a| {
1835 if let Artifact::StartSketchOnFace(s) = a {
1836 s.face_id == face_id
1837 } else if let Artifact::StartSketchOnPlane(s) = a {
1838 s.plane_id == face_id
1839 } else {
1840 false
1841 }
1842 });
1843 let sketch_on_face_code_ref = extra_artifact
1844 .and_then(|a| match a {
1845 Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
1846 Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
1847 _ => None,
1848 })
1849 .unwrap_or_default();
1851
1852 return_arr.push(Artifact::Wall(Wall {
1853 id: face_id,
1854 seg_id: curve_id,
1855 edge_cut_edge_ids: Vec::new(),
1856 sweep_id: path_sweep_id,
1857 path_ids: Vec::new(),
1858 face_code_ref: sketch_on_face_code_ref,
1859 cmd_id: artifact_command.cmd_id,
1860 }));
1861 let mut new_seg = seg.clone();
1862 new_seg.surface_id = Some(face_id);
1863 return_arr.push(Artifact::Segment(new_seg));
1864 if let Some(Artifact::Sweep(sweep)) = path.sweep_id.and_then(|id| artifacts.get(&id)) {
1865 let mut new_sweep = sweep.clone();
1866 new_sweep.surface_ids = vec![face_id];
1867 return_arr.push(Artifact::Sweep(new_sweep));
1868 }
1869 }
1870 if let Some(path) = last_path {
1871 for face in &face_info.faces {
1872 let sub_type = match face.cap {
1873 ExtrusionFaceCapType::Top => CapSubType::End,
1874 ExtrusionFaceCapType::Bottom => CapSubType::Start,
1875 ExtrusionFaceCapType::None | ExtrusionFaceCapType::Both => continue,
1876 _other => {
1877 continue;
1879 }
1880 };
1881 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
1882 continue;
1883 };
1884 let Some(path_sweep_id) = path.sweep_id else {
1885 if path.outer_path_id.is_some() {
1888 continue; }
1890 return Err(KclError::new_internal(KclErrorDetails::new(
1891 format!(
1892 "Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
1893 ),
1894 vec![range],
1895 )));
1896 };
1897 let extra_artifact = exec_artifacts.values().find(|a| {
1898 if let Artifact::StartSketchOnFace(s) = a {
1899 s.face_id == face_id
1900 } else if let Artifact::StartSketchOnPlane(s) = a {
1901 s.plane_id == face_id
1902 } else {
1903 false
1904 }
1905 });
1906 let sketch_on_face_code_ref = extra_artifact
1907 .and_then(|a| match a {
1908 Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
1909 Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
1910 _ => None,
1911 })
1912 .unwrap_or_default();
1914 return_arr.push(Artifact::Cap(Cap {
1915 id: face_id,
1916 sub_type,
1917 edge_cut_edge_ids: Vec::new(),
1918 sweep_id: path_sweep_id,
1919 path_ids: Vec::new(),
1920 face_code_ref: sketch_on_face_code_ref,
1921 cmd_id: artifact_command.cmd_id,
1922 }));
1923 let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
1924 continue;
1925 };
1926 let mut new_sweep = sweep.clone();
1927 new_sweep.surface_ids = vec![face_id];
1928 return_arr.push(Artifact::Sweep(new_sweep));
1929 }
1930 }
1931 return Ok(return_arr);
1932 }
1933 ModelingCmd::Solid3dGetAdjacencyInfo(kcmc::Solid3dGetAdjacencyInfo { .. }) => {
1934 let Some(OkModelingCmdResponse::Solid3dGetAdjacencyInfo(info)) = response else {
1935 return Ok(Vec::new());
1936 };
1937
1938 let mut return_arr = Vec::new();
1939 for (index, edge) in info.edges.iter().enumerate() {
1940 let Some(original_info) = &edge.original_info else {
1941 continue;
1942 };
1943 let edge_id = ArtifactId::new(original_info.edge_id);
1944 let Some(artifact) = artifacts.get(&edge_id) else {
1945 continue;
1946 };
1947 match artifact {
1948 Artifact::Segment(segment) => {
1949 let mut new_segment = segment.clone();
1950 new_segment.common_surface_ids =
1951 original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
1952 return_arr.push(Artifact::Segment(new_segment));
1953 }
1954 Artifact::SweepEdge(sweep_edge) => {
1955 let mut new_sweep_edge = sweep_edge.clone();
1956 new_sweep_edge.common_surface_ids =
1957 original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
1958 return_arr.push(Artifact::SweepEdge(new_sweep_edge));
1959 }
1960 _ => {}
1961 };
1962
1963 let Some(Artifact::Segment(segment)) = artifacts.get(&edge_id) else {
1964 continue;
1965 };
1966 let Some(surface_id) = segment.surface_id else {
1967 continue;
1968 };
1969 let Some(Artifact::Wall(wall)) = artifacts.get(&surface_id) else {
1970 continue;
1971 };
1972 let Some(Artifact::Sweep(sweep)) = artifacts.get(&wall.sweep_id) else {
1973 continue;
1974 };
1975 let Some(Artifact::Path(_)) = artifacts.get(&sweep.path_id) else {
1976 continue;
1977 };
1978
1979 if let Some(opposite_info) = &edge.opposite_info {
1980 return_arr.push(Artifact::SweepEdge(SweepEdge {
1981 id: opposite_info.edge_id.into(),
1982 sub_type: SweepEdgeSubType::Opposite,
1983 seg_id: edge_id,
1984 cmd_id: artifact_command.cmd_id,
1985 index,
1986 sweep_id: sweep.id,
1987 common_surface_ids: opposite_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
1988 }));
1989 let mut new_segment = segment.clone();
1990 new_segment.edge_ids = vec![opposite_info.edge_id.into()];
1991 return_arr.push(Artifact::Segment(new_segment));
1992 let mut new_sweep = sweep.clone();
1993 new_sweep.edge_ids = vec![opposite_info.edge_id.into()];
1994 return_arr.push(Artifact::Sweep(new_sweep));
1995 let mut new_wall = wall.clone();
1996 new_wall.edge_cut_edge_ids = vec![opposite_info.edge_id.into()];
1997 return_arr.push(Artifact::Wall(new_wall));
1998 }
1999 if let Some(adjacent_info) = &edge.adjacent_info {
2000 return_arr.push(Artifact::SweepEdge(SweepEdge {
2001 id: adjacent_info.edge_id.into(),
2002 sub_type: SweepEdgeSubType::Adjacent,
2003 seg_id: edge_id,
2004 cmd_id: artifact_command.cmd_id,
2005 index,
2006 sweep_id: sweep.id,
2007 common_surface_ids: adjacent_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
2008 }));
2009 let mut new_segment = segment.clone();
2010 new_segment.edge_ids = vec![adjacent_info.edge_id.into()];
2011 return_arr.push(Artifact::Segment(new_segment));
2012 let mut new_sweep = sweep.clone();
2013 new_sweep.edge_ids = vec![adjacent_info.edge_id.into()];
2014 return_arr.push(Artifact::Sweep(new_sweep));
2015 let mut new_wall = wall.clone();
2016 new_wall.edge_cut_edge_ids = vec![adjacent_info.edge_id.into()];
2017 return_arr.push(Artifact::Wall(new_wall));
2018 }
2019 }
2020 return Ok(return_arr);
2021 }
2022 ModelingCmd::Solid3dMultiJoin(cmd) => {
2023 let mut return_arr = Vec::new();
2024 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2025 id,
2026 consumed: false,
2027 sub_type: CompositeSolidSubType::Union,
2028 output_index: None,
2029 solid_ids: cmd.object_ids.iter().map(|id| id.into()).collect(),
2030 tool_ids: vec![],
2031 code_ref,
2032 composite_solid_id: None,
2033 pattern_ids: Vec::new(),
2034 }));
2035
2036 let solid_ids = cmd.object_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2037
2038 for input_id in &solid_ids {
2039 if let Some(artifact) = artifacts.get(input_id)
2040 && let Artifact::CompositeSolid(comp) = artifact
2041 {
2042 let mut new_comp = comp.clone();
2043 new_comp.composite_solid_id = Some(id);
2044 new_comp.consumed = true;
2045 return_arr.push(Artifact::CompositeSolid(new_comp));
2046 }
2047 }
2048 return Ok(return_arr);
2049 }
2050 ModelingCmd::Solid3dFilletEdge(cmd) => {
2051 let mut return_arr = Vec::new();
2052 let edge_id = if let Some(edge_id) = cmd.edge_id {
2053 ArtifactId::new(edge_id)
2054 } else {
2055 let Some(edge_id) = cmd.edge_ids.first() else {
2056 internal_error!(
2057 range,
2058 "Solid3dFilletEdge command has no edge ID: id={id:?}, cmd={cmd:?}"
2059 );
2060 };
2061 edge_id.into()
2062 };
2063 return_arr.push(Artifact::EdgeCut(EdgeCut {
2064 id,
2065 sub_type: cmd.cut_type.into(),
2066 consumed_edge_id: edge_id,
2067 edge_ids: Vec::new(),
2068 surface_id: None,
2069 code_ref,
2070 }));
2071 let consumed_edge = artifacts.get(&edge_id);
2072 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2073 let mut new_segment = consumed_edge.clone();
2074 new_segment.edge_cut_id = Some(id);
2075 return_arr.push(Artifact::Segment(new_segment));
2076 } else {
2077 }
2079 return Ok(return_arr);
2080 }
2081 ModelingCmd::Solid3dCutEdges(cmd) => {
2082 let mut return_arr = Vec::new();
2083 let edge_id = if let Some(edge_id) = cmd.edge_ids.first() {
2084 edge_id.into()
2085 } else {
2086 internal_error!(range, "Solid3dCutEdges command has no edge ID: id={id:?}, cmd={cmd:?}");
2087 };
2088 return_arr.push(Artifact::EdgeCut(EdgeCut {
2089 id,
2090 sub_type: cmd.cut_type.into(),
2091 consumed_edge_id: edge_id,
2092 edge_ids: Vec::new(),
2093 surface_id: None,
2094 code_ref,
2095 }));
2096 let consumed_edge = artifacts.get(&edge_id);
2097 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2098 let mut new_segment = consumed_edge.clone();
2099 new_segment.edge_cut_id = Some(id);
2100 return_arr.push(Artifact::Segment(new_segment));
2101 } else {
2102 }
2104 return Ok(return_arr);
2105 }
2106 ModelingCmd::EntityMakeHelix(cmd) => {
2107 let cylinder_id = ArtifactId::new(cmd.cylinder_id);
2108 let return_arr = vec![Artifact::Helix(Helix {
2109 id,
2110 axis_id: Some(cylinder_id),
2111 code_ref,
2112 trajectory_sweep_id: None,
2113 consumed: false,
2114 })];
2115 return Ok(return_arr);
2116 }
2117 ModelingCmd::EntityMakeHelixFromParams(_) => {
2118 let return_arr = vec![Artifact::Helix(Helix {
2119 id,
2120 axis_id: None,
2121 code_ref,
2122 trajectory_sweep_id: None,
2123 consumed: false,
2124 })];
2125 return Ok(return_arr);
2126 }
2127 ModelingCmd::EntityMakeHelixFromEdge(helix) => {
2128 let return_arr = vec![Artifact::Helix(Helix {
2129 id,
2130 axis_id: helix.edge_id.map(ArtifactId::new),
2131 code_ref,
2132 trajectory_sweep_id: None,
2133 consumed: false,
2134 })];
2135 return Ok(return_arr);
2138 }
2139 ModelingCmd::Solid2dAddHole(solid2d_add_hole) => {
2140 let mut return_arr = Vec::new();
2141 let outer_path = artifacts.get(&ArtifactId::new(solid2d_add_hole.object_id));
2143 if let Some(Artifact::Path(path)) = outer_path {
2144 let mut new_path = path.clone();
2145 new_path.inner_path_id = Some(ArtifactId::new(solid2d_add_hole.hole_id));
2146 return_arr.push(Artifact::Path(new_path));
2147 }
2148 let inner_solid2d = artifacts.get(&ArtifactId::new(solid2d_add_hole.hole_id));
2150 if let Some(Artifact::Path(path)) = inner_solid2d {
2151 let mut new_path = path.clone();
2152 new_path.consumed = true;
2153 new_path.outer_path_id = Some(ArtifactId::new(solid2d_add_hole.object_id));
2154 return_arr.push(Artifact::Path(new_path));
2155 }
2156 return Ok(return_arr);
2157 }
2158 ModelingCmd::BooleanIntersection(_) | ModelingCmd::BooleanSubtract(_) | ModelingCmd::BooleanUnion(_) => {
2159 let (sub_type, solid_ids, tool_ids) = match cmd {
2160 ModelingCmd::BooleanIntersection(intersection) => {
2161 let solid_ids = intersection
2162 .solid_ids
2163 .iter()
2164 .copied()
2165 .map(ArtifactId::new)
2166 .collect::<Vec<_>>();
2167 (CompositeSolidSubType::Intersect, solid_ids, Vec::new())
2168 }
2169 ModelingCmd::BooleanSubtract(subtract) => {
2170 let solid_ids = subtract
2171 .target_ids
2172 .iter()
2173 .copied()
2174 .map(ArtifactId::new)
2175 .collect::<Vec<_>>();
2176 let tool_ids = subtract
2177 .tool_ids
2178 .iter()
2179 .copied()
2180 .map(ArtifactId::new)
2181 .collect::<Vec<_>>();
2182 (CompositeSolidSubType::Subtract, solid_ids, tool_ids)
2183 }
2184 ModelingCmd::BooleanUnion(union) => {
2185 let solid_ids = union.solid_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2186 (CompositeSolidSubType::Union, solid_ids, Vec::new())
2187 }
2188 _ => internal_error!(
2189 range,
2190 "Boolean or composite command variant not handled: id={id:?}, cmd={cmd:?}"
2191 ),
2192 };
2193
2194 let mut new_solid_ids = vec![id];
2195
2196 let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2199
2200 match response {
2201 Some(OkModelingCmdResponse::BooleanIntersection(intersection)) => intersection
2202 .extra_solid_ids
2203 .iter()
2204 .copied()
2205 .map(ArtifactId::new)
2206 .filter(not_cmd_id)
2207 .for_each(|id| new_solid_ids.push(id)),
2208 Some(OkModelingCmdResponse::BooleanSubtract(subtract)) => subtract
2209 .extra_solid_ids
2210 .iter()
2211 .copied()
2212 .map(ArtifactId::new)
2213 .filter(not_cmd_id)
2214 .for_each(|id| new_solid_ids.push(id)),
2215 Some(OkModelingCmdResponse::BooleanUnion(union)) => union
2216 .extra_solid_ids
2217 .iter()
2218 .copied()
2219 .map(ArtifactId::new)
2220 .filter(not_cmd_id)
2221 .for_each(|id| new_solid_ids.push(id)),
2222 _ => {}
2223 }
2224
2225 let mut return_arr = Vec::new();
2226
2227 for solid_id in &new_solid_ids {
2229 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2231 id: *solid_id,
2232 consumed: false,
2233 sub_type,
2234 output_index: None,
2235 solid_ids: solid_ids.clone(),
2236 tool_ids: tool_ids.clone(),
2237 code_ref: code_ref.clone(),
2238 composite_solid_id: None,
2239 pattern_ids: Vec::new(),
2240 }));
2241
2242 for input_id in &solid_ids {
2244 if let Some(artifact) = artifacts.get(input_id) {
2245 match artifact {
2246 Artifact::CompositeSolid(comp) => {
2247 let mut new_comp = comp.clone();
2248 new_comp.composite_solid_id = Some(*solid_id);
2249 new_comp.consumed = true;
2250 return_arr.push(Artifact::CompositeSolid(new_comp));
2251 }
2252 Artifact::Path(path) => {
2253 let mut new_path = path.clone();
2254 new_path.composite_solid_id = Some(*solid_id);
2255
2256 if let Some(sweep_id) = new_path.sweep_id
2259 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
2260 {
2261 let mut new_sweep = sweep.clone();
2262 new_sweep.consumed = true;
2263 return_arr.push(Artifact::Sweep(new_sweep));
2264 }
2265
2266 return_arr.push(Artifact::Path(new_path));
2267 }
2268 _ => {}
2269 }
2270 }
2271 }
2272
2273 for tool_id in &tool_ids {
2275 if let Some(artifact) = artifacts.get(tool_id) {
2276 match artifact {
2277 Artifact::CompositeSolid(comp) => {
2278 let mut new_comp = comp.clone();
2279 new_comp.composite_solid_id = Some(*solid_id);
2280 new_comp.consumed = true;
2281 return_arr.push(Artifact::CompositeSolid(new_comp));
2282 }
2283 Artifact::Path(path) => {
2284 let mut new_path = path.clone();
2285 new_path.composite_solid_id = Some(*solid_id);
2286
2287 if let Some(sweep_id) = new_path.sweep_id
2290 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
2291 {
2292 let mut new_sweep = sweep.clone();
2293 new_sweep.consumed = true;
2294 return_arr.push(Artifact::Sweep(new_sweep));
2295 }
2296
2297 return_arr.push(Artifact::Path(new_path));
2298 }
2299 _ => {}
2300 }
2301 }
2302 }
2303 }
2304
2305 return Ok(return_arr);
2306 }
2307 ModelingCmd::BooleanImprint(imprint) => {
2308 let solid_ids = imprint
2309 .body_ids
2310 .iter()
2311 .copied()
2312 .map(ArtifactId::new)
2313 .collect::<Vec<_>>();
2314 let tool_ids = imprint
2315 .tool_ids
2316 .as_ref()
2317 .map(|ids| ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>())
2318 .unwrap_or_default();
2319
2320 let mut new_solid_ids = vec![id];
2321 let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2322 if let Some(OkModelingCmdResponse::BooleanImprint(imprint)) = response {
2323 imprint
2324 .extra_solid_ids
2325 .iter()
2326 .copied()
2327 .map(ArtifactId::new)
2328 .filter(not_cmd_id)
2329 .for_each(|id| new_solid_ids.push(id));
2330 }
2331
2332 let mut return_arr = Vec::new();
2333
2334 for (output_index, solid_id) in new_solid_ids.iter().enumerate() {
2335 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2336 id: *solid_id,
2337 consumed: false,
2338 sub_type: CompositeSolidSubType::Split,
2339 output_index: Some(output_index),
2340 solid_ids: solid_ids.clone(),
2341 tool_ids: tool_ids.clone(),
2342 code_ref: code_ref.clone(),
2343 composite_solid_id: None,
2344 pattern_ids: Vec::new(),
2345 }));
2346
2347 for input_id in solid_ids.iter().chain(tool_ids.iter()) {
2348 if let Some(artifact) = artifacts.get(input_id) {
2349 match artifact {
2350 Artifact::CompositeSolid(comp) => {
2351 let mut new_comp = comp.clone();
2352 new_comp.composite_solid_id = Some(*solid_id);
2353 new_comp.consumed = true;
2354 return_arr.push(Artifact::CompositeSolid(new_comp));
2355 }
2356 Artifact::Path(path) => {
2357 let mut new_path = path.clone();
2358 new_path.composite_solid_id = Some(*solid_id);
2359
2360 if let Some(sweep_id) = new_path.sweep_id
2361 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
2362 {
2363 let mut new_sweep = sweep.clone();
2364 new_sweep.consumed = true;
2365 return_arr.push(Artifact::Sweep(new_sweep));
2366 }
2367
2368 return_arr.push(Artifact::Path(new_path));
2369 }
2370 _ => {}
2371 }
2372 }
2373 }
2374 }
2375
2376 return Ok(return_arr);
2377 }
2378 _ => {}
2379 }
2380
2381 Ok(Vec::new())
2382}