1use fnv::FnvHashMap;
2use indexmap::IndexMap;
3use kittycad_modeling_cmds::{
4 self as kcmc,
5 id::ModelingCmdId,
6 ok_response::OkModelingCmdResponse,
7 shared::ExtrusionFaceCapType,
8 websocket::{BatchResponse, OkWebSocketResponseData, WebSocketResponse},
9 EnableSketchMode, ModelingCmd, SketchModeDisable,
10};
11use schemars::JsonSchema;
12use serde::{ser::SerializeSeq, Deserialize, Serialize};
13use uuid::Uuid;
14
15use crate::{
16 errors::KclErrorDetails,
17 parsing::ast::types::{Node, Program},
18 KclError, SourceRange,
19};
20
21#[cfg(test)]
22mod mermaid_tests;
23
24#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
28#[ts(export_to = "Artifact.ts")]
29#[serde(rename_all = "camelCase")]
30pub struct ArtifactCommand {
31 pub cmd_id: Uuid,
33 pub range: SourceRange,
34 pub command: ModelingCmd,
39}
40
41#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, ts_rs::TS, JsonSchema)]
42#[ts(export_to = "Artifact.ts")]
43pub struct ArtifactId(Uuid);
44
45impl ArtifactId {
46 pub fn new(uuid: Uuid) -> Self {
47 Self(uuid)
48 }
49}
50
51impl From<Uuid> for ArtifactId {
52 fn from(uuid: Uuid) -> Self {
53 Self::new(uuid)
54 }
55}
56
57impl From<&Uuid> for ArtifactId {
58 fn from(uuid: &Uuid) -> Self {
59 Self::new(*uuid)
60 }
61}
62
63impl From<ArtifactId> for Uuid {
64 fn from(id: ArtifactId) -> Self {
65 id.0
66 }
67}
68
69impl From<&ArtifactId> for Uuid {
70 fn from(id: &ArtifactId) -> Self {
71 id.0
72 }
73}
74
75impl From<ModelingCmdId> for ArtifactId {
76 fn from(id: ModelingCmdId) -> Self {
77 Self::new(*id.as_ref())
78 }
79}
80
81impl From<&ModelingCmdId> for ArtifactId {
82 fn from(id: &ModelingCmdId) -> Self {
83 Self::new(*id.as_ref())
84 }
85}
86
87pub type DummyPathToNode = Vec<()>;
88
89fn serialize_dummy_path_to_node<S>(_path_to_node: &DummyPathToNode, serializer: S) -> Result<S::Ok, S::Error>
90where
91 S: serde::Serializer,
92{
93 let seq = serializer.serialize_seq(Some(0))?;
95 seq.end()
96}
97
98#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, ts_rs::TS)]
99#[ts(export_to = "Artifact.ts")]
100#[serde(rename_all = "camelCase")]
101pub struct CodeRef {
102 pub range: SourceRange,
103 #[serde(default, serialize_with = "serialize_dummy_path_to_node")]
105 #[ts(type = "Array<[string | number, string]>")]
106 pub path_to_node: DummyPathToNode,
107}
108
109impl CodeRef {
110 pub fn placeholder(range: SourceRange) -> Self {
111 Self {
112 range,
113 path_to_node: Vec::new(),
114 }
115 }
116}
117
118#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
119#[ts(export_to = "Artifact.ts")]
120#[serde(rename_all = "camelCase")]
121pub struct Plane {
122 pub id: ArtifactId,
123 pub path_ids: Vec<ArtifactId>,
124 pub code_ref: CodeRef,
125}
126
127#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
128#[ts(export_to = "Artifact.ts")]
129#[serde(rename_all = "camelCase")]
130pub struct Path {
131 pub id: ArtifactId,
132 pub plane_id: ArtifactId,
133 pub seg_ids: Vec<ArtifactId>,
134 #[serde(default, skip_serializing_if = "Option::is_none")]
135 pub sweep_id: Option<ArtifactId>,
136 #[serde(default, skip_serializing_if = "Option::is_none")]
137 pub solid2d_id: Option<ArtifactId>,
138 pub code_ref: CodeRef,
139}
140
141#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
142#[ts(export_to = "Artifact.ts")]
143#[serde(rename_all = "camelCase")]
144pub struct Segment {
145 pub id: ArtifactId,
146 pub path_id: ArtifactId,
147 #[serde(default, skip_serializing_if = "Option::is_none")]
148 pub surface_id: Option<ArtifactId>,
149 #[serde(default, skip_serializing_if = "Vec::is_empty")]
150 pub edge_ids: Vec<ArtifactId>,
151 #[serde(default, skip_serializing_if = "Option::is_none")]
152 pub edge_cut_id: Option<ArtifactId>,
153 pub code_ref: CodeRef,
154}
155
156#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
158#[ts(export_to = "Artifact.ts")]
159#[serde(rename_all = "camelCase")]
160pub struct Sweep {
161 pub id: ArtifactId,
162 pub sub_type: SweepSubType,
163 pub path_id: ArtifactId,
164 #[serde(default, skip_serializing_if = "Vec::is_empty")]
165 pub surface_ids: Vec<ArtifactId>,
166 #[serde(default, skip_serializing_if = "Vec::is_empty")]
167 pub edge_ids: Vec<ArtifactId>,
168 pub code_ref: CodeRef,
169}
170
171#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
172#[ts(export_to = "Artifact.ts")]
173#[serde(rename_all = "camelCase")]
174pub enum SweepSubType {
175 Extrusion,
176 Revolve,
177 RevolveAboutEdge,
178 Loft,
179 Sweep,
180}
181
182#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
183#[ts(export_to = "Artifact.ts")]
184#[serde(rename_all = "camelCase")]
185pub struct Solid2d {
186 pub id: ArtifactId,
187 pub path_id: ArtifactId,
188}
189
190#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
191#[ts(export_to = "Artifact.ts")]
192#[serde(rename_all = "camelCase")]
193pub struct StartSketchOnFace {
194 pub id: ArtifactId,
195 pub face_id: ArtifactId,
196 pub code_ref: CodeRef,
197}
198
199#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
200#[ts(export_to = "Artifact.ts")]
201#[serde(rename_all = "camelCase")]
202pub struct StartSketchOnPlane {
203 pub id: ArtifactId,
204 pub plane_id: ArtifactId,
205 pub code_ref: CodeRef,
206}
207
208#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
209#[ts(export_to = "Artifact.ts")]
210#[serde(rename_all = "camelCase")]
211pub struct Wall {
212 pub id: ArtifactId,
213 pub seg_id: ArtifactId,
214 #[serde(default, skip_serializing_if = "Vec::is_empty")]
215 pub edge_cut_edge_ids: Vec<ArtifactId>,
216 pub sweep_id: ArtifactId,
217 #[serde(default, skip_serializing_if = "Vec::is_empty")]
218 pub path_ids: Vec<ArtifactId>,
219 pub face_code_ref: CodeRef,
222}
223
224#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
225#[ts(export_to = "Artifact.ts")]
226#[serde(rename_all = "camelCase")]
227pub struct Cap {
228 pub id: ArtifactId,
229 pub sub_type: CapSubType,
230 #[serde(default, skip_serializing_if = "Vec::is_empty")]
231 pub edge_cut_edge_ids: Vec<ArtifactId>,
232 pub sweep_id: ArtifactId,
233 #[serde(default, skip_serializing_if = "Vec::is_empty")]
234 pub path_ids: Vec<ArtifactId>,
235 pub face_code_ref: CodeRef,
238}
239
240#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
241#[ts(export_to = "Artifact.ts")]
242#[serde(rename_all = "camelCase")]
243pub enum CapSubType {
244 Start,
245 End,
246}
247
248#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
249#[ts(export_to = "Artifact.ts")]
250#[serde(rename_all = "camelCase")]
251pub struct SweepEdge {
252 pub id: ArtifactId,
253 pub sub_type: SweepEdgeSubType,
254 pub seg_id: ArtifactId,
255 pub sweep_id: ArtifactId,
256}
257
258#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
259#[ts(export_to = "Artifact.ts")]
260#[serde(rename_all = "camelCase")]
261pub enum SweepEdgeSubType {
262 Opposite,
263 Adjacent,
264}
265
266#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
267#[ts(export_to = "Artifact.ts")]
268#[serde(rename_all = "camelCase")]
269pub struct EdgeCut {
270 pub id: ArtifactId,
271 pub sub_type: EdgeCutSubType,
272 pub consumed_edge_id: ArtifactId,
273 #[serde(default, skip_serializing_if = "Vec::is_empty")]
274 pub edge_ids: Vec<ArtifactId>,
275 #[serde(default, skip_serializing_if = "Option::is_none")]
276 pub surface_id: Option<ArtifactId>,
277 pub code_ref: CodeRef,
278}
279
280#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
281#[ts(export_to = "Artifact.ts")]
282#[serde(rename_all = "camelCase")]
283pub enum EdgeCutSubType {
284 Fillet,
285 Chamfer,
286}
287
288impl From<kcmc::shared::CutType> for EdgeCutSubType {
289 fn from(cut_type: kcmc::shared::CutType) -> Self {
290 match cut_type {
291 kcmc::shared::CutType::Fillet => EdgeCutSubType::Fillet,
292 kcmc::shared::CutType::Chamfer => EdgeCutSubType::Chamfer,
293 }
294 }
295}
296
297#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
298#[ts(export_to = "Artifact.ts")]
299#[serde(rename_all = "camelCase")]
300pub struct EdgeCutEdge {
301 pub id: ArtifactId,
302 pub edge_cut_id: ArtifactId,
303 pub surface_id: ArtifactId,
304}
305
306#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
307#[ts(export_to = "Artifact.ts")]
308#[serde(rename_all = "camelCase")]
309pub struct Helix {
310 pub id: ArtifactId,
311 pub axis_id: Option<ArtifactId>,
314 pub code_ref: CodeRef,
315}
316
317#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
318#[ts(export_to = "Artifact.ts")]
319#[serde(tag = "type", rename_all = "camelCase")]
320pub enum Artifact {
321 Plane(Plane),
322 Path(Path),
323 Segment(Segment),
324 Solid2d(Solid2d),
325 StartSketchOnFace(StartSketchOnFace),
326 StartSketchOnPlane(StartSketchOnPlane),
327 Sweep(Sweep),
328 Wall(Wall),
329 Cap(Cap),
330 SweepEdge(SweepEdge),
331 EdgeCut(EdgeCut),
332 EdgeCutEdge(EdgeCutEdge),
333 Helix(Helix),
334}
335
336impl Artifact {
337 pub(crate) fn id(&self) -> ArtifactId {
338 match self {
339 Artifact::Plane(a) => a.id,
340 Artifact::Path(a) => a.id,
341 Artifact::Segment(a) => a.id,
342 Artifact::Solid2d(a) => a.id,
343 Artifact::StartSketchOnFace(a) => a.id,
344 Artifact::StartSketchOnPlane(a) => a.id,
345 Artifact::Sweep(a) => a.id,
346 Artifact::Wall(a) => a.id,
347 Artifact::Cap(a) => a.id,
348 Artifact::SweepEdge(a) => a.id,
349 Artifact::EdgeCut(a) => a.id,
350 Artifact::EdgeCutEdge(a) => a.id,
351 Artifact::Helix(a) => a.id,
352 }
353 }
354
355 #[expect(dead_code)]
356 pub(crate) fn code_ref(&self) -> Option<&CodeRef> {
357 match self {
358 Artifact::Plane(a) => Some(&a.code_ref),
359 Artifact::Path(a) => Some(&a.code_ref),
360 Artifact::Segment(a) => Some(&a.code_ref),
361 Artifact::Solid2d(_) => None,
362 Artifact::StartSketchOnFace(a) => Some(&a.code_ref),
363 Artifact::StartSketchOnPlane(a) => Some(&a.code_ref),
364 Artifact::Sweep(a) => Some(&a.code_ref),
365 Artifact::Wall(_) => None,
366 Artifact::Cap(_) => None,
367 Artifact::SweepEdge(_) => None,
368 Artifact::EdgeCut(a) => Some(&a.code_ref),
369 Artifact::EdgeCutEdge(_) => None,
370 Artifact::Helix(a) => Some(&a.code_ref),
371 }
372 }
373
374 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
377 match self {
378 Artifact::Plane(a) => a.merge(new),
379 Artifact::Path(a) => a.merge(new),
380 Artifact::Segment(a) => a.merge(new),
381 Artifact::Solid2d(_) => Some(new),
382 Artifact::StartSketchOnFace { .. } => Some(new),
383 Artifact::StartSketchOnPlane { .. } => Some(new),
384 Artifact::Sweep(a) => a.merge(new),
385 Artifact::Wall(a) => a.merge(new),
386 Artifact::Cap(a) => a.merge(new),
387 Artifact::SweepEdge(_) => Some(new),
388 Artifact::EdgeCut(a) => a.merge(new),
389 Artifact::EdgeCutEdge(_) => Some(new),
390 Artifact::Helix(_) => Some(new),
391 }
392 }
393}
394
395impl Plane {
396 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
397 let Artifact::Plane(new) = new else {
398 return Some(new);
399 };
400 merge_ids(&mut self.path_ids, new.path_ids);
401
402 None
403 }
404}
405
406impl Path {
407 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
408 let Artifact::Path(new) = new else {
409 return Some(new);
410 };
411 merge_opt_id(&mut self.sweep_id, new.sweep_id);
412 merge_ids(&mut self.seg_ids, new.seg_ids);
413 merge_opt_id(&mut self.solid2d_id, new.solid2d_id);
414
415 None
416 }
417}
418
419impl Segment {
420 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
421 let Artifact::Segment(new) = new else {
422 return Some(new);
423 };
424 merge_opt_id(&mut self.surface_id, new.surface_id);
425 merge_ids(&mut self.edge_ids, new.edge_ids);
426 merge_opt_id(&mut self.edge_cut_id, new.edge_cut_id);
427
428 None
429 }
430}
431
432impl Sweep {
433 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
434 let Artifact::Sweep(new) = new else {
435 return Some(new);
436 };
437 merge_ids(&mut self.surface_ids, new.surface_ids);
438 merge_ids(&mut self.edge_ids, new.edge_ids);
439
440 None
441 }
442}
443
444impl Wall {
445 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
446 let Artifact::Wall(new) = new else {
447 return Some(new);
448 };
449 merge_ids(&mut self.edge_cut_edge_ids, new.edge_cut_edge_ids);
450 merge_ids(&mut self.path_ids, new.path_ids);
451
452 None
453 }
454}
455
456impl Cap {
457 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
458 let Artifact::Cap(new) = new else {
459 return Some(new);
460 };
461 merge_ids(&mut self.edge_cut_edge_ids, new.edge_cut_edge_ids);
462 merge_ids(&mut self.path_ids, new.path_ids);
463
464 None
465 }
466}
467
468impl EdgeCut {
469 fn merge(&mut self, new: Artifact) -> Option<Artifact> {
470 let Artifact::EdgeCut(new) = new else {
471 return Some(new);
472 };
473 merge_opt_id(&mut self.surface_id, new.surface_id);
474 merge_ids(&mut self.edge_ids, new.edge_ids);
475
476 None
477 }
478}
479
480#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize, ts_rs::TS)]
481#[ts(export_to = "Artifact.ts")]
482#[serde(rename_all = "camelCase")]
483pub struct ArtifactGraph {
484 map: IndexMap<ArtifactId, Artifact>,
485}
486
487impl ArtifactGraph {
488 pub fn len(&self) -> usize {
489 self.map.len()
490 }
491}
492
493pub(super) fn build_artifact_graph(
494 artifact_commands: &[ArtifactCommand],
495 responses: &IndexMap<Uuid, WebSocketResponse>,
496 ast: &Node<Program>,
497 exec_artifacts: &IndexMap<ArtifactId, Artifact>,
498) -> Result<ArtifactGraph, KclError> {
499 let mut map = IndexMap::new();
500
501 let mut current_plane_id = None;
502
503 for artifact_command in artifact_commands {
504 if let ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) = artifact_command.command {
505 current_plane_id = Some(entity_id);
506 }
507 if let ModelingCmd::SketchModeDisable(SketchModeDisable { .. }) = artifact_command.command {
508 current_plane_id = None;
509 }
510
511 let flattened_responses = flatten_modeling_command_responses(responses);
512 let artifact_updates = artifacts_to_update(
513 &map,
514 artifact_command,
515 &flattened_responses,
516 current_plane_id,
517 ast,
518 exec_artifacts,
519 )?;
520 for artifact in artifact_updates {
521 merge_artifact_into_map(&mut map, artifact);
523 }
524 }
525
526 for exec_artifact in exec_artifacts.values() {
527 merge_artifact_into_map(&mut map, exec_artifact.clone());
528 }
529
530 Ok(ArtifactGraph { map })
531}
532
533fn flatten_modeling_command_responses(
536 responses: &IndexMap<Uuid, WebSocketResponse>,
537) -> FnvHashMap<Uuid, OkModelingCmdResponse> {
538 let mut map = FnvHashMap::default();
539 for (cmd_id, ws_response) in responses {
540 let WebSocketResponse::Success(response) = ws_response else {
541 continue;
543 };
544 match &response.resp {
545 OkWebSocketResponseData::Modeling { modeling_response } => {
546 map.insert(*cmd_id, modeling_response.clone());
547 }
548 OkWebSocketResponseData::ModelingBatch { responses } =>
549 {
550 #[expect(
551 clippy::iter_over_hash_type,
552 reason = "Since we're moving entries to another unordered map, it's fine that the order is undefined"
553 )]
554 for (cmd_id, batch_response) in responses {
555 if let BatchResponse::Success {
556 response: modeling_response,
557 } = batch_response
558 {
559 map.insert(*cmd_id.as_ref(), modeling_response.clone());
560 }
561 }
562 }
563 OkWebSocketResponseData::IceServerInfo { .. }
564 | OkWebSocketResponseData::TrickleIce { .. }
565 | OkWebSocketResponseData::SdpAnswer { .. }
566 | OkWebSocketResponseData::Export { .. }
567 | OkWebSocketResponseData::MetricsRequest { .. }
568 | OkWebSocketResponseData::ModelingSessionData { .. }
569 | OkWebSocketResponseData::Pong { .. } => {}
570 }
571 }
572
573 map
574}
575
576fn merge_artifact_into_map(map: &mut IndexMap<ArtifactId, Artifact>, new_artifact: Artifact) {
577 let id = new_artifact.id();
578 let Some(old_artifact) = map.get_mut(&id) else {
579 map.insert(id, new_artifact);
581 return;
582 };
583
584 if let Some(replacement) = old_artifact.merge(new_artifact) {
585 *old_artifact = replacement;
586 }
587}
588
589fn merge_ids(base: &mut Vec<ArtifactId>, new: Vec<ArtifactId>) {
593 let original_len = base.len();
594 for id in new {
595 let original_base = &base[..original_len];
597 if !original_base.contains(&id) {
598 base.push(id);
599 }
600 }
601}
602
603fn merge_opt_id(base: &mut Option<ArtifactId>, new: Option<ArtifactId>) {
604 *base = new;
606}
607
608fn artifacts_to_update(
609 artifacts: &IndexMap<ArtifactId, Artifact>,
610 artifact_command: &ArtifactCommand,
611 responses: &FnvHashMap<Uuid, OkModelingCmdResponse>,
612 current_plane_id: Option<Uuid>,
613 _ast: &Node<Program>,
614 exec_artifacts: &IndexMap<ArtifactId, Artifact>,
615) -> Result<Vec<Artifact>, KclError> {
616 let path_to_node = Vec::new();
620
621 let range = artifact_command.range;
622 let uuid = artifact_command.cmd_id;
623 let id = ArtifactId::new(uuid);
624
625 let Some(response) = responses.get(&uuid) else {
626 return Ok(Vec::new());
628 };
629
630 let cmd = &artifact_command.command;
631
632 match cmd {
633 ModelingCmd::MakePlane(_) => {
634 if range.is_synthetic() {
635 return Ok(Vec::new());
636 }
637 return Ok(vec![Artifact::Plane(Plane {
641 id,
642 path_ids: Vec::new(),
643 code_ref: CodeRef { range, path_to_node },
644 })]);
645 }
646 ModelingCmd::EnableSketchMode(_) => {
647 let current_plane_id = current_plane_id.ok_or_else(|| {
648 KclError::Internal(KclErrorDetails {
649 message: format!(
650 "Expected a current plane ID when processing EnableSketchMode command, but we have none: {id:?}"
651 ),
652 source_ranges: vec![range],
653 })
654 })?;
655 let existing_plane = artifacts.get(&ArtifactId::new(current_plane_id));
656 match existing_plane {
657 Some(Artifact::Wall(wall)) => {
658 return Ok(vec![Artifact::Wall(Wall {
659 id: current_plane_id.into(),
660 seg_id: wall.seg_id,
661 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
662 sweep_id: wall.sweep_id,
663 path_ids: wall.path_ids.clone(),
664 face_code_ref: wall.face_code_ref.clone(),
665 })]);
666 }
667 Some(Artifact::Cap(cap)) => {
668 return Ok(vec![Artifact::Cap(Cap {
669 id: current_plane_id.into(),
670 sub_type: cap.sub_type,
671 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
672 sweep_id: cap.sweep_id,
673 path_ids: cap.path_ids.clone(),
674 face_code_ref: cap.face_code_ref.clone(),
675 })]);
676 }
677 Some(_) | None => {
678 let path_ids = match existing_plane {
679 Some(Artifact::Plane(Plane { path_ids, .. })) => path_ids.clone(),
680 _ => Vec::new(),
681 };
682 return Ok(vec![Artifact::Plane(Plane {
683 id: current_plane_id.into(),
684 path_ids,
685 code_ref: CodeRef { range, path_to_node },
686 })]);
687 }
688 }
689 }
690 ModelingCmd::StartPath(_) => {
691 let mut return_arr = Vec::new();
692 let current_plane_id = current_plane_id.ok_or_else(|| {
693 KclError::Internal(KclErrorDetails {
694 message: format!(
695 "Expected a current plane ID when processing StartPath command, but we have none: {id:?}"
696 ),
697 source_ranges: vec![range],
698 })
699 })?;
700 return_arr.push(Artifact::Path(Path {
701 id,
702 plane_id: current_plane_id.into(),
703 seg_ids: Vec::new(),
704 sweep_id: None,
705 solid2d_id: None,
706 code_ref: CodeRef { range, path_to_node },
707 }));
708 let plane = artifacts.get(&ArtifactId::new(current_plane_id));
709 if let Some(Artifact::Plane(plane)) = plane {
710 let code_ref = plane.code_ref.clone();
711 return_arr.push(Artifact::Plane(Plane {
712 id: current_plane_id.into(),
713 path_ids: vec![id],
714 code_ref,
715 }));
716 }
717 if let Some(Artifact::Wall(wall)) = plane {
718 return_arr.push(Artifact::Wall(Wall {
719 id: current_plane_id.into(),
720 seg_id: wall.seg_id,
721 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
722 sweep_id: wall.sweep_id,
723 path_ids: vec![id],
724 face_code_ref: wall.face_code_ref.clone(),
725 }));
726 }
727 if let Some(Artifact::Cap(cap)) = plane {
728 return_arr.push(Artifact::Cap(Cap {
729 id: current_plane_id.into(),
730 sub_type: cap.sub_type,
731 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
732 sweep_id: cap.sweep_id,
733 path_ids: vec![id],
734 face_code_ref: cap.face_code_ref.clone(),
735 }));
736 }
737 return Ok(return_arr);
738 }
739 ModelingCmd::ClosePath(_) | ModelingCmd::ExtendPath(_) => {
740 let path_id = ArtifactId::new(match cmd {
741 ModelingCmd::ClosePath(c) => c.path_id,
742 ModelingCmd::ExtendPath(e) => e.path.into(),
743 _ => unreachable!(),
744 });
745 let mut return_arr = Vec::new();
746 return_arr.push(Artifact::Segment(Segment {
747 id,
748 path_id,
749 surface_id: None,
750 edge_ids: Vec::new(),
751 edge_cut_id: None,
752 code_ref: CodeRef { range, path_to_node },
753 }));
754 let path = artifacts.get(&path_id);
755 if let Some(Artifact::Path(path)) = path {
756 let mut new_path = path.clone();
757 new_path.seg_ids = vec![id];
758 return_arr.push(Artifact::Path(new_path));
759 }
760 if let OkModelingCmdResponse::ClosePath(close_path) = response {
761 return_arr.push(Artifact::Solid2d(Solid2d {
762 id: close_path.face_id.into(),
763 path_id,
764 }));
765 if let Some(Artifact::Path(path)) = path {
766 let mut new_path = path.clone();
767 new_path.solid2d_id = Some(close_path.face_id.into());
768 return_arr.push(Artifact::Path(new_path));
769 }
770 }
771 return Ok(return_arr);
772 }
773 ModelingCmd::Extrude(kcmc::Extrude { target, .. })
774 | ModelingCmd::Revolve(kcmc::Revolve { target, .. })
775 | ModelingCmd::RevolveAboutEdge(kcmc::RevolveAboutEdge { target, .. })
776 | ModelingCmd::Sweep(kcmc::Sweep { target, .. }) => {
777 let sub_type = match cmd {
778 ModelingCmd::Extrude(_) => SweepSubType::Extrusion,
779 ModelingCmd::Revolve(_) => SweepSubType::Revolve,
780 ModelingCmd::RevolveAboutEdge(_) => SweepSubType::RevolveAboutEdge,
781 ModelingCmd::Sweep(_) => SweepSubType::Sweep,
782 _ => unreachable!(),
783 };
784 let mut return_arr = Vec::new();
785 let target = ArtifactId::from(target);
786 return_arr.push(Artifact::Sweep(Sweep {
787 id,
788 sub_type,
789 path_id: target,
790 surface_ids: Vec::new(),
791 edge_ids: Vec::new(),
792 code_ref: CodeRef { range, path_to_node },
793 }));
794 let path = artifacts.get(&target);
795 if let Some(Artifact::Path(path)) = path {
796 let mut new_path = path.clone();
797 new_path.sweep_id = Some(id);
798 return_arr.push(Artifact::Path(new_path));
799 }
800 return Ok(return_arr);
801 }
802 ModelingCmd::Loft(loft_cmd) => {
803 let OkModelingCmdResponse::Loft(_) = response else {
804 return Ok(Vec::new());
805 };
806 let mut return_arr = Vec::new();
807 return_arr.push(Artifact::Sweep(Sweep {
808 id,
809 sub_type: SweepSubType::Loft,
810 path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
813 KclError::Internal(KclErrorDetails {
814 message: format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
815 source_ranges: vec![range],
816 })
817 })?),
818 surface_ids: Vec::new(),
819 edge_ids: Vec::new(),
820 code_ref: CodeRef { range, path_to_node },
821 }));
822 for section_id in &loft_cmd.section_ids {
823 let path = artifacts.get(&ArtifactId::new(*section_id));
824 if let Some(Artifact::Path(path)) = path {
825 let mut new_path = path.clone();
826 new_path.sweep_id = Some(id);
827 return_arr.push(Artifact::Path(new_path));
828 }
829 }
830 return Ok(return_arr);
831 }
832 ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
833 let OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info) = response else {
834 return Ok(Vec::new());
835 };
836 let mut return_arr = Vec::new();
837 let mut last_path = None;
838 for face in &face_info.faces {
839 if face.cap != ExtrusionFaceCapType::None {
840 continue;
841 }
842 let Some(curve_id) = face.curve_id.map(ArtifactId::new) else {
843 continue;
844 };
845 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
846 continue;
847 };
848 let Some(Artifact::Segment(seg)) = artifacts.get(&curve_id) else {
849 continue;
850 };
851 let Some(Artifact::Path(path)) = artifacts.get(&seg.path_id) else {
852 continue;
853 };
854 last_path = Some(path);
855 let path_sweep_id = path.sweep_id.ok_or_else(|| {
856 KclError::Internal(KclErrorDetails {
857 message:format!(
858 "Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}"
859 ),
860 source_ranges: vec![range],
861 })
862 })?;
863 let extra_artifact = exec_artifacts.values().find(|a| {
864 if let Artifact::StartSketchOnFace(s) = a {
865 s.face_id == face_id
866 } else {
867 false
868 }
869 });
870 let sketch_on_face_source_range = extra_artifact
871 .and_then(|a| match a {
872 Artifact::StartSketchOnFace(s) => Some(s.code_ref.range),
873 _ => None,
875 })
876 .unwrap_or_default();
877
878 return_arr.push(Artifact::Wall(Wall {
879 id: face_id,
880 seg_id: curve_id,
881 edge_cut_edge_ids: Vec::new(),
882 sweep_id: path_sweep_id,
883 path_ids: Vec::new(),
884 face_code_ref: CodeRef {
885 range: sketch_on_face_source_range,
886 path_to_node: Vec::new(),
887 },
888 }));
889 let mut new_seg = seg.clone();
890 new_seg.surface_id = Some(face_id);
891 return_arr.push(Artifact::Segment(new_seg));
892 if let Some(Artifact::Sweep(sweep)) = path.sweep_id.and_then(|id| artifacts.get(&id)) {
893 let mut new_sweep = sweep.clone();
894 new_sweep.surface_ids = vec![face_id];
895 return_arr.push(Artifact::Sweep(new_sweep));
896 }
897 }
898 if let Some(path) = last_path {
899 for face in &face_info.faces {
900 let sub_type = match face.cap {
901 ExtrusionFaceCapType::Top => CapSubType::End,
902 ExtrusionFaceCapType::Bottom => CapSubType::Start,
903 ExtrusionFaceCapType::None | ExtrusionFaceCapType::Both => continue,
904 };
905 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
906 continue;
907 };
908 let path_sweep_id = path.sweep_id.ok_or_else(|| {
909 KclError::Internal(KclErrorDetails {
910 message:format!(
911 "Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}"
912 ),
913 source_ranges: vec![range],
914 })
915 })?;
916 let extra_artifact = exec_artifacts.values().find(|a| {
917 if let Artifact::StartSketchOnFace(s) = a {
918 s.face_id == face_id
919 } else {
920 false
921 }
922 });
923 let sketch_on_face_source_range = extra_artifact
924 .and_then(|a| match a {
925 Artifact::StartSketchOnFace(s) => Some(s.code_ref.range),
926 _ => None,
927 })
928 .unwrap_or_default();
929 return_arr.push(Artifact::Cap(Cap {
930 id: face_id,
931 sub_type,
932 edge_cut_edge_ids: Vec::new(),
933 sweep_id: path_sweep_id,
934 path_ids: Vec::new(),
935 face_code_ref: CodeRef {
936 range: sketch_on_face_source_range,
937 path_to_node: Vec::new(),
938 },
939 }));
940 let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
941 continue;
942 };
943 let mut new_sweep = sweep.clone();
944 new_sweep.surface_ids = vec![face_id];
945 return_arr.push(Artifact::Sweep(new_sweep));
946 }
947 }
948 return Ok(return_arr);
949 }
950 ModelingCmd::Solid3dGetNextAdjacentEdge(kcmc::Solid3dGetNextAdjacentEdge { face_id, edge_id, .. })
951 | ModelingCmd::Solid3dGetOppositeEdge(kcmc::Solid3dGetOppositeEdge { face_id, edge_id, .. }) => {
952 let sub_type = match cmd {
953 ModelingCmd::Solid3dGetNextAdjacentEdge(_) => SweepEdgeSubType::Adjacent,
954 ModelingCmd::Solid3dGetOppositeEdge(_) => SweepEdgeSubType::Opposite,
955 _ => unreachable!(),
956 };
957 let face_id = ArtifactId::new(*face_id);
958 let edge_id = ArtifactId::new(*edge_id);
959 let Some(Artifact::Wall(wall)) = artifacts.get(&face_id) else {
960 return Ok(Vec::new());
961 };
962 let Some(Artifact::Sweep(sweep)) = artifacts.get(&wall.sweep_id) else {
963 return Ok(Vec::new());
964 };
965 let Some(Artifact::Path(_)) = artifacts.get(&sweep.path_id) else {
966 return Ok(Vec::new());
967 };
968 let Some(Artifact::Segment(segment)) = artifacts.get(&edge_id) else {
969 return Ok(Vec::new());
970 };
971 let response_edge_id = match response {
972 OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(r) => {
973 let Some(edge_id) = r.edge else {
974 return Err(KclError::Internal(KclErrorDetails {
975 message:format!(
976 "Expected Solid3dGetNextAdjacentEdge response to have an edge ID, but found none: id={id:?}, {response:?}"
977 ),
978 source_ranges: vec![range],
979 }));
980 };
981 edge_id.into()
982 }
983 OkModelingCmdResponse::Solid3dGetOppositeEdge(r) => r.edge.into(),
984 _ => {
985 return Err(KclError::Internal(KclErrorDetails {
986 message:format!(
987 "Expected Solid3dGetNextAdjacentEdge or Solid3dGetOppositeEdge response, but got: id={id:?}, {response:?}"
988 ),
989 source_ranges: vec![range],
990 }));
991 }
992 };
993
994 let mut return_arr = Vec::new();
995 return_arr.push(Artifact::SweepEdge(SweepEdge {
996 id: response_edge_id,
997 sub_type,
998 seg_id: edge_id,
999 sweep_id: sweep.id,
1000 }));
1001 let mut new_segment = segment.clone();
1002 new_segment.edge_ids = vec![response_edge_id];
1003 return_arr.push(Artifact::Segment(new_segment));
1004 let mut new_sweep = sweep.clone();
1005 new_sweep.edge_ids = vec![response_edge_id];
1006 return_arr.push(Artifact::Sweep(new_sweep));
1007 return Ok(return_arr);
1008 }
1009 ModelingCmd::Solid3dFilletEdge(cmd) => {
1010 let mut return_arr = Vec::new();
1011 return_arr.push(Artifact::EdgeCut(EdgeCut {
1012 id,
1013 sub_type: cmd.cut_type.into(),
1014 consumed_edge_id: cmd.edge_id.into(),
1015 edge_ids: Vec::new(),
1016 surface_id: None,
1017 code_ref: CodeRef { range, path_to_node },
1018 }));
1019 let consumed_edge = artifacts.get(&ArtifactId::new(cmd.edge_id));
1020 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
1021 let mut new_segment = consumed_edge.clone();
1022 new_segment.edge_cut_id = Some(id);
1023 return_arr.push(Artifact::Segment(new_segment));
1024 } else {
1025 }
1027 return Ok(return_arr);
1028 }
1029 ModelingCmd::EntityMakeHelixFromParams(_) => {
1030 let return_arr = vec![Artifact::Helix(Helix {
1031 id,
1032 axis_id: None,
1033 code_ref: CodeRef { range, path_to_node },
1034 })];
1035 return Ok(return_arr);
1036 }
1037 ModelingCmd::EntityMakeHelixFromEdge(helix) => {
1038 let edge_id = ArtifactId::new(helix.edge_id);
1039 let return_arr = vec![Artifact::Helix(Helix {
1040 id,
1041 axis_id: Some(edge_id),
1042 code_ref: CodeRef { range, path_to_node },
1043 })];
1044 return Ok(return_arr);
1047 }
1048 _ => {}
1049 }
1050
1051 Ok(Vec::new())
1052}