Bundle Example: Add a Command¶
This tutorial builds on the material from Bundle Example: Hello World.
This example describes how to create a ChimeraX bundle
that defines two new commands,
tutorial cofm and
tutorial highlight. The steps in implementing the
bundle_info.xmlcontaining 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.
Before deciding on the name and syntax of your own command, you should peruse the command style guide.
Source Code Organization¶
The source code for this example may be downloaded as a zip-format file containing a folder named tut_cmd. 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.
The files in the
tut_cmd folder are:
tut_cmd- bundle folder
bundle_info.xml- bundle information read by ChimeraX
src- source code to Python package for bundle
__init__.py- package initializer and interface to ChimeraX
cmd.py- source code to implement two
docs/user/commands/tutorial.html- help file for the
The file contents are shown below.
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
hello world example with changes highlighted.
For explanations of the unhighlighted lines, please see
Bundle Example: Hello World.
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-TutorialCommand" 9 version="0.1" package="chimerax.tut_cmd" 10 minSessionVersion="1" maxSessionVersion="1"> 11 12 <!-- Additional information about bundle source --> 13 <Author>UCSF RBVI</Author> 14 <Email>firstname.lastname@example.org</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 commands</Synopsis> 20 <Description>Example code for implementing ChimeraX bundle. 21 22Implements commands "tutorial cofm" to print the center of mass of a 23set of atoms, and "tutorial highlight" to highlight the atoms nearest 24the center of mass. 25 </Description> 26 27 <!-- Categories is a list where this bundle should appear --> 28 <Categories> 29 <Category name="General"/> 30 </Categories> 31 32 <!-- DataFiles is a list of additional files to include in bundle --> 33 <DataFiles> 34 <DataFile>docs/user/commands/tutorial.html</DataFile> 35 </DataFiles> 36 37 <!-- Dependencies on other ChimeraX/Python packages --> 38 <Dependencies> 39 <Dependency name="ChimeraX-Core" version="~=1.1"/> 40 </Dependencies> 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 two commands for printing the center of mass 48 and selecting/coloring atom(s) nearest the center of mass of atoms --> 49 <ChimeraXClassifier>ChimeraX :: Command :: tutorial cofm :: General :: 50 Print center of mass of atoms</ChimeraXClassifier> 51 <ChimeraXClassifier>ChimeraX :: Command :: tutorial highlight :: General :: 52 Highlight atoms near center of mass</ChimeraXClassifier> 53 </Classifiers> 54 55</BundleInfo>
Description tags are
changed to reflect the new bundle name and documentation
(lines 8-10 and 17-25). The
DataFiles tag is added
to include documentation files (lines 33-35).
The only other change is replacing
ChimeraXClassifier tags to declare the two commands
in this bundle (lines 49-52).
Note that the two command,
tutorial cofm (Center OF Mass)
tutorial highlight, are multi-word commands that share
the same initial word. Most bundles that provide multiple
commands should add multi-word commands that share the same
“umbrella” name, e.g.,
tutorial in this example.
All names in the command may be shortened, so
is an accepted alternative to
tutorial highlight, which
minimizes the typing burden on the user.
Note also that the
ChimeraXClassifier tag text may be split
over multiple lines for readability. Whitespace characters
:: are ignored.
src is the folder containing the source code for the
Python package that implements the bundle functionality.
devel command, used for building and
installing bundles, automatically includes all
src as part of the bundle. (Additional
files may also be included using bundle information tags
DataFiles as shown in Bundle Example: Add a Tool.)
The only required file in
.py files are typically arranged to implement
different types of functionality. For example,
is used for command-line commands;
for graphical interfaces;
io.py for reading and saving
The command registration code is essentially the same as
Bundle Example: Hello World, except that the command
ci, is used to get the full name (as listed
bundle_info.xml) of the command to be registered,
and the corresponding function and description are retrieved
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 # register_command called with BundleInfo and 12 # CommandInfo instance instead of command name 13 # (when api_version==0) 14 15 # Override method 16 @staticmethod 17 def register_command(bi, ci, logger): 18 # bi is an instance of chimerax.core.toolshed.BundleInfo 19 # ci is an instance of chimerax.core.toolshed.CommandInfo 20 # logger is an instance of chimerax.core.logger.Logger 21 22 # This method is called once for each command listed 23 # in bundle_info.xml. Since we list two commands, 24 # we expect two calls to this method. 25 26 # We check the name of the command, which should match 27 # one of the ones listed in bundle_info.xml 28 # (without the leading and trailing whitespace), 29 # and import the function to call and its argument 30 # description from the ``cmd`` module. 31 # If the description does not contain a synopsis, we 32 # add the one in ``ci``, which comes from bundle_info.xml. 33 from . import cmd 34 if ci.name == "tutorial cofm": 35 func = cmd.cofm 36 desc = cmd.cofm_desc 37 elif ci.name == "tutorial highlight": 38 func = cmd.highlight 39 desc = cmd.highlight_desc 40 else: 41 raise ValueError("trying to register unknown command: %s" % ci.name) 42 if desc.synopsis is None: 43 desc.synopsis = ci.synopsis 44 45 # We then register the function as the command callback 46 # with the chimerax.core.commands module. 47 from chimerax.core.commands import register 48 register(ci.name, desc, func) 49 50 51# Create the ``bundle_api`` object that ChimeraX expects. 52bundle_api = _MyAPI()
cmd.py contains the functions that implement the bundle commands.
For example, the
cofm function is called when the user issues a
tutorial cofm command. To report the center of mass of a set of
cofm requires several parameters supplied by the user:
the atoms of interest,
in which coordinate system to do the computation (using the atomic coordinates from the input file, or include geometric transformations relative to other models), and
whether the center calculation is weighted by the atomic masses.
It then takes the parameters, computes the center of mass, and
reports the result to the ChimeraX log. The missing link is
how the user-typed command gets translated into a call to
This is the purpose of the call to
register_command method in
register call tells ChimeraX to associate a function and
description with a command name. In this case,
cofm_desc are the function and description associated with
tutorial cofm. When the user types a command that
tutorial cofm (or some abbreviation thereof), ChimeraX
parses the input text according to a standard syntax, maps the input
words to function arguments using the command description,
and then calls the function.
The standard syntax of ChimeraX commands is of the form:
command required_arguments optional_arguments
command is the command name, possibly abbreviated and multi-word. Required arguments appear immediately after the command. If there are multiple required arguments, they must be specified in a prespecified order, i.e., they must all be present and are positional. Optional arguments appear after required arguments. They are typically keyword-value pairs and, because they are keyword-based, may be in any order.
A command description instance describes how to map input text to
Python values. It contains a list of 2-tuples for required arguments
and another for optional arguments. The first element of the 2-tuple
is a string that matches one of the command function parameter names.
The second element is a “type class”. ChimeraX provides a variety
of built-in type classes such as
AtomsArg (container of atoms),
AtomSpecArg (atom specifier).
chimerax.core.commands for the full
list. The order of the required parameters list (in the command
description) must match the expected order for required arguments
(in the input text).
1# vim: set expandtab shiftwidth=4 softtabstop=4: 2 3from chimerax.core.commands import CmdDesc # Command description 4from chimerax.atomic import AtomsArg # Collection of atoms argument 5from chimerax.core.commands import BoolArg # Boolean argument 6from chimerax.core.commands import ColorArg # Color argument 7from chimerax.core.commands import IntArg # Integer argument 8from chimerax.core.commands import EmptyArg # (see below) 9from chimerax.core.commands import Or, Bounded # Argument modifiers 10 11 12# ========================================================================== 13# Functions and descriptions for registering using ChimeraX bundle API 14# ========================================================================== 15 16 17def cofm(session, atoms, weighted=False, transformed=True): 18 """Report center of mass of given atoms.""" 19 20 # ``session`` - ``chimerax.core.session.Session`` instance 21 # ``atoms`` - ``chimerax.atomic.Atoms`` instance or None 22 # ``weighted`` - boolean, whether to include atomic mass in calculation 23 # ``transformed`` - boolean, use scene rather than original coordinates 24 25 atoms, coords, cofm = _get_cofm(session, atoms, transformed, weighted) 26 session.logger.info("%s center of mass: %s" % 27 ("weighted" if weighted else "unweighted", cofm)) 28 29 30cofm_desc = CmdDesc(required=[("atoms", Or(AtomsArg, EmptyArg))], 31 optional=[("weighted", BoolArg), 32 ("transformed", BoolArg)]) 33 34# CmdDesc contains the command description. 35# For the "cofm" command, we expect three arguments: 36# ``atoms`` - collection of atoms (required), default: all atoms 37# ``weighted`` - boolean (optional), default: False 38# ``transformed`` - boolean (optional), default: True 39# ChimeraX expects the command syntax to be something like: 40# command_name req1 req2 [opt1_keyword opt1 value] [opt2_keyword opt2_value] 41# where reqX is the value for a required argument, and optX_keyword and 42# optX_value are the keyword and value for an optional argument. 43# Required arguments are listed in the order expected. Optional arguments 44# appear after required arguments but may be in any order. 45# 46# Example commands: 47# tut cofm /A (cofm of chain A) 48# tut cofm weighted t (weighted cofm of all atoms) 49# tut cofm :23 trans false (cofm of input coordinates of residue 23) 50# 51# The required and optional arguments are passed as keyword arguments to 52# the ``CmdDesc`` constructor. Each set of arguments is passed as a 53# list of 2-tuples. The first element of the tuple must match the name 54# of a parameter of callback function. The second element must be a class 55# describing the expected input; ChimeraX provides many such classes, 56# e.g., BoolArg for boolean values, IntArg for integer values, etc. 57# The order of tuples is important for require arguments as the user 58# must enter them in that order. The order is irrelevant for optional 59# arguments since they are identified by keywords. 60# 61# Note the trick used for the "atoms" argument, which may be left out to 62# mean "use all atoms". If we make "atoms" an optional argument, the user 63# would have to enter "tut cofm atoms /A" rather than "tut cofm /A". 64# The trick is to make "atoms" required, so the input does not need to 65# include the "atoms" keyword; the value for "atoms" can be either an 66# AtomsArg or an EmptyArg. If the user enters an atom specification as 67# part of the command, then "atoms" value matches AtomsArg, which 68# translates to a ``chimerax.atomic.Atoms`` instance for the function 69# parameter; if not, "atoms" matches EmptyArg, which translates to ``None``. 70# 71 72 73def highlight(session, atoms, color, weighted=False, transformed=True, count=1): 74 """Highlight the atoms nearest the center of mass of given atoms.""" 75 76 # ``session`` - ``chimerax.core.session.Session`` instance 77 # ``atoms`` - ``chimerax.atomic.Atoms`` instance or None 78 # ``color`` - ``chimerax.core.colors.Color` instance 79 # ``weighted`` - boolean, whether to include atomic mass in calculation 80 # ``transformed`` - boolean, use scene rather than original coordinates 81 82 # Compute the center of mass first 83 atoms, coords, cofm = _get_cofm(session, atoms, transformed, weighted) 84 85 # Compute the distance of each atom from the cofm 86 # using the NumPy vector norm function 87 from numpy.linalg import norm 88 distances = norm(coords - cofm, axis=1) 89 90 # Sort the array and get the "count" indices to the closest atoms 91 if count > len(atoms): 92 count = len(atoms) 93 from numpy import argsort 94 atom_indices = argsort(distances)[:count] 95 96 # Create a collection of atoms from the indices 97 chosen = atoms[atom_indices] 98 99 # Update their "colors". Assigning a single value to an 100 # array means assign the same value for all elements. 101 chosen.colors = color.uint8x4() 102 103 104highlight_desc = CmdDesc(required=[("atoms", Or(AtomsArg, EmptyArg)), 105 ("color", ColorArg)], 106 optional=[("weighted", BoolArg), 107 ("transformed", BoolArg), 108 ("count", Bounded(IntArg, 1, 5))]) 109 110 111# ========================================================================== 112# Functions intended only for internal use by bundle 113# ========================================================================== 114 115 116def _get_cofm(session, atoms, transformed, weighted): 117 # ``session`` - ``chimerax.core.session.Session`` instance 118 # ``atoms`` - ``chimerax.atomic.Atoms`` instance 119 # ``transformed`` - boolean, use scene rather than original coordinates 120 # ``weighted`` - boolean, whether to include atomic mass in calculation 121 122 # If user did not specify the list of atoms, use all atoms 123 if atoms is None: 124 from chimerax.core.commands import all_objects 125 atoms = all_objects(session).atoms 126 127 # We can use either transformed or untransformed coordinates. 128 # Transformed coordinates are "scene coordinates", which 129 # takes into account translation and rotation of individual 130 # models. Untransformed coordinates are the coordinates 131 # read from the data files. 132 if transformed: 133 coords = atoms.scene_coords 134 else: 135 coords = atoms.coords 136 137 # ``coords`` is a ``numpy`` float array of shape (N, 3) 138 # If we want weighted center, we have to multiply coordinates 139 # by the atomic mass 140 if not weighted: 141 cofm = coords.mean(axis=0) 142 else: 143 m = atoms.elements.masses 144 c = coords * m[:,None] 145 cofm = c.sum(axis=0) / m.sum() 146 147 # To get the average coordinates, we use ``numpy.mean`` 148 # print("DEBUG: center of mass:", cofm) 149 return atoms, coords, cofm
cofm() is the function called from
__init__.py when the
user enters the
cofm command. It retrieves the array of atoms,
their coordinates, and their center of mass by calling the internal
_get_cofm() and reports the result via
session.logger, an instance of
cofm_desc contains the description of what arguments are
required or allowed for the
cofm command. The details of its
declaration are described in the comments in the example.
highlight() is the function called from
__init__.py when the
user enters the
highlight command. Like
cofm(), it retrieves
the array of atoms, their coordinates, and their center of mass by
_get_cofm(). It then
computes the distances from each atom to the center of mass using Numpy (line 88),
sorts the atom indices by distances so that indices of atoms that are closer to the center of mass are towards the front of the sort result (
argsort(distances)), and select the first
countindices (line 94),
turn the array of indices into an array of atoms (line 97), and
finally, set the color of the selected atoms (line 101). The
colorsattribute of the atomic array is an Nx4 array of integers, where N is the number of atoms and the rows (of 4 elements) are the RGBA values for each atom. The
highlight()is an instance of
uint8x4()returns its RGBA value as an array of four (
x4) 8-bit integers (
_get_cofm(), used by both
highlight(), is passed three arguments:
atoms, the atoms specified by the user, if any.
transformed, whether to retrieve transformed (scene) or untransformed (original) coordinates. Untransformed coordinates can typically be used when only a single model is involved because the atoms are fixed relative to each other. Transformed coordinates must be used when distances among multiple models are being computed (i.e., the models must all be in same coordinate system).
weighted, whether to include atomic mass as part of the center of mass computation. Frequently, an unweighted average of atomic coordinates, which is simpler and faster to compute, is sufficient for qualitative analysis.
If the user did not choose specific atoms (when
None), the usual ChimeraX interpretation is that all
atoms should be used (lines 123-125).
an instance of chimerax.core.objects.Object that contains
all open models in the current ChimeraX session, and whose
atoms attribute is an array of atoms in the included
models. Transformed and untransformed coordinates are accessed
of the atom array, respectively (lines 132-135). If atomic mass
need not be included, the “center of mass” is simply the average
of the coordinates (line 141); if a weighted calculation is required,
(a) the atomic masses are retrieved by
(b) the coordinates are scaled by the corresponding atomic masses
(line 144), and
(c) the weighted average is computed (line 145).
For performance, ChimeraX makes use of NumPy arrays in many contexts.
The container for atoms is typically a
instance, as are those for bonds, residues, and atomic structures.
Fetching the same attribute, e.g., coordinates, from a collection
of molecular data, e.g., atoms, usually results in a NumPy array.
Although code involving NumPy arrays are sometimes opaque, they are
typically much more efficient than using Python loops.
The documentation for the
tutorial command should be written
in HTML 5 and saved in a file whose name matches the command
name and has suffix
If the bundle command is a subcommand of an existing command
(e.g. color bundlecoloring) then any spaces should be
replaced by underscores.
When help files are included in bundles, documentation for
the commands may be displayed using the help command,
the same as built-in ChimeraX commands.
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 graphical tools are saved in
user/tools instead of
1<html><head> 2<link rel="stylesheet" type="text/css" href="../userdocs.css" /> 3<title>Command: tutorial</title> 4</head><body> 5 6<a name="top"></a> 7<a href="../index.html"> 8<img width="60px" src="../ChimeraX-docs-icon.svg" alt="ChimeraX docs icon" 9class="clRight" title="User Guide Index"/></a> 10 11<h3><a href="../index.html#commands">Command</a>: tutorial</h3> 12<h3 class="usage"><a href="usageconventions.html">Usage</a>: 13<br><b>tutorial cofm</b> 14 <a href="atomspec.html"><i>spec</i></a> 15[ <b>weighted</b> true | false ] 16[ <b>transformed</b> true | false ] 17</h3> 18<h3 class="usage"><a href="usageconventions.html">Usage</a>: 19<br><b>tutorial highlight</b> 20 <a href="atomspec.html"><i>spec</i></a> 21 <a href="color.html#colorname"><i>colorname</i></a> 22[ <b>weighted</b> true | false ] 23[ <b>transformed</b> true | false ] 24[ <b>count</b> <i>count</i> ] 25</h3> 26 27<a name="cofm"/> 28<p> 29The <b>tutorial cofm</b> command reports the 30center of mass for <a href="atomspec.html"><i>spec</i></a>. 31By default, the unweighted average of the transformed coordinates 32of the specified atoms is reported. 33Untransformed coordinates are the values read from the input file, 34while transformed coordinates include all user-applied rotations 35and translations. If atoms from multiple models are specified, 36transformed coordinates reflect their relative positions correctly 37while untransformed coordinates may not (unless the model coordinates 38started in the same coordinate system). To get the mass-weighted 39center, use the <b>weighted true</b> option. To get the center 40for untransformed coordinates, use the <b>transformed false</b> 41option. 42</p> 43<a name="highlight"/> 44<p> 45The <b>tutorial highlight</b> command changes the color 46of the atom(s) nearest the center of the specified atoms. 47The <b>weighted</b> and <b>transformed</b> options are 48the same as in the <b><a href="#cofm">tutorial cofm</a></b> 49command. The <b>count</b> option specifies the number 50of atoms whose color should be set. The default is one 51and may be as many as five. 52</p> 53 54<hr> 55<address>UCSF Resource for Biocomputing, Visualization, and Informatics / 56April 2018</address> 57</body></html>
While the only requirement for documentation is that it be written as HTML, it is recommended that developers write command help files following the above template, with:
a banner linking to the documentation index,
a usage section with a summary of the command syntax,
text describing each command in the bundle, and
an address for contacting the bundle author.
Note that the target links used in the HTML file are all relative
Even though the command documentation HTML file is stored with the
bundle, ChimeraX treats the links as if the file were located in
commands directory in the developer documentation tree.
This creates a virtual HTML documentation tree where command HTML
files can reference each other without having to be collected
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
build sub-folder below the source code
folder. C/C++ source files, if any, are compiled and
also copied into the
The files in
build are then assembled into a
Python wheel in the
The file with the
.whl extension in the
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
flag guarantees that ChimeraX will exit even if installation
fails for some reason.
With ChimeraX bundles being packaged as standard Python
wheel-format files, they can be distributed as plain files
and installed using the ChimeraX
command. Thus, electronic mail, web sites and file
sharing services can all be used to distribute ChimeraX
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.
Bundle Example: Hello World (previous topic)
Bundle Example: Add a Command (current topic)
Bundle Example: Add a Tool (next topic)