1use std::collections::HashMap;
4
5use anyhow::Result;
6use kcl_derive_docs::stdlib;
7use kcmc::{
8 each_cmd as mcmd,
9 ok_response::{output::EntityGetAllChildUuids, OkModelingCmdResponse},
10 websocket::OkWebSocketResponseData,
11 ModelingCmd,
12};
13use kittycad_modeling_cmds::{self as kcmc};
14
15use super::extrude::do_post_extrude;
16use crate::{
17 errors::{KclError, KclErrorDetails},
18 execution::{
19 types::{NumericType, PrimitiveType, RuntimeType},
20 ExecState, GeometryWithImportedGeometry, KclValue, Sketch, Solid,
21 },
22 parsing::ast::types::TagNode,
23 std::{extrude::NamedCapTags, Args},
24};
25
26pub async fn clone(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
30 let geometry = args.get_unlabeled_kw_arg_typed(
31 "geometry",
32 &RuntimeType::Union(vec![
33 RuntimeType::Primitive(PrimitiveType::Sketch),
34 RuntimeType::Primitive(PrimitiveType::Solid),
35 RuntimeType::imported(),
36 ]),
37 exec_state,
38 )?;
39
40 let cloned = inner_clone(geometry, exec_state, args).await?;
41 Ok(cloned.into())
42}
43
44#[stdlib {
270 name = "clone",
271 feature_tree_operation = true,
272 keywords = true,
273 unlabeled_first = true,
274 args = {
275 geometry = { docs = "The sketch, solid, or imported geometry to be cloned" },
276 }
277}]
278async fn inner_clone(
279 geometry: GeometryWithImportedGeometry,
280 exec_state: &mut ExecState,
281 args: Args,
282) -> Result<GeometryWithImportedGeometry, KclError> {
283 let new_id = exec_state.next_uuid();
284 let mut geometry = geometry.clone();
285 let old_id = geometry.id(&args.ctx).await?;
286
287 let mut new_geometry = match &geometry {
288 GeometryWithImportedGeometry::ImportedGeometry(imported) => {
289 let mut new_imported = imported.clone();
290 new_imported.id = new_id;
291 GeometryWithImportedGeometry::ImportedGeometry(new_imported)
292 }
293 GeometryWithImportedGeometry::Sketch(sketch) => {
294 let mut new_sketch = sketch.clone();
295 new_sketch.id = new_id;
296 new_sketch.original_id = new_id;
297 #[cfg(feature = "artifact-graph")]
298 {
299 new_sketch.artifact_id = new_id.into();
300 }
301 GeometryWithImportedGeometry::Sketch(new_sketch)
302 }
303 GeometryWithImportedGeometry::Solid(solid) => {
304 args.flush_batch_for_solids(exec_state, &[solid.clone()]).await?;
306
307 let mut new_solid = solid.clone();
308 new_solid.id = new_id;
309 new_solid.sketch.original_id = new_id;
310 #[cfg(feature = "artifact-graph")]
311 {
312 new_solid.artifact_id = new_id.into();
313 }
314 GeometryWithImportedGeometry::Solid(new_solid)
315 }
316 };
317
318 if args.ctx.no_engine_commands().await {
319 return Ok(new_geometry);
320 }
321
322 args.batch_modeling_cmd(new_id, ModelingCmd::from(mcmd::EntityClone { entity_id: old_id }))
323 .await?;
324
325 fix_tags_and_references(&mut new_geometry, old_id, exec_state, &args)
326 .await
327 .map_err(|e| {
328 KclError::Internal(KclErrorDetails {
329 message: format!("failed to fix tags and references: {:?}", e),
330 source_ranges: vec![args.source_range],
331 })
332 })?;
333
334 Ok(new_geometry)
335}
336async fn fix_tags_and_references(
338 new_geometry: &mut GeometryWithImportedGeometry,
339 old_geometry_id: uuid::Uuid,
340 exec_state: &mut ExecState,
341 args: &Args,
342) -> Result<()> {
343 let new_geometry_id = new_geometry.id(&args.ctx).await?;
344 let entity_id_map = get_old_new_child_map(new_geometry_id, old_geometry_id, exec_state, args).await?;
345
346 match new_geometry {
348 GeometryWithImportedGeometry::ImportedGeometry(_) => {}
349 GeometryWithImportedGeometry::Sketch(sketch) => {
350 fix_sketch_tags_and_references(sketch, &entity_id_map, exec_state).await?;
351 }
352 GeometryWithImportedGeometry::Solid(solid) => {
353 solid.sketch.id = new_geometry_id;
355 solid.sketch.original_id = new_geometry_id;
356 #[cfg(feature = "artifact-graph")]
357 {
358 solid.sketch.artifact_id = new_geometry_id.into();
359 }
360
361 fix_sketch_tags_and_references(&mut solid.sketch, &entity_id_map, exec_state).await?;
362
363 let (start_tag, end_tag) = get_named_cap_tags(solid);
364
365 for edge_cut in solid.edge_cuts.iter_mut() {
367 if let Some(id) = entity_id_map.get(&edge_cut.id()) {
368 edge_cut.set_id(*id);
369 } else {
370 crate::log::logln!(
371 "Failed to find new edge cut id for old edge cut id: {:?}",
372 edge_cut.id()
373 );
374 }
375 if let Some(new_edge_id) = entity_id_map.get(&edge_cut.edge_id()) {
376 edge_cut.set_edge_id(*new_edge_id);
377 } else {
378 crate::log::logln!("Failed to find new edge id for old edge id: {:?}", edge_cut.edge_id());
379 }
380 }
381
382 let new_solid = do_post_extrude(
385 &solid.sketch,
386 #[cfg(feature = "artifact-graph")]
387 new_geometry_id.into(),
388 crate::std::args::TyF64::new(
389 solid.height,
390 NumericType::Known(crate::execution::types::UnitType::Length(solid.units)),
391 ),
392 solid.sectional,
393 &NamedCapTags {
394 start: start_tag.as_ref(),
395 end: end_tag.as_ref(),
396 },
397 exec_state,
398 args,
399 )
400 .await?;
401
402 *solid = new_solid;
403 }
404 }
405
406 Ok(())
407}
408
409async fn get_old_new_child_map(
410 new_geometry_id: uuid::Uuid,
411 old_geometry_id: uuid::Uuid,
412 exec_state: &mut ExecState,
413 args: &Args,
414) -> Result<HashMap<uuid::Uuid, uuid::Uuid>> {
415 let response = args
417 .send_modeling_cmd(
418 exec_state.next_uuid(),
419 ModelingCmd::from(mcmd::EntityGetAllChildUuids {
420 entity_id: new_geometry_id,
421 }),
422 )
423 .await?;
424 let OkWebSocketResponseData::Modeling {
425 modeling_response:
426 OkModelingCmdResponse::EntityGetAllChildUuids(EntityGetAllChildUuids {
427 entity_ids: new_entity_ids,
428 }),
429 } = response
430 else {
431 anyhow::bail!("Expected EntityGetAllChildUuids response, got: {:?}", response);
432 };
433
434 let response = args
436 .send_modeling_cmd(
437 exec_state.next_uuid(),
438 ModelingCmd::from(mcmd::EntityGetAllChildUuids {
439 entity_id: old_geometry_id,
440 }),
441 )
442 .await?;
443 let OkWebSocketResponseData::Modeling {
444 modeling_response:
445 OkModelingCmdResponse::EntityGetAllChildUuids(EntityGetAllChildUuids {
446 entity_ids: old_entity_ids,
447 }),
448 } = response
449 else {
450 anyhow::bail!("Expected EntityGetAllChildUuids response, got: {:?}", response);
451 };
452
453 Ok(HashMap::from_iter(
455 old_entity_ids
456 .iter()
457 .zip(new_entity_ids.iter())
458 .map(|(old_id, new_id)| (*old_id, *new_id)),
459 ))
460}
461
462async fn fix_sketch_tags_and_references(
464 new_sketch: &mut Sketch,
465 entity_id_map: &HashMap<uuid::Uuid, uuid::Uuid>,
466 exec_state: &mut ExecState,
467) -> Result<()> {
468 for path in new_sketch.paths.as_mut_slice() {
470 if let Some(new_path_id) = entity_id_map.get(&path.get_id()) {
471 path.set_id(*new_path_id);
472 } else {
473 crate::log::logln!("Failed to find new path id for old path id: {:?}", path.get_id());
476 }
477 }
478
479 for path in new_sketch.paths.clone() {
483 if let Some(tag) = path.get_tag() {
485 new_sketch.add_tag(&tag, &path, exec_state);
486 }
487 }
488
489 if let Some(new_base_path) = entity_id_map.get(&new_sketch.start.geo_meta.id) {
491 new_sketch.start.geo_meta.id = *new_base_path;
492 } else {
493 crate::log::logln!(
494 "Failed to find new base path id for old base path id: {:?}",
495 new_sketch.start.geo_meta.id
496 );
497 }
498
499 Ok(())
500}
501
502fn get_named_cap_tags(solid: &Solid) -> (Option<TagNode>, Option<TagNode>) {
504 let mut start_tag = None;
505 let mut end_tag = None;
506 if let Some(start_cap_id) = solid.start_cap_id {
508 for value in &solid.value {
510 if value.get_id() == start_cap_id {
511 start_tag = value.get_tag().clone();
512 break;
513 }
514 }
515 }
516
517 if let Some(end_cap_id) = solid.end_cap_id {
519 for value in &solid.value {
521 if value.get_id() == end_cap_id {
522 end_tag = value.get_tag().clone();
523 break;
524 }
525 }
526 }
527
528 (start_tag, end_tag)
529}
530
531#[cfg(test)]
532mod tests {
533 use pretty_assertions::{assert_eq, assert_ne};
534
535 use crate::exec::KclValue;
536
537 #[tokio::test(flavor = "multi_thread")]
540 async fn kcl_test_clone_sketch() {
541 let code = r#"cube = startSketchOn(XY)
542 |> startProfile(at = [0,0])
543 |> line(end = [0, 10])
544 |> line(end = [10, 0])
545 |> line(end = [0, -10])
546 |> close()
547
548clonedCube = clone(cube)
549"#;
550 let ctx = crate::test_server::new_context(true, None).await.unwrap();
551 let program = crate::Program::parse_no_errs(code).unwrap();
552
553 let result = ctx.run_with_caching(program.clone()).await.unwrap();
555 let cube = result.variables.get("cube").unwrap();
556 let cloned_cube = result.variables.get("clonedCube").unwrap();
557
558 assert_ne!(cube, cloned_cube);
559
560 let KclValue::Sketch { value: cube } = cube else {
561 panic!("Expected a sketch, got: {:?}", cube);
562 };
563 let KclValue::Sketch { value: cloned_cube } = cloned_cube else {
564 panic!("Expected a sketch, got: {:?}", cloned_cube);
565 };
566
567 assert_ne!(cube.id, cloned_cube.id);
568 assert_ne!(cube.original_id, cloned_cube.original_id);
569 #[cfg(feature = "artifact-graph")]
570 assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
571
572 #[cfg(feature = "artifact-graph")]
573 assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
574 assert_eq!(cloned_cube.original_id, cloned_cube.id);
575
576 for (path, cloned_path) in cube.paths.iter().zip(cloned_cube.paths.iter()) {
577 assert_ne!(path.get_id(), cloned_path.get_id());
578 assert_eq!(path.get_tag(), cloned_path.get_tag());
579 }
580
581 assert_eq!(cube.tags.len(), 0);
582 assert_eq!(cloned_cube.tags.len(), 0);
583
584 ctx.close().await;
585 }
586
587 #[tokio::test(flavor = "multi_thread")]
590 async fn kcl_test_clone_solid() {
591 let code = r#"cube = startSketchOn(XY)
592 |> startProfile(at = [0,0])
593 |> line(end = [0, 10])
594 |> line(end = [10, 0])
595 |> line(end = [0, -10])
596 |> close()
597 |> extrude(length = 5)
598
599clonedCube = clone(cube)
600"#;
601 let ctx = crate::test_server::new_context(true, None).await.unwrap();
602 let program = crate::Program::parse_no_errs(code).unwrap();
603
604 let result = ctx.run_with_caching(program.clone()).await.unwrap();
606 let cube = result.variables.get("cube").unwrap();
607 let cloned_cube = result.variables.get("clonedCube").unwrap();
608
609 assert_ne!(cube, cloned_cube);
610
611 let KclValue::Solid { value: cube } = cube else {
612 panic!("Expected a solid, got: {:?}", cube);
613 };
614 let KclValue::Solid { value: cloned_cube } = cloned_cube else {
615 panic!("Expected a solid, got: {:?}", cloned_cube);
616 };
617
618 assert_ne!(cube.id, cloned_cube.id);
619 assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
620 assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
621 #[cfg(feature = "artifact-graph")]
622 assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
623 #[cfg(feature = "artifact-graph")]
624 assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
625
626 #[cfg(feature = "artifact-graph")]
627 assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
628
629 for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
630 assert_ne!(path.get_id(), cloned_path.get_id());
631 assert_eq!(path.get_tag(), cloned_path.get_tag());
632 }
633
634 for (value, cloned_value) in cube.value.iter().zip(cloned_cube.value.iter()) {
635 assert_ne!(value.get_id(), cloned_value.get_id());
636 assert_eq!(value.get_tag(), cloned_value.get_tag());
637 }
638
639 assert_eq!(cube.sketch.tags.len(), 0);
640 assert_eq!(cloned_cube.sketch.tags.len(), 0);
641
642 assert_eq!(cube.edge_cuts.len(), 0);
643 assert_eq!(cloned_cube.edge_cuts.len(), 0);
644
645 ctx.close().await;
646 }
647
648 #[tokio::test(flavor = "multi_thread")]
652 async fn kcl_test_clone_sketch_with_tags() {
653 let code = r#"cube = startSketchOn(XY)
654 |> startProfile(at = [0,0]) // tag this one
655 |> line(end = [0, 10], tag = $tag02)
656 |> line(end = [10, 0], tag = $tag03)
657 |> line(end = [0, -10], tag = $tag04)
658 |> close(tag = $tag05)
659
660clonedCube = clone(cube)
661"#;
662 let ctx = crate::test_server::new_context(true, None).await.unwrap();
663 let program = crate::Program::parse_no_errs(code).unwrap();
664
665 let result = ctx.run_with_caching(program.clone()).await.unwrap();
667 let cube = result.variables.get("cube").unwrap();
668 let cloned_cube = result.variables.get("clonedCube").unwrap();
669
670 assert_ne!(cube, cloned_cube);
671
672 let KclValue::Sketch { value: cube } = cube else {
673 panic!("Expected a sketch, got: {:?}", cube);
674 };
675 let KclValue::Sketch { value: cloned_cube } = cloned_cube else {
676 panic!("Expected a sketch, got: {:?}", cloned_cube);
677 };
678
679 assert_ne!(cube.id, cloned_cube.id);
680 assert_ne!(cube.original_id, cloned_cube.original_id);
681
682 for (path, cloned_path) in cube.paths.iter().zip(cloned_cube.paths.iter()) {
683 assert_ne!(path.get_id(), cloned_path.get_id());
684 assert_eq!(path.get_tag(), cloned_path.get_tag());
685 }
686
687 for (tag_name, tag) in &cube.tags {
688 let cloned_tag = cloned_cube.tags.get(tag_name).unwrap();
689
690 let tag_info = tag.get_cur_info().unwrap();
691 let cloned_tag_info = cloned_tag.get_cur_info().unwrap();
692
693 assert_ne!(tag_info.id, cloned_tag_info.id);
694 assert_ne!(tag_info.sketch, cloned_tag_info.sketch);
695 assert_ne!(tag_info.path, cloned_tag_info.path);
696 assert_eq!(tag_info.surface, None);
697 assert_eq!(cloned_tag_info.surface, None);
698 }
699
700 ctx.close().await;
701 }
702
703 #[tokio::test(flavor = "multi_thread")]
707 async fn kcl_test_clone_solid_with_tags() {
708 let code = r#"cube = startSketchOn(XY)
709 |> startProfile(at = [0,0]) // tag this one
710 |> line(end = [0, 10], tag = $tag02)
711 |> line(end = [10, 0], tag = $tag03)
712 |> line(end = [0, -10], tag = $tag04)
713 |> close(tag = $tag05)
714 |> extrude(length = 5) // TODO: Tag these
715
716clonedCube = clone(cube)
717"#;
718 let ctx = crate::test_server::new_context(true, None).await.unwrap();
719 let program = crate::Program::parse_no_errs(code).unwrap();
720
721 let result = ctx.run_with_caching(program.clone()).await.unwrap();
723 let cube = result.variables.get("cube").unwrap();
724 let cloned_cube = result.variables.get("clonedCube").unwrap();
725
726 assert_ne!(cube, cloned_cube);
727
728 let KclValue::Solid { value: cube } = cube else {
729 panic!("Expected a solid, got: {:?}", cube);
730 };
731 let KclValue::Solid { value: cloned_cube } = cloned_cube else {
732 panic!("Expected a solid, got: {:?}", cloned_cube);
733 };
734
735 assert_ne!(cube.id, cloned_cube.id);
736 assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
737 assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
738 #[cfg(feature = "artifact-graph")]
739 assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
740 #[cfg(feature = "artifact-graph")]
741 assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
742
743 #[cfg(feature = "artifact-graph")]
744 assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
745
746 for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
747 assert_ne!(path.get_id(), cloned_path.get_id());
748 assert_eq!(path.get_tag(), cloned_path.get_tag());
749 }
750
751 for (value, cloned_value) in cube.value.iter().zip(cloned_cube.value.iter()) {
752 assert_ne!(value.get_id(), cloned_value.get_id());
753 assert_eq!(value.get_tag(), cloned_value.get_tag());
754 }
755
756 for (tag_name, tag) in &cube.sketch.tags {
757 let cloned_tag = cloned_cube.sketch.tags.get(tag_name).unwrap();
758
759 let tag_info = tag.get_cur_info().unwrap();
760 let cloned_tag_info = cloned_tag.get_cur_info().unwrap();
761
762 assert_ne!(tag_info.id, cloned_tag_info.id);
763 assert_ne!(tag_info.sketch, cloned_tag_info.sketch);
764 assert_ne!(tag_info.path, cloned_tag_info.path);
765 assert_ne!(tag_info.surface, cloned_tag_info.surface);
766 }
767
768 assert_eq!(cube.edge_cuts.len(), 0);
769 assert_eq!(cloned_cube.edge_cuts.len(), 0);
770
771 ctx.close().await;
772 }
773
774 #[tokio::test(flavor = "multi_thread")]
776 #[ignore = "this test is not working yet, need to fix the getting of ids if sketch already closed"]
777 async fn kcl_test_clone_cube_already_closed_sketch() {
778 let code = r#"// Clone a basic solid and move it.
779
780exampleSketch = startSketchOn(XY)
781 |> startProfile(at = [0, 0])
782 |> line(end = [10, 0])
783 |> line(end = [0, 10])
784 |> line(end = [-10, 0])
785 |> line(end = [0, -10])
786 |> close()
787
788cube = extrude(exampleSketch, length = 5)
789clonedCube = clone(cube)
790 |> translate(
791 x = 25.0,
792 )"#;
793 let ctx = crate::test_server::new_context(true, None).await.unwrap();
794 let program = crate::Program::parse_no_errs(code).unwrap();
795
796 let result = ctx.run_with_caching(program.clone()).await.unwrap();
798 let cube = result.variables.get("cube").unwrap();
799 let cloned_cube = result.variables.get("clonedCube").unwrap();
800
801 assert_ne!(cube, cloned_cube);
802
803 let KclValue::Solid { value: cube } = cube else {
804 panic!("Expected a solid, got: {:?}", cube);
805 };
806 let KclValue::Solid { value: cloned_cube } = cloned_cube else {
807 panic!("Expected a solid, got: {:?}", cloned_cube);
808 };
809
810 assert_ne!(cube.id, cloned_cube.id);
811 assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
812 assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
813 #[cfg(feature = "artifact-graph")]
814 assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
815 #[cfg(feature = "artifact-graph")]
816 assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
817
818 #[cfg(feature = "artifact-graph")]
819 assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
820
821 for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
822 assert_ne!(path.get_id(), cloned_path.get_id());
823 assert_eq!(path.get_tag(), cloned_path.get_tag());
824 }
825
826 for (value, cloned_value) in cube.value.iter().zip(cloned_cube.value.iter()) {
827 assert_ne!(value.get_id(), cloned_value.get_id());
828 assert_eq!(value.get_tag(), cloned_value.get_tag());
829 }
830
831 for (tag_name, tag) in &cube.sketch.tags {
832 let cloned_tag = cloned_cube.sketch.tags.get(tag_name).unwrap();
833
834 let tag_info = tag.get_cur_info().unwrap();
835 let cloned_tag_info = cloned_tag.get_cur_info().unwrap();
836
837 assert_ne!(tag_info.id, cloned_tag_info.id);
838 assert_ne!(tag_info.sketch, cloned_tag_info.sketch);
839 assert_ne!(tag_info.path, cloned_tag_info.path);
840 assert_ne!(tag_info.surface, cloned_tag_info.surface);
841 }
842
843 for (edge_cut, cloned_edge_cut) in cube.edge_cuts.iter().zip(cloned_cube.edge_cuts.iter()) {
844 assert_ne!(edge_cut.id(), cloned_edge_cut.id());
845 assert_ne!(edge_cut.edge_id(), cloned_edge_cut.edge_id());
846 assert_eq!(edge_cut.tag(), cloned_edge_cut.tag());
847 }
848
849 ctx.close().await;
850 }
851
852 #[tokio::test(flavor = "multi_thread")]
856 async fn kcl_test_clone_solid_with_edge_cuts() {
857 let code = r#"cube = startSketchOn(XY)
858 |> startProfile(at = [0,0]) // tag this one
859 |> line(end = [0, 10], tag = $tag02)
860 |> line(end = [10, 0], tag = $tag03)
861 |> line(end = [0, -10], tag = $tag04)
862 |> close(tag = $tag05)
863 |> extrude(length = 5) // TODO: Tag these
864 |> fillet(
865 radius = 2,
866 tags = [
867 getNextAdjacentEdge(tag02),
868 ],
869 tag = $fillet01,
870 )
871 |> fillet(
872 radius = 2,
873 tags = [
874 getNextAdjacentEdge(tag04),
875 ],
876 tag = $fillet02,
877 )
878 |> chamfer(
879 length = 2,
880 tags = [
881 getNextAdjacentEdge(tag03),
882 ],
883 tag = $chamfer01,
884 )
885 |> chamfer(
886 length = 2,
887 tags = [
888 getNextAdjacentEdge(tag05),
889 ],
890 tag = $chamfer02,
891 )
892
893clonedCube = clone(cube)
894"#;
895 let ctx = crate::test_server::new_context(true, None).await.unwrap();
896 let program = crate::Program::parse_no_errs(code).unwrap();
897
898 let result = ctx.run_with_caching(program.clone()).await.unwrap();
900 let cube = result.variables.get("cube").unwrap();
901 let cloned_cube = result.variables.get("clonedCube").unwrap();
902
903 assert_ne!(cube, cloned_cube);
904
905 let KclValue::Solid { value: cube } = cube else {
906 panic!("Expected a solid, got: {:?}", cube);
907 };
908 let KclValue::Solid { value: cloned_cube } = cloned_cube else {
909 panic!("Expected a solid, got: {:?}", cloned_cube);
910 };
911
912 assert_ne!(cube.id, cloned_cube.id);
913 assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
914 assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
915 #[cfg(feature = "artifact-graph")]
916 assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
917 #[cfg(feature = "artifact-graph")]
918 assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
919
920 #[cfg(feature = "artifact-graph")]
921 assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
922
923 for (value, cloned_value) in cube.value.iter().zip(cloned_cube.value.iter()) {
924 assert_ne!(value.get_id(), cloned_value.get_id());
925 assert_eq!(value.get_tag(), cloned_value.get_tag());
926 }
927
928 for (edge_cut, cloned_edge_cut) in cube.edge_cuts.iter().zip(cloned_cube.edge_cuts.iter()) {
929 assert_ne!(edge_cut.id(), cloned_edge_cut.id());
930 assert_ne!(edge_cut.edge_id(), cloned_edge_cut.edge_id());
931 assert_eq!(edge_cut.tag(), cloned_edge_cut.tag());
932 }
933
934 ctx.close().await;
935 }
936}