Bundle Example: Add a Qt-based Tool¶
This example describes how to create a ChimeraX bundle that defines a graphical interface showing a text-input field that logs text typed by the user via the appropriate log command.
The ChimeraX user interface is built using PyQt5, which is a Python wrapping of the Qt5 C++ windowing toolkit. Bundle writers can themselves use PyQt5 to provide a graphical interface to their bundle functionality. This example shows how to build a simple graphical interface, and is not meant to cover all the capabilities of Qt in detail (and there are many!). To learn more you should explore PyQt5 tutorials and/or look at the code of other tools that do things similar to what you want your tool to do.
The steps in implementing the bundle are:
Create a
bundle_info.xml
file containing information about the bundle,Create a Python package that interfaces with ChimeraX and implements the command functionality, and
Install and test the bundle in ChimeraX.
The final step builds a Python wheel that ChimeraX uses to install the bundle. So if the bundle passes testing, it is immediately available for sharing with other users.
Source Code Organization¶
The source code for this example may be downloaded
as a zip-format file
containing a folder named tut_tool_qt
.
Alternatively, one can start with an empty folder
and create source files based on the samples below.
The source folder may be arbitrarily named, as it is
only used during installation; however, avoiding
whitespace characters in the folder name bypasses the
need to type quote characters in some steps.
Sample Files¶
The files in the tut_tool_qt
folder are:
tut_tool_qt
- bundle folderbundle_info.xml
- bundle information read by ChimeraXsrc
- source code to Python package for bundle__init__.py
- package initializer and interface to ChimeraXtool.py
- source code to implement theTutorial (Qt)
tooldocs/user/commands/tutorial.html
- help file describing the graphical tool
The file contents are shown below.
bundle_info.xml
¶
bundle_info.xml
is an eXtensible Markup Language
format file whose tags are listed in Bundle Information XML Tags.
While there are many tags defined, only a few are needed
for bundles written completely in Python. The
bundle_info.xml
in this example is similar to the one
from the Bundle Example: Add a Command example with changes highlighted.
For explanations of the unhighlighted sections, please
see Bundle Example: Hello World and Bundle Example: Add a Command.
1<!--
2ChimeraX bundle names must start with "ChimeraX-"
3to avoid clashes with package names in pypi.python.org.
4When uploaded to the ChimeraX toolshed, the bundle
5will be displayed without the ChimeraX- prefix.
6-->
7
8<BundleInfo name="ChimeraX-TutorialToolQt"
9 version="0.1" package="chimerax.tut_tool_qt"
10 minSessionVersion="1" maxSessionVersion="1">
11
12 <!-- Additional information about bundle source -->
13 <Author>UCSF RBVI</Author>
14 <Email>chimerax@cgl.ucsf.edu</Email>
15 <URL>https://www.rbvi.ucsf.edu/chimerax/</URL>
16
17 <!-- Synopsis is a one-line description
18 Description is a full multi-line description -->
19 <Synopsis>Example for adding a graphical interface tool</Synopsis>
20 <Description>Example code for implementing ChimeraX bundle.
21
22Implements tool "Tutorial (Qt)" to log typed user input.
23 </Description>
24
25 <!-- Categories is a list where this bundle should appear -->
26 <Categories>
27 <Category name="General"/>
28 </Categories>
29
30 <!-- Dependencies on other ChimeraX/Python packages -->
31 <!-- This example uses functionality from the Tutorial_Command bundle -->
32 <Dependencies>
33 <Dependency name="ChimeraX-Core" version="~=1.1"/>
34 <Dependency name="ChimeraX-UI" version="~=1.0"/>
35 </Dependencies>
36
37 <!-- Non-Python files that are part of package -->
38 <DataFiles>
39 <DataFile>docs/user/tools/tutorial.html</DataFile>
40 </DataFiles>
41
42 <Classifiers>
43 <!-- Development Status should be compatible with bundle version number -->
44 <PythonClassifier>Development Status :: 3 - Alpha</PythonClassifier>
45 <PythonClassifier>License :: Freeware</PythonClassifier>
46 <!-- ChimeraX classifiers describe supplied functionality -->
47 <!-- Register a graphical interface tool -->
48 <ChimeraXClassifier>ChimeraX :: Tool :: Tutorial (Qt) ::
49 General :: Graphical interface example</ChimeraXClassifier>
50 </Classifiers>
51
52</BundleInfo>
The BundleInfo
, Synopsis
and Description
tags are
changed to reflect the new bundle name and documentation
(lines 8-10 and 19-23). Three other changes are needed
for this bundle to declare that:
this bundle depends on the
ChimeraX-UI
bundle (line 34),non-Python files need to be included in the bundle (lines 38-40), and
a single graphical interface tool is provided in this bundle (lines 48-49).
The Dependency
tag on line 34 informs ChimeraX that the
ChimeraX-UI
bundle must be present when this bundle is installed.
If it is not, it is installed first. The ChimeraX-UI
bundle is
needed for the chimerax.ui.MainToolWindow
class
that provides the tool window that will contain our interface elements
(see tool.py` below).
The DataFiles
tag on lines 38-40 informs ChimeraX to include
non-Python files as part of the bundle when building. In this case,
docs/user/tools/tutorial.html
(implicitly in the src
folder)
which provides the help documentation for our tool.
The ChimeraXClassifier
tag on lines 48-49 informs ChimeraX that
there is one graphical interface tool named Tutorial (Qt)
in
the bundle. The last two fields (separated by ::
) are the tool
category and the tool description. ChimeraX will add a
Tutorial (Qt)
menu entry in its Tool
submenu that matches
the tool category, General
; if the submenu does not exist,
it will be created.
src
¶
src
is the folder containing the source code for the
Python package that implements the bundle functionality.
The ChimeraX devel
command, used for building and
installing bundles, automatically includes all .py
files in src
as part of the bundle. (Additional
files may also be included using bundle information tags
such as DataFiles
as shown in Bundle Example: Add a Tool.)
The only required file in src
is __init__.py
.
Other .py
files are typically arranged to implement
different types of functionality. For example, cmd.py
is used for command-line commands; tool.py
or gui.py
for graphical interfaces; io.py
for reading and saving
files, etc.
src/__init__.py
¶
As described in Bundle Example: Hello World, __init__.py
contains
the initialization code that defines the bundle_api
object
that ChimeraX needs in order to invoke bundle functionality.
ChimeraX expects bundle_api
class to be derived from
chimerax.core.toolshed.BundleAPI
with methods
overridden for registering commands, tools, etc.
1# vim: set expandtab shiftwidth=4 softtabstop=4:
2
3from chimerax.core.toolshed import BundleAPI
4
5
6# Subclass from chimerax.core.toolshed.BundleAPI and
7# override the method for registering commands,
8# inheriting all other methods from the base class.
9class _MyAPI(BundleAPI):
10
11 api_version = 1 # start_tool called with BundleInfo and
12 # ToolInfo instance (vs. BundleInfo and
13 # tool name when api_version==0 [the default])
14
15 # Override method
16 @staticmethod
17 def start_tool(session, bi, ti):
18 # session is an instance of chimerax.core.session.Session
19 # bi is an instance of chimerax.core.toolshed.BundleInfo
20 # ti is an instance of chimerax.core.toolshed.ToolInfo
21
22 # This method is called once for each time the tool is invoked.
23
24 # We check the name of the tool, which should match one of the
25 # ones listed in bundle_info.xml (without the leading and
26 # trailing whitespace), and create and return an instance of the
27 # appropriate class from the ``tool`` module.
28 if ti.name == "Tutorial (Qt)":
29 from . import tool
30 return tool.TutorialTool(session, ti.name)
31 raise ValueError("trying to start unknown tool: %s" % ti.name)
32
33 @staticmethod
34 def get_class(class_name):
35 # class_name will be a string
36 if class_name == "TutorialTool":
37 from . import tool
38 return tool.TutorialTool
39 raise ValueError("Unknown class name '%s'" % class_name)
40
41# Create the ``bundle_api`` object that ChimeraX expects.
42bundle_api = _MyAPI()
In this example, the start_tool()
method is overridden to invoke a bundle function, tool.TutorialTool()
,
when the user selects the Tutorial (Qt)
menu item from the General
submenu of the Tools
menu. (The Tutorial (Qt)
and
General
names are from the ChimeraXClassifier
tag
in bundle_info.xml
as described above.)
The arguments to start_tool()
,
in bundle API version 1,
are session
, a chimerax.core.session.Session
instance,
bi
, a chimerax.core.toolshed.BundleInfo
instance, and
ti
, a chimerax.core.toolshed.ToolInfo
instance.
session
is used to access other available data such as
open models, running tasks and the logger for displaying messages,
warnings and errors. bi
contains the bundle information and
is not used in this example. ti
contains the tool information;
in this case, it is used to make sure the name of the tool being
invoked is the expected one. If it is, tool.TutorialTool
is
called; if not, an exception is thrown, which ChimeraX will turn
into an error message displayed to the user.
The get_class()
method
is used by the ChimeraX session-saving mechanism to find needed
class objects in the bundle and is discussed in more detail in
the Sessions section below.
src/tool.py
¶
tool.py
defines the TutorialTool
class that is invoked
by ChimeraX (via the start_tool()
method of bundle_api
in __init__.py
) when the user selects the
Tutorial (Qt)
menu item from the Tools
menu. We will discuss
tool.py
in sections.
Class Initialization¶
1# vim: set expandtab shiftwidth=4 softtabstop=4:
2
3# === UCSF ChimeraX Copyright ===
4# Copyright 2016 Regents of the University of California.
5# All rights reserved. This software provided pursuant to a
6# license agreement containing restrictions on its disclosure,
7# duplication and use. For details see:
8# https://www.rbvi.ucsf.edu/chimerax/docs/licensing.html
9# This notice must be embedded in or attached to all copies,
10# including partial copies, of the software or any revisions
11# or derivations thereof.
12# === UCSF ChimeraX Copyright ===
13
14from chimerax.core.tools import ToolInstance
15
16
17class TutorialTool(ToolInstance):
18
19 # Inheriting from ToolInstance makes us known to the ChimeraX tool mangager,
20 # so we can be notified and take appropriate action when sessions are closed,
21 # saved, or restored, and we will be listed among running tools and so on.
22 #
23 # If cleaning up is needed on finish, override the 'delete' method
24 # but be sure to call 'delete' from the superclass at the end.
25
26 SESSION_ENDURING = False # Does this instance persist when session closes
27 SESSION_SAVE = True # We do save/restore in sessions
28 help = "help:user/tools/tutorial.html"
29 # Let ChimeraX know about our help page
30
Our TutorialTool
class inherits from chimerax.core.tools.ToolInstance
,
which makes it known to the ChimeraX tool manager, and
it will thereby work correctly in all the generic
ways that tools work, such as being displayed by the
command tool show.
By declaring SESSION_ENDURING
as False
, we
are telling ChimeraX’s session handling that this tool
should be closed/destroyed when a session is closed.
SESSION_SAVE
= True
tells session handling
that this tool will save state into sessions and should be
restored by sessions. This is discussed further in the
Sessions section below.
Lastly, setting the class variable help
informs
the ChimeraX help system where the help documentation for
this tool can be found, and is discussed in more detail in
the Help Documentation section.
Instance Initialization¶
31 def __init__(self, session, tool_name):
32 # 'session' - chimerax.core.session.Session instance
33 # 'tool_name' - string
34
35 # Initialize base class.
36 super().__init__(session, tool_name)
37
38 # Set name displayed on title bar (defaults to tool_name)
39 # Must be after the superclass init, which would override it.
40 self.display_name = "Tutorial — Qt-based"
41
42 # Create the main window for our tool. The window object will have
43 # a 'ui_area' where we place the widgets composing our interface.
44 # The window isn't shown until we call its 'manage' method.
45 #
46 # Note that by default, tool windows are only hidden rather than
47 # destroyed when the user clicks the window's close button. To change
48 # this behavior, specify 'close_destroys=True' in the MainToolWindow
49 # constructor.
50 from chimerax.ui import MainToolWindow
51 self.tool_window = MainToolWindow(self)
52
53 # We will be adding an item to the tool's context menu, so override
54 # the default MainToolWindow fill_context_menu method
55 self.tool_window.fill_context_menu = self.fill_context_menu
56
57 # Our user interface is simple enough that we could probably inline
58 # the code right here, but for any kind of even moderately complex
59 # interface, it is probably better to put the code in a method so
60 # that this __init__ method remains readable.
61 self._build_ui()
62
Our TutorialTool
class constructor is called with session
and
tool_name
arguments, because that is how we called it from the
start_tool()
method of our
_MyAPI
class:
from . import tool
return tool.TutorialTool(session, ti.name)
On line 36, we call our superclass (chimerax.core.tools.ToolInstance
)
constructor. It also takes a session and tool name as arguments, which is one of
the principal reasons we passed those arguments to our own constructor.
The chimerax.core.tools.ToolInstance
constructor
sets its session
attribute to be the same as the passed-in session
,
so in other parts of our code we can refer to the session with self.session
.
On lines 50 and 51, we create our MainToolWindow
instance,
which will contain our user interface. The window will not actually be shown until
we call its manage()
method, as discussed in
the following Interface Construction section.
Every tool in ChimeraX has a context menu, which will at least contain some generically
useful tool actions (e.g. Hide Tool). To add additional tool-specific items to
the context menu, we must override MainToolWindow
’s
fill_context_menu()
method (by default a no-op) with our own routine to add
our custom menu items, as discussed in more detail in the Context Menu section.
On line 55 we override that default fill_context_menu()
with self.fill_context_menu()
.
Lastly, on line 61 we call a routine to fill out our user interface, discussed in the next section.
Interface Construction¶
63 def _build_ui(self):
64 # Put our widgets in the tool window
65
66 # We will use an editable single-line text input field (QLineEdit)
67 # with a descriptive text label to the left of it (QLabel). To
68 # arrange them horizontally side by side we use QHBoxLayout
69 from Qt.QtWidgets import QLabel, QLineEdit, QHBoxLayout
70 layout = QHBoxLayout()
71 layout.addWidget(QLabel("Log this text:"))
72 self.line_edit = QLineEdit()
73
74 # Arrange for our 'return_pressed' method to be called when the
75 # user presses the Return key
76 self.line_edit.returnPressed.connect(self.return_pressed)
77 layout.addWidget(self.line_edit)
78
79 # Set the layout as the contents of our window
80 self.tool_window.ui_area.setLayout(layout)
81
82 # Show the window on the user-preferred side of the ChimeraX
83 # main window
84 self.tool_window.manage('side')
85
86 def return_pressed(self):
87 # The use has pressed the Return key; log the current text as HTML
88 from chimerax.core.commands import run
89 # ToolInstance has a 'session' attribute...
90 run(self.session, "log html %s" % self.line_edit.text())
91
The _build_ui()
method adds our user interface widgets to the tool window
and causes the tool window to be shown. PyQt5 is the windowing toolkit used by ChimeraX.
It is a Python wrapping of the (C++) Qt5 toolkit. This tutorial is in no way meant
to also be a PyQt5/Qt5 tutorial (since those toolkits are very extensive) but
merely shows how to use those toolkits in the context of ChimeraX.
To gain additional familarity with those toolkits, there are
PyQt5 tutorials and
Qt5 tutorials available on the web.
On line 69 we import the widgets will need for our interface from the PyQt5 toolkit:
A text-label widget (QLabel)
An editable single-line text entry field (QLineEdit)
A “metawidget” for laying out the above two widgets side by side (QHBoxLayout; “HBox” == “horizontal box”)
Line 70 creates our horizontal layout metawidget, and line 71 creates and adds the label we want next to our entry field to it. Note that by default widgets added to an QHBoxLayout will be ordered left to right. Line 72 creates our text-entry field and line 77 adds it to out layout.
Changes in widgets that the containing interface may care about cause the widget to
emit what Qt refers to as a “signal”.
returnPressed is the signal that
QLineEdit emits when the users presses the Return key.
A signal’s connect()
method is the way to get a particular routine to be called when
the signal is emitted, which we have done on line 76 to get our return_pressed()
method called when the returnPressed
signal is emitted.
Lines 86-90 is our handler for the returnPressed
signal. Some signals also have
arguments (detailed in each widget’s signal documentation), but the
returnPressed
signal has no arguments, so therefore our handler has no non-self
arguments.
The handler imports the run()
utility command that
runs a text string as a ChimeraX command, and then calls that routine with the session
and the appropriate log command, formed based on the
current text in the line editor (i.e. self.line_edit.text()
).
We have created both our widgets and added them to the layout. Line 80 installs our layout as the layout for the user-interface area of our tool window (the user-interface area is in fact an instance of QWidget).
Line 84 calls our tool window’s manage()
method to cause the tool window to be displayed.
The argument to manage()
specifies the general position of the tool window, with
possible values of:
- “side”
The user’s preferred side of the main window for tools (specified in Window preferences)
- “left” / “right” / “top” / “bottom”
A specific side of the main window. Normally, honoring the user’s preference with “side” is preferred, but some tools may work best at the top or bottom of the main window for example.
- None
The window should start out “floating”, not docked into the ChimeraX main window.
Some tools may use multiple windows (created via the MainToolWindow
’s
create_child_window()
method), and for those tools another possible value for
manage()
is another tool window (typically the tool’s main window), in which case
the tool window will start out tabbed with the other window.
A Useful Aside¶
In addition to the wealth of generic UI widgets provided by the Qt library, ChimeraX provides some special-purpose widgets useful for leveraging ChimeraX’s capabilities easily. They are:
- Data lists/menus
Used for choosing one or more models, structures, chains, etc. Basic classes (
ModelListWidget
et al.) described inchimerax.ui.widgets
, and atomic-model specific classes inchimerax.atomic.widgets
. Can be extended to volumes and other data types by using theclass_filter
contructor keyword.- Remember-able options
The chimera.ui.options module provides interface widgets for specifiying numbers, strings, file names, passwords, colors, and more. These options can interoperate with a
Settings
object to remember those settings, and will react to changes to that Settings object to always display the up-to-date value. These options are designed to placed in “container” classes (e.g.SettingsPanel
, also in theoptions
module) that lay out the options into columns that align their labels and their value widgets.- Python-data tables
The chimera.ui.widgets module provides an
ItemTable
class for showing Python objects as rows of a table and columns that show values derived from those objects (frequently attributes) as columns. The table columns are sortable and table cells are optionally editable. Balloon help can be shown for column headers and columns can be hidden/shown as needed. A signal is emitted when the selection in the table changes so that other widgets in your interface can be updated. Querying the table for its current selection (selected
attribute) will return the Python objects corresponding to the selected rows rather than row numbers.
Help Documentation¶
All tools will have a Help context-menu entry,
one of the “generic” context-menu items that ChimeraX
adds to all tool context menus. The Help menu item
will be disabled unless the tool specifies that it
provides help by setting the help
attribute
of the ToolInstance
instance
that it creates. We did do this on line 28:
26 SESSION_ENDURING = False # Does this instance persist when session closes
27 SESSION_SAVE = True # We do save/restore in sessions
28 help = "help:user/tools/tutorial.html"
29 # Let ChimeraX know about our help page
30
31 def __init__(self, session, tool_name):
The string we set the help
attribute to is an URL.
The “help:” prefix tells ChimeraX to use its built-in help
system to locate the help page. It could instead have been “https:”
to have the help page found on web, but this is typically not
recommended since it is best to have the help documentation
match the actual installed version of the tool, and also to
allow help to be accessed even if the user doesn’t currently
have Internet connectiity.
The remainder of the string after “help:” is the actual location
of the help page, relative to the package’s src/docs
folder.
The directory structure is chosen to allow for multiple types
of documentation for a bundle.
For example, developer documentation such as
the bundle API are saved in a devel
directory instead of
user
; documentation for typed commands are saved in
user/commands
instead of user/tools
.
As for the actual contents of the help file…
src/docs/user/tools/tutorial.html
¶
<html>
<!--
=== UCSF ChimeraX Copyright ===
Copyright 2018 Regents of the University of California.
All rights reserved. This software provided pursuant to a
license agreement containing restrictions on its disclosure,
duplication and use. For details see:
https://www.rbvi.ucsf.edu/chimerax/docs/licensing.html
This notice must be embedded in or attached to all copies,
including partial copies, of the software or any revisions
or derivations thereof.
=== UCSF ChimeraX Copyright ===
-->
<head>
<link rel="stylesheet" type="text/css" href="../userdocs.css" />
<title>Tool: Tutorial (Qt)</title>
</head><body>
<a name="top"></a>
<a href="../index.html">
<img width="60px" src="../ChimeraX-docs-icon.svg" alt="ChimeraX docs icon"
class="clRight" title="User Guide Index"/></a>
<h3><a href="../index.html#tools">Tool</a>: Tutorial (Qt)</h3>
<p>
The <b>Tutorial (Qt)</b> tool demonstrates how to
create a graphical tool interface using the
<a href="https://wiki.qt.io/Qt_for_Python">Qt for Python</a> (PySide2)
toolkit.
</p>
<p>
The tool displays a type-in field. When the user hits the Return key,
any text in the field will be shown in the log as its HTML equivalent.
That means that the text:
<pre>
<a href="https://www.cgl.ucsf.edu/chimerax">ChimeraX home page</a>
</pre>
will be logged as:
<br><br>
<a href="https://www.cgl.ucsf.edu/chimerax">ChimeraX home page</a>
</p>
<p>
Choosing <b>Clear</b> from the tool context menu will clear the text field
</p>
<hr>
<address>UCSF Resource for Biocomputing, Visualization, and Informatics /
February 2019</address>
</body></html>
The documentation for the graphical tool should be written
in HTML 5 and saved in a file with a suffix of .html
.
For our example, we named the help file tutorial.html
.
While the only requirement for documentation is that it be written as HTML, it is recommended that developers write tool help files following the above template, with:
a banner linking to the documentation index,
text describing the tool, and
an address for contacting the bundle author.
Note that the target links used in the HTML file are all relative
to ..
.
Even though the tool documentation HTML file is stored with the
bundle, ChimeraX treats the links as if the file were located in
the tools
directory in the developer documentation tree.
This creates a virtual HTML documentation tree where tool HTML
files can reference each other without having to be collected
together.
Sessions¶
As mentioned briefly earlier, the behavior of our tool when sessions
are closed, saved, or restored is control by the boolean attributes
SESSION_ENDURING
and SESSION_SAVE
, which we
set on lines 26 and 27:
25
26 SESSION_ENDURING = False # Does this instance persist when session closes
27 SESSION_SAVE = True # We do save/restore in sessions
28 help = "help:user/tools/tutorial.html"
Tools that set SESSION_ENDURING
to True will not be closed
when a session is closed (restoring a session implicitly closes the
existing session). This behavior can be appropriate for widely used
tools with no particular state to save — such as the Model Panel, which
treats the models closed and opened by the session restore in the same
fashion as other model closures and openings. Our tool does save
state (the current text of the input field), so we set SESSION_ENDURING
to False.
Tools that set SESSION_SAVE
to True will have their state
saved in sessions and need to implement a couple of
additional methods in the ToolInstance
class and one in the BundleAPI
class.
Before we get to the details of that, it would be good to go over how the
ChimeraX session-saving mechanism works, so you can have a better
understanding of how these new methods are used and should be implemented…
When a session is saved, ChimeraX looks through the session object for attributes that inherit from
chimerax.core.state.StateManager
. For such attributes it calls theirtake_snapshot()
method and stows the result. One of the state managers in the session is the tool manager. The tool manager will in turn calltake_snapshot()
on all running tools that inherit fromchimerax.core.state.State
. (which should be all of them sinceToolInstance
inherits fromState
) and stow the result. On restore, the class static methodrestore_snapshot()
is called with the data thattake_snapshot()
produced, andrestore_snapshot()
needs to return a restored object.In practice,
take_snapshot()
typically returns a dictionary with descriptive key names and associated values of various information that would be needed during restore. Frequently one of the keys is ‘version’ so that restore_snapshot can do the right thing if the format of various session data items changes. The values can be regular Python data (including numpy/tinyarray) or class instances that themselves inherit fromState
.
restore_snapshot(session, data)
usesdata
to instantiate an object of that class and return it. If it is difficult to form the constructor arguments for the class from the session data, or to completely set the object state via those arguments then you will have to use “two pass” initialization, where you call the constructor in a way that indicates that it is being restored from a session (e.g. passingNone
to an otherwise mandatory argument) and then calling some method (frequently calledset_state_from_snapshot()
) to fully initialize the minimally initialized object.Session restore knows what bundles various classes came from, but not how to get those classes from the bundle so therefore the bundle’s
BundleAPI
object needs to implement it’sget_class(class_name)
static method to return the class object that corresponds to a string containing the class name.
Our implementation of the take_snapshot()
and restore_snapshot()
methods are on lines 105 to 119:
105 def take_snapshot(self, session, flags):
106 return {
107 'version': 1,
108 'current text': self.line_edit.text()
109 }
110
111 @classmethod
112 def restore_snapshot(class_obj, session, data):
113 # Instead of using a fixed string when calling the constructor below, we could
114 # have saved the tool name during take_snapshot() (from self.tool_name, inherited
115 # from ToolInstance) and used that saved tool name. There are pros and cons to
116 # both approaches.
117 inst = class_obj(session, "Tutorial (Qt)")
118 inst.line_edit.setText(data['current text'])
119 return inst
The take_snapshot()
method forms and returns a dictionary
encapsulating the tool state. It has two keys:
- version
An integer indicating what “version” of the state dictionary it is. This key is not used currently during the restore, but if the format of the state dictionary is ever changed, it may be useful to use the version key to distinguish between the different formats and restore appropriately.
- current text
The text in the input field as the session is saved.
Note that the take_snapshot()
method could return any type
of data, but a dictionary is very flexible, in case additional state needs to be stored in later
versions of the tool. In the rare event that you have to make a change to the data that is not
backwards compatible with previous versions of the bundle, you will need to bump
the maxSessionVersion
number in the BundleInfo tag of your bundle’s
bundle_info.xml file.
Similarly, if your bundle can no longer handle session data from old versions of your bundle, you
would need to increase the minSessionVersion
.
The flags argument of take_snapshot()
can be ignored.
It is intended for use in the future to distinguish between snapshots saved for sessions vs. those
saved for scenes.
The restore_snapshot()
class method constructs an instance
of TutorialTool
, and then sets the text in the instance’s input field to what was saved in the session,
and then returns the instance.
Note that restore_snapshot()
could have been coded as a
static method (and therefore would not receive a class_obj argument), in which case you would
have to use the actual class name in the constructor call.
Lastly, for the session-restore code to be able to find the TutorialTool
class, we must
implement the get_class()
static method in
our _MyAPI
class:
33 @staticmethod
34 def get_class(class_name):
35 # class_name will be a string
36 if class_name == "TutorialTool":
37 from . import tool
38 return tool.TutorialTool
39 raise ValueError("Unknown class name '%s'" % class_name)
get_class()
is passed the needed class name as a string,
and finds and returns the corresponding class object.
get_class()
only needs to handle classes that will be
saved in sessions, not other bundle classes, and should throw an error if it gets a string that
doesn’t match a class name it expects to be involved in session saving/restoring.
Building and Testing Bundles¶
To build a bundle, start ChimeraX and execute the command:
devel build PATH_TO_SOURCE_CODE_FOLDER
Python source code and other resource files are copied
into a build
sub-folder below the source code
folder. C/C++ source files, if any, are compiled and
also copied into the build
folder.
The files in build
are then assembled into a
Python wheel in the dist
sub-folder.
The file with the .whl
extension in the dist
folder is the ChimeraX bundle.
To test the bundle, execute the ChimeraX command:
devel install PATH_TO_SOURCE_CODE_FOLDER
This will build the bundle, if necessary, and install the bundle in ChimeraX. Bundle functionality should be available immediately.
To remove temporary files created while building the bundle, execute the ChimeraX command:
devel clean PATH_TO_SOURCE_CODE_FOLDER
Some files, such as the bundle itself, may still remain and need to be removed manually.
Building bundles as part of a batch process is straightforward, as these ChimeraX commands may be invoked directly by using commands such as:
ChimeraX --nogui --exit --cmd 'devel install PATH_TO_SOURCE_CODE_FOLDER exit true'
This example executes the devel install
command without
displaying a graphics window (--nogui
) and exits immediately
after installation (exit true
). The initial --exit
flag guarantees that ChimeraX will exit even if installation
fails for some reason.
Distributing Bundles¶
With ChimeraX bundles being packaged as standard Python
wheel-format files, they can be distributed as plain files
and installed using the ChimeraX toolshed install
command. Thus, electronic mail, web sites and file
sharing services can all be used to distribute ChimeraX
bundles.
Private distributions are most useful during bundle development, when circulation may be limited to testers. When bundles are ready for public release, they can be published on the ChimeraX Toolshed, which is designed to help developers by eliminating the need for custom distribution channels, and to aid users by providing a central repository where bundles with a variety of different functionality may be found.
Customizable information for each bundle on the toolshed includes its description, screen captures, authors, citation instructions and license terms. Automatically maintained information includes release history and download statistics.
To submit a bundle for publication on the toolshed,
you must first sign in. Currently, only Google
sign in is supported. Once signed in, use the
Submit a Bundle
link at the top of the page
to initiate submission, and follow the instructions.
The first time a bundle is submitted to the toolshed,
it is held for inspection by the ChimeraX team, which
may contact the authors for more information.
Once approved, all subsequent submissions of new
versions of the bundle are posted immediately on the site.
What’s Next¶
Bundle Example: Add a Command (previous topic)
Bundle Example: Add a Tool (current topic)
Bundle Example: Read a New File Format (next topic)