1#![allow(clippy::cast_possible_truncation)]
50#![allow(clippy::cast_sign_loss)]
51#![allow(clippy::cast_precision_loss)]
52#![allow(clippy::missing_errors_doc)]
54#![allow(clippy::missing_panics_doc)]
55#![allow(clippy::too_many_lines)]
57#![allow(clippy::items_after_statements)]
59#![allow(clippy::too_many_arguments)]
61#![allow(clippy::unused_self)]
63#![allow(clippy::needless_pass_by_value)]
65#![allow(clippy::many_single_char_names)]
67#![allow(clippy::struct_excessive_bools)]
69#![allow(clippy::must_use_candidate)]
72#![allow(clippy::doc_markdown)]
74
75macro_rules! impl_structure_accessors {
78 (
79 get_fn = $get_fn:ident,
80 with_fn = $with_fn:ident,
81 with_ref_fn = $with_ref_fn:ident,
82 handle = $handle:ident,
83 type_name = $type_name:expr,
84 rust_type = $rust_type:ty,
85 doc_name = $doc_name:expr
86 ) => {
87 #[doc = concat!("Gets a registered ", $doc_name, " by name.")]
88 #[must_use]
89 pub fn $get_fn(name: &str) -> Option<$handle> {
90 crate::with_context(|ctx| {
91 if ctx.registry.contains($type_name, name) {
92 Some($handle {
93 name: name.to_string(),
94 })
95 } else {
96 None
97 }
98 })
99 }
100
101 #[doc = concat!("Executes a closure with mutable access to a registered ", $doc_name, ".\n\nReturns `None` if the ", $doc_name, " does not exist.")]
102 pub fn $with_fn<F, R>(name: &str, f: F) -> Option<R>
103 where
104 F: FnOnce(&mut $rust_type) -> R,
105 {
106 crate::with_context_mut(|ctx| {
107 ctx.registry
108 .get_mut($type_name, name)
109 .and_then(|s| s.as_any_mut().downcast_mut::<$rust_type>())
110 .map(f)
111 })
112 }
113
114 #[doc = concat!("Executes a closure with immutable access to a registered ", $doc_name, ".\n\nReturns `None` if the ", $doc_name, " does not exist.")]
115 pub fn $with_ref_fn<F, R>(name: &str, f: F) -> Option<R>
116 where
117 F: FnOnce(&$rust_type) -> R,
118 {
119 crate::with_context(|ctx| {
120 ctx.registry
121 .get($type_name, name)
122 .and_then(|s| s.as_any().downcast_ref::<$rust_type>())
123 .map(f)
124 })
125 }
126 };
127}
128
129mod app;
130mod camera_view;
131mod curve_network;
132mod floating;
133mod gizmo;
134mod groups;
135mod headless;
136mod init;
137mod point_cloud;
138mod screenshot;
139mod slice_plane;
140mod surface_mesh;
141mod transform;
142mod ui_sync;
143mod volume_grid;
144mod volume_mesh;
145
146pub use polyscope_core::{
148 Mat4, Vec2, Vec3, Vec4,
149 error::{PolyscopeError, Result},
150 gizmo::{GizmoAxis, GizmoConfig, GizmoMode, GizmoSpace, Transform},
151 group::Group,
152 options::Options,
153 pick::{PickResult, Pickable},
154 quantity::{ParamCoordsType, ParamVizStyle, Quantity, QuantityKind},
155 registry::Registry,
156 slice_plane::{MAX_SLICE_PLANES, SlicePlane, SlicePlaneUniforms},
157 state::{Context, with_context, with_context_mut},
158 structure::{HasQuantities, Structure},
159};
160
161pub use polyscope_render::{
163 AxisDirection, Camera, ColorMap, ColorMapRegistry, Material, MaterialRegistry, NavigationStyle,
164 PickElementType, ProjectionMode, RenderContext, RenderEngine, ScreenshotError,
165 ScreenshotOptions,
166};
167
168pub use polyscope_ui::{
170 AppearanceSettings, CameraSettings, GizmoAction, GizmoSettings, GroupSettings, GroupsAction,
171 SceneExtents, SelectionInfo, SlicePlaneGizmoAction, SlicePlaneSelectionInfo,
172 SlicePlaneSettings, SlicePlanesAction, ViewAction,
173};
174
175pub use polyscope_structures::volume_grid::VolumeGridVizMode;
177pub use polyscope_structures::{
178 CameraExtrinsics, CameraIntrinsics, CameraParameters, CameraView, CurveNetwork, PointCloud,
179 SurfaceMesh, VolumeCellType, VolumeGrid, VolumeMesh,
180};
181
182pub use camera_view::*;
184pub use curve_network::*;
185pub use floating::*;
186pub use gizmo::*;
187pub use groups::*;
188pub use headless::*;
189pub use init::*;
190pub use point_cloud::*;
191pub use screenshot::*;
192pub use slice_plane::*;
193pub use surface_mesh::*;
194pub use transform::*;
195pub use ui_sync::*;
196pub use volume_grid::*;
197pub use volume_mesh::*;
198
199pub fn remove_structure(name: &str) {
201 with_context_mut(|ctx| {
202 ctx.registry.remove("PointCloud", name);
204 ctx.registry.remove("SurfaceMesh", name);
205 ctx.registry.remove("CurveNetwork", name);
206 ctx.registry.remove("VolumeMesh", name);
207 ctx.registry.remove("VolumeGrid", name);
208 ctx.registry.remove("CameraView", name);
209 ctx.update_extents();
210 });
211}
212
213pub fn remove_all_structures() {
215 with_context_mut(|ctx| {
216 ctx.registry.clear();
217 ctx.update_extents();
218 });
219}
220
221pub fn remove_everything() {
228 remove_all_structures();
229 remove_all_groups();
230 remove_all_slice_planes();
231 remove_all_floating_quantities();
232 clear_file_drop_callback();
233}
234
235pub fn set_file_drop_callback(callback: impl FnMut(&[std::path::PathBuf]) + Send + Sync + 'static) {
248 with_context_mut(|ctx| {
249 ctx.file_drop_callback = Some(Box::new(callback));
250 });
251}
252
253pub fn clear_file_drop_callback() {
255 with_context_mut(|ctx| {
256 ctx.file_drop_callback = None;
257 });
258}
259
260pub fn load_blendable_material(name: &str, filenames: [&str; 4]) {
277 with_context_mut(|ctx| {
278 ctx.material_load_queue
279 .push(polyscope_core::state::MaterialLoadRequest::Blendable {
280 name: name.to_string(),
281 filenames: [
282 filenames[0].to_string(),
283 filenames[1].to_string(),
284 filenames[2].to_string(),
285 filenames[3].to_string(),
286 ],
287 });
288 });
289}
290
291pub fn load_blendable_material_ext(name: &str, base: &str, ext: &str) {
298 load_blendable_material(
299 name,
300 [
301 &format!("{base}_r{ext}"),
302 &format!("{base}_g{ext}"),
303 &format!("{base}_b{ext}"),
304 &format!("{base}_k{ext}"),
305 ],
306 );
307}
308
309pub fn load_static_material(name: &str, filename: &str) {
319 with_context_mut(|ctx| {
320 ctx.material_load_queue
321 .push(polyscope_core::state::MaterialLoadRequest::Static {
322 name: name.to_string(),
323 path: filename.to_string(),
324 });
325 });
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331 use std::sync::atomic::{AtomicU32, Ordering};
332
333 static COUNTER: AtomicU32 = AtomicU32::new(0);
335
336 fn unique_name(prefix: &str) -> String {
337 let n = COUNTER.fetch_add(1, Ordering::SeqCst);
338 format!("{prefix}_{n}")
339 }
340
341 fn setup() {
342 let _ = init();
345 }
346
347 #[test]
348 fn test_register_curve_network() {
349 setup();
350 let name = unique_name("test_cn");
351 let nodes = vec![
352 Vec3::new(0.0, 0.0, 0.0),
353 Vec3::new(1.0, 0.0, 0.0),
354 Vec3::new(1.0, 1.0, 0.0),
355 ];
356 let edges = vec![[0, 1], [1, 2]];
357
358 let handle = register_curve_network(&name, nodes, edges);
359 assert_eq!(handle.name(), name);
360
361 let found = get_curve_network(&name);
363 assert!(found.is_some());
364
365 let not_found = get_curve_network("nonexistent_xyz_123");
367 assert!(not_found.is_none());
368 }
369
370 #[test]
371 fn test_register_curve_network_line() {
372 setup();
373 let name = unique_name("line");
374 let nodes = vec![
375 Vec3::new(0.0, 0.0, 0.0),
376 Vec3::new(1.0, 0.0, 0.0),
377 Vec3::new(2.0, 0.0, 0.0),
378 Vec3::new(3.0, 0.0, 0.0),
379 ];
380
381 register_curve_network_line(&name, nodes);
382
383 let num_edges = with_curve_network_ref(&name, |cn| cn.num_edges());
384 assert_eq!(num_edges, Some(3)); }
386
387 #[test]
388 fn test_register_curve_network_loop() {
389 setup();
390 let name = unique_name("loop");
391 let nodes = vec![
392 Vec3::new(0.0, 0.0, 0.0),
393 Vec3::new(1.0, 0.0, 0.0),
394 Vec3::new(1.0, 1.0, 0.0),
395 ];
396
397 register_curve_network_loop(&name, nodes);
398
399 let num_edges = with_curve_network_ref(&name, |cn| cn.num_edges());
400 assert_eq!(num_edges, Some(3)); }
402
403 #[test]
404 fn test_register_curve_network_segments() {
405 setup();
406 let name = unique_name("segs");
407 let nodes = vec![
408 Vec3::new(0.0, 0.0, 0.0),
409 Vec3::new(1.0, 0.0, 0.0),
410 Vec3::new(2.0, 0.0, 0.0),
411 Vec3::new(3.0, 0.0, 0.0),
412 ];
413
414 register_curve_network_segments(&name, nodes);
415
416 let num_edges = with_curve_network_ref(&name, |cn| cn.num_edges());
417 assert_eq!(num_edges, Some(2)); }
419
420 #[test]
421 fn test_curve_network_handle_methods() {
422 setup();
423 let name = unique_name("handle_test");
424 let nodes = vec![Vec3::ZERO, Vec3::X];
425 let edges = vec![[0, 1]];
426
427 let handle = register_curve_network(&name, nodes, edges);
428
429 handle
431 .set_color(Vec3::new(1.0, 0.0, 0.0))
432 .set_radius(0.1, false)
433 .set_material("clay");
434
435 with_curve_network_ref(&name, |cn| {
437 assert_eq!(cn.color(), Vec4::new(1.0, 0.0, 0.0, 1.0));
438 assert_eq!(cn.radius(), 0.1);
439 assert!(!cn.radius_is_relative());
440 assert_eq!(cn.material(), "clay");
441 });
442 }
443
444 #[test]
445 fn test_with_curve_network() {
446 setup();
447 let name = unique_name("with_test");
448 let nodes = vec![Vec3::ZERO, Vec3::X, Vec3::Y];
449 let edges = vec![[0, 1], [1, 2]];
450
451 register_curve_network(&name, nodes, edges);
452
453 let result = with_curve_network(&name, |cn| {
455 cn.set_color(Vec3::new(0.5, 0.5, 0.5));
456 cn.num_nodes()
457 });
458 assert_eq!(result, Some(3));
459
460 let color = with_curve_network_ref(&name, |cn| cn.color());
462 assert_eq!(color, Some(Vec4::new(0.5, 0.5, 0.5, 1.0)));
463 }
464
465 #[test]
466 fn test_create_group() {
467 setup();
468 let name = unique_name("test_group");
469 let handle = create_group(&name);
470 assert_eq!(handle.name(), name);
471 assert!(handle.is_enabled());
472 }
473
474 #[test]
475 fn test_get_group() {
476 setup();
477 let name = unique_name("get_group");
478 create_group(&name);
479
480 let found = get_group(&name);
481 assert!(found.is_some());
482 assert_eq!(found.unwrap().name(), name);
483
484 let not_found = get_group("nonexistent_group_xyz");
485 assert!(not_found.is_none());
486 }
487
488 #[test]
489 fn test_group_enable_disable() {
490 setup();
491 let name = unique_name("enable_group");
492 let handle = create_group(&name);
493
494 assert!(handle.is_enabled());
495 handle.set_enabled(false);
496 assert!(!handle.is_enabled());
497 handle.set_enabled(true);
498 assert!(handle.is_enabled());
499 }
500
501 #[test]
502 fn test_group_add_structures() {
503 setup();
504 let group_name = unique_name("struct_group");
505 let pc_name = unique_name("pc_in_group");
506
507 register_point_cloud(&pc_name, vec![Vec3::ZERO, Vec3::X]);
509
510 let handle = create_group(&group_name);
512 handle.add_point_cloud(&pc_name);
513
514 assert_eq!(handle.num_structures(), 1);
515 }
516
517 #[test]
518 fn test_group_hierarchy() {
519 setup();
520 let parent_name = unique_name("parent_group");
521 let child_name = unique_name("child_group");
522
523 let parent = create_group(&parent_name);
524 let _child = create_group(&child_name);
525
526 parent.add_child_group(&child_name);
527
528 assert_eq!(parent.num_child_groups(), 1);
529 }
530
531 #[test]
532 fn test_remove_group() {
533 setup();
534 let name = unique_name("remove_group");
535 create_group(&name);
536
537 assert!(get_group(&name).is_some());
538 remove_group(&name);
539 assert!(get_group(&name).is_none());
540 }
541
542 #[test]
543 fn test_add_slice_plane() {
544 setup();
545 let name = unique_name("slice_plane");
546 let handle = add_slice_plane(&name);
547 assert_eq!(handle.name(), name);
548 assert!(handle.is_enabled());
549 }
550
551 #[test]
552 fn test_slice_plane_pose() {
553 setup();
554 let name = unique_name("slice_pose");
555 let handle = add_slice_plane_with_pose(&name, Vec3::new(1.0, 2.0, 3.0), Vec3::X);
556
557 assert_eq!(handle.origin(), Vec3::new(1.0, 2.0, 3.0));
558 assert_eq!(handle.normal(), Vec3::X);
559 }
560
561 #[test]
562 fn test_slice_plane_setters() {
563 setup();
564 let name = unique_name("slice_setters");
565 let handle = add_slice_plane(&name);
566
567 handle
568 .set_origin(Vec3::new(1.0, 0.0, 0.0))
569 .set_normal(Vec3::Z)
570 .set_color(Vec3::new(1.0, 0.0, 0.0))
571 .set_transparency(0.5);
572
573 assert_eq!(handle.origin(), Vec3::new(1.0, 0.0, 0.0));
574 assert_eq!(handle.normal(), Vec3::Z);
575 assert_eq!(handle.color(), Vec4::new(1.0, 0.0, 0.0, 1.0));
576 assert!((handle.transparency() - 0.5).abs() < 0.001);
577 }
578
579 #[test]
580 fn test_slice_plane_enable_disable() {
581 setup();
582 let name = unique_name("slice_enable");
583 let handle = add_slice_plane(&name);
584
585 assert!(handle.is_enabled());
586 handle.set_enabled(false);
587 assert!(!handle.is_enabled());
588 handle.set_enabled(true);
589 assert!(handle.is_enabled());
590 }
591
592 #[test]
593 fn test_remove_slice_plane() {
594 setup();
595 let name = unique_name("slice_remove");
596 add_slice_plane(&name);
597
598 assert!(get_slice_plane(&name).is_some());
599 remove_slice_plane(&name);
600 assert!(get_slice_plane(&name).is_none());
601 }
602
603 #[test]
604 fn test_select_structure() {
605 setup();
606 let name = unique_name("select_pc");
607 register_point_cloud(&name, vec![Vec3::ZERO]);
608
609 assert!(!has_selection());
610
611 select_structure("PointCloud", &name);
612 assert!(has_selection());
613
614 let selected = get_selected_structure();
615 assert!(selected.is_some());
616 let (type_name, struct_name) = selected.unwrap();
617 assert_eq!(type_name, "PointCloud");
618 assert_eq!(struct_name, name);
619
620 deselect_structure();
621 assert!(!has_selection());
622 }
623
624 #[test]
625 fn test_slice_plane_gizmo_selection() {
626 setup();
627 let name = unique_name("slice_gizmo");
628 add_slice_plane(&name);
629
630 let info = get_slice_plane_selection_info();
632 assert!(!info.has_selection);
633
634 select_slice_plane_for_gizmo(&name);
636 let info = get_slice_plane_selection_info();
637 assert!(info.has_selection);
638 assert_eq!(info.name, name);
639
640 deselect_slice_plane_gizmo();
642 let info = get_slice_plane_selection_info();
643 assert!(!info.has_selection);
644 }
645
646 #[test]
647 fn test_slice_plane_structure_mutual_exclusion() {
648 setup();
649 let pc_name = unique_name("mutual_pc");
650 let plane_name = unique_name("mutual_plane");
651
652 register_point_cloud(&pc_name, vec![Vec3::ZERO]);
653 add_slice_plane(&plane_name);
654
655 select_structure("PointCloud", &pc_name);
657 assert!(has_selection());
658
659 select_slice_plane_for_gizmo(&plane_name);
661 assert!(!has_selection()); let info = get_slice_plane_selection_info();
663 assert!(info.has_selection);
664
665 select_structure("PointCloud", &pc_name);
667 assert!(has_selection());
668 let info = get_slice_plane_selection_info();
669 assert!(!info.has_selection); }
671
672 #[test]
673 fn test_structure_transform() {
674 setup();
675 let name = unique_name("transform_pc");
676 register_point_cloud(&name, vec![Vec3::ZERO, Vec3::X]);
677
678 let transform = get_point_cloud_transform(&name);
680 assert!(transform.is_some());
681
682 let new_transform = Mat4::from_translation(Vec3::new(1.0, 2.0, 3.0));
684 set_point_cloud_transform(&name, new_transform);
685
686 let transform = get_point_cloud_transform(&name).unwrap();
687 let translation = transform.w_axis.truncate();
688 assert!((translation - Vec3::new(1.0, 2.0, 3.0)).length() < 0.001);
689 }
690
691 #[test]
692 fn test_get_slice_plane_settings() {
693 setup();
694 let name = unique_name("ui_slice_plane");
695
696 add_slice_plane_with_pose(&name, Vec3::new(1.0, 2.0, 3.0), Vec3::X);
698
699 let settings = get_slice_plane_settings();
701 let found = settings.iter().find(|s| s.name == name);
702 assert!(found.is_some());
703
704 let s = found.unwrap();
705 assert_eq!(s.origin, [1.0, 2.0, 3.0]);
706 assert_eq!(s.normal, [1.0, 0.0, 0.0]);
707 assert!(s.enabled);
708 }
709
710 #[test]
711 fn test_apply_slice_plane_settings() {
712 setup();
713 let name = unique_name("apply_slice_plane");
714
715 add_slice_plane(&name);
717
718 let settings = polyscope_ui::SlicePlaneSettings {
720 name: name.clone(),
721 enabled: false,
722 origin: [5.0, 6.0, 7.0],
723 normal: [0.0, 0.0, 1.0],
724 draw_plane: false,
725 draw_widget: true,
726 color: [1.0, 0.0, 0.0],
727 transparency: 0.8,
728 plane_size: 0.2,
729 is_selected: false,
730 };
731
732 apply_slice_plane_settings(&settings);
734
735 let handle = get_slice_plane(&name).unwrap();
737 assert!(!handle.is_enabled());
738 assert_eq!(handle.origin(), Vec3::new(5.0, 6.0, 7.0));
739 assert_eq!(handle.normal(), Vec3::Z);
740 assert!(!handle.draw_plane());
741 assert!(handle.draw_widget());
742 assert_eq!(handle.color(), Vec4::new(1.0, 0.0, 0.0, 1.0));
743 assert!((handle.transparency() - 0.8).abs() < 0.001);
744 }
745
746 #[test]
747 fn test_handle_slice_plane_action_add() {
748 setup();
749 let name = unique_name("action_add_plane");
750 let mut settings = Vec::new();
751
752 handle_slice_plane_action(
753 polyscope_ui::SlicePlanesAction::Add(name.clone()),
754 &mut settings,
755 );
756
757 assert_eq!(settings.len(), 1);
758 assert_eq!(settings[0].name, name);
759 assert!(get_slice_plane(&name).is_some());
760 }
761
762 #[test]
763 fn test_handle_slice_plane_action_remove() {
764 setup();
765 let name = unique_name("action_remove_plane");
766
767 add_slice_plane(&name);
769 let mut settings = vec![polyscope_ui::SlicePlaneSettings::with_name(&name)];
770
771 handle_slice_plane_action(polyscope_ui::SlicePlanesAction::Remove(0), &mut settings);
773
774 assert!(settings.is_empty());
775 assert!(get_slice_plane(&name).is_none());
776 }
777
778 #[test]
779 fn test_get_group_settings() {
780 setup();
781 let name = unique_name("ui_group");
782 let pc_name = unique_name("pc_in_ui_group");
783
784 let handle = create_group(&name);
786 register_point_cloud(&pc_name, vec![Vec3::ZERO]);
787 handle.add_point_cloud(&pc_name);
788
789 let settings = get_group_settings();
791 let found = settings.iter().find(|s| s.name == name);
792 assert!(found.is_some());
793
794 let s = found.unwrap();
795 assert!(s.enabled);
796 assert!(s.show_child_details);
797 assert_eq!(s.child_structures.len(), 1);
798 assert_eq!(s.child_structures[0], ("PointCloud".to_string(), pc_name));
799 }
800
801 #[test]
802 fn test_apply_group_settings() {
803 setup();
804 let name = unique_name("apply_group");
805
806 create_group(&name);
808
809 let settings = polyscope_ui::GroupSettings {
811 name: name.clone(),
812 enabled: false,
813 show_child_details: false,
814 parent_group: None,
815 child_structures: Vec::new(),
816 child_groups: Vec::new(),
817 };
818
819 apply_group_settings(&settings);
821
822 let handle = get_group(&name).unwrap();
824 assert!(!handle.is_enabled());
825 }
826
827 #[test]
828 fn test_get_gizmo_settings() {
829 setup();
830
831 set_gizmo_space(GizmoSpace::Local);
833 set_gizmo_visible(false);
834 set_gizmo_snap_translate(0.5);
835 set_gizmo_snap_rotate(15.0);
836 set_gizmo_snap_scale(0.1);
837
838 let settings = get_gizmo_settings();
839 assert!(settings.local_space); assert!(!settings.visible);
841 assert!((settings.snap_translate - 0.5).abs() < 0.001);
842 assert!((settings.snap_rotate - 15.0).abs() < 0.001);
843 assert!((settings.snap_scale - 0.1).abs() < 0.001);
844 }
845
846 #[test]
847 fn test_apply_gizmo_settings() {
848 setup();
849
850 let settings = polyscope_ui::GizmoSettings {
851 local_space: false, visible: true,
853 snap_translate: 1.0,
854 snap_rotate: 45.0,
855 snap_scale: 0.25,
856 };
857
858 apply_gizmo_settings(&settings);
859
860 assert_eq!(get_gizmo_space(), GizmoSpace::World);
861 assert!(is_gizmo_visible());
862 }
863
864 #[test]
865 fn test_get_selection_info_with_selection() {
866 setup();
867 let name = unique_name("gizmo_select_pc");
868
869 register_point_cloud(&name, vec![Vec3::ZERO]);
870 select_structure("PointCloud", &name);
871
872 let info = get_selection_info();
873 assert!(info.has_selection);
874 assert_eq!(info.type_name, "PointCloud");
875 assert_eq!(info.name, name);
876
877 deselect_structure();
878 }
879
880 #[test]
881 fn test_apply_selection_transform() {
882 setup();
883 let name = unique_name("gizmo_transform_pc");
884
885 register_point_cloud(&name, vec![Vec3::ZERO]);
886 select_structure("PointCloud", &name);
887
888 let selection = polyscope_ui::SelectionInfo {
889 has_selection: true,
890 type_name: "PointCloud".to_string(),
891 name: name.clone(),
892 translation: [1.0, 2.0, 3.0],
893 rotation_degrees: [0.0, 0.0, 0.0],
894 scale: [1.0, 1.0, 1.0],
895 centroid: [1.0, 2.0, 3.0],
896 };
897
898 apply_selection_transform(&selection);
899
900 let transform = get_point_cloud_transform(&name).unwrap();
901 let translation = transform.w_axis.truncate();
902 assert!((translation - Vec3::new(1.0, 2.0, 3.0)).length() < 0.001);
903
904 deselect_structure();
905 }
906
907 #[test]
908 fn test_remove_all_groups() {
909 setup();
910 let g1 = unique_name("rag_group1");
911 let g2 = unique_name("rag_group2");
912 create_group(&g1);
913 create_group(&g2);
914
915 assert!(get_group(&g1).is_some());
916 assert!(get_group(&g2).is_some());
917
918 remove_all_groups();
919
920 assert!(get_group(&g1).is_none());
921 assert!(get_group(&g2).is_none());
922 }
923
924 #[test]
925 fn test_remove_everything() {
926 setup();
927 let pc_name = unique_name("re_pc");
928 let group_name = unique_name("re_group");
929 let sp_name = unique_name("re_slice");
930
931 register_point_cloud(&pc_name, vec![Vec3::ZERO]);
932 create_group(&group_name);
933 add_slice_plane(&sp_name);
934
935 remove_everything();
936
937 assert!(get_point_cloud(&pc_name).is_none());
938 assert!(get_group(&group_name).is_none());
939 assert!(get_all_slice_planes().is_empty());
940 }
941
942 #[test]
943 fn test_degenerate_bounding_box() {
944 setup();
945 remove_all_structures();
947 let name = unique_name("degen_bbox");
948 register_point_cloud(&name, vec![Vec3::ONE, Vec3::ONE, Vec3::ONE]);
950
951 let (bb_min, bb_max) = with_context(|ctx| ctx.bounding_box);
952 assert!(
954 bb_max.x > bb_min.x,
955 "degenerate bbox not perturbed: min={bb_min}, max={bb_max}"
956 );
957 assert!(bb_max.y > bb_min.y);
958 assert!(bb_max.z > bb_min.z);
959 }
960}