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