ms-components 0.3.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ms_components-0.3.0 → ms_components-0.3.1}/PKG-INFO +1 -1
- ms_components-0.3.1/docs/api_reference.md +102 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/docs/usage.md +13 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_pymol/__init__.py +2 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_pymol/widget.py +212 -4
- ms_components-0.3.1/src/ms_components/opengl.py +139 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components.egg-info/PKG-INFO +1 -1
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components.egg-info/SOURCES.txt +3 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components.egg-info/scm_file_list.json +3 -0
- ms_components-0.3.1/src/ms_components.egg-info/scm_version.json +8 -0
- ms_components-0.3.1/tests/test_opengl.py +166 -0
- ms_components-0.3.1/tests/test_pymol_grid.py +278 -0
- ms_components-0.3.0/docs/api_reference.md +0 -48
- ms_components-0.3.0/src/ms_components.egg-info/scm_version.json +0 -8
- {ms_components-0.3.0 → ms_components-0.3.1}/.github/workflows/docs.yml +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/.github/workflows/publish.yml +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/.gitignore +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/LICENSE +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/README.md +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/docs/examples.md +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/docs/index.md +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/docs/ms_tutorial.md +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/examples/tutorial_demo.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/examples/tutorial_demo.toml +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/pyproject.toml +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/setup.cfg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/__init__.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/base.qss +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/chips.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/listview.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_dockwidget/__init__.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_dockwidget/resources/__init__.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_dockwidget/resources/icons/__init__.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_dockwidget/resources/icons/folder.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_dockwidget/widget.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_executors.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_monitor/__init__.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_monitor/bridge.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_monitor/cards.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_monitor/config.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_monitor/detail_panel.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_monitor/formatting.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_monitor/job_cards.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_monitor/models.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_monitor/palette.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_monitor/polling.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_monitor/widget.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_projects/__init__.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_projects/app.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_projects/browser.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_projects/scratch.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_projects/widget.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_pymol/icons/actions.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_pymol/icons/box.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_pymol/icons/center.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_pymol/icons/display.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_pymol/icons/hide.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_pymol/icons/menu.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_pymol/icons/orient.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_pymol/icons/presets.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_pymol/icons/rock.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_pymol/icons/selection.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_pymol/icons/settings.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_pymol/icons/zoom.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_pymol/plugins.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_settings/__init__.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_settings/panel.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_stepper.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/__init__.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/action_header.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/cell_delegate.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/column_panel.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/example_usage.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/filter_panel.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/icons/columns.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/icons/export.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/icons/filter.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/icons/menu.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/icons/reload.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/icons/search.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/icons/settings.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/icons/sort-asc.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/icons/sort-desc.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/icons/sort.svg +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/metric_filter_bar.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/pagination_control.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/query_builder.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/session_provider.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/smart_table_view.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/sort_panel.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/table_config.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/table_model.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/test_external_filters.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_table/test_toolbar.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_tutorial/__init__.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_tutorial/bindings.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_tutorial/controller.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_tutorial/models.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/ms_tutorial/overlay.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/pagination.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/palettes.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/step_dialog.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/theme.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/tool_panel.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components/wheel.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components.egg-info/dependency_links.txt +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components.egg-info/requires.txt +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/src/ms_components.egg-info/top_level.txt +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_app_settings.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_chips.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_dock_window.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_executors_ui.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_molsuite_monitor_bridge.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_molsuite_monitor_widgets.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_monitor_config.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_ms_projects_widget.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_progress_column.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_project_browser.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_pymol_toolbar.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_query_builder_keyset.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_shift_hscroll.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_split_button_style.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_step_dialog.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_table_toolbar.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_table_view_state.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_theme_contrast.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/tests/test_tutorial.py +0 -0
- {ms_components-0.3.0 → ms_components-0.3.1}/zensical.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ms_components
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: PySide6 UI components for MolSuite: smart table, project browser, job monitor, PyMOL dock, steppers.
|
|
5
5
|
Author: Mario S. Valdés-Tresanco, Mario E. Valdés-Tresanco
|
|
6
6
|
License-Expression: MIT
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# API reference
|
|
2
|
+
|
|
3
|
+
The reference below is generated directly from the current Python sources.
|
|
4
|
+
Names beginning with an underscore are implementation details.
|
|
5
|
+
|
|
6
|
+
## Smart table
|
|
7
|
+
|
|
8
|
+
::: ms_components.ms_table
|
|
9
|
+
|
|
10
|
+
## Monitor
|
|
11
|
+
|
|
12
|
+
::: ms_components.ms_monitor
|
|
13
|
+
|
|
14
|
+
## Hosts and executors
|
|
15
|
+
|
|
16
|
+
::: ms_components.ms_executors
|
|
17
|
+
|
|
18
|
+
## Projects
|
|
19
|
+
|
|
20
|
+
::: ms_components.ms_projects
|
|
21
|
+
|
|
22
|
+
## Settings
|
|
23
|
+
|
|
24
|
+
::: ms_components.ms_settings
|
|
25
|
+
|
|
26
|
+
## PyMOL
|
|
27
|
+
|
|
28
|
+
::: ms_components.ms_pymol
|
|
29
|
+
|
|
30
|
+
Comparison scenes provide grid and overlay layouts without reloading structures
|
|
31
|
+
or changing the camera. Assign each structure/pose pair the same positive PyMOL
|
|
32
|
+
`grid_slot`, then pass its display name:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
dock.set_scene_context(
|
|
36
|
+
"comparison",
|
|
37
|
+
grid_labels={1: "Complex A", 3: "Complex B"},
|
|
38
|
+
)
|
|
39
|
+
dock.set_comparison_layout("overlay")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`grid_labels` is also accepted by `PymolControlBar` and `PymolSceneContext`.
|
|
43
|
+
Without explicit labels, molecular object titles (or object names when no title
|
|
44
|
+
is available) are joined per positive `grid_slot` within the context's `target`.
|
|
45
|
+
Applications must pass display names if those names are not present in PyMOL.
|
|
46
|
+
|
|
47
|
+
The **Display > Comparison > Grid / Overlay** menu appears only in comparison
|
|
48
|
+
scenes. The chosen layout persists when changing poses or applying presets.
|
|
49
|
+
Overlay displays the existing coordinates together; alignment remains the
|
|
50
|
+
caller's responsibility.
|
|
51
|
+
|
|
52
|
+
Each label is a pseudoatom named `panel_text<slot>`, initially sized 14.
|
|
53
|
+
Grid labels appear at the top-left of their panel; overlay labels form a stacked
|
|
54
|
+
legend that follows viewport resizing. Receptor cartoons, carbon atoms and label
|
|
55
|
+
text share the slot's `COMPARISON_COLORS` color. Heteroatom colors are preserved.
|
|
56
|
+
The label outline uses PyMOL's background-adaptive `front` color for readability
|
|
57
|
+
on light and dark backgrounds. The palette has ten colors and repeats beyond ten
|
|
58
|
+
slots.
|
|
59
|
+
Existing labels are updated in place, so individual settings remain editable,
|
|
60
|
+
for example `cmd.set("label_size", 18, "panel_text3")`. Presets restore label
|
|
61
|
+
visibility after changing molecular representations.
|
|
62
|
+
|
|
63
|
+
To replace one pose without rebuilding the scene, pass its bytes directly:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
dock.replace_comparison_object(
|
|
67
|
+
"pose_a", pdb_bytes, "pdb",
|
|
68
|
+
reference="receptor_a", label="Complex A [P2]",
|
|
69
|
+
)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The new object inherits the reference's transformation and slot, is shown as
|
|
73
|
+
sticks, and receives the same comparison color. Camera and auto-zoom settings
|
|
74
|
+
are preserved. The previous object remains available until the replacement loads
|
|
75
|
+
successfully. This API neither fetches data nor calls the hosting application.
|
|
76
|
+
Hosts remain responsible for reading their data and discarding stale requests.
|
|
77
|
+
|
|
78
|
+
Setting any non-comparison scene context after a comparison context removes
|
|
79
|
+
the managed labels and sets `grid_mode` to zero. Applications should update
|
|
80
|
+
the scene context when leaving their off-target view, even without loading a
|
|
81
|
+
new structure. Unrelated grid scenes and user-created annotations are not
|
|
82
|
+
removed. `"offtarget"` remains accepted as an alias for `"comparison"`.
|
|
83
|
+
|
|
84
|
+
## Stepper
|
|
85
|
+
|
|
86
|
+
::: ms_components.ms_stepper
|
|
87
|
+
|
|
88
|
+
## Guided tutorials
|
|
89
|
+
|
|
90
|
+
::: ms_components.ms_tutorial
|
|
91
|
+
|
|
92
|
+
## Tool panel
|
|
93
|
+
|
|
94
|
+
::: ms_components.tool_panel
|
|
95
|
+
|
|
96
|
+
## Step dialog
|
|
97
|
+
|
|
98
|
+
::: ms_components.step_dialog
|
|
99
|
+
|
|
100
|
+
## Theme
|
|
101
|
+
|
|
102
|
+
::: ms_components.theme
|
|
@@ -178,6 +178,19 @@ panel.show()
|
|
|
178
178
|
|
|
179
179
|
## Projects, settings, and PyMOL
|
|
180
180
|
|
|
181
|
+
Applications embedding PyMOL should call
|
|
182
|
+
`ms_components.opengl.prepare_pymol_opengl()` **before creating `QApplication`**.
|
|
183
|
+
It requests desktop OpenGL and shared contexts, then checks the actual context
|
|
184
|
+
and compiles legacy GLSL shaders in an isolated process. A failed native check
|
|
185
|
+
can select `xcb` on Linux only when an X11 display passes the same check and
|
|
186
|
+
`QT_QPA_PLATFORM` was not explicitly set. Windows and macOS retain their native
|
|
187
|
+
platforms. If no tested backend works, it raises `RuntimeError` before the
|
|
188
|
+
application starts. Skip this call when PyMOL is disabled or running headlessly.
|
|
189
|
+
|
|
190
|
+
`PymolDockWidget` also requests desktop OpenGL on its own widget and validates
|
|
191
|
+
the actual context before drawing. An incompatible context displays an error
|
|
192
|
+
in the viewer instead of passing it to PyMOL.
|
|
193
|
+
|
|
181
194
|
- `ProjectsMenuWidget(app_id=...)` and `ProjectBrowserWindow(app_id=...)` list
|
|
182
195
|
and create projects through the public `ms_flow` project catalog. Connect
|
|
183
196
|
`project_requested` to open the chosen project; do not read the MolSuite
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import os
|
|
4
5
|
from collections.abc import Callable, Mapping
|
|
5
|
-
from dataclasses import dataclass, field
|
|
6
|
+
from dataclasses import dataclass, field, replace
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
10
|
from ms_components.ms_dockwidget.widget import MSDockWidget
|
|
11
|
+
from ms_components.opengl import pymol_context_error, pymol_surface_format
|
|
10
12
|
|
|
11
13
|
os.environ.setdefault("QT_API", "pyside6")
|
|
12
14
|
|
|
13
15
|
from PySide6.QtCore import QByteArray, QEvent, QSize, Qt, QTimer, Signal
|
|
14
|
-
from PySide6.QtGui import QAction, QActionGroup, QIcon, QPainter, QPixmap
|
|
16
|
+
from PySide6.QtGui import QAction, QActionGroup, QGuiApplication, QIcon, QPainter, QPixmap
|
|
15
17
|
from PySide6.QtSvg import QSvgRenderer
|
|
16
18
|
from PySide6.QtWidgets import (
|
|
17
19
|
QFrame,
|
|
@@ -36,6 +38,44 @@ except ImportError:
|
|
|
36
38
|
from . import plugins as _pymol_plugins # noqa: F401
|
|
37
39
|
|
|
38
40
|
|
|
41
|
+
class _PymolGLWidget(PyMOLGLWidget):
|
|
42
|
+
def __init__(self, parent):
|
|
43
|
+
super().__init__(parent)
|
|
44
|
+
self._opengl_error: str | None = None
|
|
45
|
+
self._opengl_error_label: QLabel | None = None
|
|
46
|
+
if hasattr(self, "setFormat"):
|
|
47
|
+
self.setFormat(pymol_surface_format(self.format()))
|
|
48
|
+
|
|
49
|
+
def initializeGL(self):
|
|
50
|
+
context = self.context()
|
|
51
|
+
self._opengl_error = pymol_context_error(context)
|
|
52
|
+
if self._opengl_error_label is not None:
|
|
53
|
+
self._opengl_error_label.hide()
|
|
54
|
+
if self._opengl_error:
|
|
55
|
+
logging.getLogger(__name__).error(self._opengl_error)
|
|
56
|
+
if self._opengl_error_label is None:
|
|
57
|
+
self._opengl_error_label = QLabel(self)
|
|
58
|
+
self._opengl_error_label.setWordWrap(True)
|
|
59
|
+
QVBoxLayout(self).addWidget(self._opengl_error_label)
|
|
60
|
+
self._opengl_error_label.setText(f"PyMOL viewer unavailable.\n{self._opengl_error}")
|
|
61
|
+
self._opengl_error_label.show()
|
|
62
|
+
return
|
|
63
|
+
fmt = context.format()
|
|
64
|
+
logging.getLogger(__name__).info(
|
|
65
|
+
"PyMOL context: %s, OpenGL %s.%s %s",
|
|
66
|
+
QGuiApplication.platformName(), *fmt.version(), fmt.profile().name,
|
|
67
|
+
)
|
|
68
|
+
super().initializeGL()
|
|
69
|
+
|
|
70
|
+
def paintGL(self):
|
|
71
|
+
if self._opengl_error is None:
|
|
72
|
+
super().paintGL()
|
|
73
|
+
|
|
74
|
+
def resizeGL(self, width, height):
|
|
75
|
+
if self._opengl_error is None:
|
|
76
|
+
super().resizeGL(width, height)
|
|
77
|
+
|
|
78
|
+
|
|
39
79
|
SELECTION_MODES = (
|
|
40
80
|
("Atoms", 0),
|
|
41
81
|
("Residues", 1),
|
|
@@ -53,6 +93,10 @@ REPRESENTATIONS = (
|
|
|
53
93
|
)
|
|
54
94
|
|
|
55
95
|
_TOOLBAR_ICONS = Path(__file__).parent / "icons"
|
|
96
|
+
COMPARISON_COLORS = (
|
|
97
|
+
"orange", "tv_green", "cyan", "magenta", "yellow",
|
|
98
|
+
"salmon", "violet", "lime", "deepteal", "hotpink",
|
|
99
|
+
)
|
|
56
100
|
|
|
57
101
|
|
|
58
102
|
@dataclass(frozen=True)
|
|
@@ -61,6 +105,11 @@ class PymolSceneContext:
|
|
|
61
105
|
target: str = "all"
|
|
62
106
|
selections: Mapping[str, str] = field(default_factory=dict)
|
|
63
107
|
default_preset: str = ""
|
|
108
|
+
grid_labels: Mapping[int, str] = field(default_factory=dict)
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def is_comparison(self) -> bool:
|
|
112
|
+
return self.kind in {"comparison", "offtarget"}
|
|
64
113
|
|
|
65
114
|
def selection(self, role: str, fallback: str = "") -> str:
|
|
66
115
|
value = str(dict(self.selections or {}).get(str(role)) or "").strip()
|
|
@@ -100,6 +149,8 @@ class PymolControlBar(QFrame):
|
|
|
100
149
|
super().__init__(parent)
|
|
101
150
|
self._cmd = cmd
|
|
102
151
|
self._context = PymolSceneContext()
|
|
152
|
+
self._grid_label_objects: set[str] = set()
|
|
153
|
+
self._comparison_layout = "grid"
|
|
103
154
|
self._presets: dict[str, PymolPresetSpec] = {}
|
|
104
155
|
self.setObjectName("PymolControlBar")
|
|
105
156
|
|
|
@@ -168,6 +219,18 @@ class PymolControlBar(QFrame):
|
|
|
168
219
|
"Apply a representation to the current selection or scene target.",
|
|
169
220
|
menu=representation_menu,
|
|
170
221
|
)
|
|
222
|
+
self.comparison_menu = representation_menu.addMenu("Comparison")
|
|
223
|
+
self.comparison_menu.menuAction().setVisible(False)
|
|
224
|
+
comparison_group = QActionGroup(self.comparison_menu)
|
|
225
|
+
comparison_group.setExclusive(True)
|
|
226
|
+
self._comparison_layout_actions: dict[str, QAction] = {}
|
|
227
|
+
for label, value in (("Grid", "grid"), ("Overlay", "overlay")):
|
|
228
|
+
action = self.comparison_menu.addAction(label)
|
|
229
|
+
action.setCheckable(True)
|
|
230
|
+
action.setChecked(value == self._comparison_layout)
|
|
231
|
+
comparison_group.addAction(action)
|
|
232
|
+
action.triggered.connect(lambda checked=False, layout=value: self.set_comparison_layout(layout))
|
|
233
|
+
self._comparison_layout_actions[value] = action
|
|
171
234
|
|
|
172
235
|
hide_menu = QMenu("Hide", bar)
|
|
173
236
|
for label, value in (("Everything", "everything"), *REPRESENTATIONS):
|
|
@@ -359,6 +422,7 @@ class PymolControlBar(QFrame):
|
|
|
359
422
|
target: str = "all",
|
|
360
423
|
selections: Mapping[str, str] | None = None,
|
|
361
424
|
default_preset: str = "",
|
|
425
|
+
grid_labels: Mapping[int, str] | None = None,
|
|
362
426
|
) -> None:
|
|
363
427
|
if isinstance(context, PymolSceneContext):
|
|
364
428
|
normalized = context
|
|
@@ -372,11 +436,136 @@ class PymolControlBar(QFrame):
|
|
|
372
436
|
if str(key).strip() and str(value).strip()
|
|
373
437
|
},
|
|
374
438
|
default_preset=str(default_preset or "").strip(),
|
|
439
|
+
grid_labels=dict(grid_labels or {}),
|
|
375
440
|
)
|
|
441
|
+
was_comparison = self._context.is_comparison
|
|
376
442
|
self._context = normalized
|
|
443
|
+
self.comparison_menu.menuAction().setVisible(normalized.is_comparison)
|
|
444
|
+
if was_comparison or normalized.is_comparison:
|
|
445
|
+
self._run_command("Grid labels", self._sync_grid_labels)
|
|
377
446
|
self.refresh_context_label()
|
|
378
447
|
self._refresh_preset_menu()
|
|
379
448
|
|
|
449
|
+
@property
|
|
450
|
+
def comparison_layout(self) -> str:
|
|
451
|
+
return self._comparison_layout
|
|
452
|
+
|
|
453
|
+
def set_comparison_layout(self, layout: str) -> None:
|
|
454
|
+
if layout not in self._comparison_layout_actions:
|
|
455
|
+
raise ValueError(f"Unsupported comparison layout: {layout}")
|
|
456
|
+
if not self._context.is_comparison:
|
|
457
|
+
raise ValueError("A comparison scene is required to change its layout.")
|
|
458
|
+
previous = self._comparison_layout
|
|
459
|
+
self._comparison_layout = layout
|
|
460
|
+
|
|
461
|
+
def apply() -> None:
|
|
462
|
+
self._sync_grid_labels()
|
|
463
|
+
self._cmd.refresh()
|
|
464
|
+
|
|
465
|
+
if not self._run_command("Comparison layout", apply):
|
|
466
|
+
self._comparison_layout = previous
|
|
467
|
+
for value, action in self._comparison_layout_actions.items():
|
|
468
|
+
action.setChecked(value == self._comparison_layout)
|
|
469
|
+
|
|
470
|
+
def _sync_grid_labels(self, *, color_objects: bool = True) -> None:
|
|
471
|
+
labels = dict(self._context.grid_labels)
|
|
472
|
+
if self._context.is_comparison:
|
|
473
|
+
self._cmd.set("grid_mode", int(self._comparison_layout == "grid"))
|
|
474
|
+
slot_names: dict[int, list[str]] = {}
|
|
475
|
+
for obj in self._cmd.get_object_list(self._context.target):
|
|
476
|
+
if obj in self._grid_label_objects:
|
|
477
|
+
continue
|
|
478
|
+
slot = self._cmd.get_setting_int("grid_slot", obj)
|
|
479
|
+
if slot > 0:
|
|
480
|
+
color = COMPARISON_COLORS[(slot - 1) % len(COMPARISON_COLORS)]
|
|
481
|
+
if color_objects:
|
|
482
|
+
self._cmd.set("cartoon_color", color, selection=obj)
|
|
483
|
+
self._cmd.color(color, f"({obj}) and elem C")
|
|
484
|
+
slot_names.setdefault(slot, []).append(self._cmd.get_title(obj, 1) or obj)
|
|
485
|
+
if not labels:
|
|
486
|
+
labels = {slot: " + ".join(names) for slot, names in slot_names.items()}
|
|
487
|
+
else:
|
|
488
|
+
labels = {}
|
|
489
|
+
self._cmd.set("grid_mode", 0)
|
|
490
|
+
|
|
491
|
+
wanted = {f"panel_text{slot}" for slot in labels}
|
|
492
|
+
for obj in self._grid_label_objects - wanted:
|
|
493
|
+
self._cmd.delete(obj)
|
|
494
|
+
self._grid_label_objects.intersection_update(wanted)
|
|
495
|
+
existing = set(self._cmd.get_names("objects"))
|
|
496
|
+
for slot, title in sorted(labels.items()):
|
|
497
|
+
if slot <= 0:
|
|
498
|
+
raise ValueError("Grid label slots must be positive integers.")
|
|
499
|
+
obj = f"panel_text{slot}"
|
|
500
|
+
if obj not in existing:
|
|
501
|
+
self._cmd.pseudoatom(obj, label=title)
|
|
502
|
+
self._grid_label_objects.add(obj)
|
|
503
|
+
self._cmd.hide("everything", obj)
|
|
504
|
+
self._cmd.set("label_relative_mode", 1, selection=obj)
|
|
505
|
+
self._cmd.set("label_size", 14, selection=obj)
|
|
506
|
+
self._cmd.set("label_position", [1, -1, 0], selection=obj)
|
|
507
|
+
self._cmd.set("grid_slot", slot, selection=obj)
|
|
508
|
+
elif obj in self._grid_label_objects:
|
|
509
|
+
self._cmd.label(obj, repr(title))
|
|
510
|
+
else:
|
|
511
|
+
raise ValueError(f"Grid label object '{obj}' already exists.")
|
|
512
|
+
color = COMPARISON_COLORS[(slot - 1) % len(COMPARISON_COLORS)]
|
|
513
|
+
self._cmd.set("label_color", color, selection=obj)
|
|
514
|
+
self._cmd.set("label_outline_color", "front", selection=obj)
|
|
515
|
+
self._cmd.show("labels", obj)
|
|
516
|
+
self._position_comparison_labels()
|
|
517
|
+
|
|
518
|
+
def _position_comparison_labels(self) -> None:
|
|
519
|
+
if not self._context.is_comparison:
|
|
520
|
+
return
|
|
521
|
+
existing = set(self._cmd.get_names("objects"))
|
|
522
|
+
height = max(1, self._cmd.get_viewport()[1])
|
|
523
|
+
offset = 0.0
|
|
524
|
+
for obj in sorted(self._grid_label_objects & existing,
|
|
525
|
+
key=lambda name: self._cmd.get_setting_int("grid_slot", name)):
|
|
526
|
+
self._cmd.set("label_screen_point", [-1, 1 - offset, 0], selection=obj)
|
|
527
|
+
if self._comparison_layout == "overlay":
|
|
528
|
+
offset += 2 * (abs(self._cmd.get_setting_float("label_size", obj)) + 6) / height
|
|
529
|
+
|
|
530
|
+
def replace_comparison_object(
|
|
531
|
+
self, name: str, data: bytes, fmt: str, *, reference: str, label: str,
|
|
532
|
+
) -> bool:
|
|
533
|
+
def apply() -> None:
|
|
534
|
+
if not self._context.is_comparison:
|
|
535
|
+
raise ValueError("A comparison scene is required to replace an object.")
|
|
536
|
+
objects = self._cmd.get_names("objects")
|
|
537
|
+
if name not in objects or reference not in objects:
|
|
538
|
+
raise ValueError("The comparison object and its reference must already exist.")
|
|
539
|
+
if name == reference:
|
|
540
|
+
raise ValueError("The comparison object must differ from its reference.")
|
|
541
|
+
slot = self._cmd.get_setting_int("grid_slot", reference)
|
|
542
|
+
if slot <= 0:
|
|
543
|
+
raise ValueError("The comparison reference must have a positive grid slot.")
|
|
544
|
+
auto_zoom = self._cmd.get_setting_int("auto_zoom")
|
|
545
|
+
temporary = self._cmd.get_unused_name("_comparison_pose")
|
|
546
|
+
try:
|
|
547
|
+
self._cmd.set("auto_zoom", 0)
|
|
548
|
+
self._cmd.load_raw(data, fmt, temporary)
|
|
549
|
+
self._cmd.matrix_copy(reference, temporary)
|
|
550
|
+
self._cmd.set("grid_slot", slot, temporary)
|
|
551
|
+
self._cmd.show("sticks", temporary)
|
|
552
|
+
self._cmd.color("atomic", temporary)
|
|
553
|
+
color = COMPARISON_COLORS[(slot - 1) % len(COMPARISON_COLORS)]
|
|
554
|
+
self._cmd.set("cartoon_color", color, temporary)
|
|
555
|
+
self._cmd.color(color, f"({temporary}) and elem C")
|
|
556
|
+
self._cmd.delete(name)
|
|
557
|
+
self._cmd.set_name(temporary, name)
|
|
558
|
+
self._context = replace(
|
|
559
|
+
self._context, grid_labels={**self._context.grid_labels, slot: label},
|
|
560
|
+
)
|
|
561
|
+
self._sync_grid_labels(color_objects=False)
|
|
562
|
+
finally:
|
|
563
|
+
if temporary in self._cmd.get_names("objects"):
|
|
564
|
+
self._cmd.delete(temporary)
|
|
565
|
+
self._cmd.set("auto_zoom", auto_zoom)
|
|
566
|
+
|
|
567
|
+
return self._run_command("Replace comparison object", apply)
|
|
568
|
+
|
|
380
569
|
def register_preset(self, spec: PymolPresetSpec) -> None:
|
|
381
570
|
key = str(spec.key or "").strip()
|
|
382
571
|
if not key:
|
|
@@ -507,6 +696,8 @@ class PymolControlBar(QFrame):
|
|
|
507
696
|
|
|
508
697
|
def apply() -> None:
|
|
509
698
|
spec.callback(self._cmd, self._context)
|
|
699
|
+
if self._context.is_comparison:
|
|
700
|
+
self._sync_grid_labels()
|
|
510
701
|
|
|
511
702
|
if self._run_command(spec.label, apply):
|
|
512
703
|
self.preset_applied.emit(normalized_key)
|
|
@@ -585,13 +776,13 @@ class PymolDockWidget(MSDockWidget):
|
|
|
585
776
|
|
|
586
777
|
def __init__(self, title: str, manager, parent=None):
|
|
587
778
|
super().__init__(title, manager, parent)
|
|
588
|
-
self.pymol_widget =
|
|
589
|
-
self.pymol_widget.installEventFilter(self)
|
|
779
|
+
self.pymol_widget = _PymolGLWidget(self)
|
|
590
780
|
if self.cmd is not None:
|
|
591
781
|
self.cmd.set("internal_gui", False)
|
|
592
782
|
|
|
593
783
|
self.control_bar = PymolControlBar(self.cmd, self)
|
|
594
784
|
self.toolbar = self.control_bar
|
|
785
|
+
self.pymol_widget.installEventFilter(self)
|
|
595
786
|
|
|
596
787
|
self._side_panel_widget: QWidget | None = None
|
|
597
788
|
self._splitter = QSplitter(Qt.Horizontal, self)
|
|
@@ -640,17 +831,31 @@ class PymolDockWidget(MSDockWidget):
|
|
|
640
831
|
target: str = "all",
|
|
641
832
|
selections: Mapping[str, str] | None = None,
|
|
642
833
|
default_preset: str = "",
|
|
834
|
+
grid_labels: Mapping[int, str] | None = None,
|
|
643
835
|
) -> None:
|
|
644
836
|
self.control_bar.set_scene_context(
|
|
645
837
|
context,
|
|
646
838
|
target=target,
|
|
647
839
|
selections=selections,
|
|
648
840
|
default_preset=default_preset,
|
|
841
|
+
grid_labels=grid_labels,
|
|
649
842
|
)
|
|
650
843
|
|
|
651
844
|
def register_preset(self, spec: PymolPresetSpec) -> None:
|
|
652
845
|
self.control_bar.register_preset(spec)
|
|
653
846
|
|
|
847
|
+
def set_comparison_layout(self, layout: str) -> None:
|
|
848
|
+
self.control_bar.set_comparison_layout(layout)
|
|
849
|
+
|
|
850
|
+
@property
|
|
851
|
+
def comparison_layout(self) -> str:
|
|
852
|
+
return self.control_bar.comparison_layout
|
|
853
|
+
|
|
854
|
+
def replace_comparison_object(
|
|
855
|
+
self, name: str, data: bytes, fmt: str, *, reference: str, label: str,
|
|
856
|
+
) -> bool:
|
|
857
|
+
return self.control_bar.replace_comparison_object(name, data, fmt, reference=reference, label=label)
|
|
858
|
+
|
|
654
859
|
def unregister_preset(self, key: str) -> None:
|
|
655
860
|
self.control_bar.unregister_preset(key)
|
|
656
861
|
|
|
@@ -687,10 +892,13 @@ class PymolDockWidget(MSDockWidget):
|
|
|
687
892
|
def eventFilter(self, watched, event):
|
|
688
893
|
if watched is self.pymol_widget and event.type() == QEvent.Type.MouseButtonRelease:
|
|
689
894
|
QTimer.singleShot(0, self.control_bar.refresh_context_label)
|
|
895
|
+
elif watched is self.pymol_widget and event.type() == QEvent.Type.Resize and self.scene_context.is_comparison:
|
|
896
|
+
QTimer.singleShot(0, self.control_bar._position_comparison_labels)
|
|
690
897
|
return super().eventFilter(watched, event)
|
|
691
898
|
|
|
692
899
|
|
|
693
900
|
__all__ = [
|
|
901
|
+
"COMPARISON_COLORS",
|
|
694
902
|
"PymolControlBar",
|
|
695
903
|
"PymolDockWidget",
|
|
696
904
|
"PymolPresetSpec",
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Desktop OpenGL setup and an isolated compatibility check for embedded PyMOL."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from PySide6.QtCore import QCoreApplication, Qt
|
|
11
|
+
from PySide6.QtGui import QOpenGLContext, QSurfaceFormat
|
|
12
|
+
|
|
13
|
+
_log = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def pymol_surface_format(base: QSurfaceFormat | None = None) -> QSurfaceFormat:
|
|
17
|
+
"""Keep stereo/multisampling while requesting the desktop API used by PyMOL's shaders."""
|
|
18
|
+
fmt = QSurfaceFormat(base) if base is not None else QSurfaceFormat()
|
|
19
|
+
fmt.setRenderableType(QSurfaceFormat.RenderableType.OpenGL)
|
|
20
|
+
fmt.setVersion(2, 1)
|
|
21
|
+
fmt.setProfile(QSurfaceFormat.OpenGLContextProfile.NoProfile)
|
|
22
|
+
fmt.setDepthBufferSize(24)
|
|
23
|
+
fmt.setStencilBufferSize(8)
|
|
24
|
+
return fmt
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def configure_pymol_opengl() -> None:
|
|
28
|
+
"""Call before QApplication so Qt's shared contexts use the same desktop API."""
|
|
29
|
+
QSurfaceFormat.setDefaultFormat(pymol_surface_format())
|
|
30
|
+
QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_ShareOpenGLContexts, True)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def pymol_context_error(context: QOpenGLContext | None) -> str | None:
|
|
34
|
+
"""Check the actual context, since drivers can ignore the requested format."""
|
|
35
|
+
if context is None or not context.isValid():
|
|
36
|
+
return "Qt could not create a valid OpenGL context."
|
|
37
|
+
fmt = context.format()
|
|
38
|
+
if context.isOpenGLES():
|
|
39
|
+
return "PyMOL requires desktop OpenGL; Qt provided OpenGL ES."
|
|
40
|
+
if fmt.version() < (2, 1):
|
|
41
|
+
return "PyMOL requires an OpenGL 2.1 or newer compatibility context."
|
|
42
|
+
if fmt.version() >= (3, 2) and fmt.profile() != QSurfaceFormat.OpenGLContextProfile.CompatibilityProfile:
|
|
43
|
+
return "PyMOL's legacy shaders require a compatibility context, not OpenGL Core."
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _probe(platform: str | None = None) -> tuple[bool, str]:
|
|
48
|
+
env = os.environ.copy()
|
|
49
|
+
if platform is not None:
|
|
50
|
+
env["QT_QPA_PLATFORM"] = platform
|
|
51
|
+
try:
|
|
52
|
+
result = subprocess.run(
|
|
53
|
+
[sys.executable, str(Path(__file__).resolve())],
|
|
54
|
+
env=env, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=10,
|
|
55
|
+
)
|
|
56
|
+
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
57
|
+
return False, str(exc)
|
|
58
|
+
detail = result.stdout.strip()
|
|
59
|
+
if result.returncode:
|
|
60
|
+
detail = f"OpenGL probe exited with {result.returncode}: {detail}\n{result.stderr[-2000:]}".strip()
|
|
61
|
+
return result.returncode == 0, detail
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def prepare_pymol_opengl() -> None:
|
|
65
|
+
"""Verify the native backend first; select X11 only after a successful fallback probe."""
|
|
66
|
+
from PySide6.QtWidgets import QApplication
|
|
67
|
+
|
|
68
|
+
if QApplication.instance() is not None:
|
|
69
|
+
raise RuntimeError("PyMOL OpenGL must be prepared before QApplication is created.")
|
|
70
|
+
configure_pymol_opengl()
|
|
71
|
+
supported, detail = _probe()
|
|
72
|
+
print(f"PyMOL OpenGL: {detail}", file=sys.stderr)
|
|
73
|
+
if supported:
|
|
74
|
+
return
|
|
75
|
+
if sys.platform.startswith("linux") and os.environ.get("DISPLAY") and not os.environ.get("QT_QPA_PLATFORM"):
|
|
76
|
+
supported, fallback_detail = _probe("xcb")
|
|
77
|
+
if supported:
|
|
78
|
+
os.environ["QT_QPA_PLATFORM"] = "xcb"
|
|
79
|
+
_log.warning("Native OpenGL check failed; using X11 for PyMOL: %s", fallback_detail)
|
|
80
|
+
return
|
|
81
|
+
detail += f"\nX11 fallback: {fallback_detail}"
|
|
82
|
+
raise RuntimeError(f"No compatible OpenGL context is available for PyMOL.\n{detail}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _main() -> int:
|
|
86
|
+
# The driver or Qt platform plugin may abort; only this short-lived process is at risk.
|
|
87
|
+
from PySide6.QtGui import QOffscreenSurface
|
|
88
|
+
from PySide6.QtOpenGL import QOpenGLShader, QOpenGLShaderProgram
|
|
89
|
+
from PySide6.QtWidgets import QApplication
|
|
90
|
+
|
|
91
|
+
configure_pymol_opengl()
|
|
92
|
+
app = QApplication(["pymol-opengl-check"])
|
|
93
|
+
context = QOpenGLContext()
|
|
94
|
+
context.setFormat(QSurfaceFormat.defaultFormat())
|
|
95
|
+
context.setShareContext(QOpenGLContext.globalShareContext())
|
|
96
|
+
if not context.create():
|
|
97
|
+
print(f"{app.platformName()}: context creation failed", flush=True)
|
|
98
|
+
return 1
|
|
99
|
+
surface = QOffscreenSurface()
|
|
100
|
+
surface.setFormat(context.format())
|
|
101
|
+
surface.create()
|
|
102
|
+
if not surface.isValid() or not context.makeCurrent(surface):
|
|
103
|
+
print(f"{app.platformName()}: could not activate the OpenGL context", flush=True)
|
|
104
|
+
return 1
|
|
105
|
+
try:
|
|
106
|
+
error = pymol_context_error(context)
|
|
107
|
+
if error is None:
|
|
108
|
+
# Exercise the legacy GLSL built-ins rejected in an ES or core-only context.
|
|
109
|
+
shader = QOpenGLShaderProgram()
|
|
110
|
+
vertex_ok = shader.addShaderFromSourceCode(QOpenGLShader.Vertex, """
|
|
111
|
+
#version 120
|
|
112
|
+
void main() {
|
|
113
|
+
gl_Position = gl_Vertex;
|
|
114
|
+
gl_FrontColor = gl_Color;
|
|
115
|
+
gl_BackColor = gl_Color;
|
|
116
|
+
}
|
|
117
|
+
""")
|
|
118
|
+
fragment_ok = shader.addShaderFromSourceCode(QOpenGLShader.Fragment, """
|
|
119
|
+
#version 120
|
|
120
|
+
void main() { gl_FragColor = gl_Color; }
|
|
121
|
+
""")
|
|
122
|
+
if not vertex_ok or not fragment_ok or not shader.link():
|
|
123
|
+
error = f"PyMOL's required legacy GLSL features are unavailable: {shader.log()}"
|
|
124
|
+
shader.removeAllShaders()
|
|
125
|
+
fmt = context.format()
|
|
126
|
+
functions = context.functions()
|
|
127
|
+
print(
|
|
128
|
+
f"{app.platformName()}: OpenGL {fmt.majorVersion()}.{fmt.minorVersion()} "
|
|
129
|
+
f"{fmt.profile().name}; {functions.glGetString(0x1F00)}; "
|
|
130
|
+
f"{functions.glGetString(0x1F01)}; {error or 'compatible'}",
|
|
131
|
+
flush=True,
|
|
132
|
+
)
|
|
133
|
+
return int(error is not None)
|
|
134
|
+
finally:
|
|
135
|
+
context.doneCurrent()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if __name__ == "__main__":
|
|
139
|
+
raise SystemExit(_main())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ms_components
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: PySide6 UI components for MolSuite: smart table, project browser, job monitor, PyMOL dock, steppers.
|
|
5
5
|
Author: Mario S. Valdés-Tresanco, Mario E. Valdés-Tresanco
|
|
6
6
|
License-Expression: MIT
|
|
@@ -18,6 +18,7 @@ src/ms_components/chips.py
|
|
|
18
18
|
src/ms_components/listview.py
|
|
19
19
|
src/ms_components/ms_executors.py
|
|
20
20
|
src/ms_components/ms_stepper.py
|
|
21
|
+
src/ms_components/opengl.py
|
|
21
22
|
src/ms_components/pagination.py
|
|
22
23
|
src/ms_components/palettes.py
|
|
23
24
|
src/ms_components/step_dialog.py
|
|
@@ -108,8 +109,10 @@ tests/test_molsuite_monitor_bridge.py
|
|
|
108
109
|
tests/test_molsuite_monitor_widgets.py
|
|
109
110
|
tests/test_monitor_config.py
|
|
110
111
|
tests/test_ms_projects_widget.py
|
|
112
|
+
tests/test_opengl.py
|
|
111
113
|
tests/test_progress_column.py
|
|
112
114
|
tests/test_project_browser.py
|
|
115
|
+
tests/test_pymol_grid.py
|
|
113
116
|
tests/test_pymol_toolbar.py
|
|
114
117
|
tests/test_query_builder_keyset.py
|
|
115
118
|
tests/test_shift_hscroll.py
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
"src/ms_components/ms_tutorial/controller.py",
|
|
89
89
|
"src/ms_components/ms_tutorial/models.py",
|
|
90
90
|
"src/ms_components/ms_tutorial/overlay.py",
|
|
91
|
+
"src/ms_components/opengl.py",
|
|
91
92
|
"src/ms_components/pagination.py",
|
|
92
93
|
"src/ms_components/palettes.py",
|
|
93
94
|
"src/ms_components/step_dialog.py",
|
|
@@ -102,8 +103,10 @@
|
|
|
102
103
|
"tests/test_molsuite_monitor_widgets.py",
|
|
103
104
|
"tests/test_monitor_config.py",
|
|
104
105
|
"tests/test_ms_projects_widget.py",
|
|
106
|
+
"tests/test_opengl.py",
|
|
105
107
|
"tests/test_progress_column.py",
|
|
106
108
|
"tests/test_project_browser.py",
|
|
109
|
+
"tests/test_pymol_grid.py",
|
|
107
110
|
"tests/test_pymol_toolbar.py",
|
|
108
111
|
"tests/test_query_builder_keyset.py",
|
|
109
112
|
"tests/test_shift_hscroll.py",
|