Skip to content

Commit cec6c39

Browse files
committed
Merge branch 'develop' of github.com:RBVI/ChimeraX into develop
2 parents dc85450 + 97c4913 commit cec6c39

File tree

28 files changed

+399
-144
lines changed

28 files changed

+399
-144
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ src/bundles/chem_group/src/__init__.py
102102
src/bundles/chem_group/bundle_info.xml
103103
src/bundles/looking_glass/holoplaycore-*.tar.gz
104104
src/bundles/md_crds/gromacs/xdrfile-1.1b/
105+
src/bundles/md_crds/gromacs/xdrfile-1.1.4/
105106
src/bundles/meeting/src/server.pem
106107
src/bundles/mmcif/mmcif_cpp/include/
107108
src/bundles/mmcif/src/mmcif.cpp

docs/devel/dependencies.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Main Packages
2323
-------------
2424

2525
* Python - most of ChimeraX is Python code
26-
* PyQt5 - Qt window toolkit
26+
* PyQt6 - Qt window toolkit
2727
* PyOpenGL - Python interface to OpenGL graphics used to render all graphics
2828
* numpy - arrays, volume data, atomic coordinates, ... (from PyPi)
2929

docs/devel/tutorials/bundle_info.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -963,7 +963,7 @@ The doc strings of that class discuss its methods in detail, but briefly:
963963
options in the ChimeraX Save dialog, you must override the
964964
:py:meth:`~chimerax.save_command.SaverInfo.save_args_widget` method and return a widget
965965
containing your interface (typically a subclass of
966-
`QFrame <https://doc.qt.io/qt-5/qframe.html>`_).
966+
`QFrame <https://doc.qt.io/qt-6/qframe.html>`_).
967967
Conversely, you must also override
968968
:py:meth:`~chimerax.save_command.SaverInfo.save_args_string_from_widget`
969969
that takes your widget and returns a string containing the corresponding options and

docs/devel/tutorials/references.rst

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
.. _Mozilla Firefox: https://www.mozilla.org/firefox/
1313
.. _NCBI: https://www.ncbi.nlm.nih.gov/
1414
.. _NumPy: https://docs.scipy.org/doc/numpy-dev/user/quickstart.html
15-
.. _Qt: https://doc.qt.io/qt-5/index.html
16-
.. _Qt5: https://doc.qt.io/qt-5/index.html
17-
.. _Qt WebEngine: https://doc.qt.io/qt-5/qtwebengine-index.html
15+
.. _Qt: https://doc.qt.io/qt-6/index.html
16+
.. _Qt6: https://doc.qt.io/qt-6/index.html
17+
.. _Qt WebEngine: https://doc.qt.io/qt-6/qtwebengine-index.html
1818
.. _PyQt: https://riverbankcomputing.com/software/pyqt/intro
19-
.. _PyQt5: https://riverbankcomputing.com/software/pyqt/intro
20-
.. _PyQt5 tutorials: https://wiki.python.org/moin/PyQt/Tutorials
19+
.. _PyQt6: https://riverbankcomputing.com/software/pyqt/intro
20+
.. _PyQt6 tutorials: https://wiki.python.org/moin/PyQt/Tutorials
2121
.. _Python wheel: https://wheel.readthedocs.org/
2222
.. _Python package: https://docs.python.org/3/tutorial/modules.html#packages
2323
.. _Python package setup scripts: https://docs.python.org/3/distutils/setupscript.html

docs/devel/tutorials/tutorial_tool_html.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ that defines a graphical interface to the two commands,
2828
``tutorial cofm`` and ``tutorial highlight``, defined
2929
in the :doc:`tutorial_command` example.
3030

31-
The ChimeraX user interface is built using `PyQt5`_,
32-
which has a significant learning curve. However, PyQt5
31+
The ChimeraX user interface is built using `PyQt6`_,
32+
which has a significant learning curve. However, PyQt6
3333
has very good support for displaying `HTML 5`_ with
3434
`JavaScript`_ in a window, which provides a simpler
3535
avenue for implementing graphical interfaces. This example
@@ -212,7 +212,7 @@ displays it in the widget using
212212

213213
The :py:class:`~chimerax.ui.htmltool.HtmlToolInstance`
214214
class also helps manage threading
215-
issues that arise from the way HTML is displayed using `PyQt5`_.
215+
issues that arise from the way HTML is displayed using `PyQt6`_.
216216
The underlying `Qt WebEngine`_ machinery uses a separate thread
217217
for rendering HTML, so developers need to make sure that code
218218
is run in the proper thread. In particular, access to shared
@@ -237,7 +237,7 @@ In this example, the custom scheme is ``tutorial``
237237
:py:meth:`~chimerax.ui.htmltool.HtmlToolInstance.handle_scheme`.
238238
is called with the clicked URL as its lone argument.
239239
Currently, the argument is an instance
240-
of :py:class:`PyQt5.QtCore.QUrl` but that may change later to remove
240+
of :py:class:`PyQt6.QtCore.QUrl` but that may change later to remove
241241
explicit dependency on PyQt.
242242
:py:meth:`~chimerax.ui.htmltool.HtmlToolInstance.handle_scheme`.
243243
is expected to parse the URL and take appropriate action depending on

docs/devel/tutorials/tutorial_tool_qt.rst

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ that defines a graphical interface showing a text-input
2626
field that logs text typed by the user via the appropriate
2727
`log command <../../user/commands/log.html>`_.
2828

29-
The ChimeraX user interface is built using `PyQt5`_,
30-
which is a Python wrapping of the `Qt5`_ C++ windowing toolkit.
31-
Bundle writers can themselves use `PyQt5`_ to provide
29+
The ChimeraX user interface is built using `PyQt6`_,
30+
which is a Python wrapping of the `Qt6`_ C++ windowing toolkit.
31+
Bundle writers can themselves use `PyQt6`_ to provide
3232
a graphical interface to their bundle functionality.
3333
This example shows how to build a simple graphical interface,
3434
and is not meant to cover all the capabilities of `Qt`_
3535
in detail (and there are many!). To learn more you should
36-
explore `PyQt5 tutorials`_
36+
explore `PyQt6 tutorials`_
3737
and/or look at the code of other tools that do things
3838
similar to what you want your tool to do.
3939

@@ -266,38 +266,43 @@ Interface Construction
266266
:end-before: def fill_context_menu
267267

268268
The :py:meth:`_build_ui` method adds our user interface widgets to the tool window
269-
and causes the tool window to be shown. `PyQt5`_ is the windowing toolkit used by ChimeraX.
270-
It is a Python wrapping of the (C++) `Qt5`_ toolkit. This tutorial is in no way meant
271-
to also be a `PyQt5`_/`Qt5`_ tutorial (since those toolkits are *very* extensive) but
269+
and causes the tool window to be shown. `PyQt6`_ is the windowing toolkit used by ChimeraX.
270+
It is a Python wrapping of the (C++) `Qt6`_ toolkit. This tutorial is in no way meant
271+
to also be a `PyQt6`_/`Qt6`_ tutorial (since those toolkits are *very* extensive) but
272272
merely shows how to use those toolkits in the context of ChimeraX.
273273
To gain additional familarity with those toolkits, there are
274-
`PyQt5 tutorials`_ and
275-
`Qt5 tutorials <https://zetcode.com/gui/qt5/>`_ available on the web.
274+
`PyQt6 tutorials`_ and
275+
`Qt6 tutorials <https://zetcode.com/gui/qt6/>`_ available on the web.
276+
Note that in ChimeraX, Qt functionality is imported from "Qt" rather than directly from PyQt6.
277+
"Qt" is a shim module that in turn imports from whatever module is actually providing
278+
the Python Qt wrapping. This insulates your code from changes in the major Qt version
279+
number (such as when we went from 5 to 6) and from ChimeraX using an alternate wrapping,
280+
such as `PySide <https://wiki.qt.io/Qt_for_Python>`_.
276281

277-
On line 69 we import the widgets will need for our interface from the `PyQt5`_ toolkit:
282+
On line 69 we import the widgets will need for our interface from the `PyQt6`_ toolkit:
278283

279-
* A text-label widget (`QLabel <https://doc.qt.io/qt-5/qlabel.html>`_)
280-
* An editable single-line text entry field (`QLineEdit <https://doc.qt.io/qt-5/qlineedit.html>`_)
281-
* A "metawidget" for laying out the above two widgets side by side (`QHBoxLayout <https://doc.qt.io/qt-5/qhboxlayout.html>`_; "HBox" == "horizontal box")
284+
* A text-label widget (`QLabel <https://doc.qt.io/qt-6/qlabel.html>`_)
285+
* An editable single-line text entry field (`QLineEdit <https://doc.qt.io/qt-6/qlineedit.html>`_)
286+
* A "metawidget" for laying out the above two widgets side by side (`QHBoxLayout <https://doc.qt.io/qt-6/qhboxlayout.html>`_; "HBox" == "horizontal box")
282287

283288
Line 70 creates our horizontal layout metawidget, and line 71 creates and adds
284289
the label we want next to our entry field to it. Note that by default widgets
285-
added to an `QHBoxLayout <https://doc.qt.io/qt-5/qhboxlayout.html>`_ will be ordered left to right.
290+
added to an `QHBoxLayout <https://doc.qt.io/qt-6/qhboxlayout.html>`_ will be ordered left to right.
286291
Line 72 creates our text-entry field and line 77 adds it to out layout.
287292

288293
Changes in widgets that the containing interface may care about cause the widget to
289294
emit what `Qt`_ refers to as a "signal".
290-
`returnPressed <https://doc.qt.io/qt-5/qlineedit.html#returnPressed>`_ is the signal that
291-
`QLineEdit <https://doc.qt.io/qt-5/qlineedit.html>`_ emits when the users presses the Return key.
295+
`returnPressed <https://doc.qt.io/qt-6/qlineedit.html#returnPressed>`_ is the signal that
296+
`QLineEdit <https://doc.qt.io/qt-6/qlineedit.html>`_ emits when the users presses the Return key.
292297
A signal's :py:meth:`connect` method is the way to get a particular routine to be called when
293298
the signal is emitted, which we have done on line 76 to get our :py:meth:`return_pressed`
294-
method called when the `returnPressed <https://doc.qt.io/qt-5/qlineedit.html#returnPressed>`_
299+
method called when the `returnPressed <https://doc.qt.io/qt-6/qlineedit.html#returnPressed>`_
295300
signal is emitted.
296301

297-
Lines 86-90 is our handler for the `returnPressed <https://doc.qt.io/qt-5/qlineedit.html#returnPressed>`_
302+
Lines 86-90 is our handler for the `returnPressed <https://doc.qt.io/qt-6/qlineedit.html#returnPressed>`_
298303
signal. Some signals also have
299304
arguments (detailed in each widget's signal documentation), but the
300-
`returnPressed <https://doc.qt.io/qt-5/qlineedit.html#returnPressed>`_
305+
`returnPressed <https://doc.qt.io/qt-6/qlineedit.html#returnPressed>`_
301306
signal has no arguments, so therefore our handler has no non-``self`` arguments.
302307
The handler imports the :py:func:`~chimerax.core.commands.run` utility command that
303308
runs a text string as a ChimeraX command, and then calls that routine with the session
@@ -306,7 +311,7 @@ current text in the line editor (*i.e.* :py:meth:`self.line_edit.text`).
306311

307312
We have created both our widgets and added them to the layout. Line 80 installs
308313
our layout as the layout for the user-interface area of our tool window (the
309-
user-interface area is in fact an instance of `QWidget <https://doc.qt.io/qt-5/qwidget.html>`_).
314+
user-interface area is in fact an instance of `QWidget <https://doc.qt.io/qt-6/qwidget.html>`_).
310315

311316
Line 84 calls our tool window's :py:meth:`manage` method to cause the tool window to be displayed.
312317
The argument to :py:meth:`manage` specifies the general position of the tool window, with
@@ -384,41 +389,41 @@ Our overriding routine is shown on lines 92-103. The routine is invoked with
384389
three arguments:
385390

386391
``menu``
387-
A `QMenu <https://doc.qt.io/qt-5/qmenu.html>`_ instance that we will add our
392+
A `QMenu <https://doc.qt.io/qt-6/qmenu.html>`_ instance that we will add our
388393
custom menu items to. It is not yet populated with the generic menu items.
389394
``x`` and ``y``
390395
The x and y position of the click that is bringing up the context menu,
391396
relative to the entire user-interface area (:py:attr:`self.toolwindow.ui_area`).
392397
These arguments are only used in the rare case where the contents of the
393398
context menu depend on exactly where in the tool the user clicked. These
394399
values are the :py:meth:`x` and :py:meth:`y` methods of the
395-
`QContextMenuEvent <https://doc.qt.io/qt-5/qcontextmenuevent.html>`_ that
400+
`QContextMenuEvent <https://doc.qt.io/qt-6/qcontextmenuevent.html>`_ that
396401
is bringing up this menu.
397402

398403
`Qt`_ abstracts actions on widgets (such as button clicks and menu selections)
399-
with its `QAction <https://doc.qt.io/qt-5/qaction.html>`_ class. In order to
404+
with its `QAction <https://doc.qt.io/qt-6/qaction.html>`_ class. In order to
400405
add a **Clear** item to the menu which will clear the text in the input field,
401-
we import the `QAction <https://doc.qt.io/qt-5/qaction.html>`_ class on line 100
406+
we import the `QAction <https://doc.qt.io/qt-6/qaction.html>`_ class on line 100
402407
and create an instance of it with the text "Clear", and associated with the
403408
context menu, on line 101.
404409

405-
When the action encapsulated by a `QAction <https://doc.qt.io/qt-5/qaction.html>`_
406-
occurs, its `triggered <https://doc.qt.io/qt-5/qaction.html#triggered>`_ signal is emitted
407-
(in a similar fashion to the `returnPressed <https://doc.qt.io/qt-5/qlineedit.html#returnPressed>`_
410+
When the action encapsulated by a `QAction <https://doc.qt.io/qt-6/qaction.html>`_
411+
occurs, its `triggered <https://doc.qt.io/qt-6/qaction.html#triggered>`_ signal is emitted
412+
(in a similar fashion to the `returnPressed <https://doc.qt.io/qt-6/qlineedit.html#returnPressed>`_
408413
signal in the :ref:`interface` section above).
409414
We arrange for our text-input field to be cleared by connecting an anonymous lambda
410-
function (that calls `self.line_edit.clear() <https://doc.qt.io/qt-5/qlineedit.html#clear>`_) to the
411-
`triggered <https://doc.qt.io/qt-5/qaction.html#triggered>`_ signal,
415+
function (that calls `self.line_edit.clear() <https://doc.qt.io/qt-6/qlineedit.html#clear>`_) to the
416+
`triggered <https://doc.qt.io/qt-6/qaction.html#triggered>`_ signal,
412417
shown on line 102.
413-
The `triggered <https://doc.qt.io/qt-5/qaction.html#triggered>`_
418+
The `triggered <https://doc.qt.io/qt-6/qaction.html#triggered>`_
414419
signal does provide an argument (which the lambda uses
415420
`*args` to ignore) indicating whether the item is checked on or off. That
416421
isn't relevant in our case because we haven't made our menu item "checkable".
417422
But you may want to add "checkable" menu items in some cases. To do so,
418-
use `QAction <https://doc.qt.io/qt-5/qaction.html>`_'s
419-
`setCheckable <https://doc.qt.io/qt-5/qaction.html#checkable-prop>`_
423+
use `QAction <https://doc.qt.io/qt-6/qaction.html>`_'s
424+
`setCheckable <https://doc.qt.io/qt-6/qaction.html#checkable-prop>`_
420425
method with a value of `True` to make it checkable and then set its initial
421-
checked/unchecked state with the `setChecked <https://doc.qt.io/qt-5/qaction.html#checked-prop>`_
426+
checked/unchecked state with the `setChecked <https://doc.qt.io/qt-6/qaction.html#checked-prop>`_
422427
method, with the appropriate boolean argument.
423428

424429
We actually add the action/item to the menu on line 103.

src/bundles/alphafold/bundle_info.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
<Providers manager="data formats">
2323
<Provider name="AlphaFold PAE" synopsis="AlphaFold predicted aligned error" category="Structure analysis"
24-
suffixes=".json" default_for=".json" nicknames="pae"
24+
suffixes=".json,.npy,.npz,.pkl" default_for=".json" nicknames="pae"
2525
reference_url="https://en.wikipedia.org/wiki/Predicted_Aligned_Error" />
2626
</Providers>
2727

src/bundles/alphafold/src/pae.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def __init__(self, session, tool_name):
5353
self._structure_menu = m
5454
layout.addWidget(m.frame)
5555

56-
self._source_file = 'file (.json or .npy or .pkl)'
56+
self._source_file = 'file (.json or .npy or .npz or .pkl)'
5757
self._source_database = f'{self.method} database ({self.database_key} id)'
5858
from chimerax.ui.widgets import EntriesRow
5959
ft = EntriesRow(parent, 'Predicted aligned error (PAE) from',
@@ -167,7 +167,7 @@ def _choose_pae_file(self):
167167
from Qt.QtWidgets import QFileDialog
168168
path, ftype = QFileDialog.getOpenFileName(parent, caption = 'Predicted aligned error',
169169
directory = dir,
170-
filter = 'PAE file (*.json *.npy *.pkl)')
170+
filter = 'PAE file (*.json *.npy *.npz *.pkl)')
171171
if path:
172172
self._pae_file.setText(path)
173173
self._open_pae()
@@ -202,8 +202,10 @@ def _open_pae_from_file(self, structure):
202202
if not isfile(path):
203203
raise UserError(f'File "{path}" does not exist.')
204204

205-
if not path.endswith('.json') and not path.endswith('.npy') and not path.endswith('.pkl'):
206-
raise UserError(f'PAE file suffix must be ".json" or ".npy" or ".pkl".')
205+
suffixes = ('.json', '.npy', '.npz', '.pkl')
206+
if len([path for suffix in suffixes if path.endswith(suffix)]) == 0:
207+
suf = ' or '.join(f'"{suffix}"' for suffix in suffixes)
208+
raise UserError(f'PAE file suffix must be {suf}.')
207209

208210
from chimerax.core.commands import run, quote_if_necessary
209211
cmd = '%s pae #%s file %s' % (self.command, structure.id_string, quote_if_necessary(path))
@@ -298,6 +300,11 @@ def _show_help(self):
298300
# pae.model_idx_2.rank_0.npy
299301
# not scores.model_idx_2.rank_0.json which contains summary scores
300302
#
303+
# Boltz-1 local run
304+
#
305+
# nipah_zmr_model_0.cif
306+
# pae_nipah_zmr_model_0.npz
307+
#
301308
# Finding json/pkl with matching prefix works except for full alphafold which
302309
# wants matching suffix.
303310
#
@@ -311,7 +318,7 @@ def _matching_pae_file(structure_path):
311318
dfiles = listdir(dir)
312319
pkl_files = [f for f in dfiles if f.endswith('.pkl')]
313320
json_files = [f for f in dfiles if f.endswith('.json') and not f.startswith('confidence_')]
314-
npy_files = [f for f in dfiles if f.endswith('.npy')]
321+
npy_files = [f for f in dfiles if f.endswith('.npy') or f.endswith('.npz')]
315322

316323
if len(pkl_files) == 0 and len(json_files) == 0 and len(npy_files) == 0:
317324
return None
@@ -326,9 +333,11 @@ def _matching_pae_file(structure_path):
326333
min_length = min(6, len(splitext(filename)[0]))
327334
mfile = None
328335

329-
# Check for precise name match of Chai-1 numpy files
336+
# Check for precise name match of Chai-1 or Boltz-1 numpy files
330337
if len(npy_files) > 0:
331338
mfile = _longest_matching_suffix(filename, npy_files, min_length = min_length)
339+
if mfile is None:
340+
mfile = _longest_matching_suffix('pae_' + filename, npy_files, min_length = min_length) # Boltz-1
332341
if mfile is None:
333342
mfile = _longest_matching_prefix(filename, npy_files, min_length = min_length)
334343

@@ -1212,13 +1221,13 @@ def _include_deleted_residues(res):
12121221
def read_pae_matrix(path):
12131222
if path.endswith('.json'):
12141223
return read_json_pae_matrix(path)
1215-
elif path.endswith('.npy'):
1224+
elif path.endswith('.npy') or path.endswith('.npz'):
12161225
return read_numpy_pae_matrix(path)
12171226
elif path.endswith('.pkl'):
12181227
return read_pickle_pae_matrix(path)
12191228
else:
12201229
from chimerax.core.errors import UserError
1221-
raise UserError(f'AlphaFold predicted aligned error (PAE) files must be in JSON (*.json) or numpy (*.npy) or pickle (*.pkl) format, {path} unrecognized format')
1230+
raise UserError(f'AlphaFold predicted aligned error (PAE) files must be in JSON (*.json) or numpy (*.npy, *.npz) or pickle (*.pkl) format, {path} unrecognized format')
12221231

12231232
# -----------------------------------------------------------------------------
12241233
#
@@ -1273,6 +1282,8 @@ def read_json_pae_matrix(path):
12731282
def read_numpy_pae_matrix(path):
12741283
import numpy
12751284
pae = numpy.load(path)
1285+
if path.endswith('.npz'):
1286+
pae = pae['pae']
12761287
return pae
12771288

12781289
# -----------------------------------------------------------------------------

src/bundles/aniso/src/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ def start_tool(session, tool_name, **kw):
2121
@staticmethod
2222
def register_command(command_name, logger):
2323
from . import cmd
24-
cmd.register_command(logger)
24+
cmd.register_command(logger, command_name)
2525

2626
bundle_api = _AnisoAPI()

0 commit comments

Comments
 (0)