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