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