Skip to content

Commit 63258e7

Browse files
committed
added jupyter mode
1 parent 702a949 commit 63258e7

2 files changed

Lines changed: 101 additions & 43 deletions

File tree

api/gconfiguration.py

Lines changed: 82 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@
2828
from gsqlite import create_sqlite_database
2929
from gutils import GColors
3030

31+
def _is_jupyter() -> bool:
32+
try:
33+
from IPython import get_ipython
34+
shell = get_ipython()
35+
return shell is not None and "IPKernelApp" in shell.config
36+
except ImportError:
37+
return False
38+
39+
_in_jupyter = _is_jupyter()
40+
3141
has_pyvista: bool = False
3242
pv: Optional[object] = None
3343
BackgroundPlotterCls = None
@@ -147,13 +157,16 @@ def plotter(self):
147157

148158
if self._plotter is None:
149159
if self.use_background_plotter and BackgroundPlotterCls is not None:
150-
# Non-blocking background window
151160
self._plotter = BackgroundPlotterCls(show=True)
152161
else:
153-
# Normal blocking Plotter
154-
self._plotter = self.pv.Plotter()
162+
if _in_jupyter:
163+
self._plotter = self.pv.Plotter(
164+
notebook=True,
165+
window_size=(self.args.width, self.args.height),
166+
)
167+
else:
168+
self._plotter = self.pv.Plotter()
155169

156-
# One-time scene setup
157170
self._plotter.add_axes()
158171
self._plotter.set_background("#303048", top="#000020")
159172
self._plotter.camera_position = "iso"
@@ -304,71 +317,92 @@ def show(self, block: bool = True):
304317
p.app.exec_()
305318
else:
306319
# Normal Plotter path
307-
if block:
320+
if _in_jupyter:
321+
322+
cpos = self._configure_camera_from_bounds(
323+
margin=-12.8,
324+
distance_scale=3.0,
325+
)
326+
return p.show(
327+
jupyter_backend="html",
328+
cpos=cpos,
329+
return_viewer=True,
330+
)
331+
332+
333+
elif block:
308334
p.show()
309335

310-
def _configure_camera_from_bounds(self):
336+
def _configure_camera_from_bounds(self, margin: float = 0.8, distance_scale: float = 4.0):
311337
if not self.use_pyvista:
312-
return
338+
return None
313339

314340
p = self.plotter
315341
if p is None:
316-
return
342+
return None
317343

318-
# If nothing is added yet, bounds can be degenerate
319344
try:
320345
xmin, xmax, ymin, ymax, zmin, zmax = p.bounds
321346
except Exception:
322-
return
347+
return None
323348

324-
# Guard against empty scene
325-
if any(not np.isfinite(v) for v in (xmin, xmax, ymin, ymax, zmin, zmax)):
326-
return
327-
if xmax == xmin and ymax == ymin and zmax == zmin:
328-
return
349+
vals = (xmin, xmax, ymin, ymax, zmin, zmax)
350+
if any(not np.isfinite(v) for v in vals):
351+
return None
352+
353+
dx = xmax - xmin
354+
dy = ymax - ymin
355+
dz = zmax - zmin
329356

330-
center = np.array([(xmin + xmax) / 2,
331-
(ymin + ymax) / 2,
332-
(zmin + zmax) / 2])
333-
scene_len = np.linalg.norm([xmax - xmin,
334-
ymax - ymin,
335-
zmax - zmin])
357+
if dx <= 0 and dy <= 0 and dz <= 0:
358+
return None
336359

337-
# iso direction
338-
direction = np.array([1.0, 1.0, 1.0]) / np.sqrt(3.0)
339-
distance = 2.5 * scene_len if scene_len > 0 else 1.0
360+
center = np.array([
361+
0.5 * (xmin + xmax),
362+
0.5 * (ymin + ymax),
363+
0.5 * (zmin + zmax),
364+
])
340365

341-
pos = center + direction * distance
342-
p.camera_position = [tuple(pos), tuple(center), (0, 0, 1)]
366+
scene_len = np.linalg.norm([dx, dy, dz])
367+
if scene_len <= 0:
368+
scene_len = max(dx, dy, dz, 1.0)
343369

344-
if self.args.add_axes_at_zero:
345-
# if you have this helper on the plotter; otherwise use your own
346-
try:
347-
p.add_axes_at_origin(xlabel="X", ylabel="Y", zlabel="Z")
348-
except AttributeError:
349-
pass
370+
direction = np.array([1.0, 1.0, 1.0])
371+
direction /= np.linalg.norm(direction)
372+
373+
# Larger distance_scale = more zoomed out
374+
distance = distance_scale * scene_len
375+
position = center + direction * distance
376+
377+
cpos = [
378+
tuple(position),
379+
tuple(center),
380+
(0, 0, 1),
381+
]
382+
383+
p.camera_position = cpos
350384

351-
# Optional: keep your original “nice” orientation
385+
# Important for html/local backend: avoid parallel projection here.
352386
try:
353-
p.view_zy() # or view_isometric, etc.
354-
except AttributeError:
387+
p.disable_parallel_projection()
388+
except Exception:
355389
pass
356390

391+
# Larger view_angle = more zoomed out in perspective projection.
357392
try:
358-
p.enable_anti_aliasing()
359-
except AttributeError:
393+
p.camera.view_angle = 45.0
394+
except Exception:
360395
pass
361396

362397
try:
363-
p.enable_parallel_projection()
364-
except AttributeError:
398+
p.reset_camera_clipping_range()
399+
except Exception:
365400
pass
366401

367-
# Sometimes helps with clipping issues
368-
if hasattr(p, "reset_camera_clipped_range"):
369-
p.reset_camera_clipped_range()
370-
371402
self._camera_initialized = True
403+
return cpos
404+
405+
372406

373407

374408
# autogeometry utility to executes show() at exit
@@ -386,6 +420,11 @@ def autogeometry(
386420
enable_pyvista: Optional[bool] = None,
387421
use_background_plotter: Optional[bool] = None,
388422
):
423+
# in jupyter: always enable pyvista, never use atexit
424+
if _in_jupyter:
425+
enable_pyvista = True
426+
auto_show = False
427+
389428
cfg = GConfiguration(
390429
experiment,
391430
application,

api/run_geometry.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import importlib.util
2+
import sys
3+
import os
4+
import pyvista as pv
5+
6+
def run_geometry(script, args=None, db="gemc.db"):
7+
if os.path.exists(db):
8+
os.remove(db)
9+
10+
pv.set_jupyter_backend("trame")
11+
pv.global_theme.trame.default_mode = "local"
12+
13+
sys.argv = [script] + (args or [])
14+
15+
spec = importlib.util.spec_from_file_location("target", script)
16+
module = importlib.util.module_from_spec(spec)
17+
spec.loader.exec_module(module)
18+
19+
return module.cfg.show(block=False)

0 commit comments

Comments
 (0)