From 85a83acc1412e6473640a37905a038019f9f1c76 Mon Sep 17 00:00:00 2001 From: NCCU Schultz Lab Date: Sun, 30 Aug 2026 15:48:20 +0000 Subject: [PATCH] Fix blank Mulliken 3D viewer with geometry persistence and lazy render The Mulliken Populations panel now resolves geometry from result.json, history context, and disk sidecars; defers py3Dmol rendering until the accordion is visible; shows an inline message when coordinates are missing; and refreshes on theme/backend re-render like other Analysis viewers. Co-authored-by: Jonathan Schultz --- quantui/app.py | 6 +++ quantui/app_analysis.py | 21 ++++++-- quantui/app_history.py | 13 ++++- quantui/populations_overlay.py | 36 +++++++++---- quantui/results_storage.py | 82 ++++++++++++++++++++++++++++ tests/test_mulliken_panel.py | 98 ++++++++++++++++++++++++++++++++++ tests/test_results_storage.py | 33 ++++++++++++ 7 files changed, 275 insertions(+), 14 deletions(-) diff --git a/quantui/app.py b/quantui/app.py index af0d22e..0c03e0f 100644 --- a/quantui/app.py +++ b/quantui/app.py @@ -1696,6 +1696,7 @@ def __init__(self) -> None: # Analysis-tab viewer when the toggle changes. self._analysis_displayed_molecule: Any = None self._mulliken_displayed_molecule: Any = None + self._mulliken_pending_molecule: Any = None # ── Build → wire → assemble ─────────────────────────────────────── self._build_widgets() @@ -3636,6 +3637,10 @@ def _rerender_3d_views(self) -> None: self._set_html_output(self._analysis_mol_output, html) self._update_analysis_backend_label(chosen) + # Mulliken Populations dedicated viewer (py3Dmol charge overlays). + if getattr(self, "_last_mulliken_charges", None): + self._show_mulliken_viewer() + def _show_mulliken_viewer(self, molecule=None) -> None: from quantui.populations_overlay import show_mulliken_viewer @@ -5991,6 +5996,7 @@ def _run_required_final_single_point(target_mol, reason: str): pyscf_log=log.getvalue(), calc_type=save_type, spectra=save_spectra, + molecule=calc_mol, ) _run_saved = True self._last_result_dir = _saved_dir diff --git a/quantui/app_analysis.py b/quantui/app_analysis.py index ee8e53c..354e0ab 100644 --- a/quantui/app_analysis.py +++ b/quantui/app_analysis.py @@ -255,6 +255,7 @@ def apply_analysis_context(app: Any, ctx: Any) -> None: if mulliken_out is not None: mulliken_out.clear_output() app._mulliken_displayed_molecule = None + app._mulliken_pending_molecule = None note = getattr(app, "_mulliken_overlay_note", None) if note is not None: note.value = "" @@ -855,6 +856,18 @@ def _mulliken_molecule(app: Any, ctx: Any = None) -> Any: mol = getattr(ctx, "molecule", None) if mol is not None: return mol + result_dir = getattr(ctx, "result_dir", None) + if result_dir is not None: + try: + from quantui import load_result + from quantui.app_history import mol_from_result_dir + + data = load_result(result_dir) + mol = mol_from_result_dir(result_dir, data) + if mol is not None: + return mol + except Exception: + pass return getattr(app, "_analysis_displayed_molecule", None) or getattr( app, "_molecule", None ) @@ -958,10 +971,10 @@ def show_mulliken_populations( ) update_mulliken_figure(app) - try: - app._show_mulliken_viewer(molecule) - except Exception: # noqa: BLE001 — viewer must never block the panel - pass + # Cache geometry for lazy render when the accordion becomes visible. + # Voilà often skips