1use ahash::AHashMap;
2use ahash::AHashSet;
3use indexmap::IndexMap;
4use kcl_api::NodePath;
5use kcl_api::artifact::*;
6use kittycad_modeling_cmds::EnableSketchMode;
7use kittycad_modeling_cmds::FaceIsPlanar;
8use kittycad_modeling_cmds::ModelingCmd;
9use kittycad_modeling_cmds::ok_response::OkModelingCmdResponse;
10use kittycad_modeling_cmds::shared::ExtrusionFaceCapType;
11use kittycad_modeling_cmds::websocket::BatchResponse;
12use kittycad_modeling_cmds::websocket::OkWebSocketResponseData;
13use kittycad_modeling_cmds::websocket::WebSocketResponse;
14use kittycad_modeling_cmds::{self as kcmc};
15use serde::Serialize;
16use uuid::Uuid;
17
18use crate::KclError;
19use crate::ModuleId;
20use crate::NodePathExt;
21use crate::SourceRange;
22use crate::errors::KclErrorDetails;
23use crate::execution::ArtifactId;
24use crate::execution::CameraLook;
25use crate::execution::CameraView;
26use crate::execution::NamedViewValue;
27use crate::execution::Orientation;
28use crate::execution::Projection;
29use crate::execution::Visibility;
30use crate::execution::cmd_id_ref_to_artifact_id;
31use crate::execution::geometry::PlaneInfo;
32use crate::execution::state::ModuleInfoMap;
33use crate::front::Constraint;
34use crate::modules::ModulePath;
35use crate::parsing::ast::types::BodyItem;
36use crate::parsing::ast::types::ImportPath;
37use crate::parsing::ast::types::ImportSelector;
38use crate::parsing::ast::types::Node;
39use crate::parsing::ast::types::Program;
40use crate::std::sketch::build_reverse_region_mapping;
41
42#[cfg(test)]
43pub(crate) mod mermaid_tests;
44#[cfg(test)]
45mod tests;
46
47macro_rules! internal_error {
48 ($range:expr, $($rest:tt)*) => {{
49 let message = format!($($rest)*);
50 debug_assert!(false, "{}", &message);
51 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![$range])));
52 }};
53}
54
55#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
59#[ts(export_to = "Artifact.ts")]
60#[serde(rename_all = "camelCase")]
61pub struct ArtifactCommand {
62 pub cmd_id: Uuid,
64 pub range: SourceRange,
67 pub command: ModelingCmd,
73 #[serde(skip_serializing_if = "Option::is_none")]
76 #[ts(skip)]
77 pub(crate) entity_clone_info: Option<EntityCloneInfo>,
78 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
82 pub omit_from_graph: bool,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
86#[serde(rename_all = "camelCase")]
87pub(crate) struct EntityCloneInfo {
88 pub source_artifact_id: ArtifactId,
89 pub result_artifact_id: ArtifactId,
90 pub source_topology_id: ArtifactId,
94}
95
96pub(super) fn artifact_plane_info(info: &PlaneInfo) -> ArtifactPlaneInfo {
97 ArtifactPlaneInfo {
98 origin: artifact_point3d(info.origin),
99 x_axis: artifact_point3d(info.x_axis),
100 y_axis: artifact_point3d(info.y_axis),
101 z_axis: artifact_point3d(info.z_axis),
102 }
103}
104
105fn artifact_point3d(point: crate::execution::Point3d) -> ArtifactPoint3d {
106 ArtifactPoint3d {
107 x: point.x,
108 y: point.y,
109 z: point.z,
110 units: point.units,
111 }
112}
113
114pub(crate) fn named_view_artifact(view: &NamedViewValue, code_ref: CodeRef) -> NamedViewArtifact {
130 let (show_ids, hide_ids) = match view.baseline() {
131 Visibility::Show => (Vec::new(), view.except_ids().to_vec()),
132 Visibility::Hide => (view.except_ids().to_vec(), Vec::new()),
133 };
134
135 NamedViewArtifact {
136 id: view.artifact_id(),
137 name: view.name().to_owned(),
138 camera: artifact_camera_view(view.camera()),
139 baseline: artifact_visibility(view.baseline()),
140 show_ids,
141 hide_ids,
142 code_ref,
143 }
144}
145
146fn artifact_camera_view(camera: &CameraView) -> ArtifactCameraView {
147 ArtifactCameraView {
148 look: artifact_camera_look(camera.look()),
149 target: camera.target().copied().map(artifact_point3d),
150 distance: camera.distance().map(|distance| distance.to_mm()),
153 projection: artifact_projection(camera.projection()),
154 }
155}
156
157fn artifact_camera_look(look: &CameraLook) -> ArtifactCameraLook {
158 match look {
159 CameraLook::Oriented { orientation } => ArtifactCameraLook::Oriented {
160 orientation: artifact_orientation(*orientation),
161 },
162 CameraLook::Directed { direction, up } => ArtifactCameraLook::Directed {
163 direction: artifact_point3d(*direction),
164 up: artifact_point3d(*up),
165 },
166 }
167}
168
169fn artifact_orientation(orientation: Orientation) -> ArtifactOrientation {
170 match orientation {
171 Orientation::Front => ArtifactOrientation::Front,
172 Orientation::Back => ArtifactOrientation::Back,
173 Orientation::Left => ArtifactOrientation::Left,
174 Orientation::Right => ArtifactOrientation::Right,
175 Orientation::Top => ArtifactOrientation::Top,
176 Orientation::Bottom => ArtifactOrientation::Bottom,
177 Orientation::Isometric => ArtifactOrientation::Isometric,
178 }
179}
180
181fn artifact_projection(projection: Projection) -> ArtifactProjection {
182 match projection {
183 Projection::Orthographic => ArtifactProjection::Orthographic,
184 Projection::Perspective => ArtifactProjection::Perspective,
185 }
186}
187
188fn artifact_visibility(visibility: Visibility) -> ArtifactVisibility {
189 match visibility {
190 Visibility::Show => ArtifactVisibility::Show,
191 Visibility::Hide => ArtifactVisibility::Hide,
192 }
193}
194
195fn artifact_sweep_method(method: kcmc::shared::ExtrudeMethod) -> ArtifactSweepMethod {
196 match method {
197 kcmc::shared::ExtrudeMethod::New => ArtifactSweepMethod::New,
198 kcmc::shared::ExtrudeMethod::Merge => ArtifactSweepMethod::Merge,
199 _ => ArtifactSweepMethod::Merge,
200 }
201}
202
203fn edge_cut_sub_type(cut_type: kcmc::shared::CutType) -> EdgeCutSubType {
204 match cut_type {
205 kcmc::shared::CutType::Fillet => EdgeCutSubType::Fillet,
206 kcmc::shared::CutType::Chamfer => EdgeCutSubType::Chamfer,
207 }
208}
209
210fn edge_cut_sub_type_v2(cut_type: kcmc::shared::CutTypeV2) -> EdgeCutSubType {
211 match cut_type {
212 kcmc::shared::CutTypeV2::Fillet { .. } => EdgeCutSubType::Fillet,
213 kcmc::shared::CutTypeV2::Chamfer { .. } => EdgeCutSubType::Chamfer,
214 kcmc::shared::CutTypeV2::Custom { .. } => EdgeCutSubType::Custom,
215 _ => EdgeCutSubType::Custom,
216 }
217}
218
219pub(crate) fn sketch_block_constraint_type(constraint: &Constraint) -> SketchBlockConstraintType {
220 match constraint {
221 Constraint::Coincident { .. } => SketchBlockConstraintType::Coincident,
222 Constraint::Distance { .. } => SketchBlockConstraintType::Distance,
223 Constraint::Diameter { .. } => SketchBlockConstraintType::Diameter,
224 Constraint::EqualRadius { .. } => SketchBlockConstraintType::EqualRadius,
225 Constraint::Fixed { .. } => SketchBlockConstraintType::Fixed,
226 Constraint::HorizontalDistance { .. } => SketchBlockConstraintType::HorizontalDistance,
227 Constraint::VerticalDistance { .. } => SketchBlockConstraintType::VerticalDistance,
228 Constraint::Horizontal { .. } => SketchBlockConstraintType::Horizontal,
229 Constraint::LinesEqualLength { .. } => SketchBlockConstraintType::LinesEqualLength,
230 Constraint::Midpoint(..) => SketchBlockConstraintType::Midpoint,
231 Constraint::Parallel { .. } => SketchBlockConstraintType::Parallel,
232 Constraint::Perpendicular { .. } => SketchBlockConstraintType::Perpendicular,
233 Constraint::Radius { .. } => SketchBlockConstraintType::Radius,
234 Constraint::Symmetric { .. } => SketchBlockConstraintType::Symmetric,
235 Constraint::Tangent { .. } => SketchBlockConstraintType::Tangent,
236 Constraint::Vertical { .. } => SketchBlockConstraintType::Vertical,
237 Constraint::Angle(..) => SketchBlockConstraintType::Angle,
238 }
239}
240
241fn merge_artifacts(old: &mut Artifact, new: Artifact) -> Option<Artifact> {
244 match old {
245 Artifact::CompositeSolid(a) => merge_composite_solid(a, new),
246 Artifact::Plane(a) => merge_plane(a, new),
247 Artifact::Path(a) => merge_path(a, new),
248 Artifact::Segment(a) => merge_segment(a, new),
249 Artifact::Solid2d(_) => Some(new),
250 Artifact::PrimitiveFace(_) => Some(new),
251 Artifact::PrimitiveEdge(_) => Some(new),
252 Artifact::StartSketchOnFace { .. } => Some(new),
253 Artifact::StartSketchOnPlane { .. } => Some(new),
254 Artifact::SketchBlock { .. } => Some(new),
255 Artifact::SketchBlockConstraint { .. } => Some(new),
256 Artifact::PlaneOfFace { .. } => Some(new),
257 Artifact::Sweep(a) => merge_sweep(a, new),
258 Artifact::Wall(a) => merge_wall(a, new),
259 Artifact::Cap(a) => merge_cap(a, new),
260 Artifact::SweepEdge(_) => Some(new),
261 Artifact::EdgeCut(a) => merge_edge_cut(a, new),
262 Artifact::EdgeCutEdge(_) => Some(new),
263 Artifact::Helix(a) => merge_helix(a, new),
264 Artifact::ImportedGeometry(_) => Some(new),
265 Artifact::GdtAnnotation(a) => merge_gdt_annotation(a, new),
266 Artifact::NamedView(_) => Some(new),
271 Artifact::Pattern(a) => merge_pattern(a, new),
272 }
273}
274
275fn merge_composite_solid(old: &mut CompositeSolid, new: Artifact) -> Option<Artifact> {
276 let Artifact::CompositeSolid(new) = new else {
277 return Some(new);
278 };
279 merge_ids(&mut old.solid_ids, new.solid_ids);
280 merge_ids(&mut old.tool_ids, new.tool_ids);
281 merge_opt_id(&mut old.composite_solid_id, new.composite_solid_id);
282 merge_ids(&mut old.pattern_ids, new.pattern_ids);
283 old.output_index = new.output_index;
284 old.consumed = new.consumed;
285 None
286}
287
288fn merge_plane(old: &mut Plane, new: Artifact) -> Option<Artifact> {
289 let Artifact::Plane(new) = new else { return Some(new) };
290 merge_ids(&mut old.path_ids, new.path_ids);
291 None
292}
293
294fn merge_path(old: &mut Path, new: Artifact) -> Option<Artifact> {
295 let Artifact::Path(new) = new else { return Some(new) };
296 merge_opt_id(&mut old.sweep_id, new.sweep_id);
297 merge_opt_id(&mut old.trajectory_sweep_id, new.trajectory_sweep_id);
298 merge_ids(&mut old.seg_ids, new.seg_ids);
299 merge_opt_id(&mut old.solid2d_id, new.solid2d_id);
300 merge_opt_id(&mut old.composite_solid_id, new.composite_solid_id);
301 merge_opt_id(&mut old.sketch_block_id, new.sketch_block_id);
302 merge_opt_id(&mut old.origin_path_id, new.origin_path_id);
303 merge_opt_id(&mut old.inner_path_id, new.inner_path_id);
304 merge_opt_id(&mut old.outer_path_id, new.outer_path_id);
305 merge_ids(&mut old.pattern_ids, new.pattern_ids);
306 old.consumed = new.consumed;
307 None
308}
309
310fn merge_segment(old: &mut Segment, new: Artifact) -> Option<Artifact> {
311 let Artifact::Segment(new) = new else { return Some(new) };
312 old.source_segment_id = new.source_segment_id.or(old.source_segment_id);
315 merge_opt_id(&mut old.original_seg_id, new.original_seg_id);
316 merge_opt_id(&mut old.surface_id, new.surface_id);
317 merge_ids(&mut old.edge_ids, new.edge_ids);
318 merge_opt_id(&mut old.edge_cut_id, new.edge_cut_id);
319 merge_ids(&mut old.common_surface_ids, new.common_surface_ids);
320 None
321}
322
323fn merge_sweep(old: &mut Sweep, new: Artifact) -> Option<Artifact> {
324 let Artifact::Sweep(new) = new else { return Some(new) };
325 merge_ids(&mut old.surface_ids, new.surface_ids);
326 merge_ids(&mut old.edge_ids, new.edge_ids);
327 old.source_sweep_id = new.source_sweep_id.or(old.source_sweep_id);
330 merge_opt_id(&mut old.trajectory_id, new.trajectory_id);
331 merge_ids(&mut old.pattern_ids, new.pattern_ids);
332 old.consumed = new.consumed;
333 None
334}
335
336fn merge_wall(old: &mut Wall, new: Artifact) -> Option<Artifact> {
337 let Artifact::Wall(new) = new else { return Some(new) };
338 merge_ids(&mut old.edge_cut_edge_ids, new.edge_cut_edge_ids);
339 merge_ids(&mut old.path_ids, new.path_ids);
340 None
341}
342
343fn merge_cap(old: &mut Cap, new: Artifact) -> Option<Artifact> {
344 let Artifact::Cap(new) = new else { return Some(new) };
345 merge_ids(&mut old.edge_cut_edge_ids, new.edge_cut_edge_ids);
346 merge_ids(&mut old.path_ids, new.path_ids);
347 None
348}
349
350fn merge_edge_cut(old: &mut EdgeCut, new: Artifact) -> Option<Artifact> {
351 let Artifact::EdgeCut(new) = new else { return Some(new) };
352 merge_opt_id(&mut old.surface_id, new.surface_id);
353 merge_ids(&mut old.edge_ids, new.edge_ids);
354 None
355}
356
357fn merge_helix(old: &mut Helix, new: Artifact) -> Option<Artifact> {
358 let Artifact::Helix(new) = new else { return Some(new) };
359 merge_opt_id(&mut old.axis_id, new.axis_id);
360 merge_opt_id(&mut old.trajectory_sweep_id, new.trajectory_sweep_id);
361 old.consumed = new.consumed;
362 None
363}
364
365fn merge_gdt_annotation(old: &mut GdtAnnotationArtifact, new: Artifact) -> Option<Artifact> {
366 let Artifact::GdtAnnotation(new) = new else {
367 return Some(new);
368 };
369 old.code_ref = new.code_ref;
370 None
371}
372
373fn merge_pattern(old: &mut Pattern, new: Artifact) -> Option<Artifact> {
374 let Artifact::Pattern(new) = new else { return Some(new) };
375 merge_ids(&mut old.copy_ids, new.copy_ids);
376 merge_ids(&mut old.copy_face_ids, new.copy_face_ids);
377 merge_ids(&mut old.copy_edge_ids, new.copy_edge_ids);
378 None
379}
380
381#[derive(Debug, Clone)]
382struct ImportCodeRef {
383 node_path: NodePath,
384 range: SourceRange,
385}
386
387fn import_statement_code_refs(
388 ast: &Node<Program>,
389 module_infos: &ModuleInfoMap,
390 programs: &crate::execution::ProgramLookup,
391 cached_body_items: usize,
392) -> AHashMap<ModuleId, ImportCodeRef> {
393 let mut code_refs = AHashMap::default();
394 for body_item in &ast.body {
395 let BodyItem::ImportStatement(import_stmt) = body_item else {
396 continue;
397 };
398 if !matches!(import_stmt.selector, ImportSelector::None { .. }) {
399 continue;
400 }
401 let Some(module_id) = module_id_for_import_path(module_infos, &import_stmt.path) else {
402 continue;
403 };
404 let range = SourceRange::from(import_stmt);
405 let node_path = NodePath::from_range(programs, cached_body_items, range).unwrap_or_default();
406 code_refs.entry(module_id).or_insert(ImportCodeRef { node_path, range });
407 }
408 code_refs
409}
410
411fn module_id_for_import_path(module_infos: &ModuleInfoMap, import_path: &ImportPath) -> Option<ModuleId> {
412 let import_path = match import_path {
413 ImportPath::Kcl { filename } => filename,
414 ImportPath::Foreign { path } => path,
415 ImportPath::Std { .. } => return None,
416 };
417
418 module_infos.iter().find_map(|(module_id, module_info)| {
419 if let ModulePath::Local {
420 original_import_path: Some(original_import_path),
421 ..
422 } = &module_info.path
423 && original_import_path == import_path
424 {
425 return Some(*module_id);
426 }
427 None
428 })
429}
430
431fn code_ref_for_range(
432 programs: &crate::execution::ProgramLookup,
433 cached_body_items: usize,
434 range: SourceRange,
435 import_code_refs: &AHashMap<ModuleId, ImportCodeRef>,
436) -> (SourceRange, NodePath) {
437 if let Some(code_ref) = import_code_refs.get(&range.module_id()) {
438 return (code_ref.range, code_ref.node_path.clone());
439 }
440
441 (
442 range,
443 NodePath::from_range(programs, cached_body_items, range).unwrap_or_default(),
444 )
445}
446
447pub(super) fn build_artifact_graph(
451 artifact_commands: &[ArtifactCommand],
452 responses: &IndexMap<Uuid, WebSocketResponse>,
453 ast: &Node<Program>,
454 exec_artifacts: &mut IndexMap<ArtifactId, Artifact>,
455 initial_graph: ArtifactGraph,
456 programs: &crate::execution::ProgramLookup,
457 module_infos: &ModuleInfoMap,
458) -> Result<ArtifactGraph, KclError> {
459 let (mut map, item_count) = initial_graph.into_parts();
460
461 let mut path_to_plane_id_map = AHashMap::default();
462 let mut current_plane_id = None;
463 let import_code_refs = import_statement_code_refs(ast, module_infos, programs, item_count);
464 let flattened_responses = flatten_modeling_command_responses(responses);
465 let entity_clone_id_maps = build_entity_clone_id_maps(artifact_commands, &flattened_responses);
466
467 for exec_artifact in exec_artifacts.values_mut() {
470 fill_in_node_paths(exec_artifact, programs, item_count, &import_code_refs);
473 }
474
475 for artifact_command in artifact_commands {
476 if artifact_command.omit_from_graph {
477 continue;
478 }
479 if let ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) = artifact_command.command {
480 current_plane_id = Some(entity_id);
481 }
482 if let ModelingCmd::StartPath(_) = artifact_command.command
487 && let Some(plane_id) = current_plane_id
488 {
489 path_to_plane_id_map.insert(artifact_command.cmd_id, plane_id);
490 }
491 if let ModelingCmd::SketchModeDisable(_) = artifact_command.command {
492 current_plane_id = None;
493 }
494
495 let artifact_updates = artifacts_to_update(
496 &map,
497 artifact_command,
498 &flattened_responses,
499 &entity_clone_id_maps,
500 &path_to_plane_id_map,
501 programs,
502 item_count,
503 exec_artifacts,
504 &import_code_refs,
505 )?;
506 for artifact in artifact_updates {
507 merge_artifact_into_map(&mut map, artifact);
509 }
510 }
511
512 for exec_artifact in exec_artifacts.values() {
513 merge_artifact_into_map(&mut map, exec_artifact.clone());
514 }
515
516 Ok(ArtifactGraph::from_parts(map, item_count + ast.body.len()))
517}
518
519fn fill_in_node_paths(
522 artifact: &mut Artifact,
523 programs: &crate::execution::ProgramLookup,
524 cached_body_items: usize,
525 import_code_refs: &AHashMap<ModuleId, ImportCodeRef>,
526) {
527 match artifact {
528 Artifact::StartSketchOnFace(face) if face.code_ref.node_path.is_empty() => {
529 let (range, node_path) =
530 code_ref_for_range(programs, cached_body_items, face.code_ref.range, import_code_refs);
531 face.code_ref.range = range;
532 face.code_ref.node_path = node_path;
533 }
534 Artifact::StartSketchOnPlane(plane) if plane.code_ref.node_path.is_empty() => {
535 let (range, node_path) =
536 code_ref_for_range(programs, cached_body_items, plane.code_ref.range, import_code_refs);
537 plane.code_ref.range = range;
538 plane.code_ref.node_path = node_path;
539 }
540 Artifact::SketchBlock(block) if block.code_ref.node_path.is_empty() => {
541 let (range, node_path) =
542 code_ref_for_range(programs, cached_body_items, block.code_ref.range, import_code_refs);
543 block.code_ref.range = range;
544 block.code_ref.node_path = node_path;
545 }
546 Artifact::SketchBlockConstraint(constraint) if constraint.code_ref.node_path.is_empty() => {
547 constraint.code_ref.node_path =
548 NodePath::from_range(programs, cached_body_items, constraint.code_ref.range).unwrap_or_default();
549 }
550 Artifact::GdtAnnotation(annotation) if annotation.code_ref.node_path.is_empty() => {
551 let (range, node_path) =
552 code_ref_for_range(programs, cached_body_items, annotation.code_ref.range, import_code_refs);
553 annotation.code_ref.range = range;
554 annotation.code_ref.node_path = node_path;
555 }
556 Artifact::NamedView(view) if view.code_ref.node_path.is_empty() => {
557 let (range, node_path) =
558 code_ref_for_range(programs, cached_body_items, view.code_ref.range, import_code_refs);
559 view.code_ref.range = range;
560 view.code_ref.node_path = node_path;
561 }
562 _ => {}
563 }
564}
565
566fn flatten_modeling_command_responses(
569 responses: &IndexMap<Uuid, WebSocketResponse>,
570) -> AHashMap<Uuid, OkModelingCmdResponse> {
571 let mut map = AHashMap::default();
572 for (cmd_id, ws_response) in responses {
573 let WebSocketResponse::Success(response) = ws_response else {
574 continue;
576 };
577 match &response.resp {
578 OkWebSocketResponseData::Modeling { modeling_response } => {
579 map.insert(*cmd_id, modeling_response.clone());
580 }
581 OkWebSocketResponseData::ModelingBatch { responses } =>
582 {
583 #[expect(
584 clippy::iter_over_hash_type,
585 reason = "Since we're moving entries to another unordered map, it's fine that the order is undefined"
586 )]
587 for (cmd_id, batch_response) in responses {
588 if let BatchResponse::Success {
589 response: modeling_response,
590 } = batch_response
591 {
592 map.insert(*cmd_id.as_ref(), modeling_response.clone());
593 }
594 }
595 }
596 OkWebSocketResponseData::IceServerInfo { .. }
597 | OkWebSocketResponseData::TrickleIce { .. }
598 | OkWebSocketResponseData::SdpAnswer { .. }
599 | OkWebSocketResponseData::Export { .. }
600 | OkWebSocketResponseData::MetricsRequest { .. }
601 | OkWebSocketResponseData::ModelingSessionData { .. }
602 | OkWebSocketResponseData::Debug { .. }
603 | OkWebSocketResponseData::Pong { .. } => {}
604 _other => {}
605 }
606 }
607
608 map
609}
610
611#[derive(Debug, Clone)]
612struct PendingEntityCloneMapping {
613 clone_cmd_id: Uuid,
614 old_entity_id: Uuid,
615 source_topology_id: Uuid,
616 old_child_ids: Option<Vec<Uuid>>,
617 source_topology_child_ids: Option<Vec<Uuid>>,
618}
619
620fn build_entity_clone_id_maps(
623 artifact_commands: &[ArtifactCommand],
624 responses: &AHashMap<Uuid, OkModelingCmdResponse>,
625) -> AHashMap<Uuid, AHashMap<ArtifactId, ArtifactId>> {
626 let mut clone_id_maps = AHashMap::default();
627 let mut pending = Vec::new();
628
629 for artifact_command in artifact_commands {
630 match &artifact_command.command {
631 ModelingCmd::EntityClone(kcmc::EntityClone { entity_id, .. }) => {
632 let source_topology_id = artifact_command
633 .entity_clone_info
634 .map(|info| Uuid::from(info.source_topology_id))
635 .unwrap_or(*entity_id);
636 pending.push(PendingEntityCloneMapping {
637 clone_cmd_id: artifact_command.cmd_id,
638 old_entity_id: *entity_id,
639 source_topology_id,
640 old_child_ids: None,
641 source_topology_child_ids: None,
642 });
643 }
644 ModelingCmd::EntityGetAllChildUuids(kcmc::EntityGetAllChildUuids { entity_id, .. }) => {
645 let Some(OkModelingCmdResponse::EntityGetAllChildUuids(child_ids_response)) =
646 responses.get(&artifact_command.cmd_id)
647 else {
648 continue;
649 };
650 let child_ids = child_ids_response.entity_ids.clone();
651
652 let mut completed_index = None;
653 for index in (0..pending.len()).rev() {
654 let pending_map = &mut pending[index];
655 if let Some(old_child_ids) = &pending_map.old_child_ids
656 && *entity_id == pending_map.clone_cmd_id
657 {
658 let mut id_map = AHashMap::default();
659 id_map.insert(
660 ArtifactId::new(pending_map.old_entity_id),
661 ArtifactId::new(pending_map.clone_cmd_id),
662 );
663 for (old_id, new_id) in old_child_ids.iter().zip(child_ids.iter()) {
664 id_map.insert(ArtifactId::new(*old_id), ArtifactId::new(*new_id));
665 }
666 if pending_map.source_topology_id != pending_map.old_entity_id
667 && let Some(source_topology_child_ids) = &pending_map.source_topology_child_ids
668 {
669 for (source_id, new_id) in source_topology_child_ids.iter().zip(child_ids.iter()) {
670 id_map.insert(ArtifactId::new(*source_id), ArtifactId::new(*new_id));
671 }
672 }
673 clone_id_maps.insert(pending_map.clone_cmd_id, id_map);
674 completed_index = Some(index);
675 break;
676 }
677 if pending_map.old_child_ids.is_none() && *entity_id == pending_map.old_entity_id {
678 pending_map.old_child_ids = Some(child_ids.clone());
679 if pending_map.source_topology_id == pending_map.old_entity_id {
680 pending_map.source_topology_child_ids = Some(child_ids.clone());
681 }
682 break;
683 }
684 if pending_map.source_topology_child_ids.is_none() && *entity_id == pending_map.source_topology_id {
685 pending_map.source_topology_child_ids = Some(child_ids.clone());
686 break;
687 }
688 }
689
690 if let Some(index) = completed_index {
691 pending.swap_remove(index);
692 }
693 }
694 _ => {}
695 }
696 }
697
698 clone_id_maps
699}
700
701fn merge_artifact_into_map(map: &mut IndexMap<ArtifactId, Artifact>, new_artifact: Artifact) {
702 fn is_primitive_artifact(artifact: &Artifact) -> bool {
703 matches!(artifact, Artifact::PrimitiveFace(_) | Artifact::PrimitiveEdge(_))
704 }
705
706 let id = new_artifact.id();
707 let Some(old_artifact) = map.get_mut(&id) else {
708 map.insert(id, new_artifact);
710 return;
711 };
712
713 if is_primitive_artifact(&new_artifact) && !is_primitive_artifact(old_artifact) {
717 return;
718 }
719
720 if let Some(replacement) = merge_artifacts(old_artifact, new_artifact) {
721 *old_artifact = replacement;
722 }
723}
724
725fn merge_ids(base: &mut Vec<ArtifactId>, new: Vec<ArtifactId>) {
729 let original_len = base.len();
730 for id in new {
731 let original_base = &base[..original_len];
733 if !original_base.contains(&id) {
734 base.push(id);
735 }
736 }
737}
738
739fn merge_opt_id(base: &mut Option<ArtifactId>, new: Option<ArtifactId>) {
741 *base = new;
743}
744
745fn remap_id_for_clone(id: ArtifactId, entity_id_map: &AHashMap<ArtifactId, ArtifactId>) -> ArtifactId {
746 entity_id_map.get(&id).copied().unwrap_or(id)
747}
748
749fn remap_opt_id_for_clone(
750 id: Option<ArtifactId>,
751 entity_id_map: &AHashMap<ArtifactId, ArtifactId>,
752) -> Option<ArtifactId> {
753 id.map(|id| remap_id_for_clone(id, entity_id_map))
754}
755
756fn remap_ids_for_clone(ids: &[ArtifactId], entity_id_map: &AHashMap<ArtifactId, ArtifactId>) -> Vec<ArtifactId> {
757 ids.iter()
758 .copied()
759 .map(|id| remap_id_for_clone(id, entity_id_map))
760 .collect()
761}
762
763fn remap_mapped_ids_for_clone(ids: &[ArtifactId], entity_id_map: &AHashMap<ArtifactId, ArtifactId>) -> Vec<ArtifactId> {
764 ids.iter().filter_map(|id| entity_id_map.get(id).copied()).collect()
765}
766
767fn add_composite_sweep_clone_id_mappings(
768 artifacts: &IndexMap<ArtifactId, Artifact>,
769 clone_cmd_id: Uuid,
770 entity_id_map: &mut AHashMap<ArtifactId, ArtifactId>,
771) {
772 let source_sweep_ids = artifacts
773 .values()
774 .filter_map(|artifact| {
775 let Artifact::Sweep(sweep) = artifact else {
776 return None;
777 };
778 if entity_id_map.contains_key(&sweep.id) {
779 return None;
780 }
781
782 let has_mapped_topology = entity_id_map.contains_key(&sweep.path_id)
783 || sweep.surface_ids.iter().any(|id| entity_id_map.contains_key(id))
784 || sweep.edge_ids.iter().any(|id| entity_id_map.contains_key(id));
785 has_mapped_topology.then_some(sweep.id)
786 })
787 .collect::<Vec<_>>();
788
789 for source_sweep_id in source_sweep_ids {
790 let source_uuid = Uuid::from(source_sweep_id);
791 let cloned_sweep_id = ArtifactId::new(Uuid::new_v5(&clone_cmd_id, source_uuid.as_bytes()));
792 entity_id_map.insert(source_sweep_id, cloned_sweep_id);
793 }
794}
795
796fn remap_artifact_for_clone(
797 artifact: &Artifact,
798 entity_id_map: &AHashMap<ArtifactId, ArtifactId>,
799 clone_code_ref: &CodeRef,
800 clone_cmd_id: Uuid,
801 source_root_id: ArtifactId,
802) -> Artifact {
803 match artifact {
804 Artifact::CompositeSolid(source) => Artifact::CompositeSolid(CompositeSolid {
805 id: remap_id_for_clone(source.id, entity_id_map),
806 consumed: if source.id == source_root_id {
807 false
808 } else {
809 source.consumed
810 },
811 sub_type: source.sub_type,
812 output_index: if source.id == source_root_id {
815 None
816 } else {
817 source.output_index
818 },
819 solid_ids: remap_ids_for_clone(&source.solid_ids, entity_id_map),
820 tool_ids: remap_ids_for_clone(&source.tool_ids, entity_id_map),
821 pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
822 code_ref: clone_code_ref.clone(),
823 composite_solid_id: if source.id == source_root_id {
824 None
825 } else {
826 remap_opt_id_for_clone(source.composite_solid_id, entity_id_map)
827 },
828 }),
829 Artifact::Plane(source) => Artifact::Plane(Plane {
830 id: remap_id_for_clone(source.id, entity_id_map),
831 path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
832 code_ref: clone_code_ref.clone(),
833 }),
834 Artifact::Path(source) => Artifact::Path(Path {
835 id: remap_id_for_clone(source.id, entity_id_map),
836 sub_type: source.sub_type,
837 plane_id: remap_id_for_clone(source.plane_id, entity_id_map),
838 seg_ids: remap_ids_for_clone(&source.seg_ids, entity_id_map),
839 consumed: if source.id == source_root_id {
840 false
841 } else {
842 source.consumed
843 },
844 sweep_id: remap_opt_id_for_clone(source.sweep_id, entity_id_map),
845 trajectory_sweep_id: remap_opt_id_for_clone(source.trajectory_sweep_id, entity_id_map),
846 solid2d_id: remap_opt_id_for_clone(source.solid2d_id, entity_id_map),
847 code_ref: clone_code_ref.clone(),
848 composite_solid_id: remap_opt_id_for_clone(source.composite_solid_id, entity_id_map),
849 sketch_block_id: remap_opt_id_for_clone(source.sketch_block_id, entity_id_map),
850 origin_path_id: remap_opt_id_for_clone(source.origin_path_id, entity_id_map),
851 inner_path_id: remap_opt_id_for_clone(source.inner_path_id, entity_id_map),
852 outer_path_id: remap_opt_id_for_clone(source.outer_path_id, entity_id_map),
853 pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
854 }),
855 Artifact::Segment(source) => Artifact::Segment(Segment {
856 id: remap_id_for_clone(source.id, entity_id_map),
857 path_id: remap_id_for_clone(source.path_id, entity_id_map),
858 source_segment_id: source.source_segment_id.or(Some(source.id)),
859 original_seg_id: remap_opt_id_for_clone(source.original_seg_id, entity_id_map),
860 surface_id: remap_opt_id_for_clone(source.surface_id, entity_id_map),
861 edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
862 edge_cut_id: remap_opt_id_for_clone(source.edge_cut_id, entity_id_map),
863 code_ref: clone_code_ref.clone(),
864 common_surface_ids: remap_ids_for_clone(&source.common_surface_ids, entity_id_map),
865 }),
866 Artifact::Solid2d(source) => Artifact::Solid2d(Solid2d {
867 id: remap_id_for_clone(source.id, entity_id_map),
868 path_id: remap_id_for_clone(source.path_id, entity_id_map),
869 }),
870 Artifact::PrimitiveFace(source) => Artifact::PrimitiveFace(PrimitiveFace {
871 id: remap_id_for_clone(source.id, entity_id_map),
872 solid_id: remap_id_for_clone(source.solid_id, entity_id_map),
873 code_ref: clone_code_ref.clone(),
874 }),
875 Artifact::PrimitiveEdge(source) => Artifact::PrimitiveEdge(PrimitiveEdge {
876 id: remap_id_for_clone(source.id, entity_id_map),
877 solid_id: remap_id_for_clone(source.solid_id, entity_id_map),
878 code_ref: clone_code_ref.clone(),
879 }),
880 Artifact::PlaneOfFace(source) => Artifact::PlaneOfFace(PlaneOfFace {
881 id: remap_id_for_clone(source.id, entity_id_map),
882 face_id: remap_id_for_clone(source.face_id, entity_id_map),
883 code_ref: clone_code_ref.clone(),
884 }),
885 Artifact::StartSketchOnFace(source) => Artifact::StartSketchOnFace(StartSketchOnFace {
886 id: remap_id_for_clone(source.id, entity_id_map),
887 face_id: remap_id_for_clone(source.face_id, entity_id_map),
888 code_ref: clone_code_ref.clone(),
889 }),
890 Artifact::StartSketchOnPlane(source) => Artifact::StartSketchOnPlane(StartSketchOnPlane {
891 id: remap_id_for_clone(source.id, entity_id_map),
892 plane_id: remap_id_for_clone(source.plane_id, entity_id_map),
893 code_ref: clone_code_ref.clone(),
894 }),
895 Artifact::SketchBlock(source) => Artifact::SketchBlock(SketchBlock {
896 id: remap_id_for_clone(source.id, entity_id_map),
897 standard_plane: source.standard_plane,
898 plane_id: remap_opt_id_for_clone(source.plane_id, entity_id_map),
899 plane_info: source.plane_info.clone(),
900 path_id: remap_opt_id_for_clone(source.path_id, entity_id_map),
901 code_ref: clone_code_ref.clone(),
902 sketch_id: source.sketch_id,
903 }),
904 Artifact::SketchBlockConstraint(source) => Artifact::SketchBlockConstraint(SketchBlockConstraint {
905 id: remap_id_for_clone(source.id, entity_id_map),
906 sketch_id: source.sketch_id,
907 constraint_id: source.constraint_id,
908 constraint_type: source.constraint_type,
909 code_ref: clone_code_ref.clone(),
910 }),
911 Artifact::Sweep(source) => Artifact::Sweep(Sweep {
912 id: remap_id_for_clone(source.id, entity_id_map),
913 sub_type: source.sub_type,
914 path_id: remap_id_for_clone(source.path_id, entity_id_map),
915 surface_ids: remap_ids_for_clone(&source.surface_ids, entity_id_map),
916 edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
917 code_ref: clone_code_ref.clone(),
918 source_sweep_id: source.source_sweep_id.or(Some(source.id)),
919 trajectory_id: remap_opt_id_for_clone(source.trajectory_id, entity_id_map),
920 method: source.method,
921 consumed: if source.id == source_root_id {
922 false
923 } else {
924 source.consumed
925 },
926 pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
927 }),
928 Artifact::Wall(source) => Artifact::Wall(Wall {
929 id: remap_id_for_clone(source.id, entity_id_map),
930 seg_id: remap_id_for_clone(source.seg_id, entity_id_map),
931 edge_cut_edge_ids: remap_ids_for_clone(&source.edge_cut_edge_ids, entity_id_map),
932 sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
933 path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
934 face_code_ref: source.face_code_ref.clone(),
935 cmd_id: clone_cmd_id,
936 }),
937 Artifact::Cap(source) => Artifact::Cap(Cap {
938 id: remap_id_for_clone(source.id, entity_id_map),
939 sub_type: source.sub_type,
940 edge_cut_edge_ids: remap_ids_for_clone(&source.edge_cut_edge_ids, entity_id_map),
941 sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
942 path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
943 face_code_ref: source.face_code_ref.clone(),
944 cmd_id: clone_cmd_id,
945 }),
946 Artifact::SweepEdge(source) => Artifact::SweepEdge(SweepEdge {
947 id: remap_id_for_clone(source.id, entity_id_map),
948 sub_type: source.sub_type,
949 seg_id: remap_id_for_clone(source.seg_id, entity_id_map),
950 cmd_id: clone_cmd_id,
951 index: source.index,
952 sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
953 common_surface_ids: remap_ids_for_clone(&source.common_surface_ids, entity_id_map),
954 }),
955 Artifact::EdgeCut(source) => Artifact::EdgeCut(EdgeCut {
956 id: remap_id_for_clone(source.id, entity_id_map),
957 sub_type: source.sub_type,
958 consumed_edge_id: remap_id_for_clone(source.consumed_edge_id, entity_id_map),
959 edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
960 surface_id: remap_opt_id_for_clone(source.surface_id, entity_id_map),
961 code_ref: clone_code_ref.clone(),
962 }),
963 Artifact::EdgeCutEdge(source) => Artifact::EdgeCutEdge(EdgeCutEdge {
964 id: remap_id_for_clone(source.id, entity_id_map),
965 edge_cut_id: remap_id_for_clone(source.edge_cut_id, entity_id_map),
966 surface_id: remap_id_for_clone(source.surface_id, entity_id_map),
967 }),
968 Artifact::Helix(source) => Artifact::Helix(Helix {
969 id: remap_id_for_clone(source.id, entity_id_map),
970 axis_id: remap_opt_id_for_clone(source.axis_id, entity_id_map),
971 code_ref: clone_code_ref.clone(),
972 trajectory_sweep_id: remap_opt_id_for_clone(source.trajectory_sweep_id, entity_id_map),
973 consumed: if source.id == source_root_id {
974 false
975 } else {
976 source.consumed
977 },
978 }),
979 Artifact::ImportedGeometry(source) => Artifact::ImportedGeometry(ImportedGeometryArtifact {
980 id: remap_id_for_clone(source.id, entity_id_map),
981 code_ref: clone_code_ref.clone(),
982 }),
983 Artifact::GdtAnnotation(source) => Artifact::GdtAnnotation(GdtAnnotationArtifact {
984 id: remap_id_for_clone(source.id, entity_id_map),
985 code_ref: clone_code_ref.clone(),
986 }),
987 Artifact::NamedView(_) => {
994 debug_assert!(false, "a named view is not reachable from a cloned body");
995 artifact.clone()
996 }
997 Artifact::Pattern(source) => Artifact::Pattern(Pattern {
998 id: remap_id_for_clone(source.id, entity_id_map),
999 sub_type: source.sub_type,
1000 source_id: remap_id_for_clone(source.source_id, entity_id_map),
1001 copy_ids: remap_ids_for_clone(&source.copy_ids, entity_id_map),
1002 copy_face_ids: remap_ids_for_clone(&source.copy_face_ids, entity_id_map),
1003 copy_edge_ids: remap_ids_for_clone(&source.copy_edge_ids, entity_id_map),
1004 code_ref: clone_code_ref.clone(),
1005 }),
1006 }
1007}
1008
1009fn pattern_source_ids(artifacts: &IndexMap<ArtifactId, Artifact>, source_id: ArtifactId) -> Vec<ArtifactId> {
1010 let mut source_ids = vec![source_id];
1011
1012 if let Some(Artifact::Path(path)) = artifacts.get(&source_id) {
1013 if let Some(sweep_id) = path.sweep_id {
1014 source_ids.push(sweep_id);
1015 }
1016 if let Some(composite_solid_id) = path.composite_solid_id {
1017 source_ids.push(composite_solid_id);
1018 }
1019 }
1020
1021 for artifact in artifacts.values() {
1022 match artifact {
1023 Artifact::Sweep(sweep) if sweep.path_id == source_id => source_ids.push(sweep.id),
1024 Artifact::CompositeSolid(composite)
1025 if composite.solid_ids.contains(&source_id) || composite.tool_ids.contains(&source_id) =>
1026 {
1027 source_ids.push(composite.id)
1028 }
1029 _ => {}
1030 }
1031 }
1032
1033 let mut unique = Vec::new();
1034 merge_ids(&mut unique, source_ids);
1035 unique
1036}
1037
1038fn pattern_source_body_id_for_copy(
1039 artifacts: &IndexMap<ArtifactId, Artifact>,
1040 copy_id: ArtifactId,
1041) -> Option<ArtifactId> {
1042 artifacts.values().find_map(|artifact| {
1043 let Artifact::Pattern(pattern) = artifact else {
1044 return None;
1045 };
1046 if !pattern.copy_ids.contains(©_id) {
1047 return None;
1048 }
1049
1050 pattern_source_ids(artifacts, pattern.source_id).into_iter().find(|id| {
1051 matches!(
1052 artifacts.get(id),
1053 Some(Artifact::Sweep(_) | Artifact::CompositeSolid(_))
1054 )
1055 })
1056 })
1057}
1058
1059fn pattern_artifact_updates(
1060 artifacts: &IndexMap<ArtifactId, Artifact>,
1061 pattern_id: ArtifactId,
1062 sub_type: PatternSubType,
1063 source_id: ArtifactId,
1064 face_edge_infos: &[kcmc::output::FaceEdgeInfo],
1065 code_ref: CodeRef,
1066) -> Vec<Artifact> {
1067 let copy_ids = face_edge_infos
1068 .iter()
1069 .map(|info| ArtifactId::new(info.object_id))
1070 .collect::<Vec<_>>();
1071 let copy_face_ids = face_edge_infos
1072 .iter()
1073 .flat_map(|info| info.faces.iter().copied().map(ArtifactId::new))
1074 .collect::<Vec<_>>();
1075 let copy_edge_ids = face_edge_infos
1076 .iter()
1077 .flat_map(|info| info.edges.iter().copied().map(ArtifactId::new))
1078 .collect::<Vec<_>>();
1079
1080 let source_ids = pattern_source_ids(artifacts, source_id);
1081 let mut return_arr = vec![Artifact::Pattern(Pattern {
1082 id: pattern_id,
1083 sub_type,
1084 source_id,
1085 copy_ids,
1086 copy_face_ids,
1087 copy_edge_ids,
1088 code_ref,
1089 })];
1090
1091 for source_id in source_ids {
1092 let Some(artifact) = artifacts.get(&source_id) else {
1093 continue;
1094 };
1095 match artifact {
1096 Artifact::Path(path) => {
1097 let mut new_path = path.clone();
1098 new_path.pattern_ids = vec![pattern_id];
1099 return_arr.push(Artifact::Path(new_path));
1100 }
1101 Artifact::Sweep(sweep) => {
1102 let mut new_sweep = sweep.clone();
1103 new_sweep.pattern_ids = vec![pattern_id];
1104 return_arr.push(Artifact::Sweep(new_sweep));
1105 }
1106 Artifact::CompositeSolid(composite) => {
1107 let mut new_composite = composite.clone();
1108 new_composite.pattern_ids = vec![pattern_id];
1109 return_arr.push(Artifact::CompositeSolid(new_composite));
1110 }
1111 _ => {}
1112 }
1113 }
1114
1115 return_arr
1116}
1117
1118fn is_single_target_self_subtract(target_ids: &[Uuid], tool_ids: &[Uuid]) -> bool {
1119 target_ids.len() == 1 && tool_ids.len() == 1 && target_ids[0] == tool_ids[0]
1120}
1121
1122fn boolean_subtract_output_artifact_ids(
1123 cmd_id: ArtifactId,
1124 target_ids: &[Uuid],
1125 tool_ids: &[Uuid],
1126 extra_solid_ids: &[Uuid],
1127) -> Vec<ArtifactId> {
1128 if is_single_target_self_subtract(target_ids, tool_ids) {
1129 return Vec::new();
1130 }
1131
1132 let mut output_ids = if target_ids.len() == 1 {
1133 vec![cmd_id]
1134 } else {
1135 Vec::new()
1136 };
1137
1138 for extra_solid_id in extra_solid_ids {
1139 let artifact_id = ArtifactId::new(*extra_solid_id);
1140 if !output_ids.contains(&artifact_id) {
1141 output_ids.push(artifact_id);
1142 }
1143 }
1144
1145 output_ids
1146}
1147
1148fn update_consumed_csg_sweep(
1149 return_arr: &mut Vec<Artifact>,
1150 artifacts: &IndexMap<ArtifactId, Artifact>,
1151 sweep_id: ArtifactId,
1152 consumed_sweep_ids: &mut AHashSet<ArtifactId>,
1153) {
1154 if consumed_sweep_ids.insert(sweep_id)
1155 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
1156 {
1157 let mut new_sweep = sweep.clone();
1158 new_sweep.consumed = true;
1159 return_arr.push(Artifact::Sweep(new_sweep));
1160 }
1161}
1162
1163fn mark_artifact_consumed_by_id(
1164 return_arr: &mut Vec<Artifact>,
1165 artifacts: &IndexMap<ArtifactId, Artifact>,
1166 artifact_id: ArtifactId,
1167 consumed_ids: &mut AHashSet<ArtifactId>,
1168) {
1169 let already_marked_as_consumed = !consumed_ids.insert(artifact_id);
1170 if already_marked_as_consumed {
1171 return;
1172 }
1173
1174 let Some(artifact) = artifacts.get(&artifact_id) else {
1175 return;
1176 };
1177
1178 match artifact {
1179 Artifact::CompositeSolid(composite) => {
1180 let mut new_composite = composite.clone();
1181 new_composite.consumed = true;
1182 return_arr.push(Artifact::CompositeSolid(new_composite));
1183 }
1184 Artifact::Path(path) => {
1185 let mut new_path = path.clone();
1186 new_path.consumed = true;
1187 return_arr.push(Artifact::Path(new_path));
1188
1189 if let Some(sweep_id) = path.sweep_id {
1190 mark_artifact_consumed_by_id(return_arr, artifacts, sweep_id, consumed_ids);
1191 }
1192 if let Some(composite_solid_id) = path.composite_solid_id {
1193 mark_artifact_consumed_by_id(return_arr, artifacts, composite_solid_id, consumed_ids);
1194 }
1195 }
1196 Artifact::Sweep(sweep) => {
1197 let mut new_sweep = sweep.clone();
1198 new_sweep.consumed = true;
1199 return_arr.push(Artifact::Sweep(new_sweep));
1200 }
1201 Artifact::Helix(helix) => {
1202 let mut new_helix = helix.clone();
1203 new_helix.consumed = true;
1204 return_arr.push(Artifact::Helix(new_helix));
1205 }
1206 _ => {}
1207 }
1208}
1209
1210fn mark_deleted_artifacts_consumed(
1211 artifacts: &IndexMap<ArtifactId, Artifact>,
1212 object_ids: &std::collections::HashSet<Uuid>,
1213) -> Vec<Artifact> {
1214 let mut return_arr = Vec::new();
1215 let mut consumed_ids = AHashSet::default();
1216
1217 #[allow(clippy::iter_over_hash_type)]
1220 for object_id in object_ids {
1221 let artifact_id = ArtifactId::new(*object_id);
1222 mark_artifact_consumed_by_id(&mut return_arr, artifacts, artifact_id, &mut consumed_ids);
1223 }
1224
1225 return_arr
1226}
1227
1228fn update_csg_input_artifacts(
1229 return_arr: &mut Vec<Artifact>,
1230 artifacts: &IndexMap<ArtifactId, Artifact>,
1231 input_ids: &[ArtifactId],
1232 composite_solid_id: Option<ArtifactId>,
1233 consumed_sweep_ids: &mut AHashSet<ArtifactId>,
1234) {
1235 for input_id in input_ids {
1236 if let Some(artifact) = artifacts.get(input_id) {
1237 match artifact {
1238 Artifact::CompositeSolid(comp) => {
1239 let mut new_comp = comp.clone();
1240 new_comp.composite_solid_id = composite_solid_id;
1241 new_comp.consumed = true;
1242 return_arr.push(Artifact::CompositeSolid(new_comp));
1243 }
1244 Artifact::Path(path) => {
1245 let mut new_path = path.clone();
1246 new_path.composite_solid_id = composite_solid_id;
1247
1248 if let Some(sweep_id) = new_path.sweep_id {
1251 update_consumed_csg_sweep(return_arr, artifacts, sweep_id, consumed_sweep_ids);
1252 }
1253
1254 return_arr.push(Artifact::Path(new_path));
1255 }
1256 Artifact::Sweep(sweep) => {
1257 update_consumed_csg_sweep(return_arr, artifacts, sweep.id, consumed_sweep_ids);
1258 }
1259 _ => {}
1260 }
1261 }
1262 }
1263}
1264
1265fn mirror_3d_artifact_updates(
1266 artifacts: &IndexMap<ArtifactId, Artifact>,
1267 original_solid_ids: &[Uuid],
1268 face_edge_infos: &[kcmc::output::FaceEdgeInfo],
1269 code_ref: CodeRef,
1270 range: SourceRange,
1271 cmd: &ModelingCmd,
1272) -> Result<Vec<Artifact>, KclError> {
1273 if original_solid_ids.len() != face_edge_infos.len() {
1274 internal_error!(
1275 range,
1276 "EntityMirrorAcross response has different number face edge info than original mirrored solids: cmd={cmd:?}, face_edge_infos={face_edge_infos:?}"
1277 );
1278 }
1279
1280 let mut return_arr = Vec::new();
1281 for (face_edge_info, original_solid_id) in face_edge_infos.iter().zip(original_solid_ids) {
1282 let original_solid_id = ArtifactId::new(*original_solid_id);
1283 let mirrored_solid_id = ArtifactId::new(face_edge_info.object_id);
1284 let source_solid = match artifacts.get(&original_solid_id) {
1285 Some(Artifact::Path(path)) => path.sweep_id.and_then(|sweep_id| artifacts.get(&sweep_id)).or_else(|| {
1286 path.composite_solid_id
1287 .and_then(|composite_id| artifacts.get(&composite_id))
1288 }),
1289 source => source,
1290 };
1291 match source_solid {
1292 Some(Artifact::Sweep(sweep)) => {
1293 let mut mirrored_sweep = sweep.clone();
1294 mirrored_sweep.id = mirrored_solid_id;
1295 mirrored_sweep.surface_ids = face_edge_info.faces.iter().copied().map(ArtifactId::new).collect();
1296 mirrored_sweep.edge_ids = face_edge_info.edges.iter().copied().map(ArtifactId::new).collect();
1297 mirrored_sweep.code_ref = code_ref.clone();
1298 mirrored_sweep.consumed = false;
1299 mirrored_sweep.pattern_ids = Vec::new();
1300 return_arr.push(Artifact::Sweep(mirrored_sweep));
1301 }
1302 Some(Artifact::CompositeSolid(composite)) => {
1303 let mut mirrored_composite = composite.clone();
1304 mirrored_composite.id = mirrored_solid_id;
1305 mirrored_composite.code_ref = code_ref.clone();
1306 mirrored_composite.consumed = false;
1307 mirrored_composite.composite_solid_id = None;
1308 mirrored_composite.pattern_ids = Vec::new();
1309 return_arr.push(Artifact::CompositeSolid(mirrored_composite));
1310 }
1311 Some(_) | None => continue,
1312 }
1313 }
1314
1315 Ok(return_arr)
1316}
1317
1318#[allow(clippy::too_many_arguments)]
1319fn artifacts_to_update(
1320 artifacts: &IndexMap<ArtifactId, Artifact>,
1321 artifact_command: &ArtifactCommand,
1322 responses: &AHashMap<Uuid, OkModelingCmdResponse>,
1323 entity_clone_id_maps: &AHashMap<Uuid, AHashMap<ArtifactId, ArtifactId>>,
1324 path_to_plane_id_map: &AHashMap<Uuid, Uuid>,
1325 programs: &crate::execution::ProgramLookup,
1326 cached_body_items: usize,
1327 exec_artifacts: &IndexMap<ArtifactId, Artifact>,
1328 import_code_refs: &AHashMap<ModuleId, ImportCodeRef>,
1329) -> Result<Vec<Artifact>, KclError> {
1330 let uuid = artifact_command.cmd_id;
1331 let response = responses.get(&uuid);
1332
1333 let path_to_node = Vec::new();
1337 let range = artifact_command.range;
1338 let (code_ref_range, node_path) = code_ref_for_range(programs, cached_body_items, range, import_code_refs);
1339 let code_ref = CodeRef {
1340 range: code_ref_range,
1341 node_path,
1342 path_to_node,
1343 };
1344
1345 let id = ArtifactId::new(uuid);
1346 let cmd = &artifact_command.command;
1347
1348 match cmd {
1349 ModelingCmd::ImportFiles(_) => {
1350 return Ok(vec![Artifact::ImportedGeometry(ImportedGeometryArtifact {
1351 id,
1352 code_ref,
1353 })]);
1354 }
1355 ModelingCmd::MakePlane(_) => {
1356 if range.is_synthetic() {
1357 return Ok(Vec::new());
1358 }
1359 return Ok(vec![Artifact::Plane(Plane {
1363 id,
1364 path_ids: Vec::new(),
1365 code_ref,
1366 })]);
1367 }
1368 ModelingCmd::FaceIsPlanar(FaceIsPlanar { object_id, .. }) => {
1369 return Ok(vec![Artifact::PlaneOfFace(PlaneOfFace {
1370 id,
1371 face_id: object_id.into(),
1372 code_ref,
1373 })]);
1374 }
1375 ModelingCmd::RemoveSceneObjects(remove) => {
1376 return Ok(mark_deleted_artifacts_consumed(artifacts, &remove.object_ids));
1377 }
1378 ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) => {
1379 let existing_plane = artifacts.get(&ArtifactId::new(*entity_id));
1380 match existing_plane {
1381 Some(Artifact::Wall(wall)) => {
1382 return Ok(vec![Artifact::Wall(Wall {
1383 id: entity_id.into(),
1384 seg_id: wall.seg_id,
1385 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1386 sweep_id: wall.sweep_id,
1387 path_ids: wall.path_ids.clone(),
1388 face_code_ref: wall.face_code_ref.clone(),
1389 cmd_id: artifact_command.cmd_id,
1390 })]);
1391 }
1392 Some(Artifact::Cap(cap)) => {
1393 return Ok(vec![Artifact::Cap(Cap {
1394 id: entity_id.into(),
1395 sub_type: cap.sub_type,
1396 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1397 sweep_id: cap.sweep_id,
1398 path_ids: cap.path_ids.clone(),
1399 face_code_ref: cap.face_code_ref.clone(),
1400 cmd_id: artifact_command.cmd_id,
1401 })]);
1402 }
1403 Some(_) | None => {
1404 let path_ids = match existing_plane {
1405 Some(Artifact::Plane(Plane { path_ids, .. })) => path_ids.clone(),
1406 _ => Vec::new(),
1407 };
1408 return Ok(vec![Artifact::Plane(Plane {
1410 id: entity_id.into(),
1411 path_ids,
1412 code_ref,
1413 })]);
1414 }
1415 }
1416 }
1417 ModelingCmd::StartPath(_) => {
1418 let mut return_arr = Vec::new();
1419 let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
1420 KclError::new_internal(KclErrorDetails::new(
1421 format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"),
1422 vec![range],
1423 ))
1424 })?;
1425 let sketch_block_id = exec_artifacts
1426 .values()
1427 .find(|a| {
1428 if let Artifact::SketchBlock(s) = a {
1429 if let Some(path_id) = s.path_id {
1430 path_id == id
1431 } else {
1432 false
1433 }
1434 } else {
1435 false
1436 }
1437 })
1438 .map(|a| a.id());
1439 return_arr.push(Artifact::Path(Path {
1440 id,
1441 sub_type: PathSubType::Sketch,
1442 plane_id: (*current_plane_id).into(),
1443 seg_ids: Vec::new(),
1444 sweep_id: None,
1445 trajectory_sweep_id: None,
1446 solid2d_id: None,
1447 code_ref,
1448 composite_solid_id: None,
1449 sketch_block_id,
1450 origin_path_id: None,
1451 inner_path_id: None,
1452 outer_path_id: None,
1453 pattern_ids: Vec::new(),
1454 consumed: false,
1455 }));
1456 let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
1457 if let Some(Artifact::Plane(plane)) = plane {
1458 let plane_code_ref = plane.code_ref.clone();
1459 return_arr.push(Artifact::Plane(Plane {
1460 id: (*current_plane_id).into(),
1461 path_ids: vec![id],
1462 code_ref: plane_code_ref,
1463 }));
1464 }
1465 if let Some(Artifact::Wall(wall)) = plane {
1466 return_arr.push(Artifact::Wall(Wall {
1467 id: (*current_plane_id).into(),
1468 seg_id: wall.seg_id,
1469 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1470 sweep_id: wall.sweep_id,
1471 path_ids: vec![id],
1472 face_code_ref: wall.face_code_ref.clone(),
1473 cmd_id: artifact_command.cmd_id,
1474 }));
1475 }
1476 if let Some(Artifact::Cap(cap)) = plane {
1477 return_arr.push(Artifact::Cap(Cap {
1478 id: (*current_plane_id).into(),
1479 sub_type: cap.sub_type,
1480 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1481 sweep_id: cap.sweep_id,
1482 path_ids: vec![id],
1483 face_code_ref: cap.face_code_ref.clone(),
1484 cmd_id: artifact_command.cmd_id,
1485 }));
1486 }
1487 return Ok(return_arr);
1488 }
1489 ModelingCmd::ClosePath(_) | ModelingCmd::ExtendPath(_) => {
1490 let path_id = ArtifactId::new(match cmd {
1491 ModelingCmd::ClosePath(c) => c.path_id,
1492 ModelingCmd::ExtendPath(e) => e.path.into(),
1493 _ => internal_error!(
1494 range,
1495 "Close or extend path command variant not handled: id={id:?}, cmd={cmd:?}"
1496 ),
1497 });
1498 let mut return_arr = Vec::new();
1499 return_arr.push(Artifact::Segment(Segment {
1500 id,
1501 path_id,
1502 source_segment_id: None,
1503 original_seg_id: None,
1504 surface_id: None,
1505 edge_ids: Vec::new(),
1506 edge_cut_id: None,
1507 code_ref,
1508 common_surface_ids: Vec::new(),
1509 }));
1510 let path = artifacts.get(&path_id);
1511 if let Some(Artifact::Path(path)) = path {
1512 let mut new_path = path.clone();
1513 new_path.seg_ids = vec![id];
1514 return_arr.push(Artifact::Path(new_path));
1515 }
1516 if let Some(OkModelingCmdResponse::ClosePath(close_path)) = response {
1517 return_arr.push(Artifact::Solid2d(Solid2d {
1518 id: close_path.face_id.into(),
1519 path_id,
1520 }));
1521 if let Some(Artifact::Path(path)) = path {
1522 let mut new_path = path.clone();
1523 new_path.solid2d_id = Some(close_path.face_id.into());
1524 return_arr.push(Artifact::Path(new_path));
1525 }
1526 }
1527 return Ok(return_arr);
1528 }
1529 ModelingCmd::CreateRegion(kcmc::CreateRegion {
1530 object_id: origin_path_id,
1531 ..
1532 })
1533 | ModelingCmd::CreateRegionFromQueryPoint(kcmc::CreateRegionFromQueryPoint {
1534 object_id: origin_path_id,
1535 ..
1536 }) => {
1537 let mut return_arr = Vec::new();
1538 let origin_path = artifacts.get(&ArtifactId::new(*origin_path_id));
1539 let Some(Artifact::Path(path)) = origin_path else {
1540 internal_error!(
1541 range,
1542 "Expected to find an existing path for the origin path of CreateRegion or CreateRegionFromQueryPoint command, but found none: origin_path={origin_path:?}, cmd={cmd:?}"
1543 );
1544 };
1545 let region_path = |seg_ids, code_ref| {
1546 Artifact::Path(Path {
1547 id,
1548 sub_type: PathSubType::Region,
1549 plane_id: path.plane_id,
1550 seg_ids,
1551 consumed: false,
1552 sweep_id: None,
1553 trajectory_sweep_id: None,
1554 solid2d_id: None,
1555 code_ref,
1556 composite_solid_id: None,
1557 sketch_block_id: None,
1558 origin_path_id: Some(ArtifactId::new(*origin_path_id)),
1559 inner_path_id: None,
1560 outer_path_id: None,
1561 pattern_ids: Vec::new(),
1562 })
1563 };
1564 let Some(
1567 OkModelingCmdResponse::CreateRegion(kcmc::output::CreateRegion { region_mapping, .. })
1568 | OkModelingCmdResponse::CreateRegionFromQueryPoint(kcmc::output::CreateRegionFromQueryPoint {
1569 region_mapping,
1570 ..
1571 }),
1572 ) = response
1573 else {
1574 return_arr.push(region_path(Vec::new(), code_ref));
1575 return Ok(return_arr);
1576 };
1577 let original_segment_ids = path.seg_ids.iter().map(Uuid::from).collect::<Vec<_>>();
1580 let reverse = build_reverse_region_mapping(region_mapping, &original_segment_ids);
1581 let region_segment_ids = reverse
1582 .values()
1583 .flat_map(|region_segment_ids| region_segment_ids.iter().copied())
1584 .map(ArtifactId::new)
1585 .collect::<Vec<_>>();
1586 return_arr.push(region_path(region_segment_ids, code_ref.clone()));
1587 for (original_segment_id, region_segment_ids) in reverse.iter() {
1588 for segment_id in region_segment_ids {
1589 return_arr.push(Artifact::Segment(Segment {
1590 id: ArtifactId::new(*segment_id),
1591 path_id: id,
1592 source_segment_id: None,
1593 original_seg_id: Some(ArtifactId::new(*original_segment_id)),
1594 surface_id: None,
1595 edge_ids: Vec::new(),
1596 edge_cut_id: None,
1597 code_ref: code_ref.clone(),
1598 common_surface_ids: Vec::new(),
1599 }))
1600 }
1601 }
1602 return Ok(return_arr);
1603 }
1604 ModelingCmd::Solid3dGetFaceUuid(kcmc::Solid3dGetFaceUuid { object_id, .. }) => {
1605 let Some(OkModelingCmdResponse::Solid3dGetFaceUuid(face_uuid)) = response else {
1606 return Ok(Vec::new());
1607 };
1608
1609 return Ok(vec![Artifact::PrimitiveFace(PrimitiveFace {
1610 id: face_uuid.face_id.into(),
1611 solid_id: (*object_id).into(),
1612 code_ref,
1613 })]);
1614 }
1615 ModelingCmd::Solid3dGetEdgeUuid(kcmc::Solid3dGetEdgeUuid { object_id, .. }) => {
1616 let Some(OkModelingCmdResponse::Solid3dGetEdgeUuid(edge_uuid)) = response else {
1617 return Ok(Vec::new());
1618 };
1619
1620 return Ok(vec![Artifact::PrimitiveEdge(PrimitiveEdge {
1621 id: edge_uuid.edge_id.into(),
1622 solid_id: (*object_id).into(),
1623 code_ref,
1624 })]);
1625 }
1626 ModelingCmd::EntityLinearPatternTransform(pattern_cmd) => {
1627 let face_edge_infos = match response {
1628 Some(OkModelingCmdResponse::EntityLinearPatternTransform(resp)) => resp.entity_face_edge_ids.as_slice(),
1629 _ => &[],
1630 };
1631 return Ok(pattern_artifact_updates(
1632 artifacts,
1633 id,
1634 PatternSubType::Transform,
1635 ArtifactId::new(pattern_cmd.entity_id),
1636 face_edge_infos,
1637 code_ref,
1638 ));
1639 }
1640 ModelingCmd::EntityLinearPattern(pattern_cmd) => {
1641 let face_edge_infos = match response {
1642 Some(OkModelingCmdResponse::EntityLinearPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
1643 _ => &[],
1644 };
1645 return Ok(pattern_artifact_updates(
1646 artifacts,
1647 id,
1648 PatternSubType::Linear,
1649 ArtifactId::new(pattern_cmd.entity_id),
1650 face_edge_infos,
1651 code_ref,
1652 ));
1653 }
1654 ModelingCmd::EntityCircularPattern(pattern_cmd) => {
1655 let face_edge_infos = match response {
1656 Some(OkModelingCmdResponse::EntityCircularPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
1657 _ => &[],
1658 };
1659 return Ok(pattern_artifact_updates(
1660 artifacts,
1661 id,
1662 PatternSubType::Circular,
1663 ArtifactId::new(pattern_cmd.entity_id),
1664 face_edge_infos,
1665 code_ref,
1666 ));
1667 }
1668 ModelingCmd::EntityMirrorAcross(kcmc::EntityMirrorAcross {
1669 ids: original_solid_ids,
1670 ..
1671 }) => {
1672 let face_edge_infos = match response {
1673 Some(OkModelingCmdResponse::EntityMirrorAcross(resp)) => resp.entity_face_edge_ids.as_slice(),
1674 None => return Ok(Vec::new()),
1677 Some(_) => internal_error!(
1678 range,
1679 "EntityMirrorAcross response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
1680 ),
1681 };
1682 return mirror_3d_artifact_updates(artifacts, original_solid_ids, face_edge_infos, code_ref, range, cmd);
1683 }
1684 ModelingCmd::EntityMirror(kcmc::EntityMirror {
1685 ids: original_path_ids, ..
1686 })
1687 | ModelingCmd::EntityMirrorAcrossEdge(kcmc::EntityMirrorAcrossEdge {
1688 ids: original_path_ids, ..
1689 }) => {
1690 let face_edge_infos = match response {
1691 Some(OkModelingCmdResponse::EntityMirror(resp)) => &resp.entity_face_edge_ids,
1692 Some(OkModelingCmdResponse::EntityMirrorAcrossEdge(resp)) => &resp.entity_face_edge_ids,
1693 _ => internal_error!(
1694 range,
1695 "Mirror response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
1696 ),
1697 };
1698 if original_path_ids.len() != face_edge_infos.len() {
1699 internal_error!(
1700 range,
1701 "EntityMirror or EntityMirrorAcrossEdge response has different number face edge info than original mirrored paths: id={id:?}, cmd={cmd:?}, response={response:?}"
1702 );
1703 }
1704 let mut return_arr = Vec::new();
1705 for (face_edge_info, original_path_id) in face_edge_infos.iter().zip(original_path_ids) {
1706 let original_path_id = ArtifactId::new(*original_path_id);
1707 let path_id = ArtifactId::new(face_edge_info.object_id);
1708 let mut path = if let Some(Artifact::Path(path)) = artifacts.get(&path_id) {
1711 path.clone()
1713 } else {
1714 let Some(Artifact::Path(original_path)) = artifacts.get(&original_path_id) else {
1717 internal_error!(
1719 range,
1720 "Couldn't find original path for mirror2d: original_path_id={original_path_id:?}, cmd={cmd:?}"
1721 );
1722 };
1723 Path {
1724 id: path_id,
1725 sub_type: original_path.sub_type,
1726 plane_id: original_path.plane_id,
1727 seg_ids: Vec::new(),
1728 sweep_id: None,
1729 trajectory_sweep_id: None,
1730 solid2d_id: None,
1731 code_ref: code_ref.clone(),
1732 composite_solid_id: None,
1733 sketch_block_id: None,
1734 origin_path_id: original_path.origin_path_id,
1735 inner_path_id: None,
1736 outer_path_id: None,
1737 pattern_ids: Vec::new(),
1738 consumed: false,
1739 }
1740 };
1741
1742 face_edge_info.edges.iter().for_each(|edge_id| {
1743 let edge_id = ArtifactId::new(*edge_id);
1744 return_arr.push(Artifact::Segment(Segment {
1745 id: edge_id,
1746 path_id: path.id,
1747 source_segment_id: None,
1748 original_seg_id: None,
1749 surface_id: None,
1750 edge_ids: Vec::new(),
1751 edge_cut_id: None,
1752 code_ref: code_ref.clone(),
1753 common_surface_ids: Vec::new(),
1754 }));
1755 path.seg_ids.push(edge_id);
1757 });
1758
1759 return_arr.push(Artifact::Path(path));
1760 }
1761 return Ok(return_arr);
1762 }
1763 ModelingCmd::EntityClone(kcmc::EntityClone { entity_id, .. }) => {
1764 let source_entity_id = ArtifactId::new(*entity_id);
1765 let entity_clone_info = artifact_command.entity_clone_info;
1766 let source_id = entity_clone_info
1767 .map(|info| info.source_artifact_id)
1768 .unwrap_or(source_entity_id);
1769 let result_id = entity_clone_info.map(|info| info.result_artifact_id).unwrap_or(id);
1770
1771 let pattern_source_body_id = if entity_clone_info.is_some() && !artifacts.contains_key(&source_id) {
1775 pattern_source_body_id_for_copy(artifacts, source_id)
1776 } else {
1777 None
1778 };
1779 let source_artifact_id = pattern_source_body_id.unwrap_or(source_id);
1780 let Some(source_artifact) = artifacts.get(&source_artifact_id) else {
1781 return Ok(Vec::new());
1782 };
1783
1784 let mut entity_id_map = entity_clone_id_maps.get(&uuid).cloned().unwrap_or_default();
1785 entity_id_map.insert(source_entity_id, id);
1786 if let Some(info) = entity_clone_info {
1787 entity_id_map.insert(info.source_topology_id, id);
1788 }
1789 entity_id_map.insert(source_id, result_id);
1790 entity_id_map.insert(source_artifact_id, result_id);
1791 if matches!(source_artifact, Artifact::CompositeSolid(_)) {
1792 add_composite_sweep_clone_id_mappings(artifacts, artifact_command.cmd_id, &mut entity_id_map);
1793 }
1794
1795 let mut cloned_artifacts = Vec::new();
1796 cloned_artifacts.push(remap_artifact_for_clone(
1797 source_artifact,
1798 &entity_id_map,
1799 &code_ref,
1800 artifact_command.cmd_id,
1801 source_artifact_id,
1802 ));
1803
1804 for artifact in artifacts.values() {
1805 let artifact_id = artifact.id();
1806 if artifact_id == source_artifact_id || !entity_id_map.contains_key(&artifact_id) {
1807 continue;
1808 }
1809 cloned_artifacts.push(remap_artifact_for_clone(
1810 artifact,
1811 &entity_id_map,
1812 &code_ref,
1813 artifact_command.cmd_id,
1814 source_artifact_id,
1815 ));
1816 }
1817
1818 return Ok(cloned_artifacts);
1819 }
1820 ModelingCmd::Extrude(_)
1821 | ModelingCmd::TwistExtrude(_)
1822 | ModelingCmd::Revolve(_)
1823 | ModelingCmd::RevolveAboutEdge(_)
1824 | ModelingCmd::ExtrudeToReference(_) => {
1825 let target = match cmd {
1826 ModelingCmd::Extrude(kcmc::Extrude {
1827 target: Some(target), ..
1828 }) => cmd_id_ref_to_artifact_id(target),
1829 ModelingCmd::Extrude(kcmc::Extrude {
1830 target: None,
1831 target_reference: Some(_),
1832 ..
1833 }) => return Ok(Vec::new()),
1834 ModelingCmd::Extrude(kcmc::Extrude { target: None, .. }) => return Ok(Vec::new()),
1835 ModelingCmd::TwistExtrude(kcmc::TwistExtrude { target, .. })
1836 | ModelingCmd::Revolve(kcmc::Revolve { target, .. })
1837 | ModelingCmd::RevolveAboutEdge(kcmc::RevolveAboutEdge { target, .. }) => {
1838 cmd_id_ref_to_artifact_id(target)
1839 }
1840 ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference {
1841 target: Some(target), ..
1842 }) => cmd_id_ref_to_artifact_id(target),
1843 ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { target: None, .. }) => {
1844 return Ok(Vec::new());
1845 }
1846 _ => internal_error!(range, "Sweep-like command variant not handled: id={id:?}, cmd={cmd:?}"),
1847 };
1848 let method = match cmd {
1850 ModelingCmd::Extrude(kcmc::Extrude { extrude_method, .. }) => *extrude_method,
1851 ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { extrude_method, .. }) => *extrude_method,
1852 ModelingCmd::TwistExtrude(_) | ModelingCmd::Sweep(_) => {
1854 kittycad_modeling_cmds::shared::ExtrudeMethod::Merge
1855 }
1856 ModelingCmd::Revolve(_) | ModelingCmd::RevolveAboutEdge(_) => {
1858 kittycad_modeling_cmds::shared::ExtrudeMethod::New
1859 }
1860 _ => kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
1861 };
1862 let method = artifact_sweep_method(method);
1863 let sub_type = match cmd {
1864 ModelingCmd::Extrude(_) => SweepSubType::Extrusion,
1865 ModelingCmd::ExtrudeToReference(_) => SweepSubType::Extrusion,
1866 ModelingCmd::TwistExtrude(_) => SweepSubType::ExtrusionTwist,
1867 ModelingCmd::Revolve(_) => SweepSubType::Revolve,
1868 ModelingCmd::RevolveAboutEdge(_) => SweepSubType::RevolveAboutEdge,
1869 _ => internal_error!(range, "Sweep-like command variant not handled: id={id:?}, cmd={cmd:?}",),
1870 };
1871 let mut return_arr = Vec::new();
1872 return_arr.push(Artifact::Sweep(Sweep {
1873 id,
1874 sub_type,
1875 path_id: target,
1876 surface_ids: Vec::new(),
1877 edge_ids: Vec::new(),
1878 code_ref,
1879 source_sweep_id: None,
1880 trajectory_id: None,
1881 method,
1882 consumed: false,
1883 pattern_ids: Vec::new(),
1884 }));
1885 let path = artifacts.get(&target);
1886 if let Some(Artifact::Path(path)) = path {
1887 let mut new_path = path.clone();
1888 new_path.sweep_id = Some(id);
1889 new_path.consumed = true;
1890 return_arr.push(Artifact::Path(new_path));
1891 if let Some(inner_path_id) = path.inner_path_id
1892 && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
1893 && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
1894 {
1895 inner_path_artifact.sweep_id = Some(id);
1896 inner_path_artifact.consumed = true;
1897 return_arr.push(Artifact::Path(inner_path_artifact))
1898 }
1899 }
1900 return Ok(return_arr);
1901 }
1902 ModelingCmd::Sweep(kcmc::Sweep { target, trajectory, .. }) => {
1903 let method = ArtifactSweepMethod::Merge;
1905 let sub_type = SweepSubType::Sweep;
1906 let mut return_arr = Vec::new();
1907 let target = cmd_id_ref_to_artifact_id(target);
1908 let trajectory = cmd_id_ref_to_artifact_id(trajectory);
1909 return_arr.push(Artifact::Sweep(Sweep {
1910 id,
1911 sub_type,
1912 path_id: target,
1913 surface_ids: Vec::new(),
1914 edge_ids: Vec::new(),
1915 code_ref,
1916 source_sweep_id: None,
1917 trajectory_id: Some(trajectory),
1918 method,
1919 consumed: false,
1920 pattern_ids: Vec::new(),
1921 }));
1922 let path = artifacts.get(&target);
1923 if let Some(Artifact::Path(path)) = path {
1924 let mut new_path = path.clone();
1925 new_path.sweep_id = Some(id);
1926 new_path.consumed = true;
1927 return_arr.push(Artifact::Path(new_path));
1928 if let Some(inner_path_id) = path.inner_path_id
1929 && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
1930 && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
1931 {
1932 inner_path_artifact.sweep_id = Some(id);
1933 inner_path_artifact.consumed = true;
1934 return_arr.push(Artifact::Path(inner_path_artifact))
1935 }
1936 }
1937 if let Some(trajectory_artifact) = artifacts.get(&trajectory) {
1938 match trajectory_artifact {
1939 Artifact::Path(path) => {
1940 let mut new_path = path.clone();
1941 new_path.trajectory_sweep_id = Some(id);
1942 new_path.consumed = true;
1943 return_arr.push(Artifact::Path(new_path));
1944 }
1945 Artifact::Helix(helix) => {
1946 let mut new_helix = helix.clone();
1947 new_helix.trajectory_sweep_id = Some(id);
1948 new_helix.consumed = true;
1949 return_arr.push(Artifact::Helix(new_helix));
1950 }
1951 _ => {}
1952 }
1953 };
1954 return Ok(return_arr);
1955 }
1956 ModelingCmd::SurfaceBlend(surface_blend_cmd) => {
1957 let surface_id_to_path_id = |surface_id: ArtifactId| -> Option<ArtifactId> {
1958 match artifacts.get(&surface_id) {
1959 Some(Artifact::Path(path)) => Some(path.id),
1960 Some(Artifact::Segment(segment)) => Some(segment.path_id),
1961 Some(Artifact::Sweep(sweep)) => Some(sweep.path_id),
1962 Some(Artifact::Wall(wall)) => artifacts.get(&wall.sweep_id).and_then(|artifact| match artifact {
1963 Artifact::Sweep(sweep) => Some(sweep.path_id),
1964 _ => None,
1965 }),
1966 Some(Artifact::Cap(cap)) => artifacts.get(&cap.sweep_id).and_then(|artifact| match artifact {
1967 Artifact::Sweep(sweep) => Some(sweep.path_id),
1968 _ => None,
1969 }),
1970 _ => None,
1971 }
1972 };
1973 let Some(first_surface_ref) = surface_blend_cmd.surfaces.first() else {
1974 internal_error!(range, "SurfaceBlend command has no surfaces: id={id:?}, cmd={cmd:?}");
1975 };
1976 let first_surface_id = ArtifactId::new(first_surface_ref.object_id);
1977 let path_id = surface_id_to_path_id(first_surface_id).unwrap_or(first_surface_id);
1978 let trajectory_id = surface_blend_cmd
1979 .surfaces
1980 .get(1)
1981 .map(|surface| ArtifactId::new(surface.object_id))
1982 .and_then(surface_id_to_path_id);
1983 let return_arr = vec![Artifact::Sweep(Sweep {
1984 id,
1985 sub_type: SweepSubType::Blend,
1986 path_id,
1987 surface_ids: Vec::new(),
1988 edge_ids: Vec::new(),
1989 code_ref,
1990 source_sweep_id: None,
1991 trajectory_id,
1992 method: ArtifactSweepMethod::New,
1993 consumed: false,
1994 pattern_ids: Vec::new(),
1995 })];
1996 return Ok(return_arr);
1997 }
1998 ModelingCmd::Loft(loft_cmd) => {
1999 let Some(OkModelingCmdResponse::Loft(_)) = response else {
2000 return Ok(Vec::new());
2001 };
2002 let mut return_arr = Vec::new();
2003 return_arr.push(Artifact::Sweep(Sweep {
2004 id,
2005 sub_type: SweepSubType::Loft,
2006 path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
2009 KclError::new_internal(KclErrorDetails::new(
2010 format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
2011 vec![range],
2012 ))
2013 })?),
2014 surface_ids: Vec::new(),
2015 edge_ids: Vec::new(),
2016 code_ref,
2017 source_sweep_id: None,
2018 trajectory_id: None,
2019 method: ArtifactSweepMethod::Merge,
2020 consumed: false,
2021 pattern_ids: Vec::new(),
2022 }));
2023 for section_id in &loft_cmd.section_ids {
2024 let path = artifacts.get(&ArtifactId::new(*section_id));
2025 if let Some(Artifact::Path(path)) = path {
2026 let mut new_path = path.clone();
2027 new_path.consumed = true;
2028 new_path.sweep_id = Some(id);
2029 return_arr.push(Artifact::Path(new_path));
2030 }
2031 }
2032 return Ok(return_arr);
2033 }
2034 ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
2035 let Some(OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info)) = response else {
2036 return Ok(Vec::new());
2037 };
2038 let mut return_arr = Vec::new();
2039 let mut last_path = None;
2040 for face in &face_info.faces {
2041 if face.cap != ExtrusionFaceCapType::None {
2042 continue;
2043 }
2044 let Some(curve_id) = face.curve_id.map(ArtifactId::new) else {
2045 continue;
2046 };
2047 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
2048 continue;
2049 };
2050 let Some(Artifact::Segment(seg)) = artifacts.get(&curve_id) else {
2051 continue;
2052 };
2053 let Some(Artifact::Path(path)) = artifacts.get(&seg.path_id) else {
2054 continue;
2055 };
2056 last_path = Some(path);
2057 let Some(path_sweep_id) = path.sweep_id else {
2058 if path.outer_path_id.is_some() {
2061 continue; }
2063 return Err(KclError::new_internal(KclErrorDetails::new(
2064 format!(
2065 "Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
2066 ),
2067 vec![range],
2068 )));
2069 };
2070 let extra_artifact = exec_artifacts.values().find(|a| {
2071 if let Artifact::StartSketchOnFace(s) = a {
2072 s.face_id == face_id
2073 } else if let Artifact::StartSketchOnPlane(s) = a {
2074 s.plane_id == face_id
2075 } else {
2076 false
2077 }
2078 });
2079 let sketch_on_face_code_ref = extra_artifact
2080 .and_then(|a| match a {
2081 Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
2082 Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
2083 _ => None,
2084 })
2085 .unwrap_or_default();
2087
2088 return_arr.push(Artifact::Wall(Wall {
2089 id: face_id,
2090 seg_id: curve_id,
2091 edge_cut_edge_ids: Vec::new(),
2092 sweep_id: path_sweep_id,
2093 path_ids: Vec::new(),
2094 face_code_ref: sketch_on_face_code_ref,
2095 cmd_id: artifact_command.cmd_id,
2096 }));
2097 let mut new_seg = seg.clone();
2098 new_seg.surface_id = Some(face_id);
2099 return_arr.push(Artifact::Segment(new_seg));
2100 if let Some(Artifact::Sweep(sweep)) = path.sweep_id.and_then(|id| artifacts.get(&id)) {
2101 let mut new_sweep = sweep.clone();
2102 new_sweep.surface_ids = vec![face_id];
2103 return_arr.push(Artifact::Sweep(new_sweep));
2104 }
2105 }
2106 if let Some(path) = last_path {
2107 for face in &face_info.faces {
2108 let sub_type = match face.cap {
2109 ExtrusionFaceCapType::Top => CapSubType::End,
2110 ExtrusionFaceCapType::Bottom => CapSubType::Start,
2111 ExtrusionFaceCapType::None | ExtrusionFaceCapType::Both => continue,
2112 _other => {
2113 continue;
2115 }
2116 };
2117 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
2118 continue;
2119 };
2120 let Some(path_sweep_id) = path.sweep_id else {
2121 if path.outer_path_id.is_some() {
2124 continue; }
2126 return Err(KclError::new_internal(KclErrorDetails::new(
2127 format!(
2128 "Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
2129 ),
2130 vec![range],
2131 )));
2132 };
2133 let extra_artifact = exec_artifacts.values().find(|a| {
2134 if let Artifact::StartSketchOnFace(s) = a {
2135 s.face_id == face_id
2136 } else if let Artifact::StartSketchOnPlane(s) = a {
2137 s.plane_id == face_id
2138 } else {
2139 false
2140 }
2141 });
2142 let sketch_on_face_code_ref = extra_artifact
2143 .and_then(|a| match a {
2144 Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
2145 Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
2146 _ => None,
2147 })
2148 .unwrap_or_default();
2150 return_arr.push(Artifact::Cap(Cap {
2151 id: face_id,
2152 sub_type,
2153 edge_cut_edge_ids: Vec::new(),
2154 sweep_id: path_sweep_id,
2155 path_ids: Vec::new(),
2156 face_code_ref: sketch_on_face_code_ref,
2157 cmd_id: artifact_command.cmd_id,
2158 }));
2159 let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
2160 continue;
2161 };
2162 let mut new_sweep = sweep.clone();
2163 new_sweep.surface_ids = vec![face_id];
2164 return_arr.push(Artifact::Sweep(new_sweep));
2165 }
2166 }
2167 return Ok(return_arr);
2168 }
2169 ModelingCmd::Solid3dGetAdjacencyInfo(kcmc::Solid3dGetAdjacencyInfo { .. }) => {
2170 let Some(OkModelingCmdResponse::Solid3dGetAdjacencyInfo(info)) = response else {
2171 return Ok(Vec::new());
2172 };
2173
2174 let mut return_arr = Vec::new();
2175 let adjacent_edge_ids = info
2176 .edges
2177 .iter()
2178 .filter_map(|edge| edge.adjacent_info.as_ref().map(|info| info.edge_id))
2179 .collect::<AHashSet<_>>();
2180 for (index, edge) in info.edges.iter().enumerate() {
2181 let Some(original_info) = &edge.original_info else {
2182 continue;
2183 };
2184 let edge_id = ArtifactId::new(original_info.edge_id);
2185 let Some(artifact) = artifacts.get(&edge_id) else {
2186 continue;
2187 };
2188 match artifact {
2189 Artifact::Segment(segment) => {
2190 let mut new_segment = segment.clone();
2191 new_segment.common_surface_ids =
2192 original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
2193 return_arr.push(Artifact::Segment(new_segment));
2194 }
2195 Artifact::SweepEdge(sweep_edge) => {
2196 let mut new_sweep_edge = sweep_edge.clone();
2197 new_sweep_edge.common_surface_ids =
2198 original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
2199 return_arr.push(Artifact::SweepEdge(new_sweep_edge));
2200 }
2201 _ => {}
2202 };
2203
2204 let Some(Artifact::Segment(segment)) = artifacts.get(&edge_id) else {
2205 continue;
2206 };
2207 let Some(surface_id) = segment.surface_id else {
2208 continue;
2209 };
2210 let Some(Artifact::Wall(wall)) = artifacts.get(&surface_id) else {
2211 continue;
2212 };
2213 let Some(Artifact::Sweep(sweep)) = artifacts.get(&wall.sweep_id) else {
2214 continue;
2215 };
2216 let Some(Artifact::Path(_)) = artifacts.get(&sweep.path_id) else {
2217 continue;
2218 };
2219
2220 if let Some(opposite_info) = &edge.opposite_info {
2221 return_arr.push(Artifact::SweepEdge(SweepEdge {
2222 id: opposite_info.edge_id.into(),
2223 sub_type: SweepEdgeSubType::Opposite,
2224 seg_id: edge_id,
2225 cmd_id: artifact_command.cmd_id,
2226 index,
2227 sweep_id: sweep.id,
2228 common_surface_ids: opposite_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
2229 }));
2230 let mut new_segment = segment.clone();
2231 new_segment.edge_ids = vec![opposite_info.edge_id.into()];
2232 return_arr.push(Artifact::Segment(new_segment));
2233 let mut new_sweep = sweep.clone();
2234 new_sweep.edge_ids = vec![opposite_info.edge_id.into()];
2235 return_arr.push(Artifact::Sweep(new_sweep));
2236 let mut new_wall = wall.clone();
2237 new_wall.edge_cut_edge_ids = vec![opposite_info.edge_id.into()];
2238 return_arr.push(Artifact::Wall(new_wall));
2239 }
2240 if let Some(adjacent_info) = &edge.adjacent_info {
2241 return_arr.push(Artifact::SweepEdge(SweepEdge {
2242 id: adjacent_info.edge_id.into(),
2243 sub_type: SweepEdgeSubType::Adjacent,
2244 seg_id: edge_id,
2245 cmd_id: artifact_command.cmd_id,
2246 index,
2247 sweep_id: sweep.id,
2248 common_surface_ids: adjacent_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
2249 }));
2250 let mut new_segment = segment.clone();
2251 new_segment.edge_ids = vec![adjacent_info.edge_id.into()];
2252 return_arr.push(Artifact::Segment(new_segment));
2253 let mut new_sweep = sweep.clone();
2254 new_sweep.edge_ids = vec![adjacent_info.edge_id.into()];
2255 return_arr.push(Artifact::Sweep(new_sweep));
2256 let mut new_wall = wall.clone();
2257 new_wall.edge_cut_edge_ids = vec![adjacent_info.edge_id.into()];
2258 return_arr.push(Artifact::Wall(new_wall));
2259 }
2260 if let Some(previous_adjacent_info) = &edge.previous_adjacent_info
2263 && !adjacent_edge_ids.contains(&previous_adjacent_info.edge_id)
2264 {
2265 return_arr.push(Artifact::SweepEdge(SweepEdge {
2266 id: previous_adjacent_info.edge_id.into(),
2267 sub_type: SweepEdgeSubType::PreviousAdjacent,
2268 seg_id: edge_id,
2269 cmd_id: artifact_command.cmd_id,
2270 index,
2271 sweep_id: sweep.id,
2272 common_surface_ids: previous_adjacent_info
2273 .faces
2274 .iter()
2275 .map(|face| ArtifactId::new(*face))
2276 .collect(),
2277 }));
2278 let mut new_segment = segment.clone();
2279 new_segment.edge_ids = vec![previous_adjacent_info.edge_id.into()];
2280 return_arr.push(Artifact::Segment(new_segment));
2281 let mut new_sweep = sweep.clone();
2282 new_sweep.edge_ids = vec![previous_adjacent_info.edge_id.into()];
2283 return_arr.push(Artifact::Sweep(new_sweep));
2284 let mut new_wall = wall.clone();
2285 new_wall.edge_cut_edge_ids = vec![previous_adjacent_info.edge_id.into()];
2286 return_arr.push(Artifact::Wall(new_wall));
2287 }
2288 }
2289 return Ok(return_arr);
2290 }
2291 ModelingCmd::Solid3dMultiJoin(cmd) => {
2292 let mut return_arr = Vec::new();
2293 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2294 id,
2295 consumed: false,
2296 sub_type: CompositeSolidSubType::Union,
2297 output_index: None,
2298 solid_ids: cmd.object_ids.iter().map(|id| id.into()).collect(),
2299 tool_ids: vec![],
2300 code_ref,
2301 composite_solid_id: None,
2302 pattern_ids: Vec::new(),
2303 }));
2304
2305 let solid_ids = cmd.object_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2306
2307 for input_id in &solid_ids {
2308 if let Some(artifact) = artifacts.get(input_id)
2309 && let Artifact::CompositeSolid(comp) = artifact
2310 {
2311 let mut new_comp = comp.clone();
2312 new_comp.composite_solid_id = Some(id);
2313 new_comp.consumed = true;
2314 return_arr.push(Artifact::CompositeSolid(new_comp));
2315 } else if let Some(Artifact::Sweep(sweep)) = artifacts.get(input_id) {
2316 let mut new_sweep = sweep.clone();
2317 new_sweep.consumed = true;
2318 return_arr.push(Artifact::Sweep(new_sweep));
2319 }
2320 }
2321 return Ok(return_arr);
2322 }
2323 ModelingCmd::Solid3dFilletEdge(cmd) => {
2324 let mut return_arr = Vec::new();
2325 let edge_id = if let Some(edge_id) = cmd.edge_id {
2326 ArtifactId::new(edge_id)
2327 } else {
2328 let Some(edge_id) = cmd.edge_ids.first() else {
2329 internal_error!(
2330 range,
2331 "Solid3dFilletEdge command has no edge ID: id={id:?}, cmd={cmd:?}"
2332 );
2333 };
2334 edge_id.into()
2335 };
2336 return_arr.push(Artifact::EdgeCut(EdgeCut {
2337 id,
2338 sub_type: edge_cut_sub_type(cmd.cut_type),
2339 consumed_edge_id: edge_id,
2340 edge_ids: Vec::new(),
2341 surface_id: None,
2342 code_ref,
2343 }));
2344 let consumed_edge = artifacts.get(&edge_id);
2345 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2346 let mut new_segment = consumed_edge.clone();
2347 new_segment.edge_cut_id = Some(id);
2348 return_arr.push(Artifact::Segment(new_segment));
2349 } else {
2350 }
2352 return Ok(return_arr);
2353 }
2354 ModelingCmd::Solid3dCutEdges(cmd) => {
2355 let mut return_arr = Vec::new();
2356 let edge_id = if let Some(edge_id) = cmd.edge_ids.first() {
2357 edge_id.into()
2358 } else {
2359 internal_error!(range, "Solid3dCutEdges command has no edge ID: id={id:?}, cmd={cmd:?}");
2360 };
2361 return_arr.push(Artifact::EdgeCut(EdgeCut {
2362 id,
2363 sub_type: edge_cut_sub_type_v2(cmd.cut_type),
2364 consumed_edge_id: edge_id,
2365 edge_ids: Vec::new(),
2366 surface_id: None,
2367 code_ref,
2368 }));
2369 let consumed_edge = artifacts.get(&edge_id);
2370 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2371 let mut new_segment = consumed_edge.clone();
2372 new_segment.edge_cut_id = Some(id);
2373 return_arr.push(Artifact::Segment(new_segment));
2374 } else {
2375 }
2377 return Ok(return_arr);
2378 }
2379 ModelingCmd::EntityMakeHelix(cmd) => {
2380 let cylinder_id = ArtifactId::new(cmd.cylinder_id);
2381 let return_arr = vec![Artifact::Helix(Helix {
2382 id,
2383 axis_id: Some(cylinder_id),
2384 code_ref,
2385 trajectory_sweep_id: None,
2386 consumed: false,
2387 })];
2388 return Ok(return_arr);
2389 }
2390 ModelingCmd::EntityMakeHelixFromParams(_) => {
2391 let return_arr = vec![Artifact::Helix(Helix {
2392 id,
2393 axis_id: None,
2394 code_ref,
2395 trajectory_sweep_id: None,
2396 consumed: false,
2397 })];
2398 return Ok(return_arr);
2399 }
2400 ModelingCmd::EntityMakeHelixFromEdge(helix) => {
2401 let return_arr = vec![Artifact::Helix(Helix {
2402 id,
2403 axis_id: helix.edge_id.map(ArtifactId::new),
2404 code_ref,
2405 trajectory_sweep_id: None,
2406 consumed: false,
2407 })];
2408 return Ok(return_arr);
2411 }
2412 ModelingCmd::Solid2dAddHole(solid2d_add_hole) => {
2413 let mut return_arr = Vec::new();
2414 let outer_path = artifacts.get(&ArtifactId::new(solid2d_add_hole.object_id));
2416 if let Some(Artifact::Path(path)) = outer_path {
2417 let mut new_path = path.clone();
2418 new_path.inner_path_id = Some(ArtifactId::new(solid2d_add_hole.hole_id));
2419 return_arr.push(Artifact::Path(new_path));
2420 }
2421 let inner_solid2d = artifacts.get(&ArtifactId::new(solid2d_add_hole.hole_id));
2423 if let Some(Artifact::Path(path)) = inner_solid2d {
2424 let mut new_path = path.clone();
2425 new_path.consumed = true;
2426 new_path.outer_path_id = Some(ArtifactId::new(solid2d_add_hole.object_id));
2427 return_arr.push(Artifact::Path(new_path));
2428 }
2429 return Ok(return_arr);
2430 }
2431 ModelingCmd::BooleanIntersection(_) | ModelingCmd::BooleanSubtract(_) | ModelingCmd::BooleanUnion(_) => {
2432 let (sub_type, solid_ids, tool_ids) = match cmd {
2433 ModelingCmd::BooleanIntersection(intersection) => {
2434 let solid_ids = intersection
2435 .solid_ids
2436 .iter()
2437 .copied()
2438 .map(ArtifactId::new)
2439 .collect::<Vec<_>>();
2440 (CompositeSolidSubType::Intersect, solid_ids, Vec::new())
2441 }
2442 ModelingCmd::BooleanSubtract(subtract) => {
2443 let solid_ids = subtract
2444 .target_ids
2445 .iter()
2446 .copied()
2447 .map(ArtifactId::new)
2448 .collect::<Vec<_>>();
2449 let tool_ids = subtract
2450 .tool_ids
2451 .iter()
2452 .copied()
2453 .map(ArtifactId::new)
2454 .collect::<Vec<_>>();
2455 (CompositeSolidSubType::Subtract, solid_ids, tool_ids)
2456 }
2457 ModelingCmd::BooleanUnion(union) => {
2458 let solid_ids = union.solid_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2459 (CompositeSolidSubType::Union, solid_ids, Vec::new())
2460 }
2461 _ => internal_error!(
2462 range,
2463 "Boolean or composite command variant not handled: id={id:?}, cmd={cmd:?}"
2464 ),
2465 };
2466
2467 let mut new_solid_ids = vec![id];
2468
2469 let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2472
2473 match (cmd, response) {
2474 (
2475 ModelingCmd::BooleanSubtract(subtract_cmd),
2476 Some(OkModelingCmdResponse::BooleanSubtract(subtract_resp)),
2477 ) => {
2478 new_solid_ids = boolean_subtract_output_artifact_ids(
2479 id,
2480 &subtract_cmd.target_ids,
2481 &subtract_cmd.tool_ids,
2482 &subtract_resp.extra_solid_ids,
2483 );
2484 }
2485 (_, Some(OkModelingCmdResponse::BooleanIntersection(intersection))) => intersection
2486 .extra_solid_ids
2487 .iter()
2488 .copied()
2489 .map(ArtifactId::new)
2490 .filter(not_cmd_id)
2491 .for_each(|id| new_solid_ids.push(id)),
2492 (_, Some(OkModelingCmdResponse::BooleanUnion(union))) => union
2493 .extra_solid_ids
2494 .iter()
2495 .copied()
2496 .map(ArtifactId::new)
2497 .filter(not_cmd_id)
2498 .for_each(|id| new_solid_ids.push(id)),
2499 _ => {}
2500 }
2501
2502 let mut return_arr = Vec::new();
2503 let mut consumed_sweep_ids = AHashSet::default();
2504 let mut input_ids = solid_ids.clone();
2505 merge_ids(&mut input_ids, tool_ids.clone());
2506
2507 if new_solid_ids.is_empty() {
2508 update_csg_input_artifacts(&mut return_arr, artifacts, &input_ids, None, &mut consumed_sweep_ids);
2509 }
2510
2511 for solid_id in &new_solid_ids {
2513 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2515 id: *solid_id,
2516 consumed: false,
2517 sub_type,
2518 output_index: None,
2519 solid_ids: solid_ids.clone(),
2520 tool_ids: tool_ids.clone(),
2521 code_ref: code_ref.clone(),
2522 composite_solid_id: None,
2523 pattern_ids: Vec::new(),
2524 }));
2525
2526 update_csg_input_artifacts(
2527 &mut return_arr,
2528 artifacts,
2529 &input_ids,
2530 Some(*solid_id),
2531 &mut consumed_sweep_ids,
2532 );
2533 }
2534
2535 return Ok(return_arr);
2536 }
2537 ModelingCmd::BooleanImprint(imprint) => {
2538 let solid_ids = imprint
2539 .body_ids
2540 .iter()
2541 .copied()
2542 .map(ArtifactId::new)
2543 .collect::<Vec<_>>();
2544 let tool_ids = imprint
2545 .tool_ids
2546 .as_ref()
2547 .map(|ids| ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>())
2548 .unwrap_or_default();
2549
2550 let mut new_solid_ids = vec![id];
2551 let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2552 if let Some(OkModelingCmdResponse::BooleanImprint(imprint)) = response {
2553 imprint
2554 .extra_solid_ids
2555 .iter()
2556 .copied()
2557 .map(ArtifactId::new)
2558 .filter(not_cmd_id)
2559 .for_each(|id| new_solid_ids.push(id));
2560 }
2561
2562 let mut return_arr = Vec::new();
2563 let mut consumed_sweep_ids = AHashSet::default();
2564
2565 for input_id in solid_ids.iter().chain(tool_ids.iter()) {
2566 let sweep_id = match artifacts.get(input_id) {
2567 Some(Artifact::Sweep(sweep)) => Some(sweep.id),
2568 Some(Artifact::Path(path)) => path.sweep_id,
2569 _ => None,
2570 };
2571
2572 if let Some(sweep_id) = sweep_id
2573 && consumed_sweep_ids.insert(sweep_id)
2574 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
2575 {
2576 let mut new_sweep = sweep.clone();
2577 new_sweep.consumed = true;
2578 return_arr.push(Artifact::Sweep(new_sweep));
2579 }
2580 }
2581
2582 for (output_index, solid_id) in new_solid_ids.iter().enumerate() {
2583 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2584 id: *solid_id,
2585 consumed: false,
2586 sub_type: CompositeSolidSubType::Split,
2587 output_index: Some(output_index),
2588 solid_ids: solid_ids.clone(),
2589 tool_ids: tool_ids.clone(),
2590 code_ref: code_ref.clone(),
2591 composite_solid_id: None,
2592 pattern_ids: Vec::new(),
2593 }));
2594
2595 for input_id in solid_ids.iter().chain(tool_ids.iter()) {
2596 if let Some(artifact) = artifacts.get(input_id) {
2597 match artifact {
2598 Artifact::CompositeSolid(comp) => {
2599 let mut new_comp = comp.clone();
2600 new_comp.composite_solid_id = Some(*solid_id);
2601 new_comp.consumed = true;
2602 return_arr.push(Artifact::CompositeSolid(new_comp));
2603 }
2604 Artifact::Path(path) => {
2605 let mut new_path = path.clone();
2606 new_path.composite_solid_id = Some(*solid_id);
2607
2608 return_arr.push(Artifact::Path(new_path));
2609 }
2610 _ => {}
2611 }
2612 }
2613 }
2614 }
2615
2616 return Ok(return_arr);
2617 }
2618 _ => {}
2619 }
2620
2621 Ok(Vec::new())
2622}