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