1use std::collections::HashMap;
4use std::sync::{OnceLock, RwLock};
5
6use glam::Vec3;
7
8use crate::error::{PolyscopeError, Result};
9use crate::gizmo::GizmoConfig;
10use crate::group::Group;
11use crate::options::Options;
12use crate::quantity::Quantity;
13use crate::registry::Registry;
14use crate::slice_plane::SlicePlane;
15
16pub type FileDropCallback = Box<dyn FnMut(&[std::path::PathBuf]) + Send + Sync>;
18
19#[derive(Debug, Clone)]
21pub enum MaterialLoadRequest {
22 Static { name: String, path: String },
24 Blendable {
26 name: String,
27 filenames: [String; 4],
28 },
29}
30
31static CONTEXT: OnceLock<RwLock<Context>> = OnceLock::new();
33
34pub struct Context {
36 pub initialized: bool,
38
39 pub registry: Registry,
41
42 pub groups: HashMap<String, Group>,
44
45 pub slice_planes: HashMap<String, SlicePlane>,
47
48 pub gizmo_config: GizmoConfig,
50
51 pub selected_structure: Option<(String, String)>,
53
54 pub selected_slice_plane: Option<String>,
56
57 pub options: Options,
59
60 pub length_scale: f32,
62
63 pub bounding_box: (Vec3, Vec3),
65
66 pub floating_quantities: Vec<Box<dyn Quantity>>,
68
69 pub file_drop_callback: Option<FileDropCallback>,
71
72 pub material_load_queue: Vec<MaterialLoadRequest>,
74}
75
76impl Default for Context {
77 fn default() -> Self {
78 Self {
79 initialized: false,
80 registry: Registry::new(),
81 groups: HashMap::new(),
82 slice_planes: HashMap::new(),
83 gizmo_config: GizmoConfig::default(),
84 selected_structure: None,
85 selected_slice_plane: None,
86 options: Options::default(),
87 length_scale: 1.0,
88 bounding_box: (Vec3::ZERO, Vec3::ONE),
89 floating_quantities: Vec::new(),
90 file_drop_callback: None,
91 material_load_queue: Vec::new(),
92 }
93 }
94}
95
96impl Context {
97 #[must_use]
99 pub fn center(&self) -> Vec3 {
100 (self.bounding_box.0 + self.bounding_box.1) * 0.5
101 }
102
103 pub fn update_extents(&mut self) {
109 if !self.options.auto_compute_scene_extents {
110 return;
111 }
112 self.recompute_extents();
113 }
114
115 pub fn recompute_extents(&mut self) {
120 let mut min = Vec3::splat(f32::MAX);
121 let mut max = Vec3::splat(f32::MIN);
122 let mut has_extent = false;
123
124 for structure in self.registry.iter() {
125 if let Some((bb_min, bb_max)) = structure.bounding_box() {
126 min = min.min(bb_min);
127 max = max.max(bb_max);
128 has_extent = true;
129 }
130 }
131
132 if has_extent {
133 self.bounding_box = (min, max);
134 self.length_scale = (max - min).length();
135
136 if min == max {
139 let offset_scale = if self.length_scale == 0.0 {
140 1e-3
141 } else {
142 self.length_scale * 1e-3
143 };
144 let offset = Vec3::splat(offset_scale / 2.0);
145 self.bounding_box = (min - offset, max + offset);
146 }
147 } else {
148 self.bounding_box = (Vec3::ZERO, Vec3::ONE);
149 self.length_scale = 1.0;
150 }
151 }
152
153 pub fn create_group(&mut self, name: &str) -> &mut Group {
155 self.groups
156 .entry(name.to_string())
157 .or_insert_with(|| Group::new(name))
158 }
159
160 #[must_use]
162 pub fn get_group(&self, name: &str) -> Option<&Group> {
163 self.groups.get(name)
164 }
165
166 pub fn get_group_mut(&mut self, name: &str) -> Option<&mut Group> {
168 self.groups.get_mut(name)
169 }
170
171 pub fn remove_group(&mut self, name: &str) -> Option<Group> {
173 self.groups.remove(name)
174 }
175
176 #[must_use]
178 pub fn has_group(&self, name: &str) -> bool {
179 self.groups.contains_key(name)
180 }
181
182 #[must_use]
184 pub fn group_names(&self) -> Vec<&str> {
185 self.groups
186 .keys()
187 .map(std::string::String::as_str)
188 .collect()
189 }
190
191 #[must_use]
198 pub fn is_structure_visible(&self, structure: &dyn crate::Structure) -> bool {
199 structure.is_enabled()
200 && self.is_structure_visible_in_groups(structure.type_name(), structure.name())
201 }
202
203 #[must_use]
209 pub fn is_structure_visible_in_groups(&self, type_name: &str, name: &str) -> bool {
210 for group in self.groups.values() {
212 if group.contains_structure(type_name, name) {
213 if !self.is_group_and_ancestors_enabled(group.name()) {
215 return false;
216 }
217 }
218 }
219 true
220 }
221
222 fn is_group_and_ancestors_enabled(&self, group_name: &str) -> bool {
224 let mut current = group_name;
225 while let Some(group) = self.groups.get(current) {
226 if !group.is_enabled() {
227 return false;
228 }
229 if let Some(parent) = group.parent_group() {
230 current = parent;
231 } else {
232 break;
233 }
234 }
235 true
236 }
237
238 pub fn add_slice_plane(&mut self, name: &str) -> &mut SlicePlane {
244 self.slice_planes
245 .entry(name.to_string())
246 .or_insert_with(|| SlicePlane::new(name))
247 }
248
249 #[must_use]
251 pub fn get_slice_plane(&self, name: &str) -> Option<&SlicePlane> {
252 self.slice_planes.get(name)
253 }
254
255 pub fn get_slice_plane_mut(&mut self, name: &str) -> Option<&mut SlicePlane> {
257 self.slice_planes.get_mut(name)
258 }
259
260 pub fn remove_slice_plane(&mut self, name: &str) -> Option<SlicePlane> {
262 self.slice_planes.remove(name)
263 }
264
265 #[must_use]
267 pub fn has_slice_plane(&self, name: &str) -> bool {
268 self.slice_planes.contains_key(name)
269 }
270
271 #[must_use]
273 pub fn slice_plane_names(&self) -> Vec<&str> {
274 self.slice_planes
275 .keys()
276 .map(std::string::String::as_str)
277 .collect()
278 }
279
280 #[must_use]
282 pub fn num_slice_planes(&self) -> usize {
283 self.slice_planes.len()
284 }
285
286 pub fn slice_planes(&self) -> impl Iterator<Item = &SlicePlane> {
288 self.slice_planes.values()
289 }
290
291 pub fn enabled_slice_planes(&self) -> impl Iterator<Item = &SlicePlane> {
293 self.slice_planes.values().filter(|sp| sp.is_enabled())
294 }
295
296 pub fn select_structure(&mut self, type_name: &str, name: &str) {
303 self.selected_slice_plane = None; self.selected_structure = Some((type_name.to_string(), name.to_string()));
305 }
306
307 pub fn deselect_structure(&mut self) {
309 self.selected_structure = None;
310 }
311
312 #[must_use]
314 pub fn selected_structure(&self) -> Option<(&str, &str)> {
315 self.selected_structure
316 .as_ref()
317 .map(|(t, n)| (t.as_str(), n.as_str()))
318 }
319
320 #[must_use]
322 pub fn has_selection(&self) -> bool {
323 self.selected_structure.is_some()
324 }
325
326 pub fn select_slice_plane(&mut self, name: &str) {
329 self.selected_structure = None; self.selected_slice_plane = Some(name.to_string());
331 }
332
333 pub fn deselect_slice_plane(&mut self) {
335 self.selected_slice_plane = None;
336 }
337
338 #[must_use]
340 pub fn selected_slice_plane(&self) -> Option<&str> {
341 self.selected_slice_plane.as_deref()
342 }
343
344 #[must_use]
346 pub fn has_slice_plane_selection(&self) -> bool {
347 self.selected_slice_plane.is_some()
348 }
349
350 #[must_use]
352 pub fn gizmo(&self) -> &GizmoConfig {
353 &self.gizmo_config
354 }
355
356 pub fn gizmo_mut(&mut self) -> &mut GizmoConfig {
358 &mut self.gizmo_config
359 }
360}
361
362pub fn init_context() -> Result<()> {
366 let context = RwLock::new(Context::default());
367
368 CONTEXT
369 .set(context)
370 .map_err(|_| PolyscopeError::AlreadyInitialized)?;
371
372 with_context_mut(|ctx| {
373 ctx.initialized = true;
374 });
375
376 Ok(())
377}
378
379pub fn is_initialized() -> bool {
381 CONTEXT
382 .get()
383 .and_then(|lock| lock.read().ok())
384 .is_some_and(|ctx| ctx.initialized)
385}
386
387pub fn with_context<F, R>(f: F) -> R
393where
394 F: FnOnce(&Context) -> R,
395{
396 let lock = CONTEXT.get().expect("polyscope not initialized");
397 let guard = lock.read().expect("context lock poisoned");
398 f(&guard)
399}
400
401pub fn with_context_mut<F, R>(f: F) -> R
407where
408 F: FnOnce(&mut Context) -> R,
409{
410 let lock = CONTEXT.get().expect("polyscope not initialized");
411 let mut guard = lock.write().expect("context lock poisoned");
412 f(&mut guard)
413}
414
415pub fn try_with_context<F, R>(f: F) -> Option<R>
419where
420 F: FnOnce(&Context) -> R,
421{
422 let lock = CONTEXT.get()?;
423 let guard = lock.read().ok()?;
424 Some(f(&guard))
425}
426
427pub fn try_with_context_mut<F, R>(f: F) -> Option<R>
431where
432 F: FnOnce(&mut Context) -> R,
433{
434 let lock = CONTEXT.get()?;
435 let mut guard = lock.write().ok()?;
436 Some(f(&mut guard))
437}
438
439pub fn shutdown_context() {
444 if let Some(lock) = CONTEXT.get() {
445 if let Ok(mut ctx) = lock.write() {
446 ctx.initialized = false;
447 ctx.registry.clear();
448 ctx.groups.clear();
449 ctx.slice_planes.clear();
450 ctx.selected_structure = None;
451 ctx.selected_slice_plane = None;
452 ctx.floating_quantities.clear();
453 ctx.material_load_queue.clear();
454 }
455 }
456}