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,
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 path_to_plane_id_map = FnvHashMap::default();
502 let mut current_plane_id = None;
503
504 for artifact_command in artifact_commands {
505 if let ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) = artifact_command.command {
506 current_plane_id = Some(entity_id);
507 }
508 if let ModelingCmd::StartPath(_) = artifact_command.command {
513 if let Some(plane_id) = current_plane_id {
514 path_to_plane_id_map.insert(artifact_command.cmd_id, plane_id);
515 }
516 }
517 if let ModelingCmd::SketchModeDisable(_) = artifact_command.command {
518 current_plane_id = None;
519 }
520
521 let flattened_responses = flatten_modeling_command_responses(responses);
522 let artifact_updates = artifacts_to_update(
523 &map,
524 artifact_command,
525 &flattened_responses,
526 &path_to_plane_id_map,
527 ast,
528 exec_artifacts,
529 )?;
530 for artifact in artifact_updates {
531 merge_artifact_into_map(&mut map, artifact);
533 }
534 }
535
536 for exec_artifact in exec_artifacts.values() {
537 merge_artifact_into_map(&mut map, exec_artifact.clone());
538 }
539
540 Ok(ArtifactGraph { map })
541}
542
543fn flatten_modeling_command_responses(
546 responses: &IndexMap<Uuid, WebSocketResponse>,
547) -> FnvHashMap<Uuid, OkModelingCmdResponse> {
548 let mut map = FnvHashMap::default();
549 for (cmd_id, ws_response) in responses {
550 let WebSocketResponse::Success(response) = ws_response else {
551 continue;
553 };
554 match &response.resp {
555 OkWebSocketResponseData::Modeling { modeling_response } => {
556 map.insert(*cmd_id, modeling_response.clone());
557 }
558 OkWebSocketResponseData::ModelingBatch { responses } =>
559 {
560 #[expect(
561 clippy::iter_over_hash_type,
562 reason = "Since we're moving entries to another unordered map, it's fine that the order is undefined"
563 )]
564 for (cmd_id, batch_response) in responses {
565 if let BatchResponse::Success {
566 response: modeling_response,
567 } = batch_response
568 {
569 map.insert(*cmd_id.as_ref(), modeling_response.clone());
570 }
571 }
572 }
573 OkWebSocketResponseData::IceServerInfo { .. }
574 | OkWebSocketResponseData::TrickleIce { .. }
575 | OkWebSocketResponseData::SdpAnswer { .. }
576 | OkWebSocketResponseData::Export { .. }
577 | OkWebSocketResponseData::MetricsRequest { .. }
578 | OkWebSocketResponseData::ModelingSessionData { .. }
579 | OkWebSocketResponseData::Pong { .. } => {}
580 }
581 }
582
583 map
584}
585
586fn merge_artifact_into_map(map: &mut IndexMap<ArtifactId, Artifact>, new_artifact: Artifact) {
587 let id = new_artifact.id();
588 let Some(old_artifact) = map.get_mut(&id) else {
589 map.insert(id, new_artifact);
591 return;
592 };
593
594 if let Some(replacement) = old_artifact.merge(new_artifact) {
595 *old_artifact = replacement;
596 }
597}
598
599fn merge_ids(base: &mut Vec<ArtifactId>, new: Vec<ArtifactId>) {
603 let original_len = base.len();
604 for id in new {
605 let original_base = &base[..original_len];
607 if !original_base.contains(&id) {
608 base.push(id);
609 }
610 }
611}
612
613fn merge_opt_id(base: &mut Option<ArtifactId>, new: Option<ArtifactId>) {
614 *base = new;
616}
617
618fn artifacts_to_update(
619 artifacts: &IndexMap<ArtifactId, Artifact>,
620 artifact_command: &ArtifactCommand,
621 responses: &FnvHashMap<Uuid, OkModelingCmdResponse>,
622 path_to_plane_id_map: &FnvHashMap<Uuid, Uuid>,
623 _ast: &Node<Program>,
624 exec_artifacts: &IndexMap<ArtifactId, Artifact>,
625) -> Result<Vec<Artifact>, KclError> {
626 let path_to_node = Vec::new();
630
631 let range = artifact_command.range;
632 let uuid = artifact_command.cmd_id;
633 let id = ArtifactId::new(uuid);
634
635 let Some(response) = responses.get(&uuid) else {
636 return Ok(Vec::new());
638 };
639
640 let cmd = &artifact_command.command;
641
642 match cmd {
643 ModelingCmd::MakePlane(_) => {
644 if range.is_synthetic() {
645 return Ok(Vec::new());
646 }
647 return Ok(vec![Artifact::Plane(Plane {
651 id,
652 path_ids: Vec::new(),
653 code_ref: CodeRef { range, path_to_node },
654 })]);
655 }
656 ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) => {
657 let existing_plane = artifacts.get(&ArtifactId::new(*entity_id));
658 match existing_plane {
659 Some(Artifact::Wall(wall)) => {
660 return Ok(vec![Artifact::Wall(Wall {
661 id: entity_id.into(),
662 seg_id: wall.seg_id,
663 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
664 sweep_id: wall.sweep_id,
665 path_ids: wall.path_ids.clone(),
666 face_code_ref: wall.face_code_ref.clone(),
667 })]);
668 }
669 Some(Artifact::Cap(cap)) => {
670 return Ok(vec![Artifact::Cap(Cap {
671 id: entity_id.into(),
672 sub_type: cap.sub_type,
673 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
674 sweep_id: cap.sweep_id,
675 path_ids: cap.path_ids.clone(),
676 face_code_ref: cap.face_code_ref.clone(),
677 })]);
678 }
679 Some(_) | None => {
680 let path_ids = match existing_plane {
681 Some(Artifact::Plane(Plane { path_ids, .. })) => path_ids.clone(),
682 _ => Vec::new(),
683 };
684 return Ok(vec![Artifact::Plane(Plane {
685 id: entity_id.into(),
686 path_ids,
687 code_ref: CodeRef { range, path_to_node },
688 })]);
689 }
690 }
691 }
692 ModelingCmd::StartPath(_) => {
693 let mut return_arr = Vec::new();
694 let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
695 KclError::Internal(KclErrorDetails {
696 message: format!(
697 "Expected a current plane ID when processing StartPath command, but we have none: {id:?}"
698 ),
699 source_ranges: vec![range],
700 })
701 })?;
702 return_arr.push(Artifact::Path(Path {
703 id,
704 plane_id: (*current_plane_id).into(),
705 seg_ids: Vec::new(),
706 sweep_id: None,
707 solid2d_id: None,
708 code_ref: CodeRef { range, path_to_node },
709 }));
710 let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
711 if let Some(Artifact::Plane(plane)) = plane {
712 let code_ref = plane.code_ref.clone();
713 return_arr.push(Artifact::Plane(Plane {
714 id: (*current_plane_id).into(),
715 path_ids: vec![id],
716 code_ref,
717 }));
718 }
719 if let Some(Artifact::Wall(wall)) = plane {
720 return_arr.push(Artifact::Wall(Wall {
721 id: (*current_plane_id).into(),
722 seg_id: wall.seg_id,
723 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
724 sweep_id: wall.sweep_id,
725 path_ids: vec![id],
726 face_code_ref: wall.face_code_ref.clone(),
727 }));
728 }
729 if let Some(Artifact::Cap(cap)) = plane {
730 return_arr.push(Artifact::Cap(Cap {
731 id: (*current_plane_id).into(),
732 sub_type: cap.sub_type,
733 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
734 sweep_id: cap.sweep_id,
735 path_ids: vec![id],
736 face_code_ref: cap.face_code_ref.clone(),
737 }));
738 }
739 return Ok(return_arr);
740 }
741 ModelingCmd::ClosePath(_) | ModelingCmd::ExtendPath(_) => {
742 let path_id = ArtifactId::new(match cmd {
743 ModelingCmd::ClosePath(c) => c.path_id,
744 ModelingCmd::ExtendPath(e) => e.path.into(),
745 _ => unreachable!(),
746 });
747 let mut return_arr = Vec::new();
748 return_arr.push(Artifact::Segment(Segment {
749 id,
750 path_id,
751 surface_id: None,
752 edge_ids: Vec::new(),
753 edge_cut_id: None,
754 code_ref: CodeRef { range, path_to_node },
755 }));
756 let path = artifacts.get(&path_id);
757 if let Some(Artifact::Path(path)) = path {
758 let mut new_path = path.clone();
759 new_path.seg_ids = vec![id];
760 return_arr.push(Artifact::Path(new_path));
761 }
762 if let OkModelingCmdResponse::ClosePath(close_path) = response {
763 return_arr.push(Artifact::Solid2d(Solid2d {
764 id: close_path.face_id.into(),
765 path_id,
766 }));
767 if let Some(Artifact::Path(path)) = path {
768 let mut new_path = path.clone();
769 new_path.solid2d_id = Some(close_path.face_id.into());
770 return_arr.push(Artifact::Path(new_path));
771 }
772 }
773 return Ok(return_arr);
774 }
775 ModelingCmd::Extrude(kcmc::Extrude { target, .. })
776 | ModelingCmd::Revolve(kcmc::Revolve { target, .. })
777 | ModelingCmd::RevolveAboutEdge(kcmc::RevolveAboutEdge { target, .. })
778 | ModelingCmd::Sweep(kcmc::Sweep { target, .. }) => {
779 let sub_type = match cmd {
780 ModelingCmd::Extrude(_) => SweepSubType::Extrusion,
781 ModelingCmd::Revolve(_) => SweepSubType::Revolve,
782 ModelingCmd::RevolveAboutEdge(_) => SweepSubType::RevolveAboutEdge,
783 ModelingCmd::Sweep(_) => SweepSubType::Sweep,
784 _ => unreachable!(),
785 };
786 let mut return_arr = Vec::new();
787 let target = ArtifactId::from(target);
788 return_arr.push(Artifact::Sweep(Sweep {
789 id,
790 sub_type,
791 path_id: target,
792 surface_ids: Vec::new(),
793 edge_ids: Vec::new(),
794 code_ref: CodeRef { range, path_to_node },
795 }));
796 let path = artifacts.get(&target);
797 if let Some(Artifact::Path(path)) = path {
798 let mut new_path = path.clone();
799 new_path.sweep_id = Some(id);
800 return_arr.push(Artifact::Path(new_path));
801 }
802 return Ok(return_arr);
803 }
804 ModelingCmd::Loft(loft_cmd) => {
805 let OkModelingCmdResponse::Loft(_) = response else {
806 return Ok(Vec::new());
807 };
808 let mut return_arr = Vec::new();
809 return_arr.push(Artifact::Sweep(Sweep {
810 id,
811 sub_type: SweepSubType::Loft,
812 path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
815 KclError::Internal(KclErrorDetails {
816 message: format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
817 source_ranges: vec![range],
818 })
819 })?),
820 surface_ids: Vec::new(),
821 edge_ids: Vec::new(),
822 code_ref: CodeRef { range, path_to_node },
823 }));
824 for section_id in &loft_cmd.section_ids {
825 let path = artifacts.get(&ArtifactId::new(*section_id));
826 if let Some(Artifact::Path(path)) = path {
827 let mut new_path = path.clone();
828 new_path.sweep_id = Some(id);
829 return_arr.push(Artifact::Path(new_path));
830 }
831 }
832 return Ok(return_arr);
833 }
834 ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
835 let OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info) = response else {
836 return Ok(Vec::new());
837 };
838 let mut return_arr = Vec::new();
839 let mut last_path = None;
840 for face in &face_info.faces {
841 if face.cap != ExtrusionFaceCapType::None {
842 continue;
843 }
844 let Some(curve_id) = face.curve_id.map(ArtifactId::new) else {
845 continue;
846 };
847 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
848 continue;
849 };
850 let Some(Artifact::Segment(seg)) = artifacts.get(&curve_id) else {
851 continue;
852 };
853 let Some(Artifact::Path(path)) = artifacts.get(&seg.path_id) else {
854 continue;
855 };
856 last_path = Some(path);
857 let path_sweep_id = path.sweep_id.ok_or_else(|| {
858 KclError::Internal(KclErrorDetails {
859 message:format!(
860 "Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}"
861 ),
862 source_ranges: vec![range],
863 })
864 })?;
865 let extra_artifact = exec_artifacts.values().find(|a| {
866 if let Artifact::StartSketchOnFace(s) = a {
867 s.face_id == face_id
868 } else {
869 false
870 }
871 });
872 let sketch_on_face_source_range = extra_artifact
873 .and_then(|a| match a {
874 Artifact::StartSketchOnFace(s) => Some(s.code_ref.range),
875 _ => None,
877 })
878 .unwrap_or_default();
879
880 return_arr.push(Artifact::Wall(Wall {
881 id: face_id,
882 seg_id: curve_id,
883 edge_cut_edge_ids: Vec::new(),
884 sweep_id: path_sweep_id,
885 path_ids: Vec::new(),
886 face_code_ref: CodeRef {
887 range: sketch_on_face_source_range,
888 path_to_node: Vec::new(),
889 },
890 }));
891 let mut new_seg = seg.clone();
892 new_seg.surface_id = Some(face_id);
893 return_arr.push(Artifact::Segment(new_seg));
894 if let Some(Artifact::Sweep(sweep)) = path.sweep_id.and_then(|id| artifacts.get(&id)) {
895 let mut new_sweep = sweep.clone();
896 new_sweep.surface_ids = vec![face_id];
897 return_arr.push(Artifact::Sweep(new_sweep));
898 }
899 }
900 if let Some(path) = last_path {
901 for face in &face_info.faces {
902 let sub_type = match face.cap {
903 ExtrusionFaceCapType::Top => CapSubType::End,
904 ExtrusionFaceCapType::Bottom => CapSubType::Start,
905 ExtrusionFaceCapType::None | ExtrusionFaceCapType::Both => continue,
906 };
907 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
908 continue;
909 };
910 let path_sweep_id = path.sweep_id.ok_or_else(|| {
911 KclError::Internal(KclErrorDetails {
912 message:format!(
913 "Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}"
914 ),
915 source_ranges: vec![range],
916 })
917 })?;
918 let extra_artifact = exec_artifacts.values().find(|a| {
919 if let Artifact::StartSketchOnFace(s) = a {
920 s.face_id == face_id
921 } else {
922 false
923 }
924 });
925 let sketch_on_face_source_range = extra_artifact
926 .and_then(|a| match a {
927 Artifact::StartSketchOnFace(s) => Some(s.code_ref.range),
928 _ => None,
929 })
930 .unwrap_or_default();
931 return_arr.push(Artifact::Cap(Cap {
932 id: face_id,
933 sub_type,
934 edge_cut_edge_ids: Vec::new(),
935 sweep_id: path_sweep_id,
936 path_ids: Vec::new(),
937 face_code_ref: CodeRef {
938 range: sketch_on_face_source_range,
939 path_to_node: Vec::new(),
940 },
941 }));
942 let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
943 continue;
944 };
945 let mut new_sweep = sweep.clone();
946 new_sweep.surface_ids = vec![face_id];
947 return_arr.push(Artifact::Sweep(new_sweep));
948 }
949 }
950 return Ok(return_arr);
951 }
952 ModelingCmd::Solid3dGetNextAdjacentEdge(kcmc::Solid3dGetNextAdjacentEdge { face_id, edge_id, .. })
953 | ModelingCmd::Solid3dGetOppositeEdge(kcmc::Solid3dGetOppositeEdge { face_id, edge_id, .. }) => {
954 let sub_type = match cmd {
955 ModelingCmd::Solid3dGetNextAdjacentEdge(_) => SweepEdgeSubType::Adjacent,
956 ModelingCmd::Solid3dGetOppositeEdge(_) => SweepEdgeSubType::Opposite,
957 _ => unreachable!(),
958 };
959 let face_id = ArtifactId::new(*face_id);
960 let edge_id = ArtifactId::new(*edge_id);
961 let Some(Artifact::Wall(wall)) = artifacts.get(&face_id) else {
962 return Ok(Vec::new());
963 };
964 let Some(Artifact::Sweep(sweep)) = artifacts.get(&wall.sweep_id) else {
965 return Ok(Vec::new());
966 };
967 let Some(Artifact::Path(_)) = artifacts.get(&sweep.path_id) else {
968 return Ok(Vec::new());
969 };
970 let Some(Artifact::Segment(segment)) = artifacts.get(&edge_id) else {
971 return Ok(Vec::new());
972 };
973 let response_edge_id = match response {
974 OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(r) => {
975 let Some(edge_id) = r.edge else {
976 return Err(KclError::Internal(KclErrorDetails {
977 message:format!(
978 "Expected Solid3dGetNextAdjacentEdge response to have an edge ID, but found none: id={id:?}, {response:?}"
979 ),
980 source_ranges: vec![range],
981 }));
982 };
983 edge_id.into()
984 }
985 OkModelingCmdResponse::Solid3dGetOppositeEdge(r) => r.edge.into(),
986 _ => {
987 return Err(KclError::Internal(KclErrorDetails {
988 message:format!(
989 "Expected Solid3dGetNextAdjacentEdge or Solid3dGetOppositeEdge response, but got: id={id:?}, {response:?}"
990 ),
991 source_ranges: vec![range],
992 }));
993 }
994 };
995
996 let mut return_arr = Vec::new();
997 return_arr.push(Artifact::SweepEdge(SweepEdge {
998 id: response_edge_id,
999 sub_type,
1000 seg_id: edge_id,
1001 sweep_id: sweep.id,
1002 }));
1003 let mut new_segment = segment.clone();
1004 new_segment.edge_ids = vec![response_edge_id];
1005 return_arr.push(Artifact::Segment(new_segment));
1006 let mut new_sweep = sweep.clone();
1007 new_sweep.edge_ids = vec![response_edge_id];
1008 return_arr.push(Artifact::Sweep(new_sweep));
1009 return Ok(return_arr);
1010 }
1011 ModelingCmd::Solid3dFilletEdge(cmd) => {
1012 let mut return_arr = Vec::new();
1013 return_arr.push(Artifact::EdgeCut(EdgeCut {
1014 id,
1015 sub_type: cmd.cut_type.into(),
1016 consumed_edge_id: cmd.edge_id.into(),
1017 edge_ids: Vec::new(),
1018 surface_id: None,
1019 code_ref: CodeRef { range, path_to_node },
1020 }));
1021 let consumed_edge = artifacts.get(&ArtifactId::new(cmd.edge_id));
1022 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
1023 let mut new_segment = consumed_edge.clone();
1024 new_segment.edge_cut_id = Some(id);
1025 return_arr.push(Artifact::Segment(new_segment));
1026 } else {
1027 }
1029 return Ok(return_arr);
1030 }
1031 ModelingCmd::EntityMakeHelixFromParams(_) => {
1032 let return_arr = vec![Artifact::Helix(Helix {
1033 id,
1034 axis_id: None,
1035 code_ref: CodeRef { range, path_to_node },
1036 })];
1037 return Ok(return_arr);
1038 }
1039 ModelingCmd::EntityMakeHelixFromEdge(helix) => {
1040 let edge_id = ArtifactId::new(helix.edge_id);
1041 let return_arr = vec![Artifact::Helix(Helix {
1042 id,
1043 axis_id: Some(edge_id),
1044 code_ref: CodeRef { range, path_to_node },
1045 })];
1046 return Ok(return_arr);
1049 }
1050 _ => {}
1051 }
1052
1053 Ok(Vec::new())
1054}