Adding New Typed Commands

Just as menu commands are added in an extension's ChimeraExtension.py file, so are typed commands. This is for two reasons:

  1. The command becomes available early, before command scripts might run.
  2. The module itself doesn't have to be imported (which would slow startup).

The addCommand Function

Typed commands are added using Midas.midas_text's addCommand function. addCommand has two mandatory arguments:

  1. A string containing the typed command name. The user will be able to shorten the name to the shortest unique prefix.
  2. The function to call when the command is invoked. Explained further in the Callback Function section below.
and it has three keyword arguments:
revFunc
Specifies a function to call when ~command is typed. If omitted, ~command will raise an error.
help
Specifies where help for the command is found. Commands whose help is provided with the Chimera documentation itself will set help to True. If help is a string, it is interpreted as an URL that will be brought up in a browser to display help. If help is a tuple, it should be a (path, package) 2-tuple, where path specifies a file relative to package's helpdir subdirectory. The file will be displayed in a browser as help. Note that though package can be an actual imported package, importing the package would defeat the purpose of avoiding importing the module early, so package can just be a string specifying the module name instead. If the help keyword is omitted, no help will be provided.
changesDisplay
A boolean that specifies if the command changes the display in the main graphics window (default: True). This is important for script processing so that Chimera knows if the display needs to update once a command has executing (and to avoid spurious extra frames during script execution -- important if recording animations).

Callback Function

The callback function you register with addCommand will be invoked with two arguments:

  1. A string containing the name of the command as registered with addCommand.
  2. The arguments to the command as typed by the user (a string).

The parsing of the typed arguments and calling of the function that actually performs the command work is typically handled through Midas.midas_text's doExtensionFunc function. This is discussed in detail below in The doExtensionFunc Function. Before getting into that, we know enough at this point that we can look at a brief example.

An Example

Here is the code from the ChimeraExtension.py file of the Define Attribute extension that implements adding the defattr command to the command line module:

def cmdAddAttr(cmdName, args):
        from AddAttr import addAttributes
        from Midas.midas_text import doExtensionFunc
        doExtensionFunc(addAttributes, args,
                                specInfo=[("spec", "models", "molecules")])

from Midas.midas_text import addCommand
addCommand("defattr", cmdAddAttr, help=True)

First, a callback function named cmdAddAttr is defined that will later be registered with addCommand. The callback imports a "workhorse" function (addAttributes) from the main module and doExtensionFunc from Midas.midas_text and then calls doExtensionFunc to process the typed arguments and call addAttributes appropriately. Note that addAttributes is imported inside the cmdAddAttr definition. If it were outside, then the whole module would be imported during Chimera startup, which we are trying to avoid.

After the cmdAddAttr function is defined, Midas.midas_text's addCommand is called to add the defattr command to the command interpreter. Since the help for the defattr command is shipped with Chimera, the help keyword argument is set to True.

The doExtensionFunc Function

As seen in the An Example section above, doExtensionFunc has two mandatory arguments: the "workhorse" function that actually carries out the operation requested by the user, and a string containing the command arguments that the user typed. doExtensionFunc introspects the workhorse function to determine how many mandatory arguments it expects and what keyword arguments it accepts. The initial arguments in the typed string are assumed to correspond to the mandatory arguments, and the remainder of the typed string is assumed to specify valid keyword/value pairs (space separated rather than "=" separated). Keywords will be matched regardless of case, and the user need only type enough characters to distinguish keywords.

doExtensionFunc has two keyword arguments:

invalid
A list of keyword arguments that cannot be used from the command line. doExtensionFunc will behave as if the workhorse function did not have these keywords.
specInfo
If the workhorse function has argument(s) whose value should be a list of Atoms, Residues, etc., for which the user needs to type an atom specifier, that information is given here. specInfo is a list of 3-tuples. The first component of the 3-tuple is the keyword the user should type or, if this is a positional argument, the name that the argument should be assumed to have for type-guessing purposes (in either case it needs to end in "spec"). The next component is the real argument name that the function uses (it will automatically be added to invalid). The final component is the method name to apply to the selection generated by the atom spec in order to extract the desired list (typically "atoms", "residues", "molecules", or "models"). If the method name is None, then the selection itself will be returned.

Typed arguments are processed using some heuristic rules to convert them to their most "natural" type. However, the argument name used by the workhorse function can influence how the typed argument is processed. In particular, if the argument name (ignoring case) ends in...

color
The typed argument is treated as a color name and is converted to a MaterialColor.
spec
The typed argument is assumed to be an atom specifier and is converted to a Selection.
file
The typed argument is a file name. If the user types "browse" or "browser" then a file selection dialog is displayed for choosing the file. If the workhorse argument name ends in savefile, then a save-style browser will be used.

Furthermore, if the user provides a keyword argument multiple times, the value provided to the workhorse function will be a list of the individual values.

In some cases it may be desirable to provide a "shim" function between the doExtensionFunc "workhorse" function and the module's true workhorse function in order to provide more user-friendly argument names or default values than those of the normal module API.

MidasError

If you want to have errors from your command-line function handled the same was as other command-line errors (i.e. shown as red text in the status line rather than raising an error dialog), then you need to have the function you register with addCommand raise MidasError in those cases instead of other error types. This may involve embedding your use of doExtensionFunc in a try/ except block and re-raising caught errors as MidasError. MidasError is defined in the Midas module.

A Second Example

Example ToolbarButtonCommand/ChimeraExtension.py

The initial code is the same as for the ToolbarButtonExtension example
import chimera.extension

class MainChainEMO(chimera.extension.EMO):

def name(self):
return 'Backbone'

def description(self):
return 'display only protein backbone'

def categories(self):
return ['Utilities']

def icon(self):
return self.path('mainchain.tiff')

def activate(self):
self.module().mainchain()

chimera.extension.manager.registerExtension(MainChainEMO(__file__))

Here we define two functions, one to handle the "mainchain" command, and one to handle the "~mainchain" command.
def mainchainCmd(cmdName, args):

Import the module's workhorse function. It is imported inside the function definition so that it does not slow down Chimera startup with extra imports in the main scope.
from ToolbarButtonCommand import mainchain

Import and use the Midas.midas_text doExtensionFunc procedure to process typed arguments and call the mainchain() function appropriately. For a simple function like mainchain(), which takes no arguments, using doExtensionFunc is probably overkill. One could instead use the approach applied in the revMainchainCmd function below and simply test for the presence of any arguments (raising MidasError if any are found) and directly calling the mainchain() function otherwise. As implemented here, using doExtensionFunc, if the user does provide arguments then doExtensionFunc will raise an error complaining that there were unknown keyword arguments supplied.
from Midas.midas_text import doExtensionFunc
doExtensionFunc(mainchain, args)

The function for "~mainchain"
def revMainchainCmd(cmdName, args):

We are going to implement ~mainchain as a synonym for "display", so we import runCommand which simplifies doing that.
from chimera import runCommand
from Midas import MidasError
if args:

Raising MidasError will cause the error message to show up in the status line as red text
raise MidasError("~mainchain takes no arguments")

runCommand takes any legal command-line command and executes it.
runCommand("display")

Now actually register the "mainchain" command with the command interpreter by using addCommand(). The first argument is the command name and the second is the callback function for doing the work. The revFunc keyword specifies the function to implement "~mainchain". The help keyword has been omitted, therefore no help will be provided.
from Midas.midas_text import addCommand
addCommand("mainchain", mainchainCmd, revFunc=revMainchainCmd)

Running the Example

The example files (ChimeraExtension.py, __init__.py, and ToolbarButton.tiff) must be saved in a directory named ToolbarButtonCommand. To run the example, start chimera, bring up the Tools preference category (via the Preferences entry in the Favorites menu; then choosing the "Tools" preference category), use the Add button to add the directory above the ToolbarButtonCommand directory. You should then be able to type "mainchain" to the Chimera command line (start the command line from the Favorites menu if necessary).