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