|
| 1 | +# Architecture snapshot (2026-01) |
| 2 | +_Last updated: 2026-01-30_ |
| 3 | + |
| 4 | +This page preserves a detailed, implementation-oriented snapshot of the current |
| 5 | +MolSysViewer architecture. It is not the source of truth, but it is useful when |
| 6 | +you need a compact description of the data flow and message protocol. |
| 7 | + |
| 8 | +If you want the current high-level view, see {doc}`architecture` and |
| 9 | +{doc}`architecture_full`. |
| 10 | + |
| 11 | +## 1. High-level layers |
| 12 | + |
| 13 | +MolSysViewer is split into: |
| 14 | + |
| 15 | +- **Python layer (`molsysviewer/`)** |
| 16 | + - `MolSysView` facade and widget wrapper. |
| 17 | + - Loaders for MolSysMT/PDB/mmCIF/URL. |
| 18 | + - Regions/layers/whole and shapes APIs. |
| 19 | + - HTML export helpers (standalone + docs-lite). |
| 20 | +- **TypeScript/Mol* layer (`molsysviewer/js/src/`)** |
| 21 | + - AnyWidget entry (`index.ts`). |
| 22 | + - `MolSysViewerController` and handlers (`loader`, `scene`, `state`, `trajectory`). |
| 23 | + - Shape builders under `shapes/`. |
| 24 | + - Popup host + popup implementation. |
| 25 | + |
| 26 | +The original design overview is preserved in {doc}`design_overview`. |
| 27 | + |
| 28 | +## 2. Python ↔ JS message protocol |
| 29 | + |
| 30 | +### 2.1 Python → JS |
| 31 | + |
| 32 | +All visual actions are sent as JSON-like dictionaries with: |
| 33 | + |
| 34 | +- `op`: an operation identifier. |
| 35 | +- `options` (or additional fields) depending on the operation. |
| 36 | + |
| 37 | +Examples |
| 38 | + |
| 39 | +- Load a MolSys payload: |
| 40 | + ```python |
| 41 | + view._send({ |
| 42 | + "op": "load_molsys_payload", |
| 43 | + "payload": payload, |
| 44 | + "label": label, |
| 45 | + }) |
| 46 | + ``` |
| 47 | +- Update visibility: |
| 48 | + ```python |
| 49 | + view._send({ |
| 50 | + "op": "update_visibility", |
| 51 | + "options": {"visible_atom_indices": visible_indices}, |
| 52 | + }) |
| 53 | + ``` |
| 54 | +- Shapes: |
| 55 | + ```python |
| 56 | + view._send({ |
| 57 | + "op": "add_sphere", |
| 58 | + "options": {...}, |
| 59 | + }) |
| 60 | + ``` |
| 61 | + |
| 62 | +The TypeScript union type `ViewerMessage` (`molsysviewer/js/src/messages/viewer-messages.ts`) |
| 63 | +is the best “contract” view. |
| 64 | + |
| 65 | +### 2.2 JS → Python |
| 66 | + |
| 67 | +The widget emits events back to Python via `widget.on_msg`: |
| 68 | + |
| 69 | +- `ready`: frontend initialized; Python flushes `_pending_messages`. |
| 70 | +- `region_ack`: region creation confirmation (`atom_indices`, `selection`). |
| 71 | +- `region_deleted` |
| 72 | +- `layer_ack` |
| 73 | +- `layer_deleted` |
| 74 | +- `registry_cleared`: `clear_all` was executed; Python must reset registries. |
| 75 | +- `camera_snapshot`: camera state sent from the frontend. |
| 76 | +- `js_log`: debug logs when `debug_js=True`. |
| 77 | + |
| 78 | +These events keep the Python mirrors (`view.regions`, `view.layers`) consistent |
| 79 | +with the Mol* state tree. |
| 80 | + |
| 81 | +## 3. Data path: MolSysMT → Mol* |
| 82 | + |
| 83 | +1. You call `MolSysView.load(molecular_system, ...)`. |
| 84 | +2. Python converts inputs to `molsysmt.MolSys`. |
| 85 | +3. `ViewerJSON` is serialized into a stable MolSys payload: |
| 86 | + - `atoms`: parallel arrays (ids, names, residue, chain, element, charge). |
| 87 | + - `structures`: snapshots with `coordinates` (Å), optional `box` (Å), optional `time`. |
| 88 | + - optional `bonds`: `indexA/indexB` (+ optional `order`). |
| 89 | +4. Python sends `{"op": "load_molsys_payload", "payload": ...}`. |
| 90 | +5. TypeScript builds Mol* objects: |
| 91 | + - `Topology`, `Coordinates`, and a `Trajectory`. |
| 92 | + - A trajectory node in the Mol* state tree (`InsertMolSysTrajectory`). |
| 93 | + - A default representation preset (`default` / `auto`). |
| 94 | +6. The controller captures the loaded structure and notifies state/trajectory handlers. |
| 95 | + |
| 96 | +This path avoids intermediate PDB conversions when MolSysMT inputs are available. |
| 97 | + |
| 98 | +### new_view convenience modes |
| 99 | + |
| 100 | +`new_view(...)` is a convenience wrapper around `MolSysView.load(...)` and adds |
| 101 | +`load_mode`: |
| 102 | + |
| 103 | +- `load_mode="selection"` (default): the selection subsets the system before loading. |
| 104 | +- `load_mode="all"`: the full system loads, the global representation is hidden, |
| 105 | + and a region tagged `selection` is created for the requested selection. |
| 106 | + |
| 107 | +## 4. Visibility and masking |
| 108 | + |
| 109 | +### 4.1 Python |
| 110 | + |
| 111 | +- `MolSysView` maintains a boolean `atom_mask` (`n_atoms`). |
| 112 | +- `hide(selection)` updates the mask and then calls `_update_visibility_in_frontend()`. |
| 113 | +- `show(selection, structure_indices, force=False)` restores parts of the mask and re-applies global visibility intent. |
| 114 | +- `visible_atom_indices` is computed as `np.nonzero(atom_mask)[0].tolist()` and sent through `update_visibility`. |
| 115 | + |
| 116 | +### 4.2 TypeScript |
| 117 | + |
| 118 | +- `StateHandlers.updateVisibility`: |
| 119 | + - If no structure is loaded yet, stores `pendingVisibility`. |
| 120 | + - Otherwise: |
| 121 | + - Clears previous transparency. |
| 122 | + - Computes the hidden selection as the complement of `visible_atom_indices`. |
| 123 | + - Applies transparency/visibility via Mol* helpers to global reps and regions. |
| 124 | + |
| 125 | +The source of truth for visibility is Python. |
| 126 | +Mol* is responsible for render-time application. |
| 127 | + |
| 128 | +## 5. Regions and layers |
| 129 | + |
| 130 | +### 5.1 Python API |
| 131 | + |
| 132 | +- `MolSysView.new_region(...)`: |
| 133 | + - Accepts a MolSysMT selection or explicit `atom_indices`. |
| 134 | + - Supports complements (`complement_of_regions=["tagA", ...]` or `"all"`). |
| 135 | + - Registers a `Region` and sends `create_region`. |
| 136 | +- `Region.set_representation(...)`: |
| 137 | + - Normalizes representation type/preset and user preset rules. |
| 138 | + - Sends `set_region_representation`. |
| 139 | +- `Region.show/hide/delete`: |
| 140 | + - Send `show_region`, `hide_region`, `delete_region`. |
| 141 | +- `MolSysView.new_layer(...)`: |
| 142 | + - Registers a `Layer` and sends `create_layer`. |
| 143 | +- `Layer.show/hide/delete/set_tag`: |
| 144 | + - Send `show_layer`, `hide_layer`, `delete_layer`, `set_layer_tag`. |
| 145 | +- `Whole.set_representation(...)`: |
| 146 | + - Resolves user preset rules in Python and sends `set_global_representation`. |
| 147 | + |
| 148 | +### 5.2 TypeScript state model |
| 149 | + |
| 150 | +`StateHandlers` maintains: |
| 151 | + |
| 152 | +- `regionIndex`: `tag -> { component, representations[], atomIndices[], selection, hidden }` |
| 153 | +- `layerMeta`: `tag -> { kind, meta }` |
| 154 | +- `tagIndex`: `tag -> Set<StateObjectRef>` (refs for shapes/overlays) |
| 155 | +- `globalReprs`: baseline/global representations for the root structure |
| 156 | +- pending flags for operations invoked before a structure exists |
| 157 | + |
| 158 | +Operations |
| 159 | + |
| 160 | +- `create_region`: build `StructureSelection` from indices, create `StructureComponent`, add reps, store refs. |
| 161 | +- `set_region_representation`: replace region reps (type, preset, or user preset). |
| 162 | +- `show/hide_region`: toggle visibility via `setSubtreeVisibility`. |
| 163 | +- `create_layer`: store metadata and acknowledge to Python. |
| 164 | +- `show/hide_layer`: toggle visibility of all refs under a tag. |
| 165 | +- `set_global_representation`: rebuild baseline/global reps and store them in `globalReprs`. |
| 166 | +- `show/hide_global`: show/hide baseline/global reps; `target="all"` can include everything. |
| 167 | + |
| 168 | +`clear_all` clears regions, layers, and global reps, removes the loaded structure, |
| 169 | +and emits `registry_cleared`. |
| 170 | + |
| 171 | +## 6. Popup (host ↔ popup) |
| 172 | + |
| 173 | +Host (notebook) |
| 174 | + |
| 175 | +- `PopupHostManager` opens a window and injects the runtime: |
| 176 | + - Blob + `import` in notebooks, or |
| 177 | + - `moduleUrl` in docs-lite exports. |
| 178 | +- Keeps a `commandLog` (Python → JS messages) to reconstruct state. |
| 179 | +- Uses `postMessage` to: |
| 180 | + - send `molsysviewer-initial-sync` (commands + camera snapshot + UI state), |
| 181 | + - stream `molsysviewer-sync-op` messages, |
| 182 | + - send `molsysviewer-sync-camera` while the user interacts. |
| 183 | + |
| 184 | +Popup |
| 185 | + |
| 186 | +- `bootPopup` creates a new `MolSysViewerController` in the popup DOM. |
| 187 | +- Replays the `commandLog` and applies the initial camera snapshot. |
| 188 | +- Syncs camera host ↔ popup without “camera fights”. |
| 189 | + |
| 190 | +This design keeps the popup as a live mirror of the host. |
| 191 | + |
| 192 | +## 7. HTML export behavior (current) |
| 193 | + |
| 194 | +- HTML export replays `_message_history` and does not require `show()` to run. |
| 195 | +- If the frontend is live, `write_html(...)` requests a fresh camera snapshot |
| 196 | + before exporting (best-effort). |
| 197 | +- UI-only changes (toggle buttons, manual style changes) do not populate |
| 198 | + `_message_history` and are not exported. |
| 199 | + |
| 200 | +## 8. What was still aspirational (at the time) |
| 201 | + |
| 202 | +The design overview mentions additional modules (hbonds, topology, alternative engines) and richer `structure.get_* / show_*` APIs. |
| 203 | +At this snapshot: |
| 204 | + |
| 205 | +- Implemented core: |
| 206 | + - MolSys payload loading, |
| 207 | + - scientific shapes (spheres, pockets, tubes, pharmacophores), |
| 208 | + - regions/layers/whole, |
| 209 | + - popup, |
| 210 | + - trajectory controls, |
| 211 | + - automatic display in notebooks via `_repr_mimebundle_`. |
| 212 | +- Still future: |
| 213 | + - dedicated hbonds module (`get_hbonds/show_hbonds`), |
| 214 | + - topology module with higher-level APIs (`get_bonds/show_bonds`), |
| 215 | + - extended numeric APIs under `structure.*`. |
| 216 | + |
| 217 | +Revisit this page when a “design” item becomes a shipped feature. |
0 commit comments