1use std::collections::HashMap;
4
5use anyhow::Result;
6use kcmc::{
7 ModelingCmd, each_cmd as mcmd,
8 ok_response::{OkModelingCmdResponse, output::EntityGetAllChildUuids},
9 websocket::OkWebSocketResponseData,
10};
11use kittycad_modeling_cmds::{self as kcmc};
12
13use super::extrude::do_post_extrude;
14use crate::{
15 errors::{KclError, KclErrorDetails},
16 execution::{
17 ExecState, ExtrudeSurface, GeometryWithImportedGeometry, KclValue, ModelingCmdMeta, Sketch, Solid,
18 types::{PrimitiveType, RuntimeType},
19 },
20 parsing::ast::types::TagNode,
21 std::{Args, extrude::NamedCapTags},
22};
23
24pub async fn clone(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
28 let geometry = args.get_unlabeled_kw_arg(
29 "geometry",
30 &RuntimeType::Union(vec![
31 RuntimeType::Primitive(PrimitiveType::Sketch),
32 RuntimeType::Primitive(PrimitiveType::Solid),
33 RuntimeType::imported(),
34 ]),
35 exec_state,
36 )?;
37
38 let cloned = inner_clone(geometry, exec_state, args).await?;
39 Ok(cloned.into())
40}
41
42async fn inner_clone(
43 geometry: GeometryWithImportedGeometry,
44 exec_state: &mut ExecState,
45 args: Args,
46) -> Result<GeometryWithImportedGeometry, KclError> {
47 let new_id = exec_state.next_uuid();
48 let mut geometry = geometry.clone();
49 let old_id = geometry.id(&args.ctx).await?;
50
51 let mut new_geometry = match &geometry {
52 GeometryWithImportedGeometry::ImportedGeometry(imported) => {
53 let mut new_imported = imported.clone();
54 new_imported.id = new_id;
55 GeometryWithImportedGeometry::ImportedGeometry(new_imported)
56 }
57 GeometryWithImportedGeometry::Sketch(sketch) => {
58 let mut new_sketch = sketch.clone();
59 new_sketch.id = new_id;
60 new_sketch.original_id = new_id;
61 new_sketch.artifact_id = new_id.into();
62 GeometryWithImportedGeometry::Sketch(new_sketch)
63 }
64 GeometryWithImportedGeometry::Solid(solid) => {
65 exec_state
67 .flush_batch_for_solids((&args).into(), std::slice::from_ref(solid))
68 .await?;
69
70 let mut new_solid = solid.clone();
71 new_solid.id = new_id;
72 new_solid.sketch.original_id = new_id;
73 new_solid.artifact_id = new_id.into();
74 GeometryWithImportedGeometry::Solid(new_solid)
75 }
76 };
77
78 if args.ctx.no_engine_commands().await {
79 return Ok(new_geometry);
80 }
81
82 exec_state
83 .batch_modeling_cmd(
84 ModelingCmdMeta::from_args_id(&args, new_id),
85 ModelingCmd::from(mcmd::EntityClone { entity_id: old_id }),
86 )
87 .await?;
88
89 fix_tags_and_references(&mut new_geometry, old_id, exec_state, &args)
90 .await
91 .map_err(|e| {
92 KclError::new_internal(KclErrorDetails::new(
93 format!("failed to fix tags and references: {e:?}"),
94 vec![args.source_range],
95 ))
96 })?;
97
98 Ok(new_geometry)
99}
100async fn fix_tags_and_references(
102 new_geometry: &mut GeometryWithImportedGeometry,
103 old_geometry_id: uuid::Uuid,
104 exec_state: &mut ExecState,
105 args: &Args,
106) -> Result<()> {
107 let new_geometry_id = new_geometry.id(&args.ctx).await?;
108 let entity_id_map = get_old_new_child_map(new_geometry_id, old_geometry_id, exec_state, args).await?;
109
110 match new_geometry {
112 GeometryWithImportedGeometry::ImportedGeometry(_) => {}
113 GeometryWithImportedGeometry::Sketch(sketch) => {
114 fix_sketch_tags_and_references(sketch, &entity_id_map, exec_state, None).await?;
115 }
116 GeometryWithImportedGeometry::Solid(solid) => {
117 solid.sketch.id = new_geometry_id;
119 solid.sketch.original_id = new_geometry_id;
120 solid.sketch.artifact_id = new_geometry_id.into();
121
122 fix_sketch_tags_and_references(&mut solid.sketch, &entity_id_map, exec_state, Some(solid.value.clone()))
123 .await?;
124
125 let (start_tag, end_tag) = get_named_cap_tags(solid);
126
127 for edge_cut in solid.edge_cuts.iter_mut() {
129 if let Some(id) = entity_id_map.get(&edge_cut.id()) {
130 edge_cut.set_id(*id);
131 } else {
132 crate::log::logln!(
133 "Failed to find new edge cut id for old edge cut id: {:?}",
134 edge_cut.id()
135 );
136 }
137 if let Some(new_edge_id) = entity_id_map.get(&edge_cut.edge_id()) {
138 edge_cut.set_edge_id(*new_edge_id);
139 } else {
140 crate::log::logln!("Failed to find new edge id for old edge id: {:?}", edge_cut.edge_id());
141 }
142 }
143
144 let new_solid = do_post_extrude(
147 &solid.sketch,
148 new_geometry_id.into(),
149 solid.sectional,
150 &NamedCapTags {
151 start: start_tag.as_ref(),
152 end: end_tag.as_ref(),
153 },
154 kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
155 exec_state,
156 args,
157 None,
158 )
159 .await?;
160
161 *solid = new_solid;
162 }
163 }
164
165 Ok(())
166}
167
168async fn get_old_new_child_map(
169 new_geometry_id: uuid::Uuid,
170 old_geometry_id: uuid::Uuid,
171 exec_state: &mut ExecState,
172 args: &Args,
173) -> Result<HashMap<uuid::Uuid, uuid::Uuid>> {
174 let response = exec_state
176 .send_modeling_cmd(
177 args.into(),
178 ModelingCmd::from(mcmd::EntityGetAllChildUuids {
179 entity_id: old_geometry_id,
180 }),
181 )
182 .await?;
183 let OkWebSocketResponseData::Modeling {
184 modeling_response:
185 OkModelingCmdResponse::EntityGetAllChildUuids(EntityGetAllChildUuids {
186 entity_ids: old_entity_ids,
187 }),
188 } = response
189 else {
190 anyhow::bail!("Expected EntityGetAllChildUuids response, got: {:?}", response);
191 };
192
193 let response = exec_state
195 .send_modeling_cmd(
196 args.into(),
197 ModelingCmd::from(mcmd::EntityGetAllChildUuids {
198 entity_id: new_geometry_id,
199 }),
200 )
201 .await?;
202 let OkWebSocketResponseData::Modeling {
203 modeling_response:
204 OkModelingCmdResponse::EntityGetAllChildUuids(EntityGetAllChildUuids {
205 entity_ids: new_entity_ids,
206 }),
207 } = response
208 else {
209 anyhow::bail!("Expected EntityGetAllChildUuids response, got: {:?}", response);
210 };
211
212 Ok(HashMap::from_iter(
214 old_entity_ids
215 .iter()
216 .zip(new_entity_ids.iter())
217 .map(|(old_id, new_id)| (*old_id, *new_id)),
218 ))
219}
220
221async fn fix_sketch_tags_and_references(
223 new_sketch: &mut Sketch,
224 entity_id_map: &HashMap<uuid::Uuid, uuid::Uuid>,
225 exec_state: &mut ExecState,
226 surfaces: Option<Vec<ExtrudeSurface>>,
227) -> Result<()> {
228 for path in new_sketch.paths.as_mut_slice() {
230 if let Some(new_path_id) = entity_id_map.get(&path.get_id()) {
231 path.set_id(*new_path_id);
232 } else {
233 crate::log::logln!("Failed to find new path id for old path id: {:?}", path.get_id());
236 }
237 }
238
239 let mut surface_id_map: HashMap<String, &ExtrudeSurface> = HashMap::new();
241 let surfaces = surfaces.unwrap_or_default();
242 for surface in surfaces.iter() {
243 if let Some(tag) = surface.get_tag() {
244 surface_id_map.insert(tag.name.clone(), surface);
245 }
246 }
247
248 for path in new_sketch.paths.clone() {
252 if let Some(tag) = path.get_tag() {
254 let mut surface = None;
255 if let Some(found_surface) = surface_id_map.get(&tag.name) {
256 let mut new_surface = (*found_surface).clone();
257 let Some(new_face_id) = entity_id_map.get(&new_surface.face_id()).copied() else {
258 anyhow::bail!(
259 "Failed to find new face id for old face id: {:?}",
260 new_surface.face_id()
261 )
262 };
263 new_surface.set_face_id(new_face_id);
264 surface = Some(new_surface);
265 }
266
267 new_sketch.add_tag(&tag, &path, exec_state, surface.as_ref());
268 }
269 }
270
271 if let Some(new_base_path) = entity_id_map.get(&new_sketch.start.geo_meta.id) {
273 new_sketch.start.geo_meta.id = *new_base_path;
274 } else {
275 crate::log::logln!(
276 "Failed to find new base path id for old base path id: {:?}",
277 new_sketch.start.geo_meta.id
278 );
279 }
280
281 Ok(())
282}
283
284fn get_named_cap_tags(solid: &Solid) -> (Option<TagNode>, Option<TagNode>) {
286 let mut start_tag = None;
287 let mut end_tag = None;
288 if let Some(start_cap_id) = solid.start_cap_id {
290 for value in &solid.value {
292 if value.get_id() == start_cap_id {
293 start_tag = value.get_tag().clone();
294 break;
295 }
296 }
297 }
298
299 if let Some(end_cap_id) = solid.end_cap_id {
301 for value in &solid.value {
303 if value.get_id() == end_cap_id {
304 end_tag = value.get_tag().clone();
305 break;
306 }
307 }
308 }
309
310 (start_tag, end_tag)
311}
312
313#[cfg(test)]
314mod tests {
315 use pretty_assertions::{assert_eq, assert_ne};
316
317 use crate::exec::KclValue;
318
319 #[tokio::test(flavor = "multi_thread")]
322 async fn kcl_test_clone_sketch() {
323 let code = r#"cube = startSketchOn(XY)
324 |> startProfile(at = [0,0])
325 |> line(end = [0, 10])
326 |> line(end = [10, 0])
327 |> line(end = [0, -10])
328 |> close()
329
330clonedCube = clone(cube)
331"#;
332 let ctx = crate::test_server::new_context(true, None).await.unwrap();
333 let program = crate::Program::parse_no_errs(code).unwrap();
334
335 let result = ctx.run_with_caching(program.clone()).await.unwrap();
337 let cube = result.variables.get("cube").unwrap();
338 let cloned_cube = result.variables.get("clonedCube").unwrap();
339
340 assert_ne!(cube, cloned_cube);
341
342 let KclValue::Sketch { value: cube } = cube else {
343 panic!("Expected a sketch, got: {cube:?}");
344 };
345 let KclValue::Sketch { value: cloned_cube } = cloned_cube else {
346 panic!("Expected a sketch, got: {cloned_cube:?}");
347 };
348
349 assert_ne!(cube.id, cloned_cube.id);
350 assert_ne!(cube.original_id, cloned_cube.original_id);
351 assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
352
353 assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
354 assert_eq!(cloned_cube.original_id, cloned_cube.id);
355
356 for (path, cloned_path) in cube.paths.iter().zip(cloned_cube.paths.iter()) {
357 assert_ne!(path.get_id(), cloned_path.get_id());
358 assert_eq!(path.get_tag(), cloned_path.get_tag());
359 }
360
361 assert_eq!(cube.tags.len(), 0);
362 assert_eq!(cloned_cube.tags.len(), 0);
363
364 ctx.close().await;
365 }
366
367 #[tokio::test(flavor = "multi_thread")]
370 async fn kcl_test_clone_solid() {
371 let code = r#"cube = startSketchOn(XY)
372 |> startProfile(at = [0,0])
373 |> line(end = [0, 10])
374 |> line(end = [10, 0])
375 |> line(end = [0, -10])
376 |> close()
377 |> extrude(length = 5)
378
379clonedCube = clone(cube)
380"#;
381 let ctx = crate::test_server::new_context(true, None).await.unwrap();
382 let program = crate::Program::parse_no_errs(code).unwrap();
383
384 let result = ctx.run_with_caching(program.clone()).await.unwrap();
386 let cube = result.variables.get("cube").unwrap();
387 let cloned_cube = result.variables.get("clonedCube").unwrap();
388
389 assert_ne!(cube, cloned_cube);
390
391 let KclValue::Solid { value: cube } = cube else {
392 panic!("Expected a solid, got: {cube:?}");
393 };
394 let KclValue::Solid { value: cloned_cube } = cloned_cube else {
395 panic!("Expected a solid, got: {cloned_cube:?}");
396 };
397
398 assert_ne!(cube.id, cloned_cube.id);
399 assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
400 assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
401 assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
402 assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
403
404 assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
405
406 for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
407 assert_ne!(path.get_id(), cloned_path.get_id());
408 assert_eq!(path.get_tag(), cloned_path.get_tag());
409 }
410
411 for (value, cloned_value) in cube.value.iter().zip(cloned_cube.value.iter()) {
412 assert_ne!(value.get_id(), cloned_value.get_id());
413 assert_eq!(value.get_tag(), cloned_value.get_tag());
414 }
415
416 assert_eq!(cube.sketch.tags.len(), 0);
417 assert_eq!(cloned_cube.sketch.tags.len(), 0);
418
419 assert_eq!(cube.edge_cuts.len(), 0);
420 assert_eq!(cloned_cube.edge_cuts.len(), 0);
421
422 ctx.close().await;
423 }
424
425 #[tokio::test(flavor = "multi_thread")]
429 async fn kcl_test_clone_sketch_with_tags() {
430 let code = r#"cube = startSketchOn(XY)
431 |> startProfile(at = [0,0]) // tag this one
432 |> line(end = [0, 10], tag = $tag02)
433 |> line(end = [10, 0], tag = $tag03)
434 |> line(end = [0, -10], tag = $tag04)
435 |> close(tag = $tag05)
436
437clonedCube = clone(cube)
438"#;
439 let ctx = crate::test_server::new_context(true, None).await.unwrap();
440 let program = crate::Program::parse_no_errs(code).unwrap();
441
442 let result = ctx.run_with_caching(program.clone()).await.unwrap();
444 let cube = result.variables.get("cube").unwrap();
445 let cloned_cube = result.variables.get("clonedCube").unwrap();
446
447 assert_ne!(cube, cloned_cube);
448
449 let KclValue::Sketch { value: cube } = cube else {
450 panic!("Expected a sketch, got: {cube:?}");
451 };
452 let KclValue::Sketch { value: cloned_cube } = cloned_cube else {
453 panic!("Expected a sketch, got: {cloned_cube:?}");
454 };
455
456 assert_ne!(cube.id, cloned_cube.id);
457 assert_ne!(cube.original_id, cloned_cube.original_id);
458
459 for (path, cloned_path) in cube.paths.iter().zip(cloned_cube.paths.iter()) {
460 assert_ne!(path.get_id(), cloned_path.get_id());
461 assert_eq!(path.get_tag(), cloned_path.get_tag());
462 }
463
464 for (tag_name, tag) in &cube.tags {
465 let cloned_tag = cloned_cube.tags.get(tag_name).unwrap();
466
467 let tag_info = tag.get_cur_info().unwrap();
468 let cloned_tag_info = cloned_tag.get_cur_info().unwrap();
469
470 assert_ne!(tag_info.id, cloned_tag_info.id);
471 assert_ne!(tag_info.sketch, cloned_tag_info.sketch);
472 assert_ne!(tag_info.path, cloned_tag_info.path);
473 assert_eq!(tag_info.surface, None);
474 assert_eq!(cloned_tag_info.surface, None);
475 }
476
477 ctx.close().await;
478 }
479
480 #[tokio::test(flavor = "multi_thread")]
484 async fn kcl_test_clone_solid_with_tags() {
485 let code = r#"cube = startSketchOn(XY)
486 |> startProfile(at = [0,0]) // tag this one
487 |> line(end = [0, 10], tag = $tag02)
488 |> line(end = [10, 0], tag = $tag03)
489 |> line(end = [0, -10], tag = $tag04)
490 |> close(tag = $tag05)
491 |> extrude(length = 5) // TODO: Tag these
492
493clonedCube = clone(cube)
494"#;
495 let ctx = crate::test_server::new_context(true, None).await.unwrap();
496 let program = crate::Program::parse_no_errs(code).unwrap();
497
498 let result = ctx.run_with_caching(program.clone()).await.unwrap();
500 let cube = result.variables.get("cube").unwrap();
501 let cloned_cube = result.variables.get("clonedCube").unwrap();
502
503 assert_ne!(cube, cloned_cube);
504
505 let KclValue::Solid { value: cube } = cube else {
506 panic!("Expected a solid, got: {cube:?}");
507 };
508 let KclValue::Solid { value: cloned_cube } = cloned_cube else {
509 panic!("Expected a solid, got: {cloned_cube:?}");
510 };
511
512 assert_ne!(cube.id, cloned_cube.id);
513 assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
514 assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
515 assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
516 assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
517
518 assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
519
520 for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
521 assert_ne!(path.get_id(), cloned_path.get_id());
522 assert_eq!(path.get_tag(), cloned_path.get_tag());
523 }
524
525 for (value, cloned_value) in cube.value.iter().zip(cloned_cube.value.iter()) {
526 assert_ne!(value.get_id(), cloned_value.get_id());
527 assert_eq!(value.get_tag(), cloned_value.get_tag());
528 }
529
530 for (tag_name, tag) in &cube.sketch.tags {
531 let cloned_tag = cloned_cube.sketch.tags.get(tag_name).unwrap();
532
533 let tag_info = tag.get_cur_info().unwrap();
534 let cloned_tag_info = cloned_tag.get_cur_info().unwrap();
535
536 assert_ne!(tag_info.id, cloned_tag_info.id);
537 assert_ne!(tag_info.sketch, cloned_tag_info.sketch);
538 assert_ne!(tag_info.path, cloned_tag_info.path);
539 assert_ne!(tag_info.surface, cloned_tag_info.surface);
540 }
541
542 assert_eq!(cube.edge_cuts.len(), 0);
543 assert_eq!(cloned_cube.edge_cuts.len(), 0);
544
545 ctx.close().await;
546 }
547
548 #[tokio::test(flavor = "multi_thread")]
550 #[ignore = "this test is not working yet, need to fix the getting of ids if sketch already closed"]
551 async fn kcl_test_clone_cube_already_closed_sketch() {
552 let code = r#"// Clone a basic solid and move it.
553
554exampleSketch = startSketchOn(XY)
555 |> startProfile(at = [0, 0])
556 |> line(end = [10, 0])
557 |> line(end = [0, 10])
558 |> line(end = [-10, 0])
559 |> line(end = [0, -10])
560 |> close()
561
562cube = extrude(exampleSketch, length = 5)
563clonedCube = clone(cube)
564 |> translate(
565 x = 25.0,
566 )"#;
567 let ctx = crate::test_server::new_context(true, None).await.unwrap();
568 let program = crate::Program::parse_no_errs(code).unwrap();
569
570 let result = ctx.run_with_caching(program.clone()).await.unwrap();
572 let cube = result.variables.get("cube").unwrap();
573 let cloned_cube = result.variables.get("clonedCube").unwrap();
574
575 assert_ne!(cube, cloned_cube);
576
577 let KclValue::Solid { value: cube } = cube else {
578 panic!("Expected a solid, got: {cube:?}");
579 };
580 let KclValue::Solid { value: cloned_cube } = cloned_cube else {
581 panic!("Expected a solid, got: {cloned_cube:?}");
582 };
583
584 assert_ne!(cube.id, cloned_cube.id);
585 assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
586 assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
587 assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
588 assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
589
590 assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
591
592 for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
593 assert_ne!(path.get_id(), cloned_path.get_id());
594 assert_eq!(path.get_tag(), cloned_path.get_tag());
595 }
596
597 for (value, cloned_value) in cube.value.iter().zip(cloned_cube.value.iter()) {
598 assert_ne!(value.get_id(), cloned_value.get_id());
599 assert_eq!(value.get_tag(), cloned_value.get_tag());
600 }
601
602 for (tag_name, tag) in &cube.sketch.tags {
603 let cloned_tag = cloned_cube.sketch.tags.get(tag_name).unwrap();
604
605 let tag_info = tag.get_cur_info().unwrap();
606 let cloned_tag_info = cloned_tag.get_cur_info().unwrap();
607
608 assert_ne!(tag_info.id, cloned_tag_info.id);
609 assert_ne!(tag_info.sketch, cloned_tag_info.sketch);
610 assert_ne!(tag_info.path, cloned_tag_info.path);
611 assert_ne!(tag_info.surface, cloned_tag_info.surface);
612 }
613
614 for (edge_cut, cloned_edge_cut) in cube.edge_cuts.iter().zip(cloned_cube.edge_cuts.iter()) {
615 assert_ne!(edge_cut.id(), cloned_edge_cut.id());
616 assert_ne!(edge_cut.edge_id(), cloned_edge_cut.edge_id());
617 assert_eq!(edge_cut.tag(), cloned_edge_cut.tag());
618 }
619
620 ctx.close().await;
621 }
622
623 #[tokio::test(flavor = "multi_thread")]
627 #[ignore] async fn kcl_test_clone_solid_with_edge_cuts() {
629 let code = r#"cube = startSketchOn(XY)
630 |> startProfile(at = [0,0]) // tag this one
631 |> line(end = [0, 10], tag = $tag02)
632 |> line(end = [10, 0], tag = $tag03)
633 |> line(end = [0, -10], tag = $tag04)
634 |> close(tag = $tag05)
635 |> extrude(length = 5) // TODO: Tag these
636 |> fillet(
637 radius = 2,
638 tags = [
639 getNextAdjacentEdge(tag02),
640 ],
641 tag = $fillet01,
642 )
643 |> fillet(
644 radius = 2,
645 tags = [
646 getNextAdjacentEdge(tag04),
647 ],
648 tag = $fillet02,
649 )
650 |> chamfer(
651 length = 2,
652 tags = [
653 getNextAdjacentEdge(tag03),
654 ],
655 tag = $chamfer01,
656 )
657 |> chamfer(
658 length = 2,
659 tags = [
660 getNextAdjacentEdge(tag05),
661 ],
662 tag = $chamfer02,
663 )
664
665clonedCube = clone(cube)
666"#;
667 let ctx = crate::test_server::new_context(true, None).await.unwrap();
668 let program = crate::Program::parse_no_errs(code).unwrap();
669
670 let result = ctx.run_with_caching(program.clone()).await.unwrap();
672 let cube = result.variables.get("cube").unwrap();
673 let cloned_cube = result.variables.get("clonedCube").unwrap();
674
675 assert_ne!(cube, cloned_cube);
676
677 let KclValue::Solid { value: cube } = cube else {
678 panic!("Expected a solid, got: {cube:?}");
679 };
680 let KclValue::Solid { value: cloned_cube } = cloned_cube else {
681 panic!("Expected a solid, got: {cloned_cube:?}");
682 };
683
684 assert_ne!(cube.id, cloned_cube.id);
685 assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
686 assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
687 assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
688 assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
689
690 assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
691
692 for (value, cloned_value) in cube.value.iter().zip(cloned_cube.value.iter()) {
693 assert_ne!(value.get_id(), cloned_value.get_id());
694 assert_eq!(value.get_tag(), cloned_value.get_tag());
695 }
696
697 for (edge_cut, cloned_edge_cut) in cube.edge_cuts.iter().zip(cloned_cube.edge_cuts.iter()) {
698 assert_ne!(edge_cut.id(), cloned_edge_cut.id());
699 assert_ne!(edge_cut.edge_id(), cloned_edge_cut.edge_id());
700 assert_eq!(edge_cut.tag(), cloned_edge_cut.tag());
701 }
702
703 ctx.close().await;
704 }
705}