Interacting with Maestro

Basic Concepts

In order to effectively write scripts that control Maestro a basic understanding of Maestro concepts is needed. The two most important concepts are the Project Table and the Workspace.

A Maestro project is a collection of Structure objects and associated data, and the Project Table is the way in which this data is viewed and modified. The Project Table contains zero or more rows. Each row is referred to as an entry and consists of a Structure object (a collection of atoms) and zero or more properties that appear as columns in the table. Each column in the Project Table corresponds to a different property. Acceptable property types are: Boolean, integer, floating point, and string. Properties are most commonly produced from computational jobs but can also be generated by Maestro or added manually by users.

The Workspace is the 3D molecular display area in the main Maestro window. The 3D items displayed in this area include the molecular structure, labels, measurements, ribbons, markers, and surfaces. You can include multiple Project Table entries simultaneously into the Workspace for viewing, analysis or modification. Note that Project Table entries loaded into the Workspace contain only their structural information, and so Structure objects retrieved via the Workspace do not have properties available.

It is important to understand that entries (structures) that have been modified in the Workspace are not immediately updated in the Project Table. By default, updates happen when the entry is implicitly or explicitly excluded from the Workspace. Thus, when writing Python scripts in which you manipulate an entry in the Workspace, you need to synchronize the Workspace changes with the Project Table before using a structure entry from the Project Table. For safety, you should also synchronize the Workspace and Project Table for cases where you simply want to get the structural information for an entry in the Project Table. You can also register a “Workspace changed” callback function to know when synchronization with the project is required.

In contrast with structural changes, entry properties are immediately updated in the project when they are edited in the Project Table.

Running Scripts from Maestro

From the Maestro Command Input Area, you can run individual functions from modules, but you can’t run scripts as a whole. The following Maestro command will run function func from script script.py:

pythonrun script.func

The script.py file must be in the directory Maestro was launched from.

Arguments can also be passed to the functions. For example, if function1 takes one argument and function2 takes two arguments:

pythonrun script.function1 5
pythonrun script.function2 3 5

The pythonrun can only interpret its arguments as integers, floats, and strings. Numbers with decimal points or in scientific notation are converted to floats, other numbers are converted to integers, and everything else is a string. (To pass numbers as strings, enclose them in single quotes.) This means that there is no support for use of None, True or False, lists, tuples, etc. — they are all converted to strings. Use pythoneval for situations where this limitation is not acceptable.

The pythonrun command works by first importing the module, then running the specified function. So, if you make changes to your module script.py, you need to force Maestro to import it again to see the changes. This can be done via:

pythonimport script

In addition to pythonrun and pythonimport, the Maestro command pythoneval is available and can take python commands in standard syntax. For example:

pythoneval import script
pythoneval script.function(5, True, ['a', 4])

Use of pythoneval is roughly equivalent to typing commands on the interactive prompt. Use the Python builtin function reload to re-import a modified module.

Note that you can define aliases in the Maestro Command Input Area. For example, to make py an alias for pythoneval, run:

alias py pythoneval

Adding Your Script to the Maestro Scripts Menu

You can choose to install or manage your scripts from the the Scripts ‣ Install and Scripts ‣ Manage menus in Maestro. However, you need to include some additional information in your script so that you can view and manage your scripts in these menus. This info consists of a module-level docstring, followed by a comment that begins with #Name: and a second comment that begins with #Command:. For example:

"""
The docstring should contain a description of the script. It is
displayed as a summary in the script installation panel.

The docstring is not strictly required but is a good practice.

"""
#Name: Compute Distances
#Command: pythonrun myscript.compute_distances

The #Name comment determines the label that will appear in the Scripts menu. The #Command comment provides a command that will be run when the item is selected in the Scripts menu. A script may have multiple #Name and #Command pairs, in which case there will be multiple entries in the Scripts menu.

It’s also possible to create hierarchical menus by using a #Name value of the form submenu:menu_item. For example,

#Name: My Scripts:Plot All Data

will result in a cascading menu My Scripts that can be used by multiple scripts. This is a powerful organization mechanism.

The Maestro Module

The Maestro module contains functions for interacting with Maestro. Note that the Maestro module may only be used for scripts that are running inside Maestro scripts. Trying to import schrodinger.maestro outside of Maestro will result in an ImportError.

Executing Maestro Commands from a Script

You can use the same commands you would type in the Maestro Command Input Area from within Python:

from schrodinger.maestro import maestro
def rotate_by(by=90):
    maestro.command(f"rotate y={by}")

The hundreds of available commands and their options are documented in the Maestro Command Reference Manual.

Note

You can review the commands that have been issued in the normal operation of Maestro by choosing Command Script Editor from the Edit menu.

There are several different ways the maestro.command function may be called, see the command function for more details.

One useful group of Maestro commands is markers, showmarkers and hidemarkers. These can be used to define a marker group, and then to show or hide them. Syntax is as follows:

markers <marker name> <ASL> red=<value> blue=<value> green=<value>
showmarkers <marker name>
hidemarkers <marker name>

Interacting with the Workspace

The workspace_get and workspace_set functions let a script get a Structure from and send a Structure to the Maestro workspace.

from schrodinger.maestro import maestro
from schrodinger import structure
st = maestro.workspace_get()
# ... manipulate structure here ...
maestro.workspace_set(st)

Note

The workspace_get function does not preserve the properties of entries in the workspace that are included from the Project Table. To obtain included project table entries and their properties, use the included_rows iterator described in the next section.

Interacting with the Project Table

The project module allows Python scripts to interact with the Project Table.

from schrodinger import maestro
from schrodinger import project

pt = maestro.project_table_get()

for row in pt.selected_rows:
    st = row.getStructure()
    # .. compute new value 'new_value' for this structure here ...
    row['r_user_New_Property'] =  new_value

pt.update()

There are three ways to iterate over entries in the Project Table:

from schrodinger import maestro
from schrodinger import project
pt = maestro.project_table_get()

# To iterate over all selected rows, use:
for row in pt.selected_rows:
    pass

# To iterate over all entries included in the workspace, use:
for row in pt.included_rows:
    pass

# To iterate over all rows:
for row in pt.all_rows:
    pass

# To get a list of schrodinger.structure.Structure objects (with properties) from the Project Table
# entries included in the workspace:
included_structures = []
for row in pt.included_rows:
    included_structures.append(row.getStructure())

Selecting entries in the project table may be performed by a number of different mechanisms. Maestro has built-in support for selecting entries with entryselectonly and similar commands. This support is provided by both the Entry Selection dialog box and the ability to define filters (see the Maestro online help and the Maestro User Manual for complete descriptions of these features).

However, there are limits to what type of selections can be generated with these features. They all rely on Maestro’s Entry Selection Language (ESL). The ESL was designed to work on the properties associated with entries. While it is possible to create expressions of arbitrary complexity with the ESL, it is not possible to make selections based on calculations performed on the actual Structure object (number of atoms etc.) nor to make selections based on functions of the entry properties (such as the difference between two properties). To set the selected state of a given entry the is_selected property can be used:

from schrodinger import maestro
from schrodinger import project
pt = maestro.project_table_get()

for row in pt.all_rows:
    if row['r_some_prop'] - row['r_some_other_prop'] > 0.001:
       row.is_selected = True
    else:
       row.is_selected = False

It’s also possible to add any number of new columns to the project table from a Python script. Simply reference new properties of any row:

from schrodinger.maestro import maestro
from schrodinger import project


 pt = maestro.project_table_get()

 for row in pt.all_rows:

     st = row.structure

     num_h_atoms = 0
     num_c_atoms = 0
     for a in st.atom:
         if a.atomic_number == 1:
             num_h_atoms += 1
         elif a.atomic_number == 6:
             num_c_atoms += 1

     # Add or overwrite if already present
     # Format for name is: <type>_<author>_<property_name>
     # type can be i (integer), r (real), b (boolean), s (string)
     # author is "m" for maestro, "user" for user, etc.
     # property_name is any text. Underscores are allowed.
     row['i_user_Num_Carbons'] = num_c_atoms
     row['i_user_Num_Hydrogens'] = num_h_atoms

 pt.refreshTable()

Registering Functions to be Called By Maestro

Normally, the Python functions you write for use in Maestro will be called explicitly with the pythonrun command. However, there are some situations in which you may want to supply a Python function that will be called when particular events occur during the normal operation of Maestro. These callback functions are a powerful way to extend and modify the default behavior of Maestro.

To support atom picking in the Workspace use maestro.picking_atom_start() and pass a function to be called when an atom is picked in the Workspace. The function you pass should be expecting a single parameter that is the atom number of the atom picked in the Workspace.

If you need to perform an action periodically, you can use maestro.periodic_callback_add():

maestro.periodic_callback_add(your_callback_function)

to register a Python function that will be called approximately 20 times a second. If you would like to perform the action less frequently, maintain a counter in your function and only take action every nth time.

You can also register a hover callback function, using maestro.hover_callback_add():

maestro.hover_callback_add(your_callback_function)

The callback will be called by Maestro whenever the pointer is paused (“hovers”) over an atom in the Workspace. When you no longer need the mouse hover callback, unregister your function using:

maestro.hover_callback_remove(your_callback_function_name)

You can add a Python function that will be called each time the Workspace is drawn using maestro.workspace_draw_function_add(). With this callback, just about anything that can be rendered with OpenGL can be drawn in the Workspace. NOTE: This API was removed in 2020-1.

It is also possible to register a callback that will be called when the contents of the Workspace are modified with maestro.workspace_changed_function_add():

maestro.workspace_changed_function_add(your_callback_function)

The callback function in this case is called with a string parameter that indicates exactly what has been modified in the workspace. It will be one of:

  • everything

  • color

  • geometry

  • visibility

  • representation

  • properties

  • coordinates

  • connectivity

  • unknown

In this way a script that does not care about, say, changes in the representation of the Structure object in the Workspace can choose to take no action in these cases.

Note

The state of the Project table is not well-defined during the “workspace changed” callback, and so you can’t reliably check on the inclusion state of an entry. Please use the project_update_callback_add function for this purpose. Also note that you should avoid calling maestro commands during these callbacks in order to maintain consistent Maestro internal state.

Displaying a GUI Within Maestro Using Python

We support (and use) the PyQt* framework, a Python wrapping for the Qt GUI library. PyQt also makes provisions for third party development; for more information please see Riverbank’s website and the PyQt FAQS.

*Any links to third party software or information available on this website are provided “as is” without warranty of any kind, either expressed or implied and such software is to be used at your own risk and you are solely responsible for compliance with any applicable third party licensing requirements. Furthermore, at any time without prior notice, we may make changes to the links pointing to third party software or documentation made available on the third party’s website and we do not guarantee the availability of any third party’s website, software or information.

Other Maestro Interactions

There are a number of other useful functions in the maestro.py module.