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