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 = sweep
794 .path_id
795 .is_some_and(|path_id| entity_id_map.contains_key(&path_id))
796 || sweep.surface_ids.iter().any(|id| entity_id_map.contains_key(id))
797 || sweep.edge_ids.iter().any(|id| entity_id_map.contains_key(id));
798 has_mapped_topology.then_some(sweep.id)
799 })
800 .collect::<Vec<_>>();
801
802 for source_sweep_id in source_sweep_ids {
803 let source_uuid = Uuid::from(source_sweep_id);
804 let cloned_sweep_id = ArtifactId::new(Uuid::new_v5(&clone_cmd_id, source_uuid.as_bytes()));
805 entity_id_map.insert(source_sweep_id, cloned_sweep_id);
806 }
807}
808
809fn remap_artifact_for_clone(
810 artifact: &Artifact,
811 entity_id_map: &AHashMap<ArtifactId, ArtifactId>,
812 clone_code_ref: &CodeRef,
813 clone_cmd_id: Uuid,
814 source_root_id: ArtifactId,
815) -> Artifact {
816 match artifact {
817 Artifact::CompositeSolid(source) => Artifact::CompositeSolid(CompositeSolid {
818 id: remap_id_for_clone(source.id, entity_id_map),
819 consumed: if source.id == source_root_id {
820 false
821 } else {
822 source.consumed
823 },
824 sub_type: source.sub_type,
825 output_index: if source.id == source_root_id {
828 None
829 } else {
830 source.output_index
831 },
832 solid_ids: remap_ids_for_clone(&source.solid_ids, entity_id_map),
833 tool_ids: remap_ids_for_clone(&source.tool_ids, entity_id_map),
834 pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
835 code_ref: clone_code_ref.clone(),
836 composite_solid_id: if source.id == source_root_id {
837 None
838 } else {
839 remap_opt_id_for_clone(source.composite_solid_id, entity_id_map)
840 },
841 }),
842 Artifact::Plane(source) => Artifact::Plane(Plane {
843 id: remap_id_for_clone(source.id, entity_id_map),
844 path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
845 code_ref: clone_code_ref.clone(),
846 }),
847 Artifact::Path(source) => Artifact::Path(Path {
848 id: remap_id_for_clone(source.id, entity_id_map),
849 sub_type: source.sub_type,
850 plane_id: remap_id_for_clone(source.plane_id, entity_id_map),
851 seg_ids: remap_ids_for_clone(&source.seg_ids, entity_id_map),
852 consumed: if source.id == source_root_id {
853 false
854 } else {
855 source.consumed
856 },
857 sweep_id: remap_opt_id_for_clone(source.sweep_id, entity_id_map),
858 trajectory_sweep_id: remap_opt_id_for_clone(source.trajectory_sweep_id, entity_id_map),
859 solid2d_id: remap_opt_id_for_clone(source.solid2d_id, entity_id_map),
860 code_ref: clone_code_ref.clone(),
861 composite_solid_id: remap_opt_id_for_clone(source.composite_solid_id, entity_id_map),
862 sketch_block_id: remap_opt_id_for_clone(source.sketch_block_id, entity_id_map),
863 origin_path_id: remap_opt_id_for_clone(source.origin_path_id, entity_id_map),
864 inner_path_id: remap_opt_id_for_clone(source.inner_path_id, entity_id_map),
865 outer_path_id: remap_opt_id_for_clone(source.outer_path_id, entity_id_map),
866 pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
867 }),
868 Artifact::Segment(source) => Artifact::Segment(Segment {
869 id: remap_id_for_clone(source.id, entity_id_map),
870 path_id: remap_id_for_clone(source.path_id, entity_id_map),
871 source_segment_id: source.source_segment_id.or(Some(source.id)),
872 original_seg_id: remap_opt_id_for_clone(source.original_seg_id, entity_id_map),
873 surface_id: remap_opt_id_for_clone(source.surface_id, entity_id_map),
874 edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
875 edge_cut_id: remap_opt_id_for_clone(source.edge_cut_id, entity_id_map),
876 code_ref: clone_code_ref.clone(),
877 common_surface_ids: remap_ids_for_clone(&source.common_surface_ids, entity_id_map),
878 }),
879 Artifact::Solid2d(source) => Artifact::Solid2d(Solid2d {
880 id: remap_id_for_clone(source.id, entity_id_map),
881 path_id: remap_id_for_clone(source.path_id, entity_id_map),
882 }),
883 Artifact::PrimitiveFace(source) => Artifact::PrimitiveFace(PrimitiveFace {
884 id: remap_id_for_clone(source.id, entity_id_map),
885 solid_id: remap_id_for_clone(source.solid_id, entity_id_map),
886 code_ref: clone_code_ref.clone(),
887 }),
888 Artifact::PrimitiveEdge(source) => Artifact::PrimitiveEdge(PrimitiveEdge {
889 id: remap_id_for_clone(source.id, entity_id_map),
890 solid_id: remap_id_for_clone(source.solid_id, entity_id_map),
891 code_ref: clone_code_ref.clone(),
892 }),
893 Artifact::PlaneOfFace(source) => Artifact::PlaneOfFace(PlaneOfFace {
894 id: remap_id_for_clone(source.id, entity_id_map),
895 face_id: remap_id_for_clone(source.face_id, entity_id_map),
896 code_ref: clone_code_ref.clone(),
897 }),
898 Artifact::StartSketchOnFace(source) => Artifact::StartSketchOnFace(StartSketchOnFace {
899 id: remap_id_for_clone(source.id, entity_id_map),
900 face_id: remap_id_for_clone(source.face_id, entity_id_map),
901 code_ref: clone_code_ref.clone(),
902 }),
903 Artifact::StartSketchOnPlane(source) => Artifact::StartSketchOnPlane(StartSketchOnPlane {
904 id: remap_id_for_clone(source.id, entity_id_map),
905 plane_id: remap_id_for_clone(source.plane_id, entity_id_map),
906 code_ref: clone_code_ref.clone(),
907 }),
908 Artifact::SketchBlock(source) => Artifact::SketchBlock(SketchBlock {
909 id: remap_id_for_clone(source.id, entity_id_map),
910 standard_plane: source.standard_plane,
911 plane_id: remap_opt_id_for_clone(source.plane_id, entity_id_map),
912 plane_info: source.plane_info.clone(),
913 path_id: remap_opt_id_for_clone(source.path_id, entity_id_map),
914 code_ref: clone_code_ref.clone(),
915 sketch_id: source.sketch_id,
916 }),
917 Artifact::SketchBlockConstraint(source) => Artifact::SketchBlockConstraint(SketchBlockConstraint {
918 id: remap_id_for_clone(source.id, entity_id_map),
919 sketch_id: source.sketch_id,
920 constraint_id: source.constraint_id,
921 constraint_type: source.constraint_type,
922 code_ref: clone_code_ref.clone(),
923 }),
924 Artifact::Sweep(source) => Artifact::Sweep(Sweep {
925 id: remap_id_for_clone(source.id, entity_id_map),
926 sub_type: source.sub_type,
927 path_id: remap_opt_id_for_clone(source.path_id, entity_id_map),
928 surface_ids: remap_ids_for_clone(&source.surface_ids, entity_id_map),
929 edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
930 code_ref: clone_code_ref.clone(),
931 source_sweep_id: source.source_sweep_id.or(Some(source.id)),
932 trajectory_id: remap_opt_id_for_clone(source.trajectory_id, entity_id_map),
933 method: source.method,
934 consumed: if source.id == source_root_id {
935 false
936 } else {
937 source.consumed
938 },
939 pattern_ids: remap_mapped_ids_for_clone(&source.pattern_ids, entity_id_map),
940 }),
941 Artifact::Wall(source) => Artifact::Wall(Wall {
942 id: remap_id_for_clone(source.id, entity_id_map),
943 seg_id: remap_id_for_clone(source.seg_id, entity_id_map),
944 edge_cut_edge_ids: remap_ids_for_clone(&source.edge_cut_edge_ids, entity_id_map),
945 sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
946 path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
947 face_code_ref: source.face_code_ref.clone(),
948 cmd_id: clone_cmd_id,
949 }),
950 Artifact::Cap(source) => Artifact::Cap(Cap {
951 id: remap_id_for_clone(source.id, entity_id_map),
952 sub_type: source.sub_type,
953 edge_cut_edge_ids: remap_ids_for_clone(&source.edge_cut_edge_ids, entity_id_map),
954 sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
955 path_ids: remap_ids_for_clone(&source.path_ids, entity_id_map),
956 face_code_ref: source.face_code_ref.clone(),
957 cmd_id: clone_cmd_id,
958 }),
959 Artifact::SweepEdge(source) => Artifact::SweepEdge(SweepEdge {
960 id: remap_id_for_clone(source.id, entity_id_map),
961 sub_type: source.sub_type,
962 seg_id: remap_id_for_clone(source.seg_id, entity_id_map),
963 cmd_id: clone_cmd_id,
964 index: source.index,
965 sweep_id: remap_id_for_clone(source.sweep_id, entity_id_map),
966 common_surface_ids: remap_ids_for_clone(&source.common_surface_ids, entity_id_map),
967 }),
968 Artifact::EdgeCut(source) => Artifact::EdgeCut(EdgeCut {
969 id: remap_id_for_clone(source.id, entity_id_map),
970 sub_type: source.sub_type,
971 source_selector_index: source.source_selector_index,
972 consumed_edge_id: remap_opt_id_for_clone(source.consumed_edge_id, entity_id_map),
973 edge_ids: remap_ids_for_clone(&source.edge_ids, entity_id_map),
974 surface_id: remap_opt_id_for_clone(source.surface_id, entity_id_map),
975 code_ref: clone_code_ref.clone(),
976 }),
977 Artifact::EdgeCutEdge(source) => Artifact::EdgeCutEdge(EdgeCutEdge {
978 id: remap_id_for_clone(source.id, entity_id_map),
979 edge_cut_id: remap_id_for_clone(source.edge_cut_id, entity_id_map),
980 surface_id: remap_id_for_clone(source.surface_id, entity_id_map),
981 }),
982 Artifact::Helix(source) => Artifact::Helix(Helix {
983 id: remap_id_for_clone(source.id, entity_id_map),
984 axis_id: remap_opt_id_for_clone(source.axis_id, entity_id_map),
985 code_ref: clone_code_ref.clone(),
986 trajectory_sweep_id: remap_opt_id_for_clone(source.trajectory_sweep_id, entity_id_map),
987 consumed: if source.id == source_root_id {
988 false
989 } else {
990 source.consumed
991 },
992 }),
993 Artifact::ImportedGeometry(source) => Artifact::ImportedGeometry(ImportedGeometryArtifact {
994 id: remap_id_for_clone(source.id, entity_id_map),
995 code_ref: clone_code_ref.clone(),
996 consumed: if source.id == source_root_id {
997 false
998 } else {
999 source.consumed
1000 },
1001 }),
1002 Artifact::GdtAnnotation(source) => Artifact::GdtAnnotation(GdtAnnotationArtifact {
1003 id: remap_id_for_clone(source.id, entity_id_map),
1004 code_ref: clone_code_ref.clone(),
1005 consumed: source.consumed,
1006 }),
1007 Artifact::NamedView(_) => {
1014 debug_assert!(false, "a named view is not reachable from a cloned body");
1015 artifact.clone()
1016 }
1017 Artifact::Pattern(source) => Artifact::Pattern(Pattern {
1018 id: remap_id_for_clone(source.id, entity_id_map),
1019 sub_type: source.sub_type,
1020 source_id: remap_id_for_clone(source.source_id, entity_id_map),
1021 copy_ids: remap_ids_for_clone(&source.copy_ids, entity_id_map),
1022 copy_face_ids: remap_ids_for_clone(&source.copy_face_ids, entity_id_map),
1023 copy_edge_ids: remap_ids_for_clone(&source.copy_edge_ids, entity_id_map),
1024 code_ref: clone_code_ref.clone(),
1025 }),
1026 }
1027}
1028
1029fn pattern_source_ids(artifacts: &IndexMap<ArtifactId, Artifact>, source_id: ArtifactId) -> Vec<ArtifactId> {
1030 let mut source_ids = vec![source_id];
1031
1032 if let Some(Artifact::Path(path)) = artifacts.get(&source_id) {
1033 if let Some(sweep_id) = path.sweep_id {
1034 source_ids.push(sweep_id);
1035 }
1036 if let Some(composite_solid_id) = path.composite_solid_id {
1037 source_ids.push(composite_solid_id);
1038 }
1039 }
1040
1041 for artifact in artifacts.values() {
1042 match artifact {
1043 Artifact::Sweep(sweep) if sweep.path_id == Some(source_id) => source_ids.push(sweep.id),
1044 Artifact::CompositeSolid(composite)
1045 if composite.solid_ids.contains(&source_id) || composite.tool_ids.contains(&source_id) =>
1046 {
1047 source_ids.push(composite.id)
1048 }
1049 _ => {}
1050 }
1051 }
1052
1053 let mut unique = Vec::new();
1054 merge_ids(&mut unique, source_ids);
1055 unique
1056}
1057
1058fn pattern_source_body_id_for_copy(
1059 artifacts: &IndexMap<ArtifactId, Artifact>,
1060 copy_id: ArtifactId,
1061) -> Option<ArtifactId> {
1062 artifacts.values().find_map(|artifact| {
1063 let Artifact::Pattern(pattern) = artifact else {
1064 return None;
1065 };
1066 if !pattern.copy_ids.contains(©_id) {
1067 return None;
1068 }
1069
1070 pattern_source_ids(artifacts, pattern.source_id).into_iter().find(|id| {
1071 matches!(
1072 artifacts.get(id),
1073 Some(Artifact::Sweep(_) | Artifact::CompositeSolid(_))
1074 )
1075 })
1076 })
1077}
1078
1079fn pattern_artifact_updates(
1080 artifacts: &IndexMap<ArtifactId, Artifact>,
1081 pattern_id: ArtifactId,
1082 sub_type: PatternSubType,
1083 source_id: ArtifactId,
1084 face_edge_infos: &[kcmc::output::FaceEdgeInfo],
1085 code_ref: CodeRef,
1086) -> Vec<Artifact> {
1087 let copy_ids = face_edge_infos
1088 .iter()
1089 .map(|info| ArtifactId::new(info.object_id))
1090 .collect::<Vec<_>>();
1091 let copy_face_ids = face_edge_infos
1092 .iter()
1093 .flat_map(|info| info.faces.iter().copied().map(ArtifactId::new))
1094 .collect::<Vec<_>>();
1095 let copy_edge_ids = face_edge_infos
1096 .iter()
1097 .flat_map(|info| info.edges.iter().copied().map(ArtifactId::new))
1098 .collect::<Vec<_>>();
1099
1100 let source_ids = pattern_source_ids(artifacts, source_id);
1101 let mut return_arr = vec![Artifact::Pattern(Pattern {
1102 id: pattern_id,
1103 sub_type,
1104 source_id,
1105 copy_ids,
1106 copy_face_ids,
1107 copy_edge_ids,
1108 code_ref,
1109 })];
1110
1111 for source_id in source_ids {
1112 let Some(artifact) = artifacts.get(&source_id) else {
1113 continue;
1114 };
1115 match artifact {
1116 Artifact::Path(path) => {
1117 let mut new_path = path.clone();
1118 new_path.pattern_ids = vec![pattern_id];
1119 return_arr.push(Artifact::Path(new_path));
1120 }
1121 Artifact::Sweep(sweep) => {
1122 let mut new_sweep = sweep.clone();
1123 new_sweep.pattern_ids = vec![pattern_id];
1124 return_arr.push(Artifact::Sweep(new_sweep));
1125 }
1126 Artifact::CompositeSolid(composite) => {
1127 let mut new_composite = composite.clone();
1128 new_composite.pattern_ids = vec![pattern_id];
1129 return_arr.push(Artifact::CompositeSolid(new_composite));
1130 }
1131 _ => {}
1132 }
1133 }
1134
1135 return_arr
1136}
1137
1138fn is_single_target_self_subtract(target_ids: &[Uuid], tool_ids: &[Uuid]) -> bool {
1139 target_ids.len() == 1 && tool_ids.len() == 1 && target_ids[0] == tool_ids[0]
1140}
1141
1142fn boolean_subtract_output_artifact_ids(
1143 cmd_id: ArtifactId,
1144 target_ids: &[Uuid],
1145 tool_ids: &[Uuid],
1146 extra_solid_ids: &[Uuid],
1147) -> Vec<ArtifactId> {
1148 if is_single_target_self_subtract(target_ids, tool_ids) {
1149 return Vec::new();
1150 }
1151
1152 let mut output_ids = if target_ids.len() == 1 {
1153 vec![cmd_id]
1154 } else {
1155 Vec::new()
1156 };
1157
1158 for extra_solid_id in extra_solid_ids {
1159 let artifact_id = ArtifactId::new(*extra_solid_id);
1160 if !output_ids.contains(&artifact_id) {
1161 output_ids.push(artifact_id);
1162 }
1163 }
1164
1165 output_ids
1166}
1167
1168fn update_consumed_csg_sweep(
1169 return_arr: &mut Vec<Artifact>,
1170 artifacts: &IndexMap<ArtifactId, Artifact>,
1171 sweep_id: ArtifactId,
1172 consumed_sweep_ids: &mut AHashSet<ArtifactId>,
1173) {
1174 if consumed_sweep_ids.insert(sweep_id)
1175 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
1176 {
1177 let mut new_sweep = sweep.clone();
1178 new_sweep.consumed = true;
1179 return_arr.push(Artifact::Sweep(new_sweep));
1180 }
1181}
1182
1183fn mark_artifact_consumed_by_id(
1184 return_arr: &mut Vec<Artifact>,
1185 artifacts: &IndexMap<ArtifactId, Artifact>,
1186 artifact_id: ArtifactId,
1187 consumed_ids: &mut AHashSet<ArtifactId>,
1188) {
1189 let already_marked_as_consumed = !consumed_ids.insert(artifact_id);
1190 if already_marked_as_consumed {
1191 return;
1192 }
1193
1194 let Some(artifact) = artifacts.get(&artifact_id) else {
1195 return;
1196 };
1197
1198 match artifact {
1199 Artifact::CompositeSolid(composite) => {
1200 let mut new_composite = composite.clone();
1201 new_composite.consumed = true;
1202 return_arr.push(Artifact::CompositeSolid(new_composite));
1203 }
1204 Artifact::Path(path) => {
1205 let mut new_path = path.clone();
1206 new_path.consumed = true;
1207 return_arr.push(Artifact::Path(new_path));
1208
1209 if let Some(sweep_id) = path.sweep_id {
1210 mark_artifact_consumed_by_id(return_arr, artifacts, sweep_id, consumed_ids);
1211 }
1212 if let Some(composite_solid_id) = path.composite_solid_id {
1213 mark_artifact_consumed_by_id(return_arr, artifacts, composite_solid_id, consumed_ids);
1214 }
1215 }
1216 Artifact::Sweep(sweep) => {
1217 let mut new_sweep = sweep.clone();
1218 new_sweep.consumed = true;
1219 return_arr.push(Artifact::Sweep(new_sweep));
1220 }
1221 Artifact::Helix(helix) => {
1222 let mut new_helix = helix.clone();
1223 new_helix.consumed = true;
1224 return_arr.push(Artifact::Helix(new_helix));
1225 }
1226 Artifact::ImportedGeometry(imported_geometry) => {
1227 let mut new_imported_geometry = imported_geometry.clone();
1228 new_imported_geometry.consumed = true;
1229 return_arr.push(Artifact::ImportedGeometry(new_imported_geometry));
1230 }
1231 Artifact::GdtAnnotation(annotation) => {
1232 let mut new_annotation = annotation.clone();
1233 new_annotation.consumed = true;
1234 return_arr.push(Artifact::GdtAnnotation(new_annotation));
1235 }
1236 _ => {}
1237 }
1238}
1239
1240fn mark_deleted_artifacts_consumed(
1241 artifacts: &IndexMap<ArtifactId, Artifact>,
1242 object_ids: &std::collections::HashSet<Uuid>,
1243) -> Vec<Artifact> {
1244 let mut return_arr = Vec::new();
1245 let mut consumed_ids = AHashSet::default();
1246
1247 #[allow(clippy::iter_over_hash_type)]
1250 for object_id in object_ids {
1251 let artifact_id = ArtifactId::new(*object_id);
1252 mark_artifact_consumed_by_id(&mut return_arr, artifacts, artifact_id, &mut consumed_ids);
1253 }
1254
1255 return_arr
1256}
1257
1258fn update_csg_input_artifacts(
1259 return_arr: &mut Vec<Artifact>,
1260 artifacts: &IndexMap<ArtifactId, Artifact>,
1261 input_ids: &[ArtifactId],
1262 composite_solid_id: Option<ArtifactId>,
1263 consumed_sweep_ids: &mut AHashSet<ArtifactId>,
1264) {
1265 for input_id in input_ids {
1266 if let Some(artifact) = artifacts.get(input_id) {
1267 match artifact {
1268 Artifact::CompositeSolid(comp) => {
1269 let mut new_comp = comp.clone();
1270 new_comp.composite_solid_id = composite_solid_id;
1271 new_comp.consumed = true;
1272 return_arr.push(Artifact::CompositeSolid(new_comp));
1273 }
1274 Artifact::Path(path) => {
1275 let mut new_path = path.clone();
1276 new_path.composite_solid_id = composite_solid_id;
1277
1278 if let Some(sweep_id) = new_path.sweep_id {
1281 update_consumed_csg_sweep(return_arr, artifacts, sweep_id, consumed_sweep_ids);
1282 }
1283
1284 return_arr.push(Artifact::Path(new_path));
1285 }
1286 Artifact::Sweep(sweep) => {
1287 update_consumed_csg_sweep(return_arr, artifacts, sweep.id, consumed_sweep_ids);
1288 }
1289 _ => {}
1290 }
1291 }
1292 }
1293}
1294
1295fn mirror_3d_artifact_updates(
1296 artifacts: &IndexMap<ArtifactId, Artifact>,
1297 original_solid_ids: &[Uuid],
1298 face_edge_infos: &[kcmc::output::FaceEdgeInfo],
1299 code_ref: CodeRef,
1300 range: SourceRange,
1301 cmd: &ModelingCmd,
1302) -> Result<Vec<Artifact>, KclError> {
1303 if original_solid_ids.len() != face_edge_infos.len() {
1304 internal_error!(
1305 range,
1306 "EntityMirrorAcross response has different number face edge info than original mirrored solids: cmd={cmd:?}, face_edge_infos={face_edge_infos:?}"
1307 );
1308 }
1309
1310 let mut return_arr = Vec::new();
1311 for (face_edge_info, original_solid_id) in face_edge_infos.iter().zip(original_solid_ids) {
1312 let original_solid_id = ArtifactId::new(*original_solid_id);
1313 let mirrored_solid_id = ArtifactId::new(face_edge_info.object_id);
1314 let source_solid = match artifacts.get(&original_solid_id) {
1315 Some(Artifact::Path(path)) => path.sweep_id.and_then(|sweep_id| artifacts.get(&sweep_id)).or_else(|| {
1316 path.composite_solid_id
1317 .and_then(|composite_id| artifacts.get(&composite_id))
1318 }),
1319 source => source,
1320 };
1321 match source_solid {
1322 Some(Artifact::Sweep(sweep)) => {
1323 let mut mirrored_sweep = sweep.clone();
1324 mirrored_sweep.id = mirrored_solid_id;
1325 mirrored_sweep.surface_ids = face_edge_info.faces.iter().copied().map(ArtifactId::new).collect();
1326 mirrored_sweep.edge_ids = face_edge_info.edges.iter().copied().map(ArtifactId::new).collect();
1327 mirrored_sweep.code_ref = code_ref.clone();
1328 mirrored_sweep.consumed = false;
1329 mirrored_sweep.pattern_ids = Vec::new();
1330 return_arr.push(Artifact::Sweep(mirrored_sweep));
1331 }
1332 Some(Artifact::CompositeSolid(composite)) => {
1333 let mut mirrored_composite = composite.clone();
1334 mirrored_composite.id = mirrored_solid_id;
1335 mirrored_composite.code_ref = code_ref.clone();
1336 mirrored_composite.consumed = false;
1337 mirrored_composite.composite_solid_id = None;
1338 mirrored_composite.pattern_ids = Vec::new();
1339 return_arr.push(Artifact::CompositeSolid(mirrored_composite));
1340 }
1341 Some(_) | None => continue,
1342 }
1343 }
1344
1345 Ok(return_arr)
1346}
1347
1348#[allow(clippy::too_many_arguments)]
1349fn artifacts_to_update(
1350 artifacts: &IndexMap<ArtifactId, Artifact>,
1351 artifact_command: &ArtifactCommand,
1352 responses: &AHashMap<Uuid, OkModelingCmdResponse>,
1353 entity_clone_id_maps: &AHashMap<Uuid, AHashMap<ArtifactId, ArtifactId>>,
1354 path_to_plane_id_map: &AHashMap<Uuid, Uuid>,
1355 programs: &crate::execution::ProgramLookup,
1356 cached_body_items: usize,
1357 exec_artifacts: &IndexMap<ArtifactId, Artifact>,
1358 import_code_refs: &AHashMap<ModuleId, ImportCodeRef>,
1359) -> Result<Vec<Artifact>, KclError> {
1360 let uuid = artifact_command.cmd_id;
1361 let response = responses.get(&uuid);
1362
1363 let path_to_node = Vec::new();
1367 let range = artifact_command.range;
1368 let (code_ref_range, node_path) = code_ref_for_range(programs, cached_body_items, range, import_code_refs);
1369 let code_ref = CodeRef {
1370 range: code_ref_range,
1371 node_path,
1372 path_to_node,
1373 };
1374
1375 let id = ArtifactId::new(uuid);
1376 let cmd = &artifact_command.command;
1377
1378 match cmd {
1379 ModelingCmd::ImportFiles(_) => {
1380 return Ok(vec![Artifact::ImportedGeometry(ImportedGeometryArtifact {
1381 id,
1382 code_ref,
1383 consumed: false,
1384 })]);
1385 }
1386 ModelingCmd::MakePlane(_) => {
1387 if range.is_synthetic() {
1388 return Ok(Vec::new());
1389 }
1390 return Ok(vec![Artifact::Plane(Plane {
1394 id,
1395 path_ids: Vec::new(),
1396 code_ref,
1397 })]);
1398 }
1399 ModelingCmd::FaceIsPlanar(FaceIsPlanar { object_id, .. }) => {
1400 return Ok(vec![Artifact::PlaneOfFace(PlaneOfFace {
1401 id,
1402 face_id: object_id.into(),
1403 code_ref,
1404 })]);
1405 }
1406 ModelingCmd::RemoveSceneObjects(remove) => {
1407 return Ok(mark_deleted_artifacts_consumed(artifacts, &remove.object_ids));
1408 }
1409 ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) => {
1410 let existing_plane = artifacts.get(&ArtifactId::new(*entity_id));
1411 match existing_plane {
1412 Some(Artifact::Wall(wall)) => {
1413 return Ok(vec![Artifact::Wall(Wall {
1414 id: entity_id.into(),
1415 seg_id: wall.seg_id,
1416 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1417 sweep_id: wall.sweep_id,
1418 path_ids: wall.path_ids.clone(),
1419 face_code_ref: wall.face_code_ref.clone(),
1420 cmd_id: artifact_command.cmd_id,
1421 })]);
1422 }
1423 Some(Artifact::Cap(cap)) => {
1424 return Ok(vec![Artifact::Cap(Cap {
1425 id: entity_id.into(),
1426 sub_type: cap.sub_type,
1427 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1428 sweep_id: cap.sweep_id,
1429 path_ids: cap.path_ids.clone(),
1430 face_code_ref: cap.face_code_ref.clone(),
1431 cmd_id: artifact_command.cmd_id,
1432 })]);
1433 }
1434 Some(_) | None => {
1435 let path_ids = match existing_plane {
1436 Some(Artifact::Plane(Plane { path_ids, .. })) => path_ids.clone(),
1437 _ => Vec::new(),
1438 };
1439 return Ok(vec![Artifact::Plane(Plane {
1441 id: entity_id.into(),
1442 path_ids,
1443 code_ref,
1444 })]);
1445 }
1446 }
1447 }
1448 ModelingCmd::StartPath(_) => {
1449 let mut return_arr = Vec::new();
1450 let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
1451 KclError::new_internal(KclErrorDetails::new(
1452 format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"),
1453 vec![range],
1454 ))
1455 })?;
1456 let sketch_block_id = exec_artifacts
1457 .values()
1458 .find(|a| {
1459 if let Artifact::SketchBlock(s) = a {
1460 if let Some(path_id) = s.path_id {
1461 path_id == id
1462 } else {
1463 false
1464 }
1465 } else {
1466 false
1467 }
1468 })
1469 .map(|a| a.id());
1470 return_arr.push(Artifact::Path(Path {
1471 id,
1472 sub_type: PathSubType::Sketch,
1473 plane_id: (*current_plane_id).into(),
1474 seg_ids: Vec::new(),
1475 sweep_id: None,
1476 trajectory_sweep_id: None,
1477 solid2d_id: None,
1478 code_ref,
1479 composite_solid_id: None,
1480 sketch_block_id,
1481 origin_path_id: None,
1482 inner_path_id: None,
1483 outer_path_id: None,
1484 pattern_ids: Vec::new(),
1485 consumed: false,
1486 }));
1487 let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
1488 if let Some(Artifact::Plane(plane)) = plane {
1489 let plane_code_ref = plane.code_ref.clone();
1490 return_arr.push(Artifact::Plane(Plane {
1491 id: (*current_plane_id).into(),
1492 path_ids: vec![id],
1493 code_ref: plane_code_ref,
1494 }));
1495 }
1496 if let Some(Artifact::Wall(wall)) = plane {
1497 return_arr.push(Artifact::Wall(Wall {
1498 id: (*current_plane_id).into(),
1499 seg_id: wall.seg_id,
1500 edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
1501 sweep_id: wall.sweep_id,
1502 path_ids: vec![id],
1503 face_code_ref: wall.face_code_ref.clone(),
1504 cmd_id: artifact_command.cmd_id,
1505 }));
1506 }
1507 if let Some(Artifact::Cap(cap)) = plane {
1508 return_arr.push(Artifact::Cap(Cap {
1509 id: (*current_plane_id).into(),
1510 sub_type: cap.sub_type,
1511 edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
1512 sweep_id: cap.sweep_id,
1513 path_ids: vec![id],
1514 face_code_ref: cap.face_code_ref.clone(),
1515 cmd_id: artifact_command.cmd_id,
1516 }));
1517 }
1518 return Ok(return_arr);
1519 }
1520 ModelingCmd::ClosePath(_) | ModelingCmd::ExtendPath(_) => {
1521 let path_id = ArtifactId::new(match cmd {
1522 ModelingCmd::ClosePath(c) => c.path_id,
1523 ModelingCmd::ExtendPath(e) => e.path.into(),
1524 _ => internal_error!(
1525 range,
1526 "Close or extend path command variant not handled: id={id:?}, cmd={cmd:?}"
1527 ),
1528 });
1529 let mut return_arr = Vec::new();
1530 return_arr.push(Artifact::Segment(Segment {
1531 id,
1532 path_id,
1533 source_segment_id: None,
1534 original_seg_id: None,
1535 surface_id: None,
1536 edge_ids: Vec::new(),
1537 edge_cut_id: None,
1538 code_ref,
1539 common_surface_ids: Vec::new(),
1540 }));
1541 let path = artifacts.get(&path_id);
1542 if let Some(Artifact::Path(path)) = path {
1543 let mut new_path = path.clone();
1544 new_path.seg_ids = vec![id];
1545 return_arr.push(Artifact::Path(new_path));
1546 }
1547 if let Some(OkModelingCmdResponse::ClosePath(close_path)) = response {
1548 return_arr.push(Artifact::Solid2d(Solid2d {
1549 id: close_path.face_id.into(),
1550 path_id,
1551 }));
1552 if let Some(Artifact::Path(path)) = path {
1553 let mut new_path = path.clone();
1554 new_path.solid2d_id = Some(close_path.face_id.into());
1555 return_arr.push(Artifact::Path(new_path));
1556 }
1557 }
1558 return Ok(return_arr);
1559 }
1560 ModelingCmd::CreateRegion(kcmc::CreateRegion {
1561 object_id: origin_path_id,
1562 ..
1563 })
1564 | ModelingCmd::CreateRegionFromQueryPoint(kcmc::CreateRegionFromQueryPoint {
1565 object_id: origin_path_id,
1566 ..
1567 }) => {
1568 let mut return_arr = Vec::new();
1569 let origin_path = artifacts.get(&ArtifactId::new(*origin_path_id));
1570 let Some(Artifact::Path(path)) = origin_path else {
1571 internal_error!(
1572 range,
1573 "Expected to find an existing path for the origin path of CreateRegion or CreateRegionFromQueryPoint command, but found none: origin_path={origin_path:?}, cmd={cmd:?}"
1574 );
1575 };
1576 let region_path = |seg_ids, code_ref| {
1577 Artifact::Path(Path {
1578 id,
1579 sub_type: PathSubType::Region,
1580 plane_id: path.plane_id,
1581 seg_ids,
1582 consumed: false,
1583 sweep_id: None,
1584 trajectory_sweep_id: None,
1585 solid2d_id: None,
1586 code_ref,
1587 composite_solid_id: None,
1588 sketch_block_id: None,
1589 origin_path_id: Some(ArtifactId::new(*origin_path_id)),
1590 inner_path_id: None,
1591 outer_path_id: None,
1592 pattern_ids: Vec::new(),
1593 })
1594 };
1595 let Some(
1598 OkModelingCmdResponse::CreateRegion(kcmc::output::CreateRegion { region_mapping, .. })
1599 | OkModelingCmdResponse::CreateRegionFromQueryPoint(kcmc::output::CreateRegionFromQueryPoint {
1600 region_mapping,
1601 ..
1602 }),
1603 ) = response
1604 else {
1605 return_arr.push(region_path(Vec::new(), code_ref));
1606 return Ok(return_arr);
1607 };
1608 let original_segment_ids = path.seg_ids.iter().map(Uuid::from).collect::<Vec<_>>();
1611 let reverse = build_reverse_region_mapping(region_mapping, &original_segment_ids);
1612 let region_segment_ids = reverse
1613 .values()
1614 .flat_map(|region_segment_ids| region_segment_ids.iter().copied())
1615 .map(ArtifactId::new)
1616 .collect::<Vec<_>>();
1617 return_arr.push(region_path(region_segment_ids, code_ref.clone()));
1618 for (original_segment_id, region_segment_ids) in reverse.iter() {
1619 for segment_id in region_segment_ids {
1620 return_arr.push(Artifact::Segment(Segment {
1621 id: ArtifactId::new(*segment_id),
1622 path_id: id,
1623 source_segment_id: None,
1624 original_seg_id: Some(ArtifactId::new(*original_segment_id)),
1625 surface_id: None,
1626 edge_ids: Vec::new(),
1627 edge_cut_id: None,
1628 code_ref: code_ref.clone(),
1629 common_surface_ids: Vec::new(),
1630 }))
1631 }
1632 }
1633 return Ok(return_arr);
1634 }
1635 ModelingCmd::Solid3dGetFaceUuid(kcmc::Solid3dGetFaceUuid { object_id, .. }) => {
1636 let Some(OkModelingCmdResponse::Solid3dGetFaceUuid(face_uuid)) = response else {
1637 return Ok(Vec::new());
1638 };
1639
1640 return Ok(vec![Artifact::PrimitiveFace(PrimitiveFace {
1641 id: face_uuid.face_id.into(),
1642 solid_id: (*object_id).into(),
1643 code_ref,
1644 })]);
1645 }
1646 ModelingCmd::Solid3dGetEdgeUuid(kcmc::Solid3dGetEdgeUuid { object_id, .. }) => {
1647 let Some(OkModelingCmdResponse::Solid3dGetEdgeUuid(edge_uuid)) = response else {
1648 return Ok(Vec::new());
1649 };
1650
1651 return Ok(vec![Artifact::PrimitiveEdge(PrimitiveEdge {
1652 id: edge_uuid.edge_id.into(),
1653 solid_id: (*object_id).into(),
1654 code_ref,
1655 })]);
1656 }
1657 ModelingCmd::EntityGetAllChildUuids(kcmc::EntityGetAllChildUuids { entity_id, .. }) => {
1658 let body_id = ArtifactId::new(*entity_id);
1659 let Some(Artifact::Sweep(sweep)) = artifacts.get(&body_id) else {
1660 return Ok(Vec::new());
1661 };
1662 if sweep.path_id.is_some() {
1663 return Ok(Vec::new());
1664 }
1665 let Some(OkModelingCmdResponse::EntityGetAllChildUuids(child_ids_response)) = response else {
1666 return Ok(Vec::new());
1667 };
1668
1669 let surface_ids = child_ids_response
1670 .entity_ids
1671 .first()
1672 .copied()
1673 .map(ArtifactId::new)
1674 .into_iter()
1675 .collect::<Vec<_>>();
1676 let edge_ids = child_ids_response
1677 .entity_ids
1678 .get(1)
1679 .copied()
1680 .map(ArtifactId::new)
1681 .into_iter()
1682 .collect::<Vec<_>>();
1683
1684 let mut updated_sweep = sweep.clone();
1685 updated_sweep.surface_ids = surface_ids.clone();
1686 updated_sweep.edge_ids = edge_ids.clone();
1687
1688 let mut return_arr = vec![Artifact::Sweep(updated_sweep)];
1689 return_arr.extend(surface_ids.into_iter().map(|id| {
1690 Artifact::PrimitiveFace(PrimitiveFace {
1691 id,
1692 solid_id: body_id,
1693 code_ref: sweep.code_ref.clone(),
1694 })
1695 }));
1696 return_arr.extend(edge_ids.into_iter().map(|id| {
1697 Artifact::PrimitiveEdge(PrimitiveEdge {
1698 id,
1699 solid_id: body_id,
1700 code_ref: sweep.code_ref.clone(),
1701 })
1702 }));
1703 return Ok(return_arr);
1704 }
1705 ModelingCmd::EntityLinearPatternTransform(pattern_cmd) => {
1706 let face_edge_infos = match response {
1707 Some(OkModelingCmdResponse::EntityLinearPatternTransform(resp)) => resp.entity_face_edge_ids.as_slice(),
1708 _ => &[],
1709 };
1710 return Ok(pattern_artifact_updates(
1711 artifacts,
1712 id,
1713 PatternSubType::Transform,
1714 ArtifactId::new(pattern_cmd.entity_id),
1715 face_edge_infos,
1716 code_ref,
1717 ));
1718 }
1719 ModelingCmd::EntityLinearPattern(pattern_cmd) => {
1720 let face_edge_infos = match response {
1721 Some(OkModelingCmdResponse::EntityLinearPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
1722 _ => &[],
1723 };
1724 return Ok(pattern_artifact_updates(
1725 artifacts,
1726 id,
1727 PatternSubType::Linear,
1728 ArtifactId::new(pattern_cmd.entity_id),
1729 face_edge_infos,
1730 code_ref,
1731 ));
1732 }
1733 ModelingCmd::EntityCircularPattern(pattern_cmd) => {
1734 let face_edge_infos = match response {
1735 Some(OkModelingCmdResponse::EntityCircularPattern(resp)) => resp.entity_face_edge_ids.as_slice(),
1736 _ => &[],
1737 };
1738 return Ok(pattern_artifact_updates(
1739 artifacts,
1740 id,
1741 PatternSubType::Circular,
1742 ArtifactId::new(pattern_cmd.entity_id),
1743 face_edge_infos,
1744 code_ref,
1745 ));
1746 }
1747 ModelingCmd::EntityMirrorAcross(kcmc::EntityMirrorAcross {
1748 ids: original_solid_ids,
1749 ..
1750 }) => {
1751 let face_edge_infos = match response {
1752 Some(OkModelingCmdResponse::EntityMirrorAcross(resp)) => resp.entity_face_edge_ids.as_slice(),
1753 None => return Ok(Vec::new()),
1756 Some(_) => internal_error!(
1757 range,
1758 "EntityMirrorAcross response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
1759 ),
1760 };
1761 return mirror_3d_artifact_updates(artifacts, original_solid_ids, face_edge_infos, code_ref, range, cmd);
1762 }
1763 ModelingCmd::EntityMirror(kcmc::EntityMirror {
1764 ids: original_path_ids, ..
1765 })
1766 | ModelingCmd::EntityMirrorAcrossEdge(kcmc::EntityMirrorAcrossEdge {
1767 ids: original_path_ids, ..
1768 }) => {
1769 let face_edge_infos = match response {
1770 Some(OkModelingCmdResponse::EntityMirror(resp)) => &resp.entity_face_edge_ids,
1771 Some(OkModelingCmdResponse::EntityMirrorAcrossEdge(resp)) => &resp.entity_face_edge_ids,
1772 _ => internal_error!(
1773 range,
1774 "Mirror response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?}"
1775 ),
1776 };
1777 if original_path_ids.len() != face_edge_infos.len() {
1778 internal_error!(
1779 range,
1780 "EntityMirror or EntityMirrorAcrossEdge response has different number face edge info than original mirrored paths: id={id:?}, cmd={cmd:?}, response={response:?}"
1781 );
1782 }
1783 let mut return_arr = Vec::new();
1784 for (face_edge_info, original_path_id) in face_edge_infos.iter().zip(original_path_ids) {
1785 let original_path_id = ArtifactId::new(*original_path_id);
1786 let path_id = ArtifactId::new(face_edge_info.object_id);
1787 let mut path = if let Some(Artifact::Path(path)) = artifacts.get(&path_id) {
1790 path.clone()
1792 } else {
1793 let Some(Artifact::Path(original_path)) = artifacts.get(&original_path_id) else {
1796 internal_error!(
1798 range,
1799 "Couldn't find original path for mirror2d: original_path_id={original_path_id:?}, cmd={cmd:?}"
1800 );
1801 };
1802 Path {
1803 id: path_id,
1804 sub_type: original_path.sub_type,
1805 plane_id: original_path.plane_id,
1806 seg_ids: Vec::new(),
1807 sweep_id: None,
1808 trajectory_sweep_id: None,
1809 solid2d_id: None,
1810 code_ref: code_ref.clone(),
1811 composite_solid_id: None,
1812 sketch_block_id: None,
1813 origin_path_id: original_path.origin_path_id,
1814 inner_path_id: None,
1815 outer_path_id: None,
1816 pattern_ids: Vec::new(),
1817 consumed: false,
1818 }
1819 };
1820
1821 face_edge_info.edges.iter().for_each(|edge_id| {
1822 let edge_id = ArtifactId::new(*edge_id);
1823 return_arr.push(Artifact::Segment(Segment {
1824 id: edge_id,
1825 path_id: path.id,
1826 source_segment_id: None,
1827 original_seg_id: None,
1828 surface_id: None,
1829 edge_ids: Vec::new(),
1830 edge_cut_id: None,
1831 code_ref: code_ref.clone(),
1832 common_surface_ids: Vec::new(),
1833 }));
1834 path.seg_ids.push(edge_id);
1836 });
1837
1838 return_arr.push(Artifact::Path(path));
1839 }
1840 return Ok(return_arr);
1841 }
1842 ModelingCmd::EntityClone(kcmc::EntityClone { entity_id, .. }) => {
1843 let source_entity_id = ArtifactId::new(*entity_id);
1844 let entity_clone_info = artifact_command.entity_clone_info;
1845 let source_id = entity_clone_info
1846 .map(|info| info.source_artifact_id)
1847 .unwrap_or(source_entity_id);
1848 let result_id = entity_clone_info.map(|info| info.result_artifact_id).unwrap_or(id);
1849
1850 let pattern_source_body_id = if entity_clone_info.is_some() && !artifacts.contains_key(&source_id) {
1854 pattern_source_body_id_for_copy(artifacts, source_id)
1855 } else {
1856 None
1857 };
1858 let source_artifact_id = pattern_source_body_id.unwrap_or(source_id);
1859 let Some(source_artifact) = artifacts.get(&source_artifact_id) else {
1860 return Ok(Vec::new());
1861 };
1862
1863 let mut entity_id_map = entity_clone_id_maps.get(&uuid).cloned().unwrap_or_default();
1864 entity_id_map.insert(source_entity_id, id);
1865 if let Some(info) = entity_clone_info {
1866 entity_id_map.insert(info.source_topology_id, id);
1867 }
1868 entity_id_map.insert(source_id, result_id);
1869 entity_id_map.insert(source_artifact_id, result_id);
1870 if matches!(source_artifact, Artifact::CompositeSolid(_)) {
1871 add_composite_sweep_clone_id_mappings(artifacts, artifact_command.cmd_id, &mut entity_id_map);
1872 }
1873
1874 let mut cloned_artifacts = Vec::new();
1875 cloned_artifacts.push(remap_artifact_for_clone(
1876 source_artifact,
1877 &entity_id_map,
1878 &code_ref,
1879 artifact_command.cmd_id,
1880 source_artifact_id,
1881 ));
1882
1883 for artifact in artifacts.values() {
1884 let artifact_id = artifact.id();
1885 if artifact_id == source_artifact_id || !entity_id_map.contains_key(&artifact_id) {
1886 continue;
1887 }
1888 cloned_artifacts.push(remap_artifact_for_clone(
1889 artifact,
1890 &entity_id_map,
1891 &code_ref,
1892 artifact_command.cmd_id,
1893 source_artifact_id,
1894 ));
1895 }
1896
1897 return Ok(cloned_artifacts);
1898 }
1899 ModelingCmd::Extrude(_)
1900 | ModelingCmd::TwistExtrude(_)
1901 | ModelingCmd::Revolve(_)
1902 | ModelingCmd::RevolveAboutEdge(_)
1903 | ModelingCmd::ExtrudeToReference(_) => {
1904 let target = match cmd {
1905 ModelingCmd::Extrude(kcmc::Extrude {
1906 target: Some(target), ..
1907 }) => cmd_id_ref_to_artifact_id(target),
1908 ModelingCmd::Extrude(kcmc::Extrude {
1909 target: None,
1910 target_reference: Some(_),
1911 ..
1912 }) => {
1913 return Ok(vec![Artifact::Sweep(Sweep {
1914 id,
1915 sub_type: SweepSubType::Extrusion,
1916 path_id: None,
1917 surface_ids: Vec::new(),
1918 edge_ids: Vec::new(),
1919 code_ref,
1920 source_sweep_id: None,
1921 trajectory_id: None,
1922 method: ArtifactSweepMethod::New,
1923 consumed: false,
1924 pattern_ids: Vec::new(),
1925 })]);
1926 }
1927 ModelingCmd::Extrude(kcmc::Extrude { target: None, .. }) => return Ok(Vec::new()),
1928 ModelingCmd::TwistExtrude(kcmc::TwistExtrude { target, .. })
1929 | ModelingCmd::Revolve(kcmc::Revolve { target, .. })
1930 | ModelingCmd::RevolveAboutEdge(kcmc::RevolveAboutEdge { target, .. }) => {
1931 cmd_id_ref_to_artifact_id(target)
1932 }
1933 ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference {
1934 target: Some(target), ..
1935 }) => cmd_id_ref_to_artifact_id(target),
1936 ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { target: None, .. }) => {
1937 return Ok(Vec::new());
1938 }
1939 _ => internal_error!(range, "Sweep-like command variant not handled: id={id:?}, cmd={cmd:?}"),
1940 };
1941 let method = match cmd {
1943 ModelingCmd::Extrude(kcmc::Extrude { extrude_method, .. }) => *extrude_method,
1944 ModelingCmd::ExtrudeToReference(kcmc::ExtrudeToReference { extrude_method, .. }) => *extrude_method,
1945 ModelingCmd::TwistExtrude(_) | ModelingCmd::Sweep(_) => {
1947 kittycad_modeling_cmds::shared::ExtrudeMethod::Merge
1948 }
1949 ModelingCmd::Revolve(_) | ModelingCmd::RevolveAboutEdge(_) => {
1951 kittycad_modeling_cmds::shared::ExtrudeMethod::New
1952 }
1953 _ => kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
1954 };
1955 let method = artifact_sweep_method(method);
1956 let sub_type = match cmd {
1957 ModelingCmd::Extrude(_) => SweepSubType::Extrusion,
1958 ModelingCmd::ExtrudeToReference(_) => SweepSubType::Extrusion,
1959 ModelingCmd::TwistExtrude(_) => SweepSubType::ExtrusionTwist,
1960 ModelingCmd::Revolve(_) => SweepSubType::Revolve,
1961 ModelingCmd::RevolveAboutEdge(_) => SweepSubType::RevolveAboutEdge,
1962 _ => internal_error!(range, "Sweep-like command variant not handled: id={id:?}, cmd={cmd:?}",),
1963 };
1964 let mut return_arr = Vec::new();
1965 return_arr.push(Artifact::Sweep(Sweep {
1966 id,
1967 sub_type,
1968 path_id: Some(target),
1969 surface_ids: Vec::new(),
1970 edge_ids: Vec::new(),
1971 code_ref,
1972 source_sweep_id: None,
1973 trajectory_id: None,
1974 method,
1975 consumed: false,
1976 pattern_ids: Vec::new(),
1977 }));
1978 let path = artifacts.get(&target);
1979 if let Some(Artifact::Path(path)) = path {
1980 let mut new_path = path.clone();
1981 new_path.sweep_id = Some(id);
1982 new_path.consumed = true;
1983 return_arr.push(Artifact::Path(new_path));
1984 if let Some(inner_path_id) = path.inner_path_id
1985 && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
1986 && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
1987 {
1988 inner_path_artifact.sweep_id = Some(id);
1989 inner_path_artifact.consumed = true;
1990 return_arr.push(Artifact::Path(inner_path_artifact))
1991 }
1992 }
1993 return Ok(return_arr);
1994 }
1995 ModelingCmd::Sweep(kcmc::Sweep { target, trajectory, .. }) => {
1996 let method = ArtifactSweepMethod::Merge;
1998 let sub_type = SweepSubType::Sweep;
1999 let mut return_arr = Vec::new();
2000 let target = cmd_id_ref_to_artifact_id(target);
2001 let trajectory = cmd_id_ref_to_artifact_id(trajectory);
2002 return_arr.push(Artifact::Sweep(Sweep {
2003 id,
2004 sub_type,
2005 path_id: Some(target),
2006 surface_ids: Vec::new(),
2007 edge_ids: Vec::new(),
2008 code_ref,
2009 source_sweep_id: None,
2010 trajectory_id: Some(trajectory),
2011 method,
2012 consumed: false,
2013 pattern_ids: Vec::new(),
2014 }));
2015 let path = artifacts.get(&target);
2016 if let Some(Artifact::Path(path)) = path {
2017 let mut new_path = path.clone();
2018 new_path.sweep_id = Some(id);
2019 new_path.consumed = true;
2020 return_arr.push(Artifact::Path(new_path));
2021 if let Some(inner_path_id) = path.inner_path_id
2022 && let Some(inner_path_artifact) = artifacts.get(&inner_path_id)
2023 && let Artifact::Path(mut inner_path_artifact) = inner_path_artifact.clone()
2024 {
2025 inner_path_artifact.sweep_id = Some(id);
2026 inner_path_artifact.consumed = true;
2027 return_arr.push(Artifact::Path(inner_path_artifact))
2028 }
2029 }
2030 if let Some(trajectory_artifact) = artifacts.get(&trajectory) {
2031 match trajectory_artifact {
2032 Artifact::Path(path) => {
2033 let mut new_path = path.clone();
2034 new_path.trajectory_sweep_id = Some(id);
2035 new_path.consumed = true;
2036 return_arr.push(Artifact::Path(new_path));
2037 }
2038 Artifact::Helix(helix) => {
2039 let mut new_helix = helix.clone();
2040 new_helix.trajectory_sweep_id = Some(id);
2041 new_helix.consumed = true;
2042 return_arr.push(Artifact::Helix(new_helix));
2043 }
2044 _ => {}
2045 }
2046 };
2047 return Ok(return_arr);
2048 }
2049 ModelingCmd::SurfaceBlend(surface_blend_cmd) => {
2050 let surface_id_to_path_id = |surface_id: ArtifactId| -> Option<ArtifactId> {
2051 match artifacts.get(&surface_id) {
2052 Some(Artifact::Path(path)) => Some(path.id),
2053 Some(Artifact::Segment(segment)) => Some(segment.path_id),
2054 Some(Artifact::Sweep(sweep)) => sweep.path_id,
2055 Some(Artifact::Wall(wall)) => artifacts.get(&wall.sweep_id).and_then(|artifact| match artifact {
2056 Artifact::Sweep(sweep) => sweep.path_id,
2057 _ => None,
2058 }),
2059 Some(Artifact::Cap(cap)) => artifacts.get(&cap.sweep_id).and_then(|artifact| match artifact {
2060 Artifact::Sweep(sweep) => sweep.path_id,
2061 _ => None,
2062 }),
2063 _ => None,
2064 }
2065 };
2066 let Some(first_surface_ref) = surface_blend_cmd.surfaces.first() else {
2067 internal_error!(range, "SurfaceBlend command has no surfaces: id={id:?}, cmd={cmd:?}");
2068 };
2069 let first_surface_id = ArtifactId::new(first_surface_ref.object_id);
2070 let path_id = surface_id_to_path_id(first_surface_id);
2071 let trajectory_id = surface_blend_cmd
2072 .surfaces
2073 .get(1)
2074 .map(|surface| ArtifactId::new(surface.object_id))
2075 .and_then(surface_id_to_path_id);
2076 let return_arr = vec![Artifact::Sweep(Sweep {
2077 id,
2078 sub_type: SweepSubType::Blend,
2079 path_id,
2080 surface_ids: Vec::new(),
2081 edge_ids: Vec::new(),
2082 code_ref,
2083 source_sweep_id: None,
2084 trajectory_id,
2085 method: ArtifactSweepMethod::New,
2086 consumed: false,
2087 pattern_ids: Vec::new(),
2088 })];
2089 return Ok(return_arr);
2090 }
2091 ModelingCmd::Loft(loft_cmd) => {
2092 let Some(OkModelingCmdResponse::Loft(_)) = response else {
2093 return Ok(Vec::new());
2094 };
2095 let mut return_arr = Vec::new();
2096 return_arr.push(Artifact::Sweep(Sweep {
2097 id,
2098 sub_type: SweepSubType::Loft,
2099 path_id: Some(ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
2102 KclError::new_internal(KclErrorDetails::new(
2103 format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
2104 vec![range],
2105 ))
2106 })?)),
2107 surface_ids: Vec::new(),
2108 edge_ids: Vec::new(),
2109 code_ref,
2110 source_sweep_id: None,
2111 trajectory_id: None,
2112 method: ArtifactSweepMethod::Merge,
2113 consumed: false,
2114 pattern_ids: Vec::new(),
2115 }));
2116 for section_id in &loft_cmd.section_ids {
2117 let path = artifacts.get(&ArtifactId::new(*section_id));
2118 if let Some(Artifact::Path(path)) = path {
2119 let mut new_path = path.clone();
2120 new_path.consumed = true;
2121 new_path.sweep_id = Some(id);
2122 return_arr.push(Artifact::Path(new_path));
2123 }
2124 }
2125 return Ok(return_arr);
2126 }
2127 ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
2128 let Some(OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info)) = response else {
2129 return Ok(Vec::new());
2130 };
2131 let mut return_arr = Vec::new();
2132 let mut last_path = None;
2133 for face in &face_info.faces {
2134 if face.cap != ExtrusionFaceCapType::None {
2135 continue;
2136 }
2137 let Some(curve_id) = face.curve_id.map(ArtifactId::new) else {
2138 continue;
2139 };
2140 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
2141 continue;
2142 };
2143 let Some(Artifact::Segment(seg)) = artifacts.get(&curve_id) else {
2144 continue;
2145 };
2146 let Some(Artifact::Path(path)) = artifacts.get(&seg.path_id) else {
2147 continue;
2148 };
2149 last_path = Some(path);
2150 let Some(path_sweep_id) = path.sweep_id else {
2151 if path.outer_path_id.is_some() {
2154 continue; }
2156 return Err(KclError::new_internal(KclErrorDetails::new(
2157 format!(
2158 "Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
2159 ),
2160 vec![range],
2161 )));
2162 };
2163 let extra_artifact = exec_artifacts.values().find(|a| {
2164 if let Artifact::StartSketchOnFace(s) = a {
2165 s.face_id == face_id
2166 } else if let Artifact::StartSketchOnPlane(s) = a {
2167 s.plane_id == face_id
2168 } else {
2169 false
2170 }
2171 });
2172 let sketch_on_face_code_ref = extra_artifact
2173 .and_then(|a| match a {
2174 Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
2175 Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
2176 _ => None,
2177 })
2178 .unwrap_or_default();
2180
2181 return_arr.push(Artifact::Wall(Wall {
2182 id: face_id,
2183 seg_id: curve_id,
2184 edge_cut_edge_ids: Vec::new(),
2185 sweep_id: path_sweep_id,
2186 path_ids: Vec::new(),
2187 face_code_ref: sketch_on_face_code_ref,
2188 cmd_id: artifact_command.cmd_id,
2189 }));
2190 let mut new_seg = seg.clone();
2191 new_seg.surface_id = Some(face_id);
2192 return_arr.push(Artifact::Segment(new_seg));
2193 if let Some(Artifact::Sweep(sweep)) = path.sweep_id.and_then(|id| artifacts.get(&id)) {
2194 let mut new_sweep = sweep.clone();
2195 new_sweep.surface_ids = vec![face_id];
2196 return_arr.push(Artifact::Sweep(new_sweep));
2197 }
2198 }
2199 if let Some(path) = last_path {
2200 for face in &face_info.faces {
2201 let sub_type = match face.cap {
2202 ExtrusionFaceCapType::Top => CapSubType::End,
2203 ExtrusionFaceCapType::Bottom => CapSubType::Start,
2204 ExtrusionFaceCapType::None | ExtrusionFaceCapType::Both => continue,
2205 _other => {
2206 continue;
2208 }
2209 };
2210 let Some(face_id) = face.face_id.map(ArtifactId::new) else {
2211 continue;
2212 };
2213 let Some(path_sweep_id) = path.sweep_id else {
2214 if path.outer_path_id.is_some() {
2217 continue; }
2219 return Err(KclError::new_internal(KclErrorDetails::new(
2220 format!(
2221 "Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none:\n{id:#?}\n{path:#?}"
2222 ),
2223 vec![range],
2224 )));
2225 };
2226 let extra_artifact = exec_artifacts.values().find(|a| {
2227 if let Artifact::StartSketchOnFace(s) = a {
2228 s.face_id == face_id
2229 } else if let Artifact::StartSketchOnPlane(s) = a {
2230 s.plane_id == face_id
2231 } else {
2232 false
2233 }
2234 });
2235 let sketch_on_face_code_ref = extra_artifact
2236 .and_then(|a| match a {
2237 Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()),
2238 Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()),
2239 _ => None,
2240 })
2241 .unwrap_or_default();
2243 return_arr.push(Artifact::Cap(Cap {
2244 id: face_id,
2245 sub_type,
2246 edge_cut_edge_ids: Vec::new(),
2247 sweep_id: path_sweep_id,
2248 path_ids: Vec::new(),
2249 face_code_ref: sketch_on_face_code_ref,
2250 cmd_id: artifact_command.cmd_id,
2251 }));
2252 let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
2253 continue;
2254 };
2255 let mut new_sweep = sweep.clone();
2256 new_sweep.surface_ids = vec![face_id];
2257 return_arr.push(Artifact::Sweep(new_sweep));
2258 }
2259 }
2260 return Ok(return_arr);
2261 }
2262 ModelingCmd::Solid3dGetAdjacencyInfo(kcmc::Solid3dGetAdjacencyInfo { .. }) => {
2263 let Some(OkModelingCmdResponse::Solid3dGetAdjacencyInfo(info)) = response else {
2264 return Ok(Vec::new());
2265 };
2266
2267 let mut return_arr = Vec::new();
2268 let adjacent_edge_ids = info
2269 .edges
2270 .iter()
2271 .filter_map(|edge| edge.adjacent_info.as_ref().map(|info| info.edge_id))
2272 .collect::<AHashSet<_>>();
2273 for (index, edge) in info.edges.iter().enumerate() {
2274 let Some(original_info) = &edge.original_info else {
2275 continue;
2276 };
2277 let edge_id = ArtifactId::new(original_info.edge_id);
2278 let Some(artifact) = artifacts.get(&edge_id) else {
2279 continue;
2280 };
2281 match artifact {
2282 Artifact::Segment(segment) => {
2283 let mut new_segment = segment.clone();
2284 new_segment.common_surface_ids =
2285 original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
2286 return_arr.push(Artifact::Segment(new_segment));
2287 }
2288 Artifact::SweepEdge(sweep_edge) => {
2289 let mut new_sweep_edge = sweep_edge.clone();
2290 new_sweep_edge.common_surface_ids =
2291 original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect();
2292 return_arr.push(Artifact::SweepEdge(new_sweep_edge));
2293 }
2294 _ => {}
2295 };
2296
2297 let Some(Artifact::Segment(segment)) = artifacts.get(&edge_id) else {
2298 continue;
2299 };
2300 let Some(surface_id) = segment.surface_id else {
2301 continue;
2302 };
2303 let Some(Artifact::Wall(wall)) = artifacts.get(&surface_id) else {
2304 continue;
2305 };
2306 let Some(Artifact::Sweep(sweep)) = artifacts.get(&wall.sweep_id) else {
2307 continue;
2308 };
2309 let Some(path_id) = sweep.path_id else {
2310 continue;
2311 };
2312 let Some(Artifact::Path(_)) = artifacts.get(&path_id) else {
2313 continue;
2314 };
2315
2316 if let Some(opposite_info) = &edge.opposite_info {
2317 return_arr.push(Artifact::SweepEdge(SweepEdge {
2318 id: opposite_info.edge_id.into(),
2319 sub_type: SweepEdgeSubType::Opposite,
2320 seg_id: edge_id,
2321 cmd_id: artifact_command.cmd_id,
2322 index,
2323 sweep_id: sweep.id,
2324 common_surface_ids: opposite_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
2325 }));
2326 let mut new_segment = segment.clone();
2327 new_segment.edge_ids = vec![opposite_info.edge_id.into()];
2328 return_arr.push(Artifact::Segment(new_segment));
2329 let mut new_sweep = sweep.clone();
2330 new_sweep.edge_ids = vec![opposite_info.edge_id.into()];
2331 return_arr.push(Artifact::Sweep(new_sweep));
2332 let mut new_wall = wall.clone();
2333 new_wall.edge_cut_edge_ids = vec![opposite_info.edge_id.into()];
2334 return_arr.push(Artifact::Wall(new_wall));
2335 }
2336 if let Some(adjacent_info) = &edge.adjacent_info {
2337 return_arr.push(Artifact::SweepEdge(SweepEdge {
2338 id: adjacent_info.edge_id.into(),
2339 sub_type: SweepEdgeSubType::Adjacent,
2340 seg_id: edge_id,
2341 cmd_id: artifact_command.cmd_id,
2342 index,
2343 sweep_id: sweep.id,
2344 common_surface_ids: adjacent_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(),
2345 }));
2346 let mut new_segment = segment.clone();
2347 new_segment.edge_ids = vec![adjacent_info.edge_id.into()];
2348 return_arr.push(Artifact::Segment(new_segment));
2349 let mut new_sweep = sweep.clone();
2350 new_sweep.edge_ids = vec![adjacent_info.edge_id.into()];
2351 return_arr.push(Artifact::Sweep(new_sweep));
2352 let mut new_wall = wall.clone();
2353 new_wall.edge_cut_edge_ids = vec![adjacent_info.edge_id.into()];
2354 return_arr.push(Artifact::Wall(new_wall));
2355 }
2356 if let Some(previous_adjacent_info) = &edge.previous_adjacent_info
2359 && !adjacent_edge_ids.contains(&previous_adjacent_info.edge_id)
2360 {
2361 return_arr.push(Artifact::SweepEdge(SweepEdge {
2362 id: previous_adjacent_info.edge_id.into(),
2363 sub_type: SweepEdgeSubType::PreviousAdjacent,
2364 seg_id: edge_id,
2365 cmd_id: artifact_command.cmd_id,
2366 index,
2367 sweep_id: sweep.id,
2368 common_surface_ids: previous_adjacent_info
2369 .faces
2370 .iter()
2371 .map(|face| ArtifactId::new(*face))
2372 .collect(),
2373 }));
2374 let mut new_segment = segment.clone();
2375 new_segment.edge_ids = vec![previous_adjacent_info.edge_id.into()];
2376 return_arr.push(Artifact::Segment(new_segment));
2377 let mut new_sweep = sweep.clone();
2378 new_sweep.edge_ids = vec![previous_adjacent_info.edge_id.into()];
2379 return_arr.push(Artifact::Sweep(new_sweep));
2380 let mut new_wall = wall.clone();
2381 new_wall.edge_cut_edge_ids = vec![previous_adjacent_info.edge_id.into()];
2382 return_arr.push(Artifact::Wall(new_wall));
2383 }
2384 }
2385 return Ok(return_arr);
2386 }
2387 ModelingCmd::Solid3dMultiJoin(cmd) => {
2388 let mut return_arr = Vec::new();
2389 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2390 id,
2391 consumed: false,
2392 sub_type: CompositeSolidSubType::Union,
2393 output_index: None,
2394 solid_ids: cmd.object_ids.iter().map(|id| id.into()).collect(),
2395 tool_ids: vec![],
2396 code_ref,
2397 composite_solid_id: None,
2398 pattern_ids: Vec::new(),
2399 }));
2400
2401 let solid_ids = cmd.object_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2402
2403 for input_id in &solid_ids {
2404 if let Some(artifact) = artifacts.get(input_id)
2405 && let Artifact::CompositeSolid(comp) = artifact
2406 {
2407 let mut new_comp = comp.clone();
2408 new_comp.composite_solid_id = Some(id);
2409 new_comp.consumed = true;
2410 return_arr.push(Artifact::CompositeSolid(new_comp));
2411 } else if let Some(Artifact::Sweep(sweep)) = artifacts.get(input_id) {
2412 let mut new_sweep = sweep.clone();
2413 new_sweep.consumed = true;
2414 return_arr.push(Artifact::Sweep(new_sweep));
2415 }
2416 }
2417 return Ok(return_arr);
2418 }
2419 ModelingCmd::Solid3dFilletEdge(cmd) => {
2420 let mut return_arr = Vec::new();
2421 let edge_id = if let Some(edge_id) = cmd.edge_id {
2422 ArtifactId::new(edge_id)
2423 } else {
2424 let Some(edge_id) = cmd.edge_ids.first() else {
2425 internal_error!(
2426 range,
2427 "Solid3dFilletEdge command has no edge ID: id={id:?}, cmd={cmd:?}"
2428 );
2429 };
2430 edge_id.into()
2431 };
2432 return_arr.push(Artifact::EdgeCut(EdgeCut {
2433 id,
2434 sub_type: edge_cut_sub_type(cmd.cut_type),
2435 source_selector_index: None,
2436 consumed_edge_id: Some(edge_id),
2437 edge_ids: Vec::new(),
2438 surface_id: None,
2439 code_ref,
2440 }));
2441 let consumed_edge = artifacts.get(&edge_id);
2442 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2443 let mut new_segment = consumed_edge.clone();
2444 new_segment.edge_cut_id = Some(id);
2445 return_arr.push(Artifact::Segment(new_segment));
2446 } else {
2447 }
2449 return Ok(return_arr);
2450 }
2451 ModelingCmd::Solid3dCutEdges(cmd) => {
2452 let mut return_arr = Vec::new();
2453 let edge_id = if let Some(edge_id) = cmd.edge_ids.first() {
2454 edge_id.into()
2455 } else {
2456 internal_error!(range, "Solid3dCutEdges command has no edge ID: id={id:?}, cmd={cmd:?}");
2457 };
2458 return_arr.push(Artifact::EdgeCut(EdgeCut {
2459 id,
2460 sub_type: edge_cut_sub_type_v2(cmd.cut_type),
2461 source_selector_index: None,
2462 consumed_edge_id: Some(edge_id),
2463 edge_ids: Vec::new(),
2464 surface_id: None,
2465 code_ref,
2466 }));
2467 let consumed_edge = artifacts.get(&edge_id);
2468 if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
2469 let mut new_segment = consumed_edge.clone();
2470 new_segment.edge_cut_id = Some(id);
2471 return_arr.push(Artifact::Segment(new_segment));
2472 } else {
2473 }
2475 return Ok(return_arr);
2476 }
2477 ModelingCmd::Solid3dCutEdgeReferences(cmd) => {
2478 let face_ids = std::iter::once(id).chain(cmd.extra_face_ids.iter().copied().map(ArtifactId::new));
2479 return Ok(face_ids
2480 .take(cmd.edges_references.len())
2481 .enumerate()
2482 .map(|(source_selector_index, face_id)| {
2483 Artifact::EdgeCut(EdgeCut {
2484 id: face_id,
2485 sub_type: edge_cut_sub_type_v2(cmd.cut_type),
2486 source_selector_index: Some(source_selector_index),
2487 consumed_edge_id: None,
2488 edge_ids: Vec::new(),
2489 surface_id: None,
2490 code_ref: code_ref.clone(),
2491 })
2492 })
2493 .collect());
2494 }
2495 ModelingCmd::EntityMakeHelix(cmd) => {
2496 let cylinder_id = ArtifactId::new(cmd.cylinder_id);
2497 let return_arr = vec![Artifact::Helix(Helix {
2498 id,
2499 axis_id: Some(cylinder_id),
2500 code_ref,
2501 trajectory_sweep_id: None,
2502 consumed: false,
2503 })];
2504 return Ok(return_arr);
2505 }
2506 ModelingCmd::EntityMakeHelixFromParams(_) => {
2507 let return_arr = vec![Artifact::Helix(Helix {
2508 id,
2509 axis_id: None,
2510 code_ref,
2511 trajectory_sweep_id: None,
2512 consumed: false,
2513 })];
2514 return Ok(return_arr);
2515 }
2516 ModelingCmd::EntityMakeHelixFromEdge(helix) => {
2517 let return_arr = vec![Artifact::Helix(Helix {
2518 id,
2519 axis_id: helix.edge_id.map(ArtifactId::new),
2520 code_ref,
2521 trajectory_sweep_id: None,
2522 consumed: false,
2523 })];
2524 return Ok(return_arr);
2527 }
2528 ModelingCmd::Solid2dAddHole(solid2d_add_hole) => {
2529 let mut return_arr = Vec::new();
2530 let outer_path = artifacts.get(&ArtifactId::new(solid2d_add_hole.object_id));
2532 if let Some(Artifact::Path(path)) = outer_path {
2533 let mut new_path = path.clone();
2534 new_path.inner_path_id = Some(ArtifactId::new(solid2d_add_hole.hole_id));
2535 return_arr.push(Artifact::Path(new_path));
2536 }
2537 let inner_solid2d = artifacts.get(&ArtifactId::new(solid2d_add_hole.hole_id));
2539 if let Some(Artifact::Path(path)) = inner_solid2d {
2540 let mut new_path = path.clone();
2541 new_path.consumed = true;
2542 new_path.outer_path_id = Some(ArtifactId::new(solid2d_add_hole.object_id));
2543 return_arr.push(Artifact::Path(new_path));
2544 }
2545 return Ok(return_arr);
2546 }
2547 ModelingCmd::BooleanIntersection(_) | ModelingCmd::BooleanSubtract(_) | ModelingCmd::BooleanUnion(_) => {
2548 let (sub_type, solid_ids, tool_ids) = match cmd {
2549 ModelingCmd::BooleanIntersection(intersection) => {
2550 let solid_ids = intersection
2551 .solid_ids
2552 .iter()
2553 .copied()
2554 .map(ArtifactId::new)
2555 .collect::<Vec<_>>();
2556 (CompositeSolidSubType::Intersect, solid_ids, Vec::new())
2557 }
2558 ModelingCmd::BooleanSubtract(subtract) => {
2559 let solid_ids = subtract
2560 .target_ids
2561 .iter()
2562 .copied()
2563 .map(ArtifactId::new)
2564 .collect::<Vec<_>>();
2565 let tool_ids = subtract
2566 .tool_ids
2567 .iter()
2568 .copied()
2569 .map(ArtifactId::new)
2570 .collect::<Vec<_>>();
2571 (CompositeSolidSubType::Subtract, solid_ids, tool_ids)
2572 }
2573 ModelingCmd::BooleanUnion(union) => {
2574 let solid_ids = union.solid_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
2575 (CompositeSolidSubType::Union, solid_ids, Vec::new())
2576 }
2577 _ => internal_error!(
2578 range,
2579 "Boolean or composite command variant not handled: id={id:?}, cmd={cmd:?}"
2580 ),
2581 };
2582
2583 let mut new_solid_ids = vec![id];
2584
2585 let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2588
2589 match (cmd, response) {
2590 (
2591 ModelingCmd::BooleanSubtract(subtract_cmd),
2592 Some(OkModelingCmdResponse::BooleanSubtract(subtract_resp)),
2593 ) => {
2594 new_solid_ids = boolean_subtract_output_artifact_ids(
2595 id,
2596 &subtract_cmd.target_ids,
2597 &subtract_cmd.tool_ids,
2598 &subtract_resp.extra_solid_ids,
2599 );
2600 }
2601 (_, Some(OkModelingCmdResponse::BooleanIntersection(intersection))) => intersection
2602 .extra_solid_ids
2603 .iter()
2604 .copied()
2605 .map(ArtifactId::new)
2606 .filter(not_cmd_id)
2607 .for_each(|id| new_solid_ids.push(id)),
2608 (_, Some(OkModelingCmdResponse::BooleanUnion(union))) => union
2609 .extra_solid_ids
2610 .iter()
2611 .copied()
2612 .map(ArtifactId::new)
2613 .filter(not_cmd_id)
2614 .for_each(|id| new_solid_ids.push(id)),
2615 _ => {}
2616 }
2617
2618 let mut return_arr = Vec::new();
2619 let mut consumed_sweep_ids = AHashSet::default();
2620 let mut input_ids = solid_ids.clone();
2621 merge_ids(&mut input_ids, tool_ids.clone());
2622
2623 if new_solid_ids.is_empty() {
2624 update_csg_input_artifacts(&mut return_arr, artifacts, &input_ids, None, &mut consumed_sweep_ids);
2625 }
2626
2627 for solid_id in &new_solid_ids {
2629 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2631 id: *solid_id,
2632 consumed: false,
2633 sub_type,
2634 output_index: None,
2635 solid_ids: solid_ids.clone(),
2636 tool_ids: tool_ids.clone(),
2637 code_ref: code_ref.clone(),
2638 composite_solid_id: None,
2639 pattern_ids: Vec::new(),
2640 }));
2641
2642 update_csg_input_artifacts(
2643 &mut return_arr,
2644 artifacts,
2645 &input_ids,
2646 Some(*solid_id),
2647 &mut consumed_sweep_ids,
2648 );
2649 }
2650
2651 return Ok(return_arr);
2652 }
2653 ModelingCmd::BooleanImprint(imprint) => {
2654 let solid_ids = imprint
2655 .body_ids
2656 .iter()
2657 .copied()
2658 .map(ArtifactId::new)
2659 .collect::<Vec<_>>();
2660 let tool_ids = imprint
2661 .tool_ids
2662 .as_ref()
2663 .map(|ids| ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>())
2664 .unwrap_or_default();
2665
2666 let mut new_solid_ids = vec![id];
2667 let not_cmd_id = move |solid_id: &ArtifactId| *solid_id != id;
2668 if let Some(OkModelingCmdResponse::BooleanImprint(imprint)) = response {
2669 imprint
2670 .extra_solid_ids
2671 .iter()
2672 .copied()
2673 .map(ArtifactId::new)
2674 .filter(not_cmd_id)
2675 .for_each(|id| new_solid_ids.push(id));
2676 }
2677
2678 let mut return_arr = Vec::new();
2679 let mut consumed_sweep_ids = AHashSet::default();
2680
2681 for input_id in solid_ids.iter().chain(tool_ids.iter()) {
2682 let sweep_id = match artifacts.get(input_id) {
2683 Some(Artifact::Sweep(sweep)) => Some(sweep.id),
2684 Some(Artifact::Path(path)) => path.sweep_id,
2685 _ => None,
2686 };
2687
2688 if let Some(sweep_id) = sweep_id
2689 && consumed_sweep_ids.insert(sweep_id)
2690 && let Some(Artifact::Sweep(sweep)) = artifacts.get(&sweep_id)
2691 {
2692 let mut new_sweep = sweep.clone();
2693 new_sweep.consumed = true;
2694 return_arr.push(Artifact::Sweep(new_sweep));
2695 }
2696 }
2697
2698 for (output_index, solid_id) in new_solid_ids.iter().enumerate() {
2699 return_arr.push(Artifact::CompositeSolid(CompositeSolid {
2700 id: *solid_id,
2701 consumed: false,
2702 sub_type: CompositeSolidSubType::Split,
2703 output_index: Some(output_index),
2704 solid_ids: solid_ids.clone(),
2705 tool_ids: tool_ids.clone(),
2706 code_ref: code_ref.clone(),
2707 composite_solid_id: None,
2708 pattern_ids: Vec::new(),
2709 }));
2710
2711 for input_id in solid_ids.iter().chain(tool_ids.iter()) {
2712 if let Some(artifact) = artifacts.get(input_id) {
2713 match artifact {
2714 Artifact::CompositeSolid(comp) => {
2715 let mut new_comp = comp.clone();
2716 new_comp.composite_solid_id = Some(*solid_id);
2717 new_comp.consumed = true;
2718 return_arr.push(Artifact::CompositeSolid(new_comp));
2719 }
2720 Artifact::Path(path) => {
2721 let mut new_path = path.clone();
2722 new_path.composite_solid_id = Some(*solid_id);
2723
2724 return_arr.push(Artifact::Path(new_path));
2725 }
2726 _ => {}
2727 }
2728 }
2729 }
2730 }
2731
2732 return Ok(return_arr);
2733 }
2734 _ => {}
2735 }
2736
2737 Ok(Vec::new())
2738}