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}
119
120#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
121#[ts(export_to = "Artifact.ts")]
122#[serde(rename_all = "camelCase")]
123pub enum CompositeSolidSubType {
124 Intersect,
125 Subtract,
126 Split,
127 Union,
128}
129
130#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
131#[ts(export_to = "Artifact.ts")]
132#[serde(rename_all = "camelCase")]
133pub struct Plane {
134 pub id: ArtifactId,
135 pub path_ids: Vec<ArtifactId>,
136 pub code_ref: CodeRef,
137}
138
139#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
140#[ts(export_to = "Artifact.ts")]
141#[serde(rename_all = "camelCase")]
142pub struct Path {
143 pub id: ArtifactId,
144 pub sub_type: PathSubType,
145 pub plane_id: ArtifactId,
146 pub seg_ids: Vec<ArtifactId>,
147 pub consumed: bool,
149 #[serde(default, skip_serializing_if = "Option::is_none")]
150 pub sweep_id: Option<ArtifactId>,
153 pub trajectory_sweep_id: Option<ArtifactId>,
155 #[serde(default, skip_serializing_if = "Option::is_none")]
156 pub solid2d_id: Option<ArtifactId>,
157 pub code_ref: CodeRef,
158 #[serde(default, skip_serializing_if = "Option::is_none")]
161 pub composite_solid_id: Option<ArtifactId>,
162 #[serde(default, skip_serializing_if = "Option::is_none")]
165 pub sketch_block_id: Option<ArtifactId>,
166 #[serde(default, skip_serializing_if = "Option::is_none")]
169 pub origin_path_id: Option<ArtifactId>,
170 #[serde(default, skip_serializing_if = "Option::is_none")]
172 pub inner_path_id: Option<ArtifactId>,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
176 pub outer_path_id: Option<ArtifactId>,
177}
178
179#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
180#[ts(export_to = "Artifact.ts")]
181#[serde(rename_all = "camelCase")]
182pub enum PathSubType {
183 Sketch,
184 Region,
185}
186
187#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
188#[ts(export_to = "Artifact.ts")]
189#[serde(rename_all = "camelCase")]
190pub struct Segment {
191 pub id: ArtifactId,
192 pub path_id: ArtifactId,
193 #[serde(default, skip_serializing_if = "Option::is_none")]
196 pub original_seg_id: Option<ArtifactId>,
197 #[serde(default, skip_serializing_if = "Option::is_none")]
198 pub surface_id: Option<ArtifactId>,
199 pub edge_ids: Vec<ArtifactId>,
200 #[serde(default, skip_serializing_if = "Option::is_none")]
201 pub edge_cut_id: Option<ArtifactId>,
202 pub code_ref: CodeRef,
203 pub common_surface_ids: Vec<ArtifactId>,
204}
205
206#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
208#[ts(export_to = "Artifact.ts")]
209#[serde(rename_all = "camelCase")]
210pub struct Sweep {
211 pub id: ArtifactId,
212 pub sub_type: SweepSubType,
213 pub path_id: ArtifactId,
214 pub surface_ids: Vec<ArtifactId>,
215 pub edge_ids: Vec<ArtifactId>,
216 pub code_ref: CodeRef,
217 pub trajectory_id: Option<ArtifactId>,
221 pub method: kittycad_modeling_cmds::shared::ExtrudeMethod,
222 pub consumed: bool,
224}
225
226#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
227#[ts(export_to = "Artifact.ts")]
228#[serde(rename_all = "camelCase")]
229pub enum SweepSubType {
230 Extrusion,
231 ExtrusionTwist,
232 Revolve,
233 RevolveAboutEdge,
234 Loft,
235 Blend,
236 Sweep,
237}
238
239#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
240#[ts(export_to = "Artifact.ts")]
241#[serde(rename_all = "camelCase")]
242pub struct Solid2d {
243 pub id: ArtifactId,
244 pub path_id: ArtifactId,
245}
246
247#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
248#[ts(export_to = "Artifact.ts")]
249#[serde(rename_all = "camelCase")]
250pub struct PrimitiveFace {
251 pub id: ArtifactId,
252 pub solid_id: ArtifactId,
253 pub code_ref: CodeRef,
254}
255
256#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
257#[ts(export_to = "Artifact.ts")]
258#[serde(rename_all = "camelCase")]
259pub struct PrimitiveEdge {
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 PlaneOfFace {
269 pub id: ArtifactId,
270 pub face_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 StartSketchOnFace {
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 StartSketchOnPlane {
287 pub id: ArtifactId,
288 pub plane_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 SketchBlock {
296 pub id: ArtifactId,
297 #[serde(default, skip_serializing_if = "Option::is_none")]
299 pub standard_plane: Option<PlaneName>,
300 #[serde(default, skip_serializing_if = "Option::is_none")]
302 pub plane_id: Option<ArtifactId>,
303 #[serde(default, skip_serializing_if = "Option::is_none")]
307 pub path_id: Option<ArtifactId>,
308 pub code_ref: CodeRef,
309 pub sketch_id: ObjectId,
311}
312
313#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
314#[ts(export_to = "Artifact.ts")]
315#[serde(rename_all = "camelCase")]
316pub enum SketchBlockConstraintType {
317 Angle,
318 Coincident,
319 Distance,
320 Diameter,
321 EqualRadius,
322 Fixed,
323 HorizontalDistance,
324 VerticalDistance,
325 Horizontal,
326 LinesEqualLength,
327 Midpoint,
328 Parallel,
329 Perpendicular,
330 Radius,
331 Symmetric,
332 Tangent,
333 Vertical,
334}
335
336impl From<&Constraint> for SketchBlockConstraintType {
337 fn from(constraint: &Constraint) -> Self {
338 match constraint {
339 Constraint::Coincident { .. } => SketchBlockConstraintType::Coincident,
340 Constraint::Distance { .. } => SketchBlockConstraintType::Distance,
341 Constraint::Diameter { .. } => SketchBlockConstraintType::Diameter,
342 Constraint::EqualRadius { .. } => SketchBlockConstraintType::EqualRadius,
343 Constraint::Fixed { .. } => SketchBlockConstraintType::Fixed,
344 Constraint::HorizontalDistance { .. } => SketchBlockConstraintType::HorizontalDistance,
345 Constraint::VerticalDistance { .. } => SketchBlockConstraintType::VerticalDistance,
346 Constraint::Horizontal { .. } => SketchBlockConstraintType::Horizontal,
347 Constraint::LinesEqualLength { .. } => SketchBlockConstraintType::LinesEqualLength,
348 Constraint::Midpoint(..) => SketchBlockConstraintType::Midpoint,
349 Constraint::Parallel { .. } => SketchBlockConstraintType::Parallel,
350 Constraint::Perpendicular { .. } => SketchBlockConstraintType::Perpendicular,
351 Constraint::Radius { .. } => SketchBlockConstraintType::Radius,
352 Constraint::Symmetric { .. } => SketchBlockConstraintType::Symmetric,
353 Constraint::Tangent { .. } => SketchBlockConstraintType::Tangent,
354 Constraint::Vertical { .. } => SketchBlockConstraintType::Vertical,
355 Constraint::Angle(..) => SketchBlockConstraintType::Angle,
356 }
357 }
358}
359
360#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
361#[ts(export_to = "Artifact.ts")]
362#[serde(rename_all = "camelCase")]
363pub struct SketchBlockConstraint {
364 pub id: ArtifactId,
365 pub sketch_id: ObjectId,
367 pub constraint_id: ObjectId,
369 pub constraint_type: SketchBlockConstraintType,
370 pub code_ref: CodeRef,
371}
372
373#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
374#[ts(export_to = "Artifact.ts")]
375#[serde(rename_all = "camelCase")]
376pub struct Wall {
377 pub id: ArtifactId,
378 pub seg_id: ArtifactId,
379 pub edge_cut_edge_ids: Vec<ArtifactId>,
380 pub sweep_id: ArtifactId,
381 pub path_ids: Vec<ArtifactId>,
382 pub face_code_ref: CodeRef,
385 pub cmd_id: uuid::Uuid,
387}
388
389#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
390#[ts(export_to = "Artifact.ts")]
391#[serde(rename_all = "camelCase")]
392pub struct Cap {
393 pub id: ArtifactId,
394 pub sub_type: CapSubType,
395 pub edge_cut_edge_ids: Vec<ArtifactId>,
396 pub sweep_id: ArtifactId,
397 pub path_ids: Vec<ArtifactId>,
398 pub face_code_ref: CodeRef,
401 pub cmd_id: uuid::Uuid,
403}
404
405#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
406#[ts(export_to = "Artifact.ts")]
407#[serde(rename_all = "camelCase")]
408pub enum CapSubType {
409 Start,
410 End,
411}
412
413#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
414#[ts(export_to = "Artifact.ts")]
415#[serde(rename_all = "camelCase")]
416pub struct SweepEdge {
417 pub id: ArtifactId,
418 pub sub_type: SweepEdgeSubType,
419 pub seg_id: ArtifactId,
420 pub cmd_id: uuid::Uuid,
421 #[serde(skip)]
423 pub index: usize,
424 pub sweep_id: ArtifactId,
425 pub common_surface_ids: Vec<ArtifactId>,
426}
427
428#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
429#[ts(export_to = "Artifact.ts")]
430#[serde(rename_all = "camelCase")]
431pub enum SweepEdgeSubType {
432 Opposite,
433 Adjacent,
434}
435
436#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
437#[ts(export_to = "Artifact.ts")]
438#[serde(rename_all = "camelCase")]
439pub struct EdgeCut {
440 pub id: ArtifactId,
441 pub sub_type: EdgeCutSubType,
442 pub consumed_edge_id: ArtifactId,
443 pub edge_ids: Vec<ArtifactId>,
444 #[serde(default, skip_serializing_if = "Option::is_none")]
445 pub surface_id: Option<ArtifactId>,
446 pub code_ref: CodeRef,
447}
448
449#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
450#[ts(export_to = "Artifact.ts")]
451#[serde(rename_all = "camelCase")]
452pub enum EdgeCutSubType {
453 Fillet,
454 Chamfer,
455 Custom,
456}
457
458impl From<kcmc::shared::CutType> for EdgeCutSubType {
459 fn from(cut_type: kcmc::shared::CutType) -> Self {
460 match cut_type {
461 kcmc::shared::CutType::Fillet => EdgeCutSubType::Fillet,
462 kcmc::shared::CutType::Chamfer => EdgeCutSubType::Chamfer,
463 }
464 }
465}
466
467impl From<kcmc::shared::CutTypeV2> for EdgeCutSubType {
468 fn from(cut_type: kcmc::shared::CutTypeV2) -> Self {
469 match cut_type {
470 kcmc::shared::CutTypeV2::Fillet { .. } => EdgeCutSubType::Fillet,
471 kcmc::shared::CutTypeV2::Chamfer { .. } => EdgeCutSubType::Chamfer,
472 kcmc::shared::CutTypeV2::Custom { .. } => EdgeCutSubType::Custom,
473 _other => EdgeCutSubType::Custom,
475 }
476 }
477}
478
479#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
480#[ts(export_to = "Artifact.ts")]
481#[serde(rename_all = "camelCase")]
482pub struct EdgeCutEdge {
483 pub id: ArtifactId,
484 pub edge_cut_id: ArtifactId,
485 pub surface_id: ArtifactId,
486}
487
488#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
489#[ts(export_to = "Artifact.ts")]
490#[serde(rename_all = "camelCase")]
491pub struct Helix {
492 pub id: ArtifactId,
493 pub axis_id: Option<ArtifactId>,
496 pub code_ref: CodeRef,
497 pub trajectory_sweep_id: Option<ArtifactId>,
499 pub consumed: bool,
501}
502
503#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
504#[ts(export_to = "Artifact.ts")]
505#[serde(tag = "type", rename_all = "camelCase")]
506#[expect(clippy::large_enum_variant)]
507pub enum Artifact {
508 CompositeSolid(CompositeSolid),
509 Plane(Plane),
510 Path(Path),
511 Segment(Segment),
512 Solid2d(Solid2d),
513 PrimitiveFace(PrimitiveFace),
514 PrimitiveEdge(PrimitiveEdge),
515 PlaneOfFace(PlaneOfFace),
516 StartSketchOnFace(StartSketchOnFace),
517 StartSketchOnPlane(StartSketchOnPlane),
518 SketchBlock(SketchBlock),
519 SketchBlockConstraint(SketchBlockConstraint),
520 Sweep(Sweep),
521 Wall(Wall),
522 Cap(Cap),
523 SweepEdge(SweepEdge),
524 EdgeCut(EdgeCut),
525 EdgeCutEdge(EdgeCutEdge),
526 Helix(Helix),
527}
528
529impl Artifact {
530 pub(crate) fn id(&self) -> ArtifactId {
531 match self {
532 Artifact::CompositeSolid(a) => a.id,
533 Artifact::Plane(a) => a.id,
534 Artifact::Path(a) => a.id,
535 Artifact::Segment(a) => a.id,
536 Artifact::Solid2d(a) => a.id,
537 Artifact::PrimitiveFace(a) => a.id,
538 Artifact::PrimitiveEdge(a) => a.id,
539 Artifact::StartSketchOnFace(a) => a.id,
540 Artifact::StartSketchOnPlane(a) => a.id,
541 Artifact::SketchBlock(a) => a.id,
542 Artifact::SketchBlockConstraint(a) => a.id,
543 Artifact::PlaneOfFace(a) => a.id,
544 Artifact::Sweep(a) => a.id,
545 Artifact::Wall(a) => a.id,
546 Artifact::Cap(a) => a.id,
547 Artifact::SweepEdge(a) => a.id,
548 Artifact::EdgeCut(a) => a.id,
549 Artifact::EdgeCutEdge(a) => a.id,
550 Artifact::Helix(a) => a.id,
551 }
552 }
553
554 pub fn code_ref(&self) -> Option<&CodeRef> {
557 match self {
558 Artifact::CompositeSolid(a) => Some(&a.code_ref),
559 Artifact::Plane(a) => Some(&a.code_ref),
560 Artifact::Path(a) => Some(&a.code_ref),
561 Artifact::Segment(a) => Some(&a.code_ref),
562 Artifact::Solid2d(_) => None,
563 Artifact::PrimitiveFace(a) => Some(&a.code_ref),
564 Artifact::PrimitiveEdge(a) => Some(&a.code_ref),
565 Artifact::StartSketchOnFace(a) => Some(&a.code_ref),
566 Artifact::StartSketchOnPlane(a) => Some(&a.code_ref),
567 Artifact::SketchBlock(a) => Some(&a.code_ref),
568 Artifact::SketchBlockConstraint(a) => Some(&a.code_ref),
569 Artifact::PlaneOfFace(a) => Some(&a.code_ref),
570 Artifact::Sweep(a) => Some(&a.code_ref),
571 Artifact::Wall(_) => None,
572 Artifact::Cap(_) => None,
573 Artifact::SweepEdge(_) => None,
574 Artifact::EdgeCut(a) => Some(&a.code_ref),
575 Artifact::EdgeCutEdge(_) => None,
576 Artifact::Helix(a) => Some(&a.code_ref),
577 }
578 }
579
580 pub fn face_code_ref(&self) -> Option<&CodeRef> {
583 match self {
584 Artifact::CompositeSolid(_)
585 | Artifact::Plane(_)
586 | Artifact::Path(_)
587 | Artifact::Segment(_)
588 | Artifact::Solid2d(_)
589 | Artifact::PrimitiveEdge(_)
590 | Artifact::StartSketchOnFace(_)
591 | Artifact::PlaneOfFace(_)
592 | Artifact::StartSketchOnPlane(_)
593 | Artifact::SketchBlock(_)
594 | Artifact::SketchBlockConstraint(_)
595 | Artifact::Sweep(_) => None,
596 Artifact::PrimitiveFace(a) => Some(&a.code_ref),
597 Artifact::Wall(a) => Some(&a.face_code_ref),
598 Artifact::Cap(a) => Some(&a.face_code_ref),
599 Artifact::SweepEdge(_) | Artifact::EdgeCut(_) | Artifact::EdgeCutEdge(_) | Artifact::Helix(_) => None,
600 }
601 }
602
603 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
606 match self {
607 Artifact::CompositeSolid(a) => a.merge(new),
608 Artifact::Plane(a) => a.merge(new),
609 Artifact::Path(a) => a.merge(new),
610 Artifact::Segment(a) => a.merge(new),
611 Artifact::Solid2d(_) => Some(new),
612 Artifact::PrimitiveFace(_) => Some(new),
613 Artifact::PrimitiveEdge(_) => Some(new),
614 Artifact::StartSketchOnFace { .. } => Some(new),
615 Artifact::StartSketchOnPlane { .. } => Some(new),
616 Artifact::SketchBlock { .. } => Some(new),
617 Artifact::SketchBlockConstraint { .. } => Some(new),
618 Artifact::PlaneOfFace { .. } => Some(new),
619 Artifact::Sweep(a) => a.merge(new),
620 Artifact::Wall(a) => a.merge(new),
621 Artifact::Cap(a) => a.merge(new),
622 Artifact::SweepEdge(_) => Some(new),
623 Artifact::EdgeCut(a) => a.merge(new),
624 Artifact::EdgeCutEdge(_) => Some(new),
625 Artifact::Helix(a) => a.merge(new),
626 }
627 }
628}
629
630impl CompositeSolid {
631 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
632 let Artifact::CompositeSolid(new) = new else {
633 return Some(new);
634 };
635 merge_ids(&mut self.solid_ids, new.solid_ids);
636 merge_ids(&mut self.tool_ids, new.tool_ids);
637 merge_opt_id(&mut self.composite_solid_id, new.composite_solid_id);
638 self.output_index = new.output_index;
639 self.consumed = new.consumed;
640
641 None
642 }
643}
644
645impl Plane {
646 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
647 let Artifact::Plane(new) = new else {
648 return Some(new);
649 };
650 merge_ids(&mut self.path_ids, new.path_ids);
651
652 None
653 }
654}
655
656impl Path {
657 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
658 let Artifact::Path(new) = new else {
659 return Some(new);
660 };
661 merge_opt_id(&mut self.sweep_id, new.sweep_id);
662 merge_opt_id(&mut self.trajectory_sweep_id, new.trajectory_sweep_id);
663 merge_ids(&mut self.seg_ids, new.seg_ids);
664 merge_opt_id(&mut self.solid2d_id, new.solid2d_id);
665 merge_opt_id(&mut self.composite_solid_id, new.composite_solid_id);
666 merge_opt_id(&mut self.sketch_block_id, new.sketch_block_id);
667 merge_opt_id(&mut self.origin_path_id, new.origin_path_id);
668 merge_opt_id(&mut self.inner_path_id, new.inner_path_id);
669 merge_opt_id(&mut self.outer_path_id, new.outer_path_id);
670 self.consumed = new.consumed;
671
672 None
673 }
674}
675
676impl Segment {
677 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
678 let Artifact::Segment(new) = new else {
679 return Some(new);
680 };
681 merge_opt_id(&mut self.original_seg_id, new.original_seg_id);
682 merge_opt_id(&mut self.surface_id, new.surface_id);
683 merge_ids(&mut self.edge_ids, new.edge_ids);
684 merge_opt_id(&mut self.edge_cut_id, new.edge_cut_id);
685 merge_ids(&mut self.common_surface_ids, new.common_surface_ids);
686
687 None
688 }
689}
690
691impl Sweep {
692 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
693 let Artifact::Sweep(new) = new else {
694 return Some(new);
695 };
696 merge_ids(&mut self.surface_ids, new.surface_ids);
697 merge_ids(&mut self.edge_ids, new.edge_ids);
698 merge_opt_id(&mut self.trajectory_id, new.trajectory_id);
699 self.consumed = new.consumed;
700
701 None
702 }
703}
704
705impl Wall {
706 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
707 let Artifact::Wall(new) = new else {
708 return Some(new);
709 };
710 merge_ids(&mut self.edge_cut_edge_ids, new.edge_cut_edge_ids);
711 merge_ids(&mut self.path_ids, new.path_ids);
712
713 None
714 }
715}
716
717impl Cap {
718 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
719 let Artifact::Cap(new) = new else {
720 return Some(new);
721 };
722 merge_ids(&mut self.edge_cut_edge_ids, new.edge_cut_edge_ids);
723 merge_ids(&mut self.path_ids, new.path_ids);
724
725 None
726 }
727}
728
729impl EdgeCut {
730 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
731 let Artifact::EdgeCut(new) = new else {
732 return Some(new);
733 };
734 merge_opt_id(&mut self.surface_id, new.surface_id);
735 merge_ids(&mut self.edge_ids, new.edge_ids);
736
737 None
738 }
739}
740
741impl Helix {
742 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
743 let Artifact::Helix(new) = new else {
744 return Some(new);
745 };
746 merge_opt_id(&mut self.axis_id, new.axis_id);
747 merge_opt_id(&mut self.trajectory_sweep_id, new.trajectory_sweep_id);
748 self.consumed = new.consumed;
749
750 None
751 }
752}
753
754#[derive(Debug, Clone, Default, PartialEq, Serialize, ts_rs::TS)]
755#[ts(export_to = "Artifact.ts")]
756#[serde(rename_all = "camelCase")]
757pub struct ArtifactGraph {
758 map: IndexMap<ArtifactId, Artifact>,
759 pub(super) item_count: usize,
760}
761
762impl ArtifactGraph {
763 pub fn get(&self, id: &ArtifactId) -> Option<&Artifact> {
764 self.map.get(id)
765 }
766
767 pub fn len(&self) -> usize {
768 self.map.len()
769 }
770
771 pub fn is_empty(&self) -> bool {
772 self.map.is_empty()
773 }
774
775 #[cfg(test)]
776 pub(crate) fn iter(&self) -> impl Iterator<Item = (&ArtifactId, &Artifact)> {
777 self.map.iter()
778 }
779
780 pub fn values(&self) -> impl Iterator<Item = &Artifact> {
781 self.map.values()
782 }
783
784 pub fn clear(&mut self) {
785 self.map.clear();
786 self.item_count = 0;
787 }
788
789 fn into_map(self) -> IndexMap<ArtifactId, Artifact> {
791 self.map
792 }
793}
794
795#[derive(Debug, Clone)]
796struct ImportCodeRef {
797 node_path: NodePath,
798 range: SourceRange,
799}
800
801fn import_statement_code_refs(
802 ast: &Node<Program>,
803 module_infos: &ModuleInfoMap,
804 programs: &crate::execution::ProgramLookup,
805 cached_body_items: usize,
806) -> FnvHashMap<ModuleId, ImportCodeRef> {
807 let mut code_refs = FnvHashMap::default();
808 for body_item in &ast.body {
809 let BodyItem::ImportStatement(import_stmt) = body_item else {
810 continue;
811 };
812 if !matches!(import_stmt.selector, ImportSelector::None { .. }) {
813 continue;
814 }
815 let Some(module_id) = module_id_for_import_path(module_infos, &import_stmt.path) else {
816 continue;
817 };
818 let range = SourceRange::from(import_stmt);
819 let node_path = NodePath::from_range(programs, cached_body_items, range).unwrap_or_default();
820 code_refs.entry(module_id).or_insert(ImportCodeRef { node_path, range });
821 }
822 code_refs
823}
824
825fn module_id_for_import_path(module_infos: &ModuleInfoMap, import_path: &ImportPath) -> Option<ModuleId> {
826 let import_path = match import_path {
827 ImportPath::Kcl { filename } => filename,
828 ImportPath::Foreign { path } => path,
829 ImportPath::Std { .. } => return None,
830 };
831
832 module_infos.iter().find_map(|(module_id, module_info)| {
833 if let ModulePath::Local {
834 original_import_path: Some(original_import_path),
835 ..
836 } = &module_info.path
837 && original_import_path == import_path
838 {
839 return Some(*module_id);
840 }
841 None
842 })
843}
844
845fn code_ref_for_range(
846 programs: &crate::execution::ProgramLookup,
847 cached_body_items: usize,
848 range: SourceRange,
849 import_code_refs: &FnvHashMap<ModuleId, ImportCodeRef>,
850) -> (SourceRange, NodePath) {
851 if let Some(code_ref) = import_code_refs.get(&range.module_id()) {
852 return (code_ref.range, code_ref.node_path.clone());
853 }
854
855 (
856 range,
857 NodePath::from_range(programs, cached_body_items, range).unwrap_or_default(),
858 )
859}
860
861pub(super) fn build_artifact_graph(
865 artifact_commands: &[ArtifactCommand],
866 responses: &IndexMap<Uuid, WebSocketResponse>,
867 ast: &Node<Program>,
868 exec_artifacts: &mut IndexMap<ArtifactId, Artifact>,
869 initial_graph: ArtifactGraph,
870 programs: &crate::execution::ProgramLookup,
871 module_infos: &ModuleInfoMap,
872) -> Result<ArtifactGraph, KclError> {
873 let item_count = initial_graph.item_count;
874 let mut map = initial_graph.into_map();
875
876 let mut path_to_plane_id_map = FnvHashMap::default();
877 let mut current_plane_id = None;
878 let import_code_refs = import_statement_code_refs(ast, module_infos, programs, item_count);
879
880 for exec_artifact in exec_artifacts.values_mut() {
883 fill_in_node_paths(exec_artifact, programs, item_count, &import_code_refs);
886 }
887
888 for artifact_command in artifact_commands {
889 if let ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) = artifact_command.command {
890 current_plane_id = Some(entity_id);
891 }
892 if let ModelingCmd::StartPath(_) = artifact_command.command
897 && let Some(plane_id) = current_plane_id
898 {
899 path_to_plane_id_map.insert(artifact_command.cmd_id, plane_id);
900 }
901 if let ModelingCmd::SketchModeDisable(_) = artifact_command.command {
902 current_plane_id = None;
903 }
904
905 let flattened_responses = flatten_modeling_command_responses(responses);
906 let artifact_updates = artifacts_to_update(
907 &map,
908 artifact_command,
909 &flattened_responses,
910 &path_to_plane_id_map,
911 programs,
912 item_count,
913 exec_artifacts,
914 &import_code_refs,
915 )?;
916 for artifact in artifact_updates {
917 merge_artifact_into_map(&mut map, artifact);
919 }
920 }
921
922 for exec_artifact in exec_artifacts.values() {
923 merge_artifact_into_map(&mut map, exec_artifact.clone());
924 }
925
926 Ok(ArtifactGraph {
927 map,
928 item_count: item_count + ast.body.len(),
929 })
930}
931
932fn fill_in_node_paths(
935 artifact: &mut Artifact,
936 programs: &crate::execution::ProgramLookup,
937 cached_body_items: usize,
938 import_code_refs: &FnvHashMap<ModuleId, ImportCodeRef>,
939) {
940 match artifact {
941 Artifact::StartSketchOnFace(face) if face.code_ref.node_path.is_empty() => {
942 let (range, node_path) =
943 code_ref_for_range(programs, cached_body_items, face.code_ref.range, import_code_refs);
944 face.code_ref.range = range;
945 face.code_ref.node_path = node_path;
946 }
947 Artifact::StartSketchOnPlane(plane) if plane.code_ref.node_path.is_empty() => {
948 let (range, node_path) =
949 code_ref_for_range(programs, cached_body_items, plane.code_ref.range, import_code_refs);
950 plane.code_ref.range = range;
951 plane.code_ref.node_path = node_path;
952 }
953 Artifact::SketchBlock(block) if block.code_ref.node_path.is_empty() => {
954 let (range, node_path) =
955 code_ref_for_range(programs, cached_body_items, block.code_ref.range, import_code_refs);
956 block.code_ref.range = range;
957 block.code_ref.node_path = node_path;
958 }
959 Artifact::SketchBlockConstraint(constraint) if constraint.code_ref.node_path.is_empty() => {
960 constraint.code_ref.node_path =
961 NodePath::from_range(programs, cached_body_items, constraint.code_ref.range).unwrap_or_default();
962 }
963 _ => {}
964 }
965}
966
967fn flatten_modeling_command_responses(
970 responses: &IndexMap<Uuid, WebSocketResponse>,
971) -> FnvHashMap<Uuid, OkModelingCmdResponse> {
972 let mut map = FnvHashMap::default();
973 for (cmd_id, ws_response) in responses {
974 let WebSocketResponse::Success(response) = ws_response else {
975 continue;
977 };
978 match &response.resp {
979 OkWebSocketResponseData::Modeling { modeling_response } => {
980 map.insert(*cmd_id, modeling_response.clone());
981 }
982 OkWebSocketResponseData::ModelingBatch { responses } =>
983 {
984 #[expect(
985 clippy::iter_over_hash_type,
986 reason = "Since we're moving entries to another unordered map, it's fine that the order is undefined"
987 )]
988 for (cmd_id, batch_response) in responses {
989 if let BatchResponse::Success {
990 response: modeling_response,
991 } = batch_response
992 {
993 map.insert(*cmd_id.as_ref(), modeling_response.clone());
994 }
995 }
996 }
997 OkWebSocketResponseData::IceServerInfo { .. }
998 | OkWebSocketResponseData::TrickleIce { .. }
999 | OkWebSocketResponseData::SdpAnswer { .. }
1000 | OkWebSocketResponseData::Export { .. }
1001 | OkWebSocketResponseData::MetricsRequest { .. }
1002 | OkWebSocketResponseData::ModelingSessionData { .. }
1003 | OkWebSocketResponseData::Debug { .. }
1004 | OkWebSocketResponseData::Pong { .. } => {}
1005 _other => {}
1006 }
1007 }
1008
1009 map
1010}
1011
1012fn merge_artifact_into_map(map: &mut IndexMap<ArtifactId, Artifact>, new_artifact: Artifact) {
1013 fn is_primitive_artifact(artifact: &Artifact) -> bool {
1014 matches!(artifact, Artifact::PrimitiveFace(_) | Artifact::PrimitiveEdge(_))
1015 }
1016
1017 let id = new_artifact.id();
1018 let Some(old_artifact) = map.get_mut(&id) else {
1019 map.insert(id, new_artifact);
1021 return;
1022 };
1023
1024 if is_primitive_artifact(&new_artifact) && !is_primitive_artifact(old_artifact) {
1028 return;
1029 }
1030
1031 if let Some(replacement) = old_artifact.merge(new_artifact) {
1032 *old_artifact = replacement;
1033 }
1034}
1035
1036fn merge_ids(base: &mut Vec<ArtifactId>, new: Vec<ArtifactId>) {
1040 let original_len = base.len();
1041 for id in new {
1042 let original_base = &base[..original_len];
1044 if !original_base.contains(&id) {
1045 base.push(id);
1046 }
1047 }
1048}
1049
1050fn merge_opt_id(base: &mut Option<ArtifactId>, new: Option<ArtifactId>) {
1052 *base = new;
1054}
1055
1056#[allow(clippy::too_many_arguments)]
1057fn artifacts_to_update(
1058 artifacts: &IndexMap<ArtifactId, Artifact>,
1059 artifact_command: &ArtifactCommand,
1060 responses: &FnvHashMap<Uuid, OkModelingCmdResponse>,
1061 path_to_plane_id_map: &FnvHashMap<Uuid, Uuid>,
1062 programs: &crate::execution::ProgramLookup,
1063 cached_body_items: usize,
1064 exec_artifacts: &IndexMap<ArtifactId, Artifact>,
1065 import_code_refs: &FnvHashMap<ModuleId, ImportCodeRef>,
1066) -> Result<Vec<Artifact>, KclError> {
1067 let uuid = artifact_command.cmd_id;
1068 let response = responses.get(&uuid);
1069
1070 let path_to_node = Vec::new();
1074 let range = artifact_command.range;
1075 let (code_ref_range, node_path) = code_ref_for_range(programs, cached_body_items, range, import_code_refs);
1076 let code_ref = CodeRef {
1077 range: code_ref_range,
1078 node_path,
1079 path_to_node,
1080 };
1081
1082 let id = ArtifactId::new(uuid);
1083 let cmd = &artifact_command.command;
1084
1085 match cmd {
1086 ModelingCmd::MakePlane(_) => {
1087 if range.is_synthetic() {
1088 return Ok(Vec::new());
1089 }
1090 return Ok(vec![Artifact::Plane(Plane {
1094 id,
1095 path_ids: Vec::new(),
1096 code_ref,
1097 })]);
1098 }
1099 ModelingCmd::FaceIsPlanar(FaceIsPlanar { object_id, .. }) => {
1100 return Ok(vec![Artifact::PlaneOfFace(PlaneOfFace {
1101 id,
1102 face_id: object_id.into(),
1103 code_ref,
1104 })]);
1105 }
1106 ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) => {
1107 let existing_plane = artifacts.get(&ArtifactId::new(*entity_id));
1108 match existing_plane {
1109 Some(Artifact::Wall(wall)) => {
1110 return Ok(vec![Artifact::Wall(Wall {
1111 id: entity_id.into(),
1112 seg_id: wall.seg_id,
1113 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1114 sweep_id: wall.sweep_id,
1115 path_ids: wall.path_ids.clone(),
1116 face_code_ref: wall.face_code_ref.clone(),
1117 cmd_id: artifact_command.cmd_id,
1118 })]);
1119 }
1120 Some(Artifact::Cap(cap)) => {
1121 return Ok(vec![Artifact::Cap(Cap {
1122 id: entity_id.into(),
1123 sub_type: cap.sub_type,
1124 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1125 sweep_id: cap.sweep_id,
1126 path_ids: cap.path_ids.clone(),
1127 face_code_ref: cap.face_code_ref.clone(),
1128 cmd_id: artifact_command.cmd_id,
1129 })]);
1130 }
1131 Some(_) | None => {
1132 let path_ids = match existing_plane {
1133 Some(Artifact::Plane(Plane { path_ids, .. })) => path_ids.clone(),
1134 _ => Vec::new(),
1135 };
1136 return Ok(vec![Artifact::Plane(Plane {
1138 id: entity_id.into(),
1139 path_ids,
1140 code_ref,
1141 })]);
1142 }
1143 }
1144 }
1145 ModelingCmd::StartPath(_) => {
1146 let mut return_arr = Vec::new();
1147 let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
1148 KclError::new_internal(KclErrorDetails::new(
1149 format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"),
1150 vec![range],
1151 ))
1152 })?;
1153 let sketch_block_id = exec_artifacts
1154 .values()
1155 .find(|a| {
1156 if let Artifact::SketchBlock(s) = a {
1157 if let Some(path_id) = s.path_id {
1158 path_id == id
1159 } else {
1160 false
1161 }
1162 } else {
1163 false
1164 }
1165 })
1166 .map(|a| a.id());
1167 return_arr.push(Artifact::Path(Path {
1168 id,
1169 sub_type: PathSubType::Sketch,
1170 plane_id: (*current_plane_id).into(),
1171 seg_ids: Vec::new(),
1172 sweep_id: None,
1173 trajectory_sweep_id: None,
1174 solid2d_id: None,
1175 code_ref,
1176 composite_solid_id: None,
1177 sketch_block_id,
1178 origin_path_id: None,
1179 inner_path_id: None,
1180 outer_path_id: None,
1181 consumed: false,
1182 }));
1183 let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
1184 if let Some(Artifact::Plane(plane)) = plane {
1185 let plane_code_ref = plane.code_ref.clone();
1186 return_arr.push(Artifact::Plane(Plane {
1187 id: (*current_plane_id).into(),
1188 path_ids: vec![id],
1189 code_ref: plane_code_ref,
1190 }));
1191 }
1192 if let Some(Artifact::Wall(wall)) = plane {
1193 return_arr.push(Artifact::Wall(Wall {
1194 id: (*current_plane_id).into(),
1195 seg_id: wall.seg_id,
1196 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1197 sweep_id: wall.sweep_id,
1198 path_ids: vec![id],
1199 face_code_ref: wall.face_code_ref.clone(),
1200 cmd_id: artifact_command.cmd_id,
1201 }));
1202 }
1203 if let Some(Artifact::Cap(cap)) = plane {
1204 return_arr.push(Artifact::Cap(Cap {
1205 id: (*current_plane_id).into(),
1206 sub_type: cap.sub_type,
1207 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1208 sweep_id: cap.sweep_id,
1209 path_ids: vec![id],
1210 face_code_ref: cap.face_code_ref.clone(),
1211 cmd_id: artifact_command.cmd_id,
1212 }));
1213 }
1214 return Ok(return_arr);
1215 }
1216 ModelingCmd::ClosePath(_) | ModelingCmd::ExtendPath(_) => {
1217 let path_id = ArtifactId::new(match cmd {
1218 ModelingCmd::ClosePath(c) => c.path_id,
1219 ModelingCmd::ExtendPath(e) => e.path.into(),
1220 _ => internal_error!(
1221 range,
1222 "Close or extend path command variant not handled: id={id:?}, cmd={cmd:?}"
1223 ),
1224 });
1225 let mut return_arr = Vec::new();
1226 return_arr.push(Artifact::Segment(Segment {
1227 id,
1228 path_id,
1229 original_seg_id: None,
1230 surface_id: None,
1231 edge_ids: Vec::new(),
1232 edge_cut_id: None,
1233 code_ref,
1234 common_surface_ids: Vec::new(),
1235 }));
1236 let path = artifacts.get(&path_id);
1237 if let Some(Artifact::Path(path)) = path {
1238 let mut new_path = path.clone();
1239 new_path.seg_ids = vec![id];
1240 return_arr.push(Artifact::Path(new_path));
1241 }
1242 if let Some(OkModelingCmdResponse::ClosePath(close_path)) = response {
1243 return_arr.push(Artifact::Solid2d(Solid2d {
1244 id: close_path.face_id.into(),
1245 path_id,
1246 }));
1247 if let Some(Artifact::Path(path)) = path {
1248 let mut new_path = path.clone();
1249 new_path.solid2d_id = Some(close_path.face_id.into());
1250 return_arr.push(Artifact::Path(new_path));
1251 }
1252 }
1253 return Ok(return_arr);
1254 }
1255 ModelingCmd::CreateRegion(kcmc::CreateRegion {
1256 object_id: origin_path_id,
1257 ..
1258 })
1259 | ModelingCmd::CreateRegionFromQueryPoint(kcmc::CreateRegionFromQueryPoint {
1260 object_id: origin_path_id,
1261 ..
1262 }) => {
1263 let mut return_arr = Vec::new();
1264 let origin_path = artifacts.get(&ArtifactId::new(*origin_path_id));
1265 let Some(Artifact::Path(path)) = origin_path else {
1266 internal_error!(
1267 range,
1268 "Expected to find an existing path for the origin path of CreateRegion or CreateRegionFromQueryPoint command, but found none: origin_path={origin_path:?}, cmd={cmd:?}"
1269 );
1270 };
1271 return_arr.push(Artifact::Path(Path {
1273 id,
1274 sub_type: PathSubType::Region,
1275 plane_id: path.plane_id,
1276 seg_ids: Vec::new(),
1277 consumed: false,
1278 sweep_id: None,
1279 trajectory_sweep_id: None,
1280 solid2d_id: None,
1281 code_ref: code_ref.clone(),
1282 composite_solid_id: None,
1283 sketch_block_id: None,
1284 origin_path_id: Some(ArtifactId::new(*origin_path_id)),
1285 inner_path_id: None,
1286 outer_path_id: None,
1287 }));
1288 let Some(
1291 OkModelingCmdResponse::CreateRegion(kcmc::output::CreateRegion { region_mapping, .. })
1292 | OkModelingCmdResponse::CreateRegionFromQueryPoint(kcmc::output::CreateRegionFromQueryPoint {
1293 region_mapping,
1294 ..
1295 }),
1296 ) = response
1297 else {
1298 return Ok(return_arr);
1299 };
1300 let original_segment_ids = path.seg_ids.iter().map(|p| p.0).collect::<Vec<_>>();
1303 let reverse = build_reverse_region_mapping(region_mapping, &original_segment_ids);
1304 for (original_segment_id, region_segment_ids) in reverse.iter() {
1305 for segment_id in region_segment_ids {
1306 return_arr.push(Artifact::Segment(Segment {
1307 id: ArtifactId::new(*segment_id),
1308 path_id: id,
1309 original_seg_id: Some(ArtifactId::new(*original_segment_id)),
1310 surface_id: None,
1311 edge_ids: Vec::new(),
1312 edge_cut_id: None,
1313 code_ref: code_ref.clone(),
1314 common_surface_ids: Vec::new(),
1315 }))
1316 }
1317 }
1318 return Ok(return_arr);
1319 }
1320 ModelingCmd::Solid3dGetFaceUuid(kcmc::Solid3dGetFaceUuid { object_id, .. }) => {
1321 let Some(OkModelingCmdResponse::Solid3dGetFaceUuid(face_uuid)) = response else {
1322 return Ok(Vec::new());
1323 };
1324
1325 return Ok(vec![Artifact::PrimitiveFace(PrimitiveFace {
1326 id: face_uuid.face_id.into(),
1327 solid_id: (*object_id).into(),
1328 code_ref,
1329 })]);
1330 }
1331 ModelingCmd::Solid3dGetEdgeUuid(kcmc::Solid3dGetEdgeUuid { object_id, .. }) => {
1332 let Some(OkModelingCmdResponse::Solid3dGetEdgeUuid(edge_uuid)) = response else {
1333 return Ok(Vec::new());
1334 };
1335
1336 return Ok(vec![Artifact::PrimitiveEdge(PrimitiveEdge {
1337 id: edge_uuid.edge_id.into(),
1338 solid_id: (*object_id).into(),
1339 code_ref,
1340 })]);
1341 }
1342 ModelingCmd::EntityMirror(kcmc::EntityMirror {
1343 ids: original_path_ids, ..
1344 })
1345 | ModelingCmd::EntityMirrorAcrossEdge(kcmc::EntityMirrorAcrossEdge {
1346 ids: original_path_ids, ..
1347 }) => {
1348 let face_edge_infos = match response {
1349 Some(OkModelingCmdResponse::EntityMirror(resp)) => &resp.entity_face_edge_ids,
1350 Some(OkModelingCmdResponse::EntityMirrorAcrossEdge(resp)) => &resp.entity_face_edge_ids,
1351 _ => internal_error!(
1352 range,
1353 "Mirror response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
1354 ),
1355 };
1356 if original_path_ids.len() != face_edge_infos.len() {
1357 internal_error!(
1358 range,
1359 "EntityMirror or EntityMirrorAcrossEdge response has different number face edge info than original mirrored paths: id={id:?}, cmd={cmd:?}, response={response:?}"
1360 );
1361 }
1362 let mut return_arr = Vec::new();
1363 for (face_edge_info, original_path_id) in face_edge_infos.iter().zip(original_path_ids) {
1364 let original_path_id = ArtifactId::new(*original_path_id);
1365 let path_id = ArtifactId::new(face_edge_info.object_id);
1366 let mut path = if let Some(Artifact::Path(path)) = artifacts.get(&path_id) {
1369 path.clone()
1371 } else {
1372 let Some(Artifact::Path(original_path)) = artifacts.get(&original_path_id) else {
1375 internal_error!(
1377 range,
1378 "Couldn't find original path for mirror2d: original_path_id={original_path_id:?}, cmd={cmd:?}"
1379 );
1380 };
1381 Path {
1382 id: path_id,
1383 sub_type: original_path.sub_type,
1384 plane_id: original_path.plane_id,
1385 seg_ids: Vec::new(),
1386 sweep_id: None,
1387 trajectory_sweep_id: None,
1388 solid2d_id: None,
1389 code_ref: code_ref.clone(),
1390 composite_solid_id: None,
1391 sketch_block_id: None,
1392 origin_path_id: original_path.origin_path_id,
1393 inner_path_id: None,
1394 outer_path_id: None,
1395 consumed: false,
1396 }
1397 };
1398
1399 face_edge_info.edges.iter().for_each(|edge_id| {
1400 let edge_id = ArtifactId::new(*edge_id);
1401 return_arr.push(Artifact::Segment(Segment {
1402 id: edge_id,
1403 path_id: path.id,
1404 original_seg_id: None,
1405 surface_id: None,
1406 edge_ids: Vec::new(),
1407 edge_cut_id: None,
1408 code_ref: code_ref.clone(),
1409 common_surface_ids: Vec::new(),
1410 }));
1411 path.seg_ids.push(edge_id);
1413 });
1414
1415 return_arr.push(Artifact::Path(path));
1416 }
1417 return Ok(return_arr);
1418 }
1419 ModelingCmd::Extrude(kcmc::Extrude { target, .. })
1420 | ModelingCmd::TwistExtrude(kcmc::TwistExtrude { target, .. })
1421 | ModelingCmd::Revolve(kcmc::Revolve { target, .. })
1422 | ModelingCmd::RevolveAboutEdge(kcmc::RevolveAboutEdge { target, .. })
1423 | ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { target, .. }) => {
1424 let method = match cmd {
1426 ModelingCmd::Extrude(kcmc::Extrude { extrude_method, .. }) => *extrude_method,
1427 ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { extrude_method, .. }) => *extrude_method,
1428 ModelingCmd::TwistExtrude(_) | ModelingCmd::Sweep(_) => {
1430 kittycad_modeling_cmds::shared::ExtrudeMethod::Merge
1431 }
1432 ModelingCmd::Revolve(_) | ModelingCmd::RevolveAboutEdge(_) => {
1434 kittycad_modeling_cmds::shared::ExtrudeMethod::New
1435 }
1436 _ => kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
1437 };
1438 let sub_type = match cmd {
1439 ModelingCmd::Extrude(_) => SweepSubType::Extrusion,
1440 ModelingCmd::ExtrudeToReference(_) => SweepSubType::Extrusion,
1441 ModelingCmd::TwistExtrude(_) => SweepSubType::ExtrusionTwist,
1442 ModelingCmd::Revolve(_) => SweepSubType::Revolve,
1443 ModelingCmd::RevolveAboutEdge(_) => SweepSubType::RevolveAboutEdge,
1444 _ => internal_error!(range, "Sweep-like command variant not handled: id={id:?}, cmd={cmd:?}",),
1445 };
1446 let mut return_arr = Vec::new();
1447 let target = ArtifactId::from(target);
1448 return_arr.push(Artifact::Sweep(Sweep {
1449 id,
1450 sub_type,
1451 path_id: target,
1452 surface_ids: Vec::new(),
1453 edge_ids: Vec::new(),
1454 code_ref,
1455 trajectory_id: None,
1456 method,
1457 consumed: false,
1458 }));
1459 let path = artifacts.get(&target);
1460 if let Some(Artifact::Path(path)) = path {
1461 let mut new_path = path.clone();
1462 new_path.sweep_id = Some(id);
1463 new_path.consumed = true;
1464 return_arr.push(Artifact::Path(new_path));
1465 if let Some(inner_path_id) = path.inner_path_id
1466 && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
1467 && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
1468 {
1469 inner_path_artifact.sweep_id = Some(id);
1470 inner_path_artifact.consumed = true;
1471 return_arr.push(Artifact::Path(inner_path_artifact))
1472 }
1473 }
1474 return Ok(return_arr);
1475 }
1476 ModelingCmd::Sweep(kcmc::Sweep { target, trajectory, .. }) => {
1477 let method = kittycad_modeling_cmds::shared::ExtrudeMethod::Merge;
1479 let sub_type = SweepSubType::Sweep;
1480 let mut return_arr = Vec::new();
1481 let target = ArtifactId::from(target);
1482 let trajectory = ArtifactId::from(trajectory);
1483 return_arr.push(Artifact::Sweep(Sweep {
1484 id,
1485 sub_type,
1486 path_id: target,
1487 surface_ids: Vec::new(),
1488 edge_ids: Vec::new(),
1489 code_ref,
1490 trajectory_id: Some(trajectory),
1491 method,
1492 consumed: false,
1493 }));
1494 let path = artifacts.get(&target);
1495 if let Some(Artifact::Path(path)) = path {
1496 let mut new_path = path.clone();
1497 new_path.sweep_id = Some(id);
1498 new_path.consumed = true;
1499 return_arr.push(Artifact::Path(new_path));
1500 if let Some(inner_path_id) = path.inner_path_id
1501 && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
1502 && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
1503 {
1504 inner_path_artifact.sweep_id = Some(id);
1505 inner_path_artifact.consumed = true;
1506 return_arr.push(Artifact::Path(inner_path_artifact))
1507 }
1508 }
1509 if let Some(trajectory_artifact) = artifacts.get(&trajectory) {
1510 match trajectory_artifact {
1511 Artifact::Path(path) => {
1512 let mut new_path = path.clone();
1513 new_path.trajectory_sweep_id = Some(id);
1514 new_path.consumed = true;
1515 return_arr.push(Artifact::Path(new_path));
1516 }
1517 Artifact::Helix(helix) => {
1518 let mut new_helix = helix.clone();
1519 new_helix.trajectory_sweep_id = Some(id);
1520 new_helix.consumed = true;
1521 return_arr.push(Artifact::Helix(new_helix));
1522 }
1523 _ => {}
1524 }
1525 };
1526 return Ok(return_arr);
1527 }
1528 ModelingCmd::SurfaceBlend(surface_blend_cmd) => {
1529 let surface_id_to_path_id = |surface_id: ArtifactId| -> Option<ArtifactId> {
1530 match artifacts.get(&surface_id) {
1531 Some(Artifact::Path(path)) => Some(path.id),
1532 Some(Artifact::Segment(segment)) => Some(segment.path_id),
1533 Some(Artifact::Sweep(sweep)) => Some(sweep.path_id),
1534 Some(Artifact::Wall(wall)) => artifacts.get(&wall.sweep_id).and_then(|artifact| match artifact {
1535 Artifact::Sweep(sweep) => Some(sweep.path_id),
1536 _ => None,
1537 }),
1538 Some(Artifact::Cap(cap)) => artifacts.get(&cap.sweep_id).and_then(|artifact| match artifact {
1539 Artifact::Sweep(sweep) => Some(sweep.path_id),
1540 _ => None,
1541 }),
1542 _ => None,
1543 }
1544 };
1545 let Some(first_surface_ref) = surface_blend_cmd.surfaces.first() else {
1546 internal_error!(range, "SurfaceBlend command has no surfaces: id={id:?}, cmd={cmd:?}");
1547 };
1548 let first_surface_id = ArtifactId::new(first_surface_ref.object_id);
1549 let path_id = surface_id_to_path_id(first_surface_id).unwrap_or(first_surface_id);
1550 let trajectory_id = surface_blend_cmd
1551 .surfaces
1552 .get(1)
1553 .map(|surface| ArtifactId::new(surface.object_id))
1554 .and_then(surface_id_to_path_id);
1555 let return_arr = vec![Artifact::Sweep(Sweep {
1556 id,
1557 sub_type: SweepSubType::Blend,
1558 path_id,
1559 surface_ids: Vec::new(),
1560 edge_ids: Vec::new(),
1561 code_ref,
1562 trajectory_id,
1563 method: kittycad_modeling_cmds::shared::ExtrudeMethod::New,
1564 consumed: false,
1565 })];
1566 return Ok(return_arr);
1567 }
1568 ModelingCmd::Loft(loft_cmd) => {
1569 let Some(OkModelingCmdResponse::Loft(_)) = response else {
1570 return Ok(Vec::new());
1571 };
1572 let mut return_arr = Vec::new();
1573 return_arr.push(Artifact::Sweep(Sweep {
1574 id,
1575 sub_type: SweepSubType::Loft,
1576 path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
1579 KclError::new_internal(KclErrorDetails::new(
1580 format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
1581 vec![range],
1582 ))
1583 })?),
1584 surface_ids: Vec::new(),
1585 edge_ids: Vec::new(),
1586 code_ref,
1587 trajectory_id: None,
1588 method: kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
1589 consumed: false,
1590 }));
1591 for section_id in &loft_cmd.section_ids {
1592 let path = artifacts.get(&ArtifactId::new(*section_id));
1593 if let Some(Artifact::Path(path)) = path {
1594 let mut new_path = path.clone();
1595 new_path.consumed = true;
1596 new_path.sweep_id = Some(id);
1597 return_arr.push(Artifact::Path(new_path));
1598 }
1599 }
1600 return Ok(return_arr);
1601 }
1602 ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
1603 let Some(OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info)) = response else {
1604 return Ok(Vec::new());
1605 };
1606 let mut return_arr = Vec::new();
1607 let mut last_path = None;
1608 for face in &face_info.faces {
1609 if face.cap != ExtrusionFaceCapType::None {
1610 continue;
1611 }
1612 let Some(curve_id) = face.curve_id.map(ArtifactId::new) else {
1613 continue;
1614 };
1615 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
1616 continue;
1617 };
1618 let Some(Artifact::Segment(seg)) = artifacts.get(&curve_id) else {
1619 continue;
1620 };
1621 let Some(Artifact::Path(path)) = artifacts.get(&seg.path_id) else {
1622 continue;
1623 };
1624 last_path = Some(path);
1625 let Some(path_sweep_id) = path.sweep_id else {
1626 if path.outer_path_id.is_some() {
1629 continue; }
1631 return Err(KclError::new_internal(KclErrorDetails::new(
1632 format!(
1633 "Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
1634 ),
1635 vec![range],
1636 )));
1637 };
1638 let extra_artifact = exec_artifacts.values().find(|a| {
1639 if let Artifact::StartSketchOnFace(s) = a {
1640 s.face_id == face_id
1641 } else if let Artifact::StartSketchOnPlane(s) = a {
1642 s.plane_id == face_id
1643 } else {
1644 false
1645 }
1646 });
1647 let sketch_on_face_code_ref = extra_artifact
1648 .and_then(|a| match a {
1649 Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
1650 Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
1651 _ => None,
1652 })
1653 .unwrap_or_default();
1655
1656 return_arr.push(Artifact::Wall(Wall {
1657 id: face_id,
1658 seg_id: curve_id,
1659 edge_cut_edge_ids: Vec::new(),
1660 sweep_id: path_sweep_id,
1661 path_ids: Vec::new(),
1662 face_code_ref: sketch_on_face_code_ref,
1663 cmd_id: artifact_command.cmd_id,
1664 }));
1665 let mut new_seg = seg.clone();
1666 new_seg.surface_id = Some(face_id);
1667 return_arr.push(Artifact::Segment(new_seg));
1668 if let Some(Artifact::Sweep(sweep)) = path.sweep_id.and_then(|id| artifacts.get(&id)) {
1669 let mut new_sweep = sweep.clone();
1670 new_sweep.surface_ids = vec![face_id];
1671 return_arr.push(Artifact::Sweep(new_sweep));
1672 }
1673 }
1674 if let Some(path) = last_path {
1675 for face in &face_info.faces {
1676 let sub_type = match face.cap {
1677 ExtrusionFaceCapType::Top => CapSubType::End,
1678 ExtrusionFaceCapType::Bottom => CapSubType::Start,
1679 ExtrusionFaceCapType::None | ExtrusionFaceCapType::Both => continue,
1680 _other => {
1681 continue;
1683 }
1684 };
1685 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
1686 continue;
1687 };
1688 let Some(path_sweep_id) = path.sweep_id else {
1689 if path.outer_path_id.is_some() {
1692 continue; }
1694 return Err(KclError::new_internal(KclErrorDetails::new(
1695 format!(
1696 "Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
1697 ),
1698 vec![range],
1699 )));
1700 };
1701 let extra_artifact = exec_artifacts.values().find(|a| {
1702 if let Artifact::StartSketchOnFace(s) = a {
1703 s.face_id == face_id
1704 } else if let Artifact::StartSketchOnPlane(s) = a {
1705 s.plane_id == face_id
1706 } else {
1707 false
1708 }
1709 });
1710 let sketch_on_face_code_ref = extra_artifact
1711 .and_then(|a| match a {
1712 Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
1713 Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
1714 _ => None,
1715 })
1716 .unwrap_or_default();
1718 return_arr.push(Artifact::Cap(Cap {
1719 id: face_id,
1720 sub_type,
1721 edge_cut_edge_ids: Vec::new(),
1722 sweep_id: path_sweep_id,
1723 path_ids: Vec::new(),
1724 face_code_ref: sketch_on_face_code_ref,
1725 cmd_id: artifact_command.cmd_id,
1726 }));
1727 let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
1728 continue;
1729 };
1730 let mut new_sweep = sweep.clone();
1731 new_sweep.surface_ids = vec![face_id];
1732 return_arr.push(Artifact::Sweep(new_sweep));
1733 }
1734 }
1735 return Ok(return_arr);
1736 }
1737 ModelingCmd::Solid3dGetAdjacencyInfo(kcmc::Solid3dGetAdjacencyInfo { .. }) => {
1738 let Some(OkModelingCmdResponse::Solid3dGetAdjacencyInfo(info)) = response else {
1739 return Ok(Vec::new());
1740 };
1741
1742 let mut return_arr = Vec::new();
1743 for (index, edge) in info.edges.iter().enumerate() {
1744 let Some(original_info) = &edge.original_info else {
1745 continue;
1746 };
1747 let edge_id = ArtifactId::new(original_info.edge_id);
1748 let Some(artifact) = artifacts.get(&edge_id) else {
1749 continue;
1750 };
1751 match artifact {
1752 Artifact::Segment(segment) => {
1753 let mut new_segment = segment.clone();
1754 new_segment.common_surface_ids =
1755 original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
1756 return_arr.push(Artifact::Segment(new_segment));
1757 }
1758 Artifact::SweepEdge(sweep_edge) => {
1759 let mut new_sweep_edge = sweep_edge.clone();
1760 new_sweep_edge.common_surface_ids =
1761 original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
1762 return_arr.push(Artifact::SweepEdge(new_sweep_edge));
1763 }
1764 _ => {}
1765 };
1766
1767 let Some(Artifact::Segment(segment)) = artifacts.get(&edge_id) else {
1768 continue;
1769 };
1770 let Some(surface_id) = segment.surface_id else {
1771 continue;
1772 };
1773 let Some(Artifact::Wall(wall)) = artifacts.get(&surface_id) else {
1774 continue;
1775 };
1776 let Some(Artifact::Sweep(sweep)) = artifacts.get(&wall.sweep_id) else {
1777 continue;
1778 };
1779 let Some(Artifact::Path(_)) = artifacts.get(&sweep.path_id) else {
1780 continue;
1781 };
1782
1783 if let Some(opposite_info) = &edge.opposite_info {
1784 return_arr.push(Artifact::SweepEdge(SweepEdge {
1785 id: opposite_info.edge_id.into(),
1786 sub_type: SweepEdgeSubType::Opposite,
1787 seg_id: edge_id,
1788 cmd_id: artifact_command.cmd_id,
1789 index,
1790 sweep_id: sweep.id,
1791 common_surface_ids: opposite_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
1792 }));
1793 let mut new_segment = segment.clone();
1794 new_segment.edge_ids = vec![opposite_info.edge_id.into()];
1795 return_arr.push(Artifact::Segment(new_segment));
1796 let mut new_sweep = sweep.clone();
1797 new_sweep.edge_ids = vec![opposite_info.edge_id.into()];
1798 return_arr.push(Artifact::Sweep(new_sweep));
1799 let mut new_wall = wall.clone();
1800 new_wall.edge_cut_edge_ids = vec![opposite_info.edge_id.into()];
1801 return_arr.push(Artifact::Wall(new_wall));
1802 }
1803 if let Some(adjacent_info) = &edge.adjacent_info {
1804 return_arr.push(Artifact::SweepEdge(SweepEdge {
1805 id: adjacent_info.edge_id.into(),
1806 sub_type: SweepEdgeSubType::Adjacent,
1807 seg_id: edge_id,
1808 cmd_id: artifact_command.cmd_id,
1809 index,
1810 sweep_id: sweep.id,
1811 common_surface_ids: adjacent_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
1812 }));
1813 let mut new_segment = segment.clone();
1814 new_segment.edge_ids = vec![adjacent_info.edge_id.into()];
1815 return_arr.push(Artifact::Segment(new_segment));
1816 let mut new_sweep = sweep.clone();
1817 new_sweep.edge_ids = vec![adjacent_info.edge_id.into()];
1818 return_arr.push(Artifact::Sweep(new_sweep));
1819 let mut new_wall = wall.clone();
1820 new_wall.edge_cut_edge_ids = vec![adjacent_info.edge_id.into()];
1821 return_arr.push(Artifact::Wall(new_wall));
1822 }
1823 }
1824 return Ok(return_arr);
1825 }
1826 ModelingCmd::Solid3dMultiJoin(cmd) => {
1827 let mut return_arr = Vec::new();
1828 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
1829 id,
1830 consumed: false,
1831 sub_type: CompositeSolidSubType::Union,
1832 output_index: None,
1833 solid_ids: cmd.object_ids.iter().map(|id| id.into()).collect(),
1834 tool_ids: vec![],
1835 code_ref,
1836 composite_solid_id: None,
1837 }));
1838
1839 let solid_ids = cmd.object_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
1840
1841 for input_id in &solid_ids {
1842 if let Some(artifact) = artifacts.get(input_id)
1843 && let Artifact::CompositeSolid(comp) = artifact
1844 {
1845 let mut new_comp = comp.clone();
1846 new_comp.composite_solid_id = Some(id);
1847 new_comp.consumed = true;
1848 return_arr.push(Artifact::CompositeSolid(new_comp));
1849 }
1850 }
1851 return Ok(return_arr);
1852 }
1853 ModelingCmd::Solid3dFilletEdge(cmd) => {
1854 let mut return_arr = Vec::new();
1855 let edge_id = if let Some(edge_id) = cmd.edge_id {
1856 ArtifactId::new(edge_id)
1857 } else {
1858 let Some(edge_id) = cmd.edge_ids.first() else {
1859 internal_error!(
1860 range,
1861 "Solid3dFilletEdge command has no edge ID: id={id:?}, cmd={cmd:?}"
1862 );
1863 };
1864 edge_id.into()
1865 };
1866 return_arr.push(Artifact::EdgeCut(EdgeCut {
1867 id,
1868 sub_type: cmd.cut_type.into(),
1869 consumed_edge_id: edge_id,
1870 edge_ids: Vec::new(),
1871 surface_id: None,
1872 code_ref,
1873 }));
1874 let consumed_edge = artifacts.get(&edge_id);
1875 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
1876 let mut new_segment = consumed_edge.clone();
1877 new_segment.edge_cut_id = Some(id);
1878 return_arr.push(Artifact::Segment(new_segment));
1879 } else {
1880 }
1882 return Ok(return_arr);
1883 }
1884 ModelingCmd::Solid3dCutEdges(cmd) => {
1885 let mut return_arr = Vec::new();
1886 let edge_id = if let Some(edge_id) = cmd.edge_ids.first() {
1887 edge_id.into()
1888 } else {
1889 internal_error!(range, "Solid3dCutEdges command has no edge ID: id={id:?}, cmd={cmd:?}");
1890 };
1891 return_arr.push(Artifact::EdgeCut(EdgeCut {
1892 id,
1893 sub_type: cmd.cut_type.into(),
1894 consumed_edge_id: edge_id,
1895 edge_ids: Vec::new(),
1896 surface_id: None,
1897 code_ref,
1898 }));
1899 let consumed_edge = artifacts.get(&edge_id);
1900 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
1901 let mut new_segment = consumed_edge.clone();
1902 new_segment.edge_cut_id = Some(id);
1903 return_arr.push(Artifact::Segment(new_segment));
1904 } else {
1905 }
1907 return Ok(return_arr);
1908 }
1909 ModelingCmd::EntityMakeHelix(cmd) => {
1910 let cylinder_id = ArtifactId::new(cmd.cylinder_id);
1911 let return_arr = vec![Artifact::Helix(Helix {
1912 id,
1913 axis_id: Some(cylinder_id),
1914 code_ref,
1915 trajectory_sweep_id: None,
1916 consumed: false,
1917 })];
1918 return Ok(return_arr);
1919 }
1920 ModelingCmd::EntityMakeHelixFromParams(_) => {
1921 let return_arr = vec![Artifact::Helix(Helix {
1922 id,
1923 axis_id: None,
1924 code_ref,
1925 trajectory_sweep_id: None,
1926 consumed: false,
1927 })];
1928 return Ok(return_arr);
1929 }
1930 ModelingCmd::EntityMakeHelixFromEdge(helix) => {
1931 let return_arr = vec![Artifact::Helix(Helix {
1932 id,
1933 axis_id: helix.edge_id.map(ArtifactId::new),
1934 code_ref,
1935 trajectory_sweep_id: None,
1936 consumed: false,
1937 })];
1938 return Ok(return_arr);
1941 }
1942 ModelingCmd::Solid2dAddHole(solid2d_add_hole) => {
1943 let mut return_arr = Vec::new();
1944 let outer_path = artifacts.get(&ArtifactId::new(solid2d_add_hole.object_id));
1946 if let Some(Artifact::Path(path)) = outer_path {
1947 let mut new_path = path.clone();
1948 new_path.inner_path_id = Some(ArtifactId::new(solid2d_add_hole.hole_id));
1949 return_arr.push(Artifact::Path(new_path));
1950 }
1951 let inner_solid2d = artifacts.get(&ArtifactId::new(solid2d_add_hole.hole_id));
1953 if let Some(Artifact::Path(path)) = inner_solid2d {
1954 let mut new_path = path.clone();
1955 new_path.consumed = true;
1956 new_path.outer_path_id = Some(ArtifactId::new(solid2d_add_hole.object_id));
1957 return_arr.push(Artifact::Path(new_path));
1958 }
1959 return Ok(return_arr);
1960 }
1961 ModelingCmd::BooleanIntersection(_) | ModelingCmd::BooleanSubtract(_) | ModelingCmd::BooleanUnion(_) => {
1962 let (sub_type, solid_ids, tool_ids) = match cmd {
1963 ModelingCmd::BooleanIntersection(intersection) => {
1964 let solid_ids = intersection
1965 .solid_ids
1966 .iter()
1967 .copied()
1968 .map(ArtifactId::new)
1969 .collect::<Vec<_>>();
1970 (CompositeSolidSubType::Intersect, solid_ids, Vec::new())
1971 }
1972 ModelingCmd::BooleanSubtract(subtract) => {
1973 let solid_ids = subtract
1974 .target_ids
1975 .iter()
1976 .copied()
1977 .map(ArtifactId::new)
1978 .collect::<Vec<_>>();
1979 let tool_ids = subtract
1980 .tool_ids
1981 .iter()
1982 .copied()
1983 .map(ArtifactId::new)
1984 .collect::<Vec<_>>();
1985 (CompositeSolidSubType::Subtract, solid_ids, tool_ids)
1986 }
1987 ModelingCmd::BooleanUnion(union) => {
1988 let solid_ids = union.solid_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
1989 (CompositeSolidSubType::Union, solid_ids, Vec::new())
1990 }
1991 _ => internal_error!(
1992 range,
1993 "Boolean or composite command variant not handled: id={id:?}, cmd={cmd:?}"
1994 ),
1995 };
1996
1997 let mut new_solid_ids = vec![id];
1998
1999 let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2002
2003 match response {
2004 Some(OkModelingCmdResponse::BooleanIntersection(intersection)) => intersection
2005 .extra_solid_ids
2006 .iter()
2007 .copied()
2008 .map(ArtifactId::new)
2009 .filter(not_cmd_id)
2010 .for_each(|id| new_solid_ids.push(id)),
2011 Some(OkModelingCmdResponse::BooleanSubtract(subtract)) => subtract
2012 .extra_solid_ids
2013 .iter()
2014 .copied()
2015 .map(ArtifactId::new)
2016 .filter(not_cmd_id)
2017 .for_each(|id| new_solid_ids.push(id)),
2018 Some(OkModelingCmdResponse::BooleanUnion(union)) => union
2019 .extra_solid_ids
2020 .iter()
2021 .copied()
2022 .map(ArtifactId::new)
2023 .filter(not_cmd_id)
2024 .for_each(|id| new_solid_ids.push(id)),
2025 _ => {}
2026 }
2027
2028 let mut return_arr = Vec::new();
2029
2030 for solid_id in &new_solid_ids {
2032 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2034 id: *solid_id,
2035 consumed: false,
2036 sub_type,
2037 output_index: None,
2038 solid_ids: solid_ids.clone(),
2039 tool_ids: tool_ids.clone(),
2040 code_ref: code_ref.clone(),
2041 composite_solid_id: None,
2042 }));
2043
2044 for input_id in &solid_ids {
2046 if let Some(artifact) = artifacts.get(input_id) {
2047 match artifact {
2048 Artifact::CompositeSolid(comp) => {
2049 let mut new_comp = comp.clone();
2050 new_comp.composite_solid_id = Some(*solid_id);
2051 new_comp.consumed = true;
2052 return_arr.push(Artifact::CompositeSolid(new_comp));
2053 }
2054 Artifact::Path(path) => {
2055 let mut new_path = path.clone();
2056 new_path.composite_solid_id = Some(*solid_id);
2057
2058 if let Some(sweep_id) = new_path.sweep_id
2061 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
2062 {
2063 let mut new_sweep = sweep.clone();
2064 new_sweep.consumed = true;
2065 return_arr.push(Artifact::Sweep(new_sweep));
2066 }
2067
2068 return_arr.push(Artifact::Path(new_path));
2069 }
2070 _ => {}
2071 }
2072 }
2073 }
2074
2075 for tool_id in &tool_ids {
2077 if let Some(artifact) = artifacts.get(tool_id) {
2078 match artifact {
2079 Artifact::CompositeSolid(comp) => {
2080 let mut new_comp = comp.clone();
2081 new_comp.composite_solid_id = Some(*solid_id);
2082 new_comp.consumed = true;
2083 return_arr.push(Artifact::CompositeSolid(new_comp));
2084 }
2085 Artifact::Path(path) => {
2086 let mut new_path = path.clone();
2087 new_path.composite_solid_id = Some(*solid_id);
2088
2089 if let Some(sweep_id) = new_path.sweep_id
2092 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
2093 {
2094 let mut new_sweep = sweep.clone();
2095 new_sweep.consumed = true;
2096 return_arr.push(Artifact::Sweep(new_sweep));
2097 }
2098
2099 return_arr.push(Artifact::Path(new_path));
2100 }
2101 _ => {}
2102 }
2103 }
2104 }
2105 }
2106
2107 return Ok(return_arr);
2108 }
2109 ModelingCmd::BooleanImprint(imprint) => {
2110 let solid_ids = imprint
2111 .body_ids
2112 .iter()
2113 .copied()
2114 .map(ArtifactId::new)
2115 .collect::<Vec<_>>();
2116 let tool_ids = imprint
2117 .tool_ids
2118 .as_ref()
2119 .map(|ids| ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>())
2120 .unwrap_or_default();
2121
2122 let mut new_solid_ids = vec![id];
2123 let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2124 if let Some(OkModelingCmdResponse::BooleanImprint(imprint)) = response {
2125 imprint
2126 .extra_solid_ids
2127 .iter()
2128 .copied()
2129 .map(ArtifactId::new)
2130 .filter(not_cmd_id)
2131 .for_each(|id| new_solid_ids.push(id));
2132 }
2133
2134 let mut return_arr = Vec::new();
2135
2136 for (output_index, solid_id) in new_solid_ids.iter().enumerate() {
2137 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2138 id: *solid_id,
2139 consumed: false,
2140 sub_type: CompositeSolidSubType::Split,
2141 output_index: Some(output_index),
2142 solid_ids: solid_ids.clone(),
2143 tool_ids: tool_ids.clone(),
2144 code_ref: code_ref.clone(),
2145 composite_solid_id: None,
2146 }));
2147
2148 for input_id in solid_ids.iter().chain(tool_ids.iter()) {
2149 if let Some(artifact) = artifacts.get(input_id) {
2150 match artifact {
2151 Artifact::CompositeSolid(comp) => {
2152 let mut new_comp = comp.clone();
2153 new_comp.composite_solid_id = Some(*solid_id);
2154 new_comp.consumed = true;
2155 return_arr.push(Artifact::CompositeSolid(new_comp));
2156 }
2157 Artifact::Path(path) => {
2158 let mut new_path = path.clone();
2159 new_path.composite_solid_id = Some(*solid_id);
2160
2161 if let Some(sweep_id) = new_path.sweep_id
2162 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
2163 {
2164 let mut new_sweep = sweep.clone();
2165 new_sweep.consumed = true;
2166 return_arr.push(Artifact::Sweep(new_sweep));
2167 }
2168
2169 return_arr.push(Artifact::Path(new_path));
2170 }
2171 _ => {}
2172 }
2173 }
2174 }
2175 }
2176
2177 return Ok(return_arr);
2178 }
2179 _ => {}
2180 }
2181
2182 Ok(Vec::new())
2183}