"""
Core functions for interaction with Maestro.
These allow Maestro to be controlled from Python scripts running in the
embedded Maestro interpreter.
Copyright Schrodinger, LLC. All rights reserved.
NOTE: Any new function added to this file should be also added its test
version in maestrointerface.py file.
"""
import warnings
from contextlib import contextmanager
import numpy
from schrodinger import structure
from schrodinger.infra import mm
from schrodinger.infra import mmproj
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets
from schrodinger.structutils import analyze
from schrodinger.ui import maestro_ui
try:
    import _pymaecxx
except ImportError:
    raise ImportError(
        "The maestro module can only be imported for scripts running inside of Maestro."
    )
_pymae = _pymaecxx
# Constants for the Question Dialog
BUTTON1, BUTTON2 = list(range(2))
# Constants for job incorporation callbacks
NOT_MY_JOB = 0
NOT_INCORPORATABLE = 1
WILL_HANDLE_JOB = 2
# Constants for the workspace_changed_function_add function
# Use the constant variable names in your functions to do comparisions
# (not the values themselves)
WORKSPACE_CHANGED_EVERYTHING = "everything"
WORKSPACE_CHANGED_APPEND = "append"  # Atoms appended to the Workspace
WORKSPACE_CHANGED_COLOR = "color"
WORKSPACE_CHANGED_GEOMETRY = "geometry"
WORKSPACE_CHANGED_VISIBILITY = "visibility"
WORKSPACE_CHANGED_REPRESENTATION = "representation"
WORKSPACE_CHANGED_PROPERTIES = "properties"
WORKSPACE_CHANGED_COORDINATES = "coordinates"
WORKSPACE_CHANGED_CONNECTIVITY = "connectivity"
WORKSPACE_CHANGED_SELECTION = "selection"
WORKSPACE_CHANGED_UNKNOWN = "unknown"
# Constants for use with get_directory()
PREFERENCES, TEMPORARY_DATA, TEMPORARY_PROJECTS = list(range(3))
# Icon IDs for markers
GRAPHICS_ICON_NONE = 0
GRAPHICS_ICON_LOCK = 1
GRAPHICS_ICON_SPRING = 2
GRAPHICS_ICON_EYE = 3
GRAPHICS_ICON_EQUALS = 4
GRAPHICS_ICON_RSI = 5
GRAPHICS_ICON_TRANSROT = 6
GRAPHICS_ICON_TORSIONROTATE = 7
GRAPHICS_ICON_CHECK = 8
GRAPHICS_ICON_SCISSORS = 9
GRAPHICS_ICON_BREAK = 10
GRAPHICS_ICON_DDRIVE = 11
GRAPHICS_ICON_LONEPAIR = 12
GRAPHICS_ICON_SMAGGLASS = 13
GRAPHICS_ICON_GMAGGLASS = 14
GRAPHICS_ICON_MAGGLASS = 15
GRAPHICS_ICON_WMAGGLASS = 16
GRAPHICS_ICON_IMAGGLASS = 17
GRAPHICS_ICON_VMAGGLASS = 18
GRAPHICS_ICON_EMAGGLASS = 19
GRAPHICS_ICON_ARROWDOWN = 20
GRAPHICS_ICON_SCANATOM = 21
GRAPHICS_ICON_SCANDIST = 22
GRAPHICS_ICON_SCANANGLE = 23
GRAPHICS_ICON_FRAGMARK = 24
GRAPHICS_ICON_CHECKMARK = 25
GRAPHICS_ICON_DIAMOND = 26
# Styles for atom markers
GRAPHICS_ATOM_MARKER_STYLE_STAR = 0
GRAPHICS_ATOM_MARKER_STYLE_GENERAL = 1
LABEL_DRAW, LABEL_BOUNDING_BOX = list(range(2))
LABEL_CENTER_HORIZONTAL, LABEL_CENTER_VERTICAL, LABEL_USER_OFFSETS = list(
    range(3))
_main_window = None
_picking_loss_callback = None
_picking_default_callback = None
# Pick states
PICK_ATOMS = _pymae.MM_PICKSTATE_ATOMS
PICK_RESIDUES = _pymae.MM_PICKSTATE_RESIDUES
PICK_CHAINS = _pymae.MM_PICKSTATE_CHAINS
PICK_MOLECULES = _pymae.MM_PICKSTATE_MOLECULES
PICK_ENTRIES = _pymae.MM_PICKSTATE_ENTRIES
PICK_SSA = _pymae.MM_PICKSTATE_SSA
#############################################################################
# Module classes
#############################################################################
[docs]class MaestroCommand(Exception):
    """ Raises an Exception and is used whenever a command fails to execute. """
[docs]    def __init__(self, *args):
        """ See class comment """
        Exception.__init__(self, *args)  
#############################################################################
# Module variables
#############################################################################
# The list of TK widgets we know about (all scripts):
_tk_widgets = []
# Function (callable form) that will be called when an atom or bond is picked:
_pick_callback = None
# Function (callable form) that will be called when atoms are picked:
_pick_lasso_callback = None
# Function (callable form) that will be called when objects are picked:
_pick_lasso_object_callback = None
# Function (callable form) that will be called when picking for ASL is
# performed
_pick_asl_callback = None
# Create a dispatch table, indexed by the type values returned by
# m2io_get_type_by_name. Note that we use "set_integer" for the
# boolean type:
_mmproj_set_data_dispatch_table = [
    mmproj.mmproj_index_entry_set_real_data,
    mmproj.mmproj_index_entry_set_integer_data,
    mmproj.mmproj_index_entry_set_string_data,
    mmproj.mmproj_index_entry_set_integer_data
]
class _Callback:
    """
    Class that allows each callback function to be registered by
    specifying it in the string format ("module.function") or in
    callable reference format (reference to function itself).
    ValueError is raised if the same function is registered twice
    and when attempting to remove a function that is no longer registered.
    Ev:63303
    """
    def __init__(self, name, c_add_func, c_remove_func):
        self.name = name
        self.c_add_func = c_add_func
        self.c_remove_func = c_remove_func
        self.script_functions = []
    def invoke(self, *args):
        """ Invoke callbacks specified as callable references """
        # We iterate through a copy of script_functions in case one of the
        # callback functions removes itself as a callback.  (Removing items from
        # a list that's being iterated through can lead to undefined behavior.)
        for func in self.script_functions[:]:
            func(*args)
    def invokeJobIncorporation(self, *args):
        """
        We need a special invoke method for job incorporation callbacks
        as they return values which we need to test. See EV 60588 for details
        """
        for func in self.script_functions:
            res = func(*args)
            if res == NOT_INCORPORATABLE:
                _pymae.mae_set_python_job_not_incorporatable()
                return
            elif res == WILL_HANDLE_JOB:
                _pymae.mae_set_python_handled_job_incorporation()
                return
    def add(self, callback_func):
        """ Add a callback (callable reference or string format) """
        if callable(callback_func):
            if callback_func in self.script_functions:
                raise ValueError("Same %s function registered twice" %
                                 self.name)
            if self.script_functions == []:  # First 'callable reference' function registered
                try:
                    self.c_add_func(
                        "schrodinger.maestro.maestro._invoke_%s_callbacks" %
                        self.name)
                except mm.MmException:
                    print(
                        'maestro.py: tried to re-register maestro._invoke_%s_callbacks'
                        % self.name)
            self.script_functions.append(callback_func)
        else:
            raise ValueError("Specified %s function is not callable" %
                             self.name)
    def remove(self, callback_func):
        """ Remove a callback (callable reference or string format) """
        if callable(callback_func):
            if callback_func not in self.script_functions:
                print(
                    "maestro.py warning: Requested to remove non-registered %s function"
                    % self.name)
                return  # do not throw an exception as this may happen with multiple instances of smae panel
            self.script_functions.remove(callback_func)
            if not self.script_functions:  # Last 'callable reference' function to be removed
                self.c_remove_func(
                    "schrodinger.maestro.maestro._invoke_%s_callbacks" %
                    self.name)
        else:
            raise ValueError("Specified %s function is not callable" %
                             self.name)
PERIODIC_CALLBACK = 'periodic'
WORKSPACE_CHANGED_CALLBACK = 'workspace_changed'
HOVER_CALLBACK = 'hover'
PROJECT_CLOSE_CALLBACK = 'project_close'
PROJECT_RENAME_CALLBACK = 'project_rename'
PROJECT_UPDATE_CALLBACK = 'project_update'
JOB_INCORPORATION_CALLBACK = 'job_incorporation'
WORKSPACE_BOUNDING_BOX_CALLBACK = 'workspace_bounding_box'
RIGHT_CLICK_CALLBACK = 'right_click'
LEVEL_OF_DETAIL_CALLBACK = 'level_of_detail'
COMMAND_CALLBACK = 'command'
ROTATION_POINTS_CALLBACK = 'rotation_points'
# A list of _Callback objects:
_callbacks = {
    PERIODIC_CALLBACK: _Callback(PERIODIC_CALLBACK, _pymae.mae_add_periodic_cb,
                                 _pymae.mae_remove_periodic_cb),
    WORKSPACE_CHANGED_CALLBACK: _Callback(
        WORKSPACE_CHANGED_CALLBACK, _pymae.mae_workspace_changed_function_add,
        _pymae.mae_workspace_changed_function_remove),
    HOVER_CALLBACK: _Callback(HOVER_CALLBACK, _pymae.mae_hover_cb_add,
                              _pymae.mae_hover_cb_remove),
    PROJECT_CLOSE_CALLBACK: _Callback(PROJECT_CLOSE_CALLBACK,
                                      _pymae.mae_project_close_cb_add,
                                      _pymae.mae_project_close_cb_remove),
    PROJECT_RENAME_CALLBACK: _Callback(PROJECT_RENAME_CALLBACK,
                                       _pymae.mae_project_rename_cb_add,
                                       _pymae.mae_project_rename_cb_remove),
    PROJECT_UPDATE_CALLBACK: _Callback(PROJECT_UPDATE_CALLBACK,
                                       _pymae.mae_project_update_cb_add,
                                       _pymae.mae_project_update_cb_remove),
    JOB_INCORPORATION_CALLBACK: _Callback(
        JOB_INCORPORATION_CALLBACK, _pymae.mae_job_incorporation_function_add,
        _pymae.mae_job_incorporation_function_remove),
    WORKSPACE_BOUNDING_BOX_CALLBACK: _Callback(
        WORKSPACE_BOUNDING_BOX_CALLBACK,
        _pymae.mae_workspace_bounding_box_function_add,
        _pymae.mae_workspace_bounding_box_function_remove),
    RIGHT_CLICK_CALLBACK: _Callback(RIGHT_CLICK_CALLBACK,
                                    _pymae.mae_right_click_cb_add,
                                    _pymae.mae_right_click_cb_remove),
    LEVEL_OF_DETAIL_CALLBACK: _Callback(LEVEL_OF_DETAIL_CALLBACK,
                                        _pymae.mae_level_of_detail_cb_add,
                                        _pymae.mae_level_of_detail_cb_remove),
    COMMAND_CALLBACK: _Callback(COMMAND_CALLBACK, _pymae.mae_command_cb_add,
                                _pymae.mae_command_cb_remove)
}
[docs]def is_maestro_elements():
    """
    Return True if running Maestro Elements, False otherwise
    """
    inst = maestro_ui.MaestroHub.instance()
    return inst.isElementsProfileMode() 
#############################################################################
# Module methods
#############################################################################
#****************************************************************************
[docs]def set_prompt_mode_for_next_export(overwrite_prompt):
    """
    :param overwrite_prompt is a Boolean.
        If True, then the prompt will be displayed if the file exists.
        If False, then no prompt is given and the file is simply
        overwritten.
        This only stays in effect for the next call to maestro.command().
        After that, whether the command succeeded or not and whether or
        not entryexport was actually invoked as part of the commands,
        prompting is re-enabled.
    """
    if (not overwrite_prompt):
        _pymae.set_export_overwrite_prompt(0) 
#****************************************************************************
[docs]@contextmanager
def suspend_view_updates():
    """
    A context manager used to manage turning on and off updating of the views
    in Maestro. This is typically used to control whether views are updated
    in response to commmands being issued::
        with suspend_view_updates():
            maestro.command(cmd1)
            maestro.command(cmd2)
            maestro.command(cmd3)
    The views in Maestro will only be updated following the processing of
    the final command.
    """
    _pymae.mae_set_ignore_view_updates(True)
    yield
    _pymae.mae_set_ignore_view_updates(False)
    _pymae.mae_update_views() 
#****************************************************************************
[docs]def command(keyword, *operands, **options):
    """
    Issues a command from a Python script to be executed
    by Maestro. See the Maestro Command Reference for available commands.
    If the command execution fails, then a MaestroCommand exception is
    raised.
    There are three ways to call this routine:
    i) Pass a single string as an argument which simply
    executes the string as is.
    or
    ii) Pass a multi-line string (triple quoted) where each
    line contains a Maestro command which is executed as is.
    or
    iii) Allow this routine to assemble
    the arguments into a command string for you.  If you choose this option,
    then the arguments must be in the following order: keyword, operand(s), option(s).
    See the Maestro Command Reference for details on keywords, operands
    and options.
    """
    status = True
    keyword = str(keyword)  # Convert non-string (ex: unicode) to string type
    cmd_list = keyword.split("\n")
    if len(cmd_list) > 1:
        # Multiple line command. Assume each one is a complete Maestro
        # command:
        for cmd in cmd_list:
            status = _pymae.mae_command(cmd)
            if (not status):
                raise MaestroCommand("Maestro command: %s failed to execute" %
                                     cmd)
    else:
        #Single line keyword:
        final_command = keyword + " "
        for i in operands:
            final_command = final_command + str(i) + " "
        for k in list(options):
            final_command = final_command + k + "=" + str(options[k]) + " "
        # *** final_command now holds the reassembled command
        status = _pymae.mae_command(final_command)
        if (not status):
            raise MaestroCommand("Maestro command: %s failed to execute" %
                                 final_command) 
#****************************************************************************
[docs]def redraw(changed_type=None):
    """
    Forces an immediate redraw of the main Maestro Workspace. All
    currently visible objects are redrawn. Unless the Workspace
    really needs to be redrawn immediately (i.e. while the script is
    running), the preferred method of redrawing the Workspace is to
    use redraw_request().
    If some atoms have been changed in the main
    CT then changed_type can be used to indicate the nature of the change
    to the CT since the last draw. The value should be one of
    WORKSPACE_CHANGED-prefixed values listed at the top of the file.
    """
    if changed_type:
        _pymae.mae_changed_atoms(changed_type)
    _pymae.mae_workspace_redraw() 
#****************************************************************************
[docs]def redraw_request(changed_type=None):
    """
    Requests the main Maestro Workspace be redrawn. This is the
    preferred method of redrawing the Workspace. If the Workspace is to
    be redrawn when the script finishes running, use this command
    to avoid multiple and unnecessary redrawing of the main
    Workspace.
    If some atoms have been changed in the main
    CT then changed_type can be used to indicate the nature of the change
    to the CT since the last draw. The value should be one of
    WORKSPACE_CHANGED-prefixed values listed at the top of the file.
    """
    if changed_type:
        _pymae.mae_changed_atoms(changed_type)
    _pymae.mae_workspace_redraw_request() 
#****************************************************************************
# EV 57754
[docs]def regenerate_markers():
    """
    Forces Maestro to update markers for distances, labels, etc.
    This should be done after atoms are moved in the structure returned by
    workspace_get().
    """
    _pymae.mae_regenerate_markers() 
#****************************************************************************
[docs]def update_surface_display():
    """
    Forces a redraw of displayed surfaces (mostly for included project entries)
    to reflect any changes. This should be done after modifying entry surfaces
    directly, without the use of maestro commands. A separate maestro
    redraw_request is not needed.
    """
    _pymae.mae_update_surface_display() 
#****************************************************************************
[docs]def create_entry_from_workspace(entry_name='entry'):
    """
    Creates a new Project Table entry from Workspace
    """
    cmd = 'entrywscreate "%s" all' % entry_name
    command(cmd) 
[docs]def workspace_get(copy=True):
    """
    Return a Structure object that contains all the atoms in the Maestro
    Workspace.  Project table properties are not included.
    For a description of basic concepts that clarify
    the relationship between the Workspace and Maestro Projects
    as well as when and how changes in one affect the other,
    please see the Basic Concepts section in the python tutorial.
    The Scripting with Python tutorial is part of the General documentation
    and can be accessed from within Maestro's Help facility from the
    Manuals Index.
    By default the 'copy' parameter is True and a copy of the structure
    corresponding to the Maestro Workspace is returned. If you want to
    actually make changes to the structure in the Workspace then use
    maestro.workspace_set() to update the Maestro Workspace (note that new
    molecules can not be added to the Workspace; to add molecules to an entry,
    use the ProjectRow.getStructure() and ProjectRow.setStructure() API).
    You can also use maestro.workspace_get(copy=False) to get a Structure
    object that corresponds to the actual Maestro workspace. This was the
    behavior in versions before the 2008 release but this approach is no
    longer recommended.
    When using copy=False, any change to the Workspace structure by Maestro
    may render your retrieved Structure instance invalid and further use may
    cause a core dump. The safest thing to do is assume that your Structure
    is only valid until the next Maestro command completes.
    Also, when using copy=False any call to workspace_set() will render your
    structure invalid.
    """
    st = structure.Structure(_pymae.mae_get_main_ct())
    st._cpp_structure.releaseOwnership()
    if copy:
        return st.copy()
    else:
        return st 
[docs]def will_create_scratch_entry(st):
    """
    Return True if the given structure, when passed to mae_set_main_ct(),
    would cause scratch entries to be created. Scratch entries have been
    deprecated in Maestro, and cause annoyance to users, as their presense
    shows warning dialogs to pop-up asking them whether to add it to the PT
    or discard it.
    Instead of calling workspace_set(), to add new molecules to the Workspace,
    use one of these methods:
    1) To create a new entry (replacing the Workspace):
        Project.importStructure(st_to_add, wsreplace=True)
    2) To create a new entry (appending to the Workspace):
        Project.importStructure(st_to_add).in_workspace = IN_WORKSPACE
    3) To append molecules to existing entry:
        entry = Project.getRow(entry_id)
        row_st = entry.getStructure()
        row_st.extend(st_to_add)
        entry.setStructure(row_st)
    """
    workspace_eids = get_included_entry_ids()
    for mol in st.molecule:
        mol_entry_ids = {atom.entry_id for atom in mol.atom}
        # At least one atom in the molecule should have a valid entry ID
        # for Maestro to know what entry to associate the molecule with:
        if not mol_entry_ids.intersection(workspace_eids):
            return True
    return False 
[docs]def workspace_set(struct,
                  regenerate_markers=True,
                  copy=True,
                  check_scratch_entry=True):
    """
    Sets the main Workspace connection table to the given Structure object.
    Structure properties are ignored.
    For a description of basic concepts that clarify
    the relationship between the Workspace and Maestro Projects
    as well as when and how changes in one affect the other,
    please see the Basic Concepts section in the python tutorial.
    The Scripting with Python tutorial is part of the General documentation
    and can be accessed from within Maestro's Help facility from the
    Manuals Index.
    Setting regenerate_markers to True (default) will force Maestro to update
    markers for distances, labels, etc. and to apply any changes to the
    Workspace structure. It should be set to True in most circumstances.
    :param check_scratch_entry: True if ct structure needs to be validated for
        scratch entries. will_create_scratch_entry() is an expensive call and
        should be avoided whenever caller knows that structure does not have
        scratch entries (like trajectory snapshot, trajectory playing).
    :type check_scratch_entry: bool
    """
    if check_scratch_entry and will_create_scratch_entry(struct):
        # If the call would create (deprecated) scratch entries, raise an
        # exception.
        raise ValueError(
            "workspace_set(): Molecules found that do not have "
            "a valid entry_id set. Use Project.importStructure() to add new "
            "entries to the Workspace, or ProjectRow.setStructure() to "
            "add atoms to an existing entry.")
    if copy:
        mct = mm.mmct_ct_duplicate(struct)
    else:
        # Maestro takes the ownership of this ct, so release from python.
        struct._cpp_structure.releaseOwnership()
        mct = struct.handle
    _pymae.mae_set_main_ct(mct)
    # Regenerate workspace markers (if requested):
    if regenerate_markers:
        _pymae.mae_regenerate_markers() 
[docs]def get_included_entry():
    """
    Returns a structure of the included entry with properties.
    Raises RuntimeError if no entries are included or if more than one
    entry is included. Returned Structure is a copy of the included entry.
    Scratch entries, if any, are ignored.
    """
    pt = project_table_get()
    num_included = len(pt.included_rows)
    if num_included == 0:
        raise RuntimeError("No entries are included in Workspace")
    elif num_included > 1:
        raise RuntimeError("More than one entry is included in Workspace")
    row = next(iter(pt.included_rows))
    st = row.getStructure(props=True, copy=True)
    return st 
[docs]def get_included_entries():
    """
    Returns a list of structure objects with properties for included entries.
    Will return an empty list if no entries are included. Returned structures
    are copies of the entry structures. If there are scratch entries in the
    Workspace, they will be returned last in the list.
    """
    structures = []
    pt = project_table_get()
    project_table_synchronize()
    for row in pt.included_rows:
        st = row.getStructure(props=True, copy=True, workspace_sync=False)
        # PYTHON-1944
        # Inconsistent entry_id data returned by maestro.get_included_entries()
        #
        # Create a bitset with all the bits enabled
        # Then set the entry id for it
        bs = mm.mmbs_new(st.atom_total)
        mm.mmbs_fill(bs)
        mm.mmct_ct_set_entry_name(st, row.entry_id, bs)
        mm.mmbs_delete(bs)
        structures.append(st)
    # Include scratch entry (if any):
    scratch_atoms = {}
    workspace_st = workspace_get(copy=True)
    for a in workspace_st.atom:
        entry_id = a.entry_id
        if entry_id.startswith("Scratch"):
            if entry_id not in scratch_atoms:
                scratch_atoms[entry_id] = []
            scratch_atoms[entry_id].append(a.index)
    for k in list(scratch_atoms):
        scratch_st = workspace_st.extract(scratch_atoms[k])
        # PYTHON-1945
        # maestro.get_included_entries() should set s_m_entry_id
        # for Scratch entries
        #
        # Create a bitset with all the bits enabled
        # Then set the entry id for it
        bs = mm.mmbs_new(scratch_st.atom_total)
        mm.mmbs_fill(bs)
        mm.mmct_ct_set_entry_name(scratch_st, k, bs)
        mm.mmbs_delete(bs)
        scratch_st.property['s_m_entry_id'] = k
        structures.append(scratch_st)
    return structures 
[docs]def get_included_entry_ids():
    """
    Return a set of entry ids for all structures in the workspace
    (including scratch entry)
    :return: Set of entry ids
    :rtype: set of str
    """
    return set(_pymae.mae_get_included_entry_ids()) 
[docs]def is_function_registered(callback_type, func):
    """
    This returns whether a function has already been registered with a
    callback.
    :param callback_type: What callback you're checking, these are the keys
                          to the _callbacks dict.
    :type callback_type: str
    :param func: The function you're checked to see if it's registered
    :type func: A function
    :return: Is the function registered already?
    :rtype: bool
    """
    if callback_type not in _callbacks:
        raise ValueError("Invalid callback type %s" % callback_type)
    return func in _callbacks[callback_type].script_functions 
#****************************************************************************
[docs]def workspace_changed_function_add(callback_func):
    """
    Register a function to be called when the Workspace contents are
    changed in some way. The function registered should expect to receive
    a single string parameter - one of the following values which indicates
    what has changed for the workspace structure.  You should check
    against the module-level constants that are provided.  These
    start with WORKSPACE_CHANGED-prefixed.  See top of module for more details.
    This feature will usually be used in conjunction with a workspace
    drawing callback but it can be used for anytime a Python script needs
    to know that the contents of the workspace have changed.
    Note that the state of the Project table is not defined during this
    callback if the callback was triggered by the inclusion or exclusion
    of an entry from the project. In other words you can't reliably check
    on the inclusion state of an entry during this callback. Also you
    shouldn't issue Maestro commands from the callback.
    """
    _callbacks['workspace_changed'].add(callback_func) 
#****************************************************************************
[docs]def workspace_changed_function_remove(callback_func):
    """
    Remove the named function so it is no longer called when the
    Workspace is changed
    Removing a non-existent function will cause an error.
    """
    _callbacks['workspace_changed'].remove(callback_func) 
_rotation_cb = None
_rotation_removed_cb = None
#****************************************************************************
[docs]def rotation_points_add(rotation_center, points, changed_cb, removed_cb):
    """
    Add points which will be rotated around the rotation_center as the user
    rotates the Workspace using the mouse.
    :param rotation_center: The coordinates of the center of rotation.
    :type rotation_center: List of 3 floats
    :param points: Coordinates that should be rotated around the center.
    :type points: List of 3-item lists.
    :param changed_cb: Function to be called as points are rotated. It will be
                       called with a single argument: A list of new coordinates
                       for all points: `[[x0,y0,z0], [x1,y1,z1], ...]`
    :type changed_cb: callable
    :param removed_cb: Function to be called when the rotation mode was exited.
    :type removed_cb: callable
    """
    global _rotation_cb, _rotation_removed_cb
    num_points = len(points)
    flattened_points = []
    for point in points:
        if len(point) != 3:
            raise ValueError("Invalid coordinate: %s" % point)
        flattened_points += point
    if not callable(changed_cb):
        raise ValueError("Specified changed_cb function is not callable")
    if not callable(removed_cb):
        raise ValueError("Specified removed_cb function is not callable")
    if _rotation_cb or _rotation_removed_cb:
        # Un-register the previous callback, if any:
        _pymae.mae_remove_rotation_points()
    _rotation_cb = changed_cb
    _rotation_removed_cb = removed_cb
    _pymae.mae_add_rotation_points(
        rotation_center, flattened_points,
        "schrodinger.maestro.maestro._invoke_rotation_callback",
        "schrodinger.maestro.maestro._invoke_rotation_removed_callback") 
#****************************************************************************
[docs]def rotation_points_remove():
    """
    Removes the functions and points which were added with the
    rotation_points_add() function.
    """
    global _rotation_cb, _rotation_removed_cb
    _pymae.mae_remove_rotation_points()
    _rotation_cb = None
    _rotation_removed_cb = None 
_translation_cb = None
_translation_removed_cb = None
#****************************************************************************
[docs]def translation_points_add(points, changed_cb, removed_cb):
    """
    Add points which will be translated as the user translates the Workspace
    using the mouse.
    :param points: Model coordinates that should be translated, in the form:
                   `[ [x0, y0, z0], [x1, y1, z1], [x2, y2, z2], ... ]`
    :type points: List of 3-item lists.
    :param changed_cb: Function to be called as points are translated. It will
                       be called with a single argument: A list of new
                       coordinates for all points:
                       `[[x0,y0,z0], [x1,y1,z1], ...]`
    :type changed_cb: callable
    :param removed_cb: Function to be called when the translation mode is
                       exited.
    :type removed_cb: callable
    """
    global _translation_cb, _translation_removed_cb
    num_points = len(points)
    flattened_points = []
    for point in points:
        if len(point) != 3:
            raise ValueError("Invalid coordinate: %s" % point)
        flattened_points += point
    if not callable(changed_cb):
        raise ValueError("Specified changed_cb function is not callable")
    if not callable(removed_cb):
        raise ValueError("Specified removed_cb function is not callable")
    if _translation_cb or _translation_removed_cb:
        # Un-register the previous callback, if any:
        _pymae.mae_remove_translation_points()
    _translation_cb = changed_cb
    _translation_removed_cb = removed_cb
    _pymae.mae_add_translation_points(
        flattened_points, num_points,
        "schrodinger.maestro.maestro._invoke_translation_callback",
        "schrodinger.maestro.maestro._invoke_translation_removed_callback") 
#****************************************************************************
[docs]def translation_points_remove():
    """
    Removes the functions and points which were added with the
    translation_points_add() function.
    """
    global _translation_cb, _translation_removed_cb
    _pymae.mae_remove_translation_points()
    _translation_cb = None
    _translation_removed_cb = None 
#****************************************************************************
[docs]def get_model_coordinates(window_x, window_y):
    """
    Returns model coordinates corresponding to the given window coordinates
    Returns (x, y, z)
    """
    return _pymae.mae_get_model_coordinates(window_x, window_y) 
#****************************************************************************
[docs]def job_incorporation_function_add(callback_func):
    """
    Register a function to be called when a job is available to be
    incorporated. The function should expect to be called with two
    parameters. The first is the job ID (a string). The second is
    a Boolean which indicates if this is just a test of whether
    the Python script is prepared to handle the incorporation of the job.
    If this parameter is true, then the callback
    should only test whether it has the ability to incorporate the job,
    but not to do the actual job incorporation. This is done to support
    a feature that allows maestro to get user approval to incorporate jobs
    even though they are not being monitored. If the second parameter
    is false, then the callback should attempt to incorporate
    the job, if it can.
    If the function is either prepared to handle the incorporation or
    is actually going to handle it then it should return
    maestro.WILL_HANDLE_JOB. If it is not able to handle it
    at this time then it should return
    maestro.NOT_INCORPORATABLE.
    If it is not going to handle the incorporation it
    should return maestro.NOT_MY_JOB
    """
    _callbacks['job_incorporation'].add(callback_func) 
#****************************************************************************
[docs]def job_incorporation_function_remove(callback_func):
    """
    Remove the named function so it is no longer called when the
    a job is available to be incorporated
    Removing a non-existent function will cause an error.
    """
    _callbacks['job_incorporation'].remove(callback_func) 
#***************************************************************************
[docs]def project_get():
    """
    Gets a handle to the currently open project. Note: this is the
    actual handle to the project and not a copy, so it should never be deleted
    from within a script. The handle can be operated on by mmproj_*
    interface routines, but there are higher-level interfaces available
    to perform many project operations and these are the preferred mechanisms
    for manipulating the Project Table data from a Python script.
    """
    return _pymae.mae_get_project() 
#****************************************************************************
[docs]def project_table_get():
    """
    Creates an instance of a Project using the currently opened project.
    """
    from schrodinger import project
    pt = project.Project(project_handle=_pymae.mae_get_project())
    return pt 
#****************************************************************************
[docs]def project_table_update():
    """
    Forces a redraw of the Project Table to reflect any changes. This
    function may be called at anytime in order to update the Project Table
    after new properties or entries have been added.
    """
    return _pymae.mae_update_project_table() 
#***************************************************************************
[docs]def project_table_synchronize():
    """
    Synchronizes the Workspace with the project according to the users'
    synchronization preferences. Any changes in the Workspace will be
    saved in to the project. If the user has 'Automatic' set then this
    will be done silently. If they have 'Prompt' set then they will be
    prompted as to whether they want to synchronize. If 'Manual' then
    no synchronization will be done.
    This function returns True if the synchronization took place,
    False if the user canceled it when prompted.
    """
    res = _pymae.mae_project_synchronize()
    if res:
        return True
    else:
        return False 
#****************************************************************************
[docs]def process_pending_events(wait_for_more_events=False):
    """
    Give some processor time to Maestro, for it to process pending events.
    :type wait_for_more_events: bool
    :param wait_for_more_events: When True, WaitForMoreEvents flag is
            passed to QCoreApplication::processEvents, thus waiting for
            events if no pending events are available. This is False by default,
            i.e. the function returns control immediately.
    """
    _pymae.mae_process_pending_events(wait_for_more_events) 
#****************************************************************************
[docs]def picking_atom_start(help_string, callback_func, allow_locked_entries=False):
    """
    :type help_string: string
    :param help_string: Displayed in the picking banner
    :type callback_func: callable
    :type callback_func: Function called when an atom is picked
    :type allow_locked_entries: bool
    :param allow_locked_entries: When True picking is permitted for locked
        entries
    Requests that atom picks are sent to the function callback_func. This must
    be a callable which expects to take a single integer parameter: the atom
    number of the picked atom. This atom number is appropriate for the CT
    handle returned from get_main_ct(). When picks are no longer needed, the
    script should call stop_picking().
    Note: It is possible for a script to have the ability to receive picks if a
    panel in Maestro is activated for picking (picks can only go to one place
    at any given time). Scripts which use picking_atom_start() may also want to
    consider the use of maestro.picking_loss_notify() so they can be aware when
    the user has moved to another pick mode and their panel is no longer
    receiving picks from the workspace.
    """
    global _pick_callback
    _pick_callback = callback_func
    func = "schrodinger.maestro.maestro._invoke_pick_callback"
    _pymae.mae_start_picking_atom(help_string, func, allow_locked_entries) 
#****************************************************************************
[docs]def picking_bond_start(help_string, callback_func, allow_locked_entries=False):
    """
    :type help_string: string
    :param help_string: Displayed in the picking banner
    :type callback_func : callable
    :type callback_func : Function called when an atom is picked
    :type allow_locked_entries: bool
    :param allow_locked_entries: When True picking is permitted for locked
        entries
    Requests that bond picks are sent to the function callback_func.
    This must be a callable which expects to take a two integer parameters:
    the atom numbers around the picked bond. The first atom corresponds
    to the atom nearest the half-bond picked. These atom numbers are
    appropriate for the CT handle returned from get_main_ct(). When
    picks are no longer needed, the script should call picking_stop().
    Note: it is possible for a script to have the ability to receive picks
    if a panel in Maestro is activated for picking (picks can only go to
    one place at any given time).
    Scripts which use picking_bond_start() may also want to consider the
    use of maestro.picking_loss_notify() so they can be aware when the
    user has moved to another pick mode and their panel is no longer
    receiving picks from the workspace.
    """
    global _pick_callback
    _pick_callback = callback_func
    func = "schrodinger.maestro.maestro._invoke_pick_callback"
    _pymae.mae_start_picking_bond(help_string, func, allow_locked_entries) 
#****************************************************************************
[docs]def picking_stop():
    """
    Requests that picks no longer be sent to the callback function
    specified earlier with picking_atom_start(), picking_bond_start(),
    or picking_lasso_start(). This should be called whenever
    a script no longer needs to receive picks and must be called before
    a Tkinter-based interactive script terminates.
    """
    global _pick_callback
    global _pick_lasso_callback
    global _pick_lasso_object_callback
    global _pick_asl_callback
    _pick_callback = None
    _pick_lasso_callback = None
    _pick_lasso_object_callback = None
    _pick_asl_callback = None
    _pymae.mae_stop_picking() 
#****************************************************************************
[docs]def picking_loss_notify(callback_func):
    """
    Requests that if atom picks are no longer being sent to the function
    registered by maestro.picking_atom_start(), maestro.picking_bond_start(),
    or maestro.picking_lasso_start(), the specified function will be called.
    This must be a callable which takes no parameters. This function must
    re-registed each time you turn on picking via maestro.picking_atom_start(),
    maestro.picking_bond_start(), or maestro.picking_lasso_start().
    """
    global _picking_loss_callback
    _picking_loss_callback = callback_func
    func = "schrodinger.maestro.maestro.invoke_picking_loss_callback"
    _pymae.mae_set_lost_picking_cb(func) 
#****************************************************************************
[docs]def add_picking_default_notify(callback_func):
    """
    Requests that if the picking mode switches to the default picking mode,
    then the specified function will be called.
    This must be a callable which takes no parameters. This function must
    be removed with remove_picking_default_notify() when the script no longer
    wants to receive notifications.
    """
    global _picking_default_callback
    _picking_default_callback = callback_func
    func = "schrodinger.maestro.maestro.invoke_picking_default_callback"
    _pymae.mae_set_default_picking_notify_cb(func) 
#****************************************************************************
[docs]def remove_picking_default_notify():
    """
    Removes the notification function for when Maestro switches to default
    picking.
    """
    global _picking_default_callback
    _picking_default_callback = None
    _pymae.mae_set_default_picking_notify_cb("") 
#****************************************************************************
[docs]def picking_lasso_start(help_string, callback_func, allow_locked_entries=False):
    """
    Requests that atom picks are sent to the function callback_func.
    :param help_string: the text to be displayed in Maestro main window
                        status bar, e.g., "Pick atom to delete".
    :param callback_func: the callable which expects to take an ASL
                          expression, e.g., pick_atom_cb(self, asl). If a
                          single atom is picked, the parameter 'asl' is
                          something like "atom.num 15". If a lasso selection
                          is done, the parameter 'asl' is something like
                          "at.n 5-8,15-18,1". Atom numbers are appropriate for
                          the CT handle returned from get_main_ct(), and they
                          can be extracted by mmasl_parse_input() based on the
                          CT handle and the ASL string. When picks are no
                          longer needed, the script should call picking_stop().
    :type allow_locked_entries: bool
    :param allow_locked_entries: When True picking is permitted for locked
        entries
    Note: it is possible for a script to have the ability to receive picks if a
    panel in Maestro is activated for picking (picks can only go to one place
    at any given time).
    Scripts which use picking_lasso_start() may also want to consider the use
    of maestro.picking_loss_notify() so they can be aware when the user has
    moved to another pick mode and their panel is no longer receiving picks
    from the workspace.
    """
    global _pick_lasso_callback
    _pick_lasso_callback = callback_func
    func = "schrodinger.maestro.maestro._invoke_pick_lasso_callback"
    _pymae.mae_start_picking_lasso(help_string, func, allow_locked_entries) 
#****************************************************************************
[docs]def picking_lasso_object_start(help_string, callback_func):
    """
    Requests that graphics object picks are sent to the function callback_func.
    :param help_string: the text to be displayed in Maestro main window
                        status bar, e.g., "Pick object to delete".
    :param callback_func: the callable which expects to take either a single
                          integer pick ID or a string containing multiple
                          pick IDs separated by commas. When picks are no
                          longer needed, the script should call picking_stop().
    Note: it is possible for a script to have the ability to receive picks if a
    panel in Maestro is activated for picking (picks can only go to one place
    at any given time).
    Scripts which use picking_lasso_object_start() may also want to consider the
    use of maestro.picking_loss_notify() so they can be aware when the user has
    moved to another pick mode and their panel is no longer receiving picks
    from the Workspace.
    """
    global _pick_lasso_object_callback
    _pick_lasso_object_callback = callback_func
    func = "schrodinger.maestro.maestro._invoke_pick_lasso_object_callback"
    _pymae.mae_start_picking_lasso_object(help_string, func) 
#****************************************************************************
[docs]def picking_asl_start(help_string,
                      pickstate,
                      callback_func,
                      allow_locked_entries=False):
    """
    Requests that atom picks are sent to the function callback_func.
    :param help_string: the text to be displayed in Maestro main window
                        banner , e.g., "Pick atom to delete".
    :param pick_state: One of the pick states defined above - PICK_ATOMS,
        PICK_RESIDUES, PICK_MOLECULES etc.
    :param callback_func: the callable which expects to take an ASL
                          expression, e.g., pick_atom_cb(self, asl). If a
                          single atom is picked, the parameter 'asl' is
                          something like "atom.num 15". If a lasso selection
                          is done, the parameter 'asl' is something like
                          "at.n 5-8,15-18,1". Atom numbers are appropriate for
                          the CT handle returned from get_main_ct(), and they
                          can be extracted by mmasl_parse_input() based on the
                          CT handle and the ASL string. When picks are no
                          longer needed, the script should call picking_stop().
    :type allow_locked_entries: bool
    :param allow_locked_entries: When True picking is permitted for locked
        entries
    Note: it is possible for a script to have the ability to receive picks if a
    panel in Maestro is activated for picking (picks can only go to one place
    at any given time).
    Scripts which use picking_asl_start() may also want to consider the use
    of maestro.picking_loss_notify() so they can be aware when the user has
    moved to another pick mode and their panel is no longer receiving picks
    from the workspace.
    """
    global _pick_asl_callback
    _pick_asl_callback = callback_func
    func = "schrodinger.maestro.maestro._invoke_pick_asl_callback"
    _pymae.mae_start_picking_asl(help_string, pickstate, func,
                                 allow_locked_entries) 
#****************************************************************************
[docs]def command_callback_add(callback_func):
    """
    Register a function that will be called each time a command is processed
    in Maestro.
    This must be a callable which takes a single parameter - the text
    of the command processed.
    """
    _callbacks['command'].add(callback_func) 
#****************************************************************************
[docs]def command_callback_remove(callback_func):
    """
    Remove the named function so it is no longer called when
    a command is issued in Maestro
    Removing a non-existent function will cause an error.
    """
    _callbacks['command'].remove(callback_func) 
#****************************************************************************
[docs]def right_click_callback_add(callback_func):
    """
    Register a function to be called when the user makes a right-click in the
    Workspace. Note this will override any normal Maestro right-mouse features
    such as the built-in right-mouse menu.
    This must be a callable which takes three parameters, the x and y positions
    of the mouse when clicked and the atom number of any atom under the
    mouse (or an invalid index if there's no atom under the mouse). Use
    mm.mmct_valid_atom to determine if the index is valid.
    """
    _callbacks['right_click'].add(callback_func) 
#****************************************************************************
[docs]def right_click_callback_remove(callback_func):
    """
    Remove the named function so it is no longer called when
    a right-mouse click is done in Maestro.
    Removing a non-existent function will cause an error.
    """
    _callbacks['right_click'].remove(callback_func) 
#****************************************************************************
[docs]def level_of_detail_callback_add(callback_func):
    """
    Register a callback to be called when the level of detail in the
    Maestro Workspace changes. This is typically used for graphical
    representation like whether to show the bond orders.
    The callback function takes no parameters.
    """
    _callbacks['level_of_detail'].add(callback_func) 
#****************************************************************************
[docs]def level_of_detail_callback_remove(callback_func):
    """
    Remove the callback so it is no longer called when the level
    of detail in the Workspace changes.
    Removing a non-existent function will cause an error.
    """
    _callbacks['level_of_detail'].remove(callback_func) 
#****************************************************************************
[docs]def project_close_callback_add(callback_func):
    """
    Register a function to be called when current project is about to be closed.
    This must be a callable which takes no parameters.
    If Python scripts want to cancel Maestro project closing operation,
    they should call Maestro function mae_project_close_cancel().
    This function can be used for anytime a Python script needs
    to know that current project is about to be closed.
    """
    _callbacks['project_close'].add(callback_func) 
#****************************************************************************
[docs]def project_close_callback_remove(callback_func):
    """
    Remove the named function so it is no longer called when
    current project is about to be closed
    Removing a non-existent function will cause an error.
    """
    _callbacks['project_close'].remove(callback_func) 
#****************************************************************************
[docs]def project_rename_callback_add(callback_func):
    """
    Register a function to be called when current project is about to be renamed.
    This must be a callable which takes no parameters.
    This function can be used for anytime a Python script needs
    to know that current project is about to be renamed.
    """
    _callbacks['project_rename'].add(callback_func) 
#****************************************************************************
[docs]def project_rename_callback_remove(callback_func):
    """
    Remove the named function so it is no longer called when
    current project is about to be renamed.
    Removing a non-existent function will cause an error.
    """
    _callbacks['project_rename'].remove(callback_func) 
#****************************************************************************
[docs]def project_update_callback_add(callback_func):
    """
    Register a function to be called when current project is updated in some
    way. This might be because of changes in selection, inclusion or
    properties.
    The function must be a callable which takes no parameters.
    """
    _callbacks['project_update'].add(callback_func) 
#****************************************************************************
[docs]def project_update_callback_remove(callback_func):
    """
    Remove the named function so it is no longer called when
    current project is updated.
    Removing a non-existent function will cause an error.
    """
    _callbacks['project_update'].remove(callback_func) 
#****************************************************************************
[docs]def project_close_cancel():
    """
    Forces to cancel the processing of closing current project. This
    function may be called at the end of callback function from
    project_close_callback_add(), if the script doesn't want to close
    current project. This might cause bad behavior if it's done
    unconditionally.
    """
    return _pymae.mae_project_close_cancel() 
#****************************************************************************
# EV 70968
[docs]def workspace_bounding_box_function_add(func):
    """
    Register a function to be called when the bounding box for a fit to
    screen operation is being performed. This is intended to take into
    account python 3D graphics objects when fitting Maestro Workspace.
    The Workspace fit to screen is required whenever new 3D graphics objects
    are added, or when Maestro changes the contents of the
    Workspace (different structures, markers, text, surfaces, etc.)
    func: This argument is the client-supplied function to call.
    A common way to do this is to have one function for creating the
    graphical objects you wish to draw and another to do the actual drawing
    Example:
     Calls from within Maestro would be like:
     pythonrun workspace_graphics_sphere_centroid.create_centroid
     pythonrun workspace_graphics_sphere_centroid.add
     where workspace_graphics_sphere_centroid is a .py file containing
     these two functions.  The first one creates the object, the second one
     registers the bounding box function by telling the maestro module
     about itself:
     maestro.workspace_bounding_box_function_add(my_bounding_box_func)
     To remove it have an additional function in your .py, something like
     remove() which tells the maestro module to remove the named function
     from the list of callback functions:
     maestro.workspace_bounding_box_function_remove(my_bounding_box_func)
    """
    _callbacks['workspace_bounding_box'].add(func) 
#****************************************************************************
# EV 70968
[docs]def workspace_bounding_box_function_remove(func):
    """
    Remove the named function so it is no longer called when the
    Workspace is fit to screen.
    Removing a non-existent function will cause an error.
    For an example see the docstring for workspace_bounding_box_function_add()
    """
    _callbacks['workspace_bounding_box'].remove(func) 
#****************************************************************************
# EV 70968
[docs]def set_workspace_bounding_box(min1, min2, min3, min4, min5, min6, max1, max2,
                               max3, max4, max5, max6):
    """
    Adds a Python function to be called each time the workspace bounding box
    callback function is called. The Python function passes the bounding box
    of Python 3D graphics objects to Maestro.
    """
    _pymae.mae_set_workspace_bounding_box(min1, min2, min3, min4, min5, min6,
                                          max1, max2, max3, max4, max5, max6) 
#****************************************************************************
[docs]def hover_callback_add(callback_func):
    """
    Adds a Python function to be called each time the mouse rests over an atom
    in the Workspace. This function should expect to receive a single
    parameter: the number of the atom which the mouse is currently resting over
    (or an invalid index if none---see mm.mmct_valid_atom). To remove the
    callback use hover_callback_remove() but note that this cannot be done from
    within a the callback function itself - in other words the hover callback
    cannot self-terminate.
    """
    _callbacks['hover'].add(callback_func) 
#****************************************************************************
[docs]def hover_callback_remove(callback_func):
    """
    Removes a Python function from the list of functions to be called
    each time the mouse rests over an atom in the Workspace. This function
    must have been previously added with hover_callback_add(). Note that
    this function cannot be called from within the callback_func() itself,
    as this will cause Maestro to crash.
    """
    _callbacks['hover'].remove(callback_func) 
#****************************************************************************
[docs]def feedback_string_set(feedback_string):
    """Sets the string that appears in the status bar. """
    _pymae.mae_set_feedback_string(feedback_string) 
#****************************************************************************
[docs]def add_job_launch_log(job_name, log_string):
    """
    A python function that adds the given job launch info to the
    project's annotation. The job name is passed as one of the
    argument so that we can selectively log job launches if necessary.
    """
    _pymae.mae_log_job_launch(job_name, log_string) 
#****************************************************************************
[docs]def periodic_callback_add(callback_func):
    """
    Adds a Python function which is called periodically during Maestro's
    operation (about 20 times a second). When the periodic task is no longer
    required, it should be removed with periodic_callback_remove().
    NOTE: periodic_callback_remove() cannot be called from
    the callback_func() itself.
    """
    _callbacks['periodic'].add(callback_func) 
#****************************************************************************
[docs]def periodic_callback_remove(callback_func):
    """
    Removes a Python function being called periodically during
    Maestro's operations. This function should have been previously added
    with periodic_callback_add().
    NOTE: periodic_callback_remove() cannot be called from
    the callback_func() itself.
    """
    _callbacks['periodic'].remove(callback_func) 
#****************************************************************************
[docs]def selected_atoms_get():
    """
    Gets a list of the numbers of the currently-selected atoms in the Workspace.
    """
    return _pymae.mae_get_workspace_selection() 
#****************************************************************************
[docs]def selected_atoms_get_asl():
    """
    Returns an ASL expression corresponding to the currently selected atoms
    in the Workspace. If there are no selected atoms in the workspace then
    None is returned.
    """
    asl = _pymae.mae_get_workspace_selection_asl()
    if not asl:
        return None
    return asl 
[docs]def selected_atoms_get_smarts():
    """
    Returns a SMARTS pattern for the currently selected Workspace atoms.
    Raises ValueError if no atoms are selected, if selection is not
    continuous, or if more than 50 atoms are selected.
    :return: SMARTS pattern that would match the selected atoms.
    :rtype: str
    """
    selected_atoms = selected_atoms_get()
    if not selected_atoms:
        raise ValueError("There are no atoms selected in the Workspace")
    if len(selected_atoms) > 50:
        # Large selection sizes will take longer, and will result in a huge
        # smarts pattern string.
        raise ValueError("There are too many atoms (%i)" % len(selected_atoms) +
                         "selected in the Workspace. Maximum allowed is 50")
    st = workspace_get().copy()
    selected_atoms = [st.atom[at] for at in selected_atoms]
    mol_nums = {at.molecule_number for at in selected_atoms}
    if len(mol_nums) > 1:
        raise ValueError("Selected atoms must be from the same molecule.")
    # Deleting atoms this way preserves atom objects:
    mol_atoms = st.molecule[mol_nums.pop()].getAtomIndices()
    del_atoms = [at for at in st.getAtomIndices() if at not in mol_atoms]
    st.deleteAtoms(del_atoms)
    selected_atoms = [at.index for at in selected_atoms]
    try:
        smarts = analyze.generate_smarts_canvas(st,
                                                selected_atoms,
                                                honor_maestro_prefs=True)
    except ValueError as err:
        raise
    return smarts 
#****************************************************************************
[docs]def get_command_option(command, option, item=None):
    """
    Gets the value of the option for the given command. If there is an error
    such as the command does not exist or the option does not exist, then empty
    string, "",  is returned. Note that this function may return None if
    fetching the option fails or if there's a buffer overflow.
    """
    if item:
        s = item
    else:
        s = ""
    return _pymae.mae_get_command_option(command, option, s) 
#****************************************************************************
[docs]def get_command_items(command):
    """
    Gets the names of the items currently associated with the given
    command. For example if the command is 'set' then the nmaes of all
    the currently defined sets will be returned.
    """
    return _pymae.mae_get_command_model_names(command) 
#****************************************************************************
[docs]def get_current_command_item(command):
    """
    Gets the name of the currently selelected item associated with the
    given command. For example if the command is 'set' then it will be the
    name of the currently selected set. If no items are selected then None will
    be returned.
    """
    return _pymae.mae_get_command_current_model_name(command) 
#****************************************************************************
[docs]def get_font_names():
    """
    Returns a list of names of available fonts for drawing in the Maestro
    workspace
    """
    return _pymae.mae_get_font_names() 
#****************************************************************************
[docs]def create_single_line_text(text_to_draw,
                            x,
                            y,
                            z,
                            font_name="Sans Serif",
                            r=1.0,
                            g=1.0,
                            b=1.0,
                            a=1.0,
                            font_size=14,
                            is_bold=False,
                            is_italic=False,
                            pick_category="",
                            pick_id=0):
    """
    Draw text_to_draw string at position x, y, z in the 3D workspace.
    This function replaces draw_string, using a graphics text object instead of
    a workspace drawing callback, but only draws a single line of text.
    :param text_to_draw: String to render.
    :type text_to_draw: str
    :param x: X coordinate (Angstroms)
    :type x: float
    :param y: Y coordinate (Angstroms)
    :type y: float
    :param z: Z coordinate (Angstroms)
    :type z: float
    :param font_name: Font to be used (one returned by get_font_names())
    :type font_name: str
    :param r: Red color component, range is 0.0 to 1.0
    :type r: float
    :param g: Green color component, range is 0.0 to 1.0
    :type g: float
    :param b: Blue color component, range is 0.0 to 1.0
    :type b: float
    :param a: Alpha color component, range is 0.0 to 1.0
    :type a: float
    :param font_size: Font pixel size
    :type font_size: int
    :param is_bold: Whether to use bold font
    :type is_bold: bool
    :param is_italic: Whether to use italic font
    :type is_italic: bool
    :param pick_category: A valid pick category or empty string. If the pick
        category is empty, then the text will not be pickable.
    :type pick_category: str
    :param pick_id: A pick ID to identify the object during picking, or 0
        (the default value) to make it not pickable
    :type pick_id: int
    :rtype: int
    :return: The handle of a Maestro text array object
    """
    return _pymaecxx.mae_create_graphics_text(text_to_draw, x, y, z, font_name,
                                              r, g, b, a, font_size, is_bold,
                                              is_italic, pick_category, pick_id) 
#****************************************************************************
[docs]def create_single_line_z_buffered_text(text_to_draw,
                                       x,
                                       y,
                                       z,
                                       font_name="Sans Serif",
                                       r=1.0,
                                       g=1.0,
                                       b=1.0,
                                       a=1.0,
                                       font_size=14,
                                       is_bold=False,
                                       is_italic=False):
    """
    Draw text_to_draw string at position x, y, z in the 3D workspace.
    This function replaces draw_string, using a graphics text object instead of
    a workspace drawing callback, but only draws a single line of text. This
    text is Z-buffered.
    :param text_to_draw: String to render.
    :type text_to_draw: str
    :param x: X coordinate (Angstroms)
    :type x: float
    :param y: Y coordinate (Angstroms)
    :type y: float
    :param z: Z coordinate (Angstroms)
    :type z: float
    :param font_name: Font to be used (one returned by get_font_names())
    :type font_name: str
    :param r: Red color component, range is 0.0 to 1.0
    :type r: float
    :param g: Green color component, range is 0.0 to 1.0
    :type g: float
    :param b: Blue color component, range is 0.0 to 1.0
    :type b: float
    :param a: Alpha color component, range is 0.0 to 1.0
    :type a: float
    :param font_size: Font pixel size
    :type font_size: int
    :param is_bold: Whether to use bold font
    :type is_bold: bool
    :param is_italic: Whether to use italic font
    :type is_italic: bool
    :rtype: int
    :return: The handle of a Maestro text array object
    """
    return _pymaecxx.mae_create_graphics_z_buffered_text(
        text_to_draw, x, y, z, font_name, r, g, b, a, font_size, is_bold,
        is_italic) 
#****************************************************************************
[docs]def create_multiline_text(text_to_draw,
                          x,
                          y,
                          z,
                          font_name="Sans Serif",
                          r=1.0,
                          g=1.0,
                          b=1.0,
                          a=1.0,
                          font_size=14,
                          is_bold=False,
                          is_italic=False):
    """
    Draw text_to_draw string at position x, y, z in the 3D workspace.
    This function replaces draw_string, using a graphics text object instead of a workspace drawing callback,
    and supports multi-line text with embedded newlines.
    :param text_to_draw: String to render.
    :type text_to_draw: str
    :param x: X coordinate (Angstroms)
    :type x: float
    :param y: Y coordinate (Angstroms)
    :type y: float
    :param z: Z coordinate (Angstroms)
    :type z: float
    :param font_name: Font to be used (one returned by get_font_names())
    :type font_name: str
    :param r: Red color component, range is 0.0 to 1.0
    :type r: float
    :param g: Green color component, range is 0.0 to 1.0
    :type g: float
    :param b: Blue color component, range is 0.0 to 1.0
    :type b: float
    :param a: Alpha color component, range is 0.0 to 1.0
    :type a: float
    :param font_size: Font pixel size
    :type font_size: int
    :param is_bold: Whether to use bold font
    :type is_bold: bool
    :param is_italic: Whether to use italic font
    :type is_italic: bool
    :rtype: int
    :return: The handle of a Maestro text2 object
    """
    return _pymaecxx.mae_create_graphics_text2(text_to_draw, x, y, z, font_name,
                                               r, g, b, a, font_size, is_bold,
                                               is_italic) 
#****************************************************************************
[docs]def draw_string(text_to_draw,
                x,
                y,
                z,
                font_name="Sans Serif",
                r=1.0,
                g=1.0,
                b=1.0,
                a=1.0,
                font_size=14,
                is_bold=False,
                is_italic=False):
    """
    Draw text_to_draw string at position x, y, z in the 3D workspace.
    This function would normally only be used in a workspace drawing callback,
    and is deprecated in favor of create_multiline_text or create_single_line_text.
    :param text_to_draw: String to render.
    :type text_to_draw: str
    :param x: X coordinate (Angstroms)
    :type x: float
    :param y: Y coordinate (Angstroms)
    :type y: float
    :param z: Z coordinate (Angstroms)
    :type z: float
    :param font_name: Font to be used (one returned by get_font_names())
    :type font_name: str
    :param r: Red color component, range is 0.0 to 1.0
    :type r: float
    :param g: Green color component, range is 0.0 to 1.0
    :type g: float
    :param b: Blue color component, range is 0.0 to 1.0
    :type b: float
    :param a: Alpha color component, range is 0.0 to 1.0
    :type a: float
    :param font_size: Font pixel size
    :type font_size: int
    :param is_bold: Whether to use bold font
    :type is_bold: bool
    :param is_italic: Whether to use italic font
    :type is_italic: bool
    """
    warnings.warn(
        "maestro.draw_string() is deprecated. Use maestro.create_multiline_text() or maestro.create_single_line_text() instead.",
        DeprecationWarning,
        stacklevel=2)
    return _pymaecxx.mae_draw_string(text_to_draw, x, y, z, font_name, r, g, b,
                                     a, font_size, is_bold, is_italic) 
#****************************************************************************
[docs]def draw_html_string(text_to_draw,
                     x,
                     y,
                     z,
                     is_transparent=True,
                     xoffset=0.0,
                     yoffset=0.0,
                     adjustments=None,
                     mode=LABEL_DRAW,
                     use_default_font=True):
    """
    Draw a string (with optional html tags) at position x,y,z in the
    3D workspace. Uses the default font.
    :param text_to_draw: string to render.  Can contain some limited
                         html: <sub></sub> and <sup></sup> tags
                         for sub- and super-scripts, respectively.
                         Can also pass a non-formatted string (ie doesn't
                         require you have html tags in it)
    :param x: double - X coordinate, Angstrom space
    :param y: double - Y coordinate, Angstrom space
    :param z: double - Z coordinate, Angstrom space
    :param is_transparent: bool - when false will render a box around
                           the text in the background color.
                           When true, no box is rendered, i.e.
                           the background is transparent.
                           Default is true.
    :param xoffset: float - X offset in pixels, from bottom left.
                            Default = 0.0.
    :param yoffset: float - Y offset in pixels, from bottom left
                            Default = 0.0.
    :param adjustments:  Set/list of which adjustments, if any, to apply.
                         Default is None meaning no centerin is done and
                         no offsets are applied.  Values which can
                         be placed into adjustments are:
                         LABEL_CENTER_HORIZONTAL
                         LABEL_CENTER_VERTICAL
                         LABEL_USER_OFFSETS
                         Centering, if any, is applied first.
                         Then user offsets, if any, are applied.
    :param mode: LABEL_DRAW draws the label and returns bounding box
                 LABEL_BOUNDING_BOX only returns the bounding box
                 The default is LABEL_DRAW.
    :param use_default_font: bool indicating whether to use the default
                 font or let the caller of this routine set the font.
                 If the latter, then the caller must use MM_Font.useFont()
                 or similar to set the font or the text won't render.
    :return:  Bounding box list (indices precede value here only for
              informational purposes - they're not part of the returned
              values):
              0:left, 1:right, 2:bottom, 3:top, 4:near, 5:far
              Or empty list if there was an error
    This function would normally only be used in a workspace drawing callback.
    """
    ret_list = []
    if mode == LABEL_DRAW:
        final_mode = 1
    elif mode == LABEL_BOUNDING_BOX:
        final_mode = 2
    else:
        return ret_list
    horizontal_centering = 0
    vertical_centering = 0
    use_offsets = 0
    if adjustments:
        if type(adjustments) != list and type(adjustments) != set:
            # Assume user passed in just one argument
            # Auto-convert to a set for them
            adjustments = set(adjustments)
        if LABEL_CENTER_HORIZONTAL in adjustments:
            horizontal_centering = 1
        if LABEL_CENTER_VERTICAL in adjustments:
            vertical_centering = 1
        if LABEL_USER_OFFSETS in adjustments:
            use_offsets = 1
    use_default_font_int = 1 if use_default_font else 0
    (left, right,
     bottom, top,
     near, far) = \
     
_pymae.mae_draw_markup_string(text_to_draw, x, y, z,
                                    is_transparent,
                                    xoffset, yoffset, use_offsets,
                                    horizontal_centering,
                                    vertical_centering,
                                    final_mode,
                                    use_default_font_int
                                    )
    ret_list.append(left)
    ret_list.append(right)
    ret_list.append(bottom)
    ret_list.append(top)
    ret_list.append(near)
    ret_list.append(far)
    return ret_list 
#****************************************************************************
[docs]def create_atom_marker(atom,
                       r,
                       g,
                       b,
                       hr,
                       hg,
                       hb,
                       highlight,
                       icon=GRAPHICS_ICON_NONE,
                       style=GRAPHICS_ATOM_MARKER_STYLE_STAR):
    """
    Create a 2D atom marker.
    :param atom: atom number
    :param r: red color component, range is 0.0 to 1.0
    :param g: green color component, range is 0.0 to 1.0
    :param b: blue color component, range is 0.0 to 1.0
    :param hr: highlight red color component, range is 0.0 to 1.0
    :param hg: highlight green color component, range is 0.0 to 1.0
    :param hb: highlight blue color component, range is 0.0 to 1.0
    :param highlight: uses highlight color and line width or not, icon will
                      drawn in highlight color
    :param icon: is one of the GRAPHICS_ICON_* values listed at the top of the
                 file.
    :param style: is a style for atom markers either
                  GRAPHICS_ATOM_MARKER_STYLE_STAR or
                  GRAPHICS_ATOM_MARKER_STYLE_GENERAL
    :return: handle of the marker, on error this function returns -1
    """
    handle = _pymae.mae_create_atom_marker(atom, r, g, b, hr, hg, hb, highlight,
                                           icon, style)
    return handle 
#****************************************************************************
[docs]def create_atom_pair_marker(atom1,
                            atom2,
                            r,
                            g,
                            b,
                            hr,
                            hg,
                            hb,
                            highlight,
                            text,
                            icon=GRAPHICS_ICON_NONE):
    """
    Create a 2D atom pair marker.
    :param atom1: first atom number
    :param atom2: second atom number
    :param r: red color component, range is 0.0 to 1.0
    :param g: green color component, range is 0.0 to 1.0
    :param b: blue color component, range is 0.0 to 1.0
    :param hr: highlight red color component, range is 0.0 to 1.0
    :param hg: highlight green color component, range is 0.0 to 1.0
    :param hb: highlight blue color component, range is 0.0 to 1.0
    :param highlight: uses highlight color and line width or not, icon and text
                      will drawn in highlight color
    :param text: draw text if it's not NULL, if it's drawn then icon is hidden
    :param icon: one of the GRAPHICS_ICON values listed at the top of the file
    :return: handle of the marker, on error this function returns -1
    """
    handle = _pymae.mae_create_atom_pair_marker(atom1, atom2, r, g, b, hr, hg,
                                                hb, highlight, text, icon)
    return handle 
#****************************************************************************
[docs]def create_atom_triple_marker(atom1,
                              atom2,
                              atom3,
                              r,
                              g,
                              b,
                              hr,
                              hg,
                              hb,
                              highlight,
                              text,
                              icon=GRAPHICS_ICON_NONE):
    """
    Create a 2D atom triple marker.
    :param atom1: first atom number
    :param atom2: second atom number
    :param atom3: third atom number
    :param r: red color component, range is 0.0 to 1.0
    :param g: green color component, range is 0.0 to 1.0
    :param b: blue color component, range is 0.0 to 1.0
    :param hr: highlight red color component, range is 0.0 to 1.0
    :param hg: highlight green color component, range is 0.0 to 1.0
    :param hb: highlight blue color component, range is 0.0 to 1.0
    :param highlight: uses highlight color and line width or not, icon and text
                      will drawn in highlight color
    :param text: draw text if it's not NULL, if it's drawn then icon is hidden
    :param icon: one of the GRAPHICS_ICON values listed at the top of the file
    :return: handle of the marker, on error this function returns -1.
    """
    handle = _pymae.mae_create_atom_triple_marker(atom1, atom2, atom3, r, g, b,
                                                  hr, hg, hb, highlight, text,
                                                  icon)
    return handle 
#****************************************************************************
[docs]def create_atom_quad_marker(atom1,
                            atom2,
                            atom3,
                            atom4,
                            r,
                            g,
                            b,
                            hr,
                            hg,
                            hb,
                            highlight,
                            text,
                            icon=GRAPHICS_ICON_NONE):
    """
    Create a 2D atom quad marker.
    :param atom1: is the first atom number
    :param atom2: is the second atom number
    :param atom3: is the third atom number
    :param atom4: is the fourth atom number
    :param r: is the red color component, range is 0.0 to 1.0
    :param g: is the green color component, range is 0.0 to 1.0
    :param b: is the blue color component, range is 0.0 to 1.0
    :param hr: is the highlight red color component, range is 0.0 to 1.0
    :param hg: is the highlight green color component, range is 0.0 to 1.0
    :param hb: is the highlight blue color component, range is 0.0 to 1.0
    :param highlight: uses highlight color and line width or not, icon and text
                      will drawn in highlight color
    :param text: draw text if it's not NULL, if it's drawn then icon is hidden
    :param icon: ne of the GRAPHICS_ICON values listed at the top of the file
    :return: handle of the marker, on error this function returns -1.
    """
    handle = _pymae.mae_create_atom_quad_marker(atom1, atom2, atom3, atom4, r,
                                                g, b, hr, hg, hb, highlight,
                                                text, icon)
    return handle 
#****************************************************************************
[docs]def get_temp_location():
    """
    Returns a path to a temporary directory which is likely to be
    writeable and will be removed when this Maestro session is
    finished. This would typically used when temporary files need
    to be created, for example if file conversion is required as
    part of a script workflow
    """
    return _pymae.mae_get_temp_location() 
#****************************************************************************
#****************************************************************************
[docs]def write_entries_from_project(filename,
                               which_entries,
                               Htreatment=None,
                               synchronize=True,
                               append=False,
                               props=True):
    """
    Write entries from the project to a file. The parameters to this
    method are:
    :param filename: the name of the file which to write these entries to. The
                     suffix determines the format of the file using the same
                     rules as Structure.write()
    :type filename: str
    :param which_entries: entries to write 'All', 'Included' or 'Selected'
    :type which_entries: str
    :param Htreatment: If not None then hydrogens will be added to the
                       structures before they are written to the file using the
                       specified H-treatment.  Allowed treatment names are:
                       All-atom with Osp3/Nsp3-Lp, All-atom with No-Lp,
                       Csp3 United-atom with S-Lp, Csp3 United-atom with No-Lp,
                       All-atom with S-Lp, C sp2/sp3 United-atom with No-Lp,
                       C sp2/sp3, N,O,S United-atom with No-Lp. The one you
                       almost certainly want is 'All-Atom with No-Lp'
    :type Htreatment: str
    :param synchronize: If True, first synchronize the Workspace with the
                        project. It is recommended this be done.
    :type synchronize: bool
    :param append: If True, append to the specified file. If False, overwrite.
    :type append: bool
    :param props: If True, CT-level properties should be written.
    :type props: bool
    If synchronization was done and the Maestro user had 'Prompt' mode active
    then they may choose to cancel the operation. If they cancel then nothing
    will be written and this method will return False, otherwise it returns
    True.
    """
    if synchronize:
        ret = project_table_synchronize()
        if not ret:
            # User canceled - return now without doing anything more
            return ret
    # Get the project and the appropriate iterator
    pt = project_table_get()
    if which_entries == "all":
        iter = pt.all_rows
    elif which_entries == "selected":
        iter = pt.selected_rows
    elif which_entries == "included":
        iter = pt.included_rows
    else:
        raise Exception("Unknown which parameter: %s" % which_entries)
    if Htreatment is not None:
        mm.mmhtreat_initialize(mm.error_handler)
        treatment = mm.mmhtreat_get_treatment_index(Htreatment)
        if treatment < 0:
            raise Exception("%s: is an unknown Hydrogen Treatment" \
                  
% Htreatment)
    for i, row in enumerate(iter):
        st = row.getStructure(props=props, workspace_sync=False)
        st.retype()
        if Htreatment is not None:
            for iatom in st.atom:
                mm.mmhtreat_hadd(treatment, st, iatom)
        if i == 0:
            if append:
                st.append(filename)
            else:
                st.write(filename)
        else:
            st.append(filename)
    if Htreatment is not None:
        mm.mmhtreat_terminate()
    return True 
#####################################################################
# Tk event handling
#####################################################################
#****************************************************************************
[docs]def tk_toplevel_add(toplevel_widget):
    """
    Notifies Maestro that a new Tk toplevel has been created and
    should be displayed and share event processing with Maestro itself.
    Note: this is the preferred way to run a Tkinter script from within
    Maestro and should be called instead of using the
    Tkinter.MainLoop() function.
    """
    global _tk_widgets
    # In case tk_toplevel_add() gets called on same widget twice:
    if toplevel_widget in _tk_widgets:
        return
    _tk_widgets.append(toplevel_widget)
    # Start Maestro event processing:
    _pymae.mae_start_tk_events() 
#****************************************************************************
[docs]def tk_toplevel_remove(toplevel_widget):
    """
    Notifies Maestro that a Tk toplevel previously registered via
    tk_toplevel_add() no longer needs to be displayed or have events
    passed to it. Note: this function does not actually hide the
    toplevel widget. The script should call destroy() on the widget
    after remove_tk_toplevel() in order to hide the widget.
    """
    global _tk_widgets
    try:
        _tk_widgets.remove(toplevel_widget)
    except ValueError:  # This widget was either never added or already removed
        pass
    else:
        # After removing the widget from the list, decrement the script count
        # and stop maestro TK events if this was the last Python script:
        _pymae.mae_stop_tk_events() 
#****************************************************************************
def _update_tk_widgets():
    """This function should not be called from within a user script!"""
    global _tk_widgets
    for wd in _tk_widgets:
        wd.update_idletasks()
        wd.update()
#****************************************************************************
def _release_tk_widget_grabs():
    """This function should not be called from within a user script!"""
    # Release event-handling from all Python widgets:
    global _tk_widgets
    for wd in _tk_widgets:
        try:
            grab_wd = wd.grab_current()
        except KeyError:
            # grab_current() sometimes does not work as expected (Ev:58350)
            grab_wd = None
        if grab_wd:
            grab_wd.grab_release()
#****************************************************************************
[docs]def atom_selection_dialog(description,
                          current_asl="",
                          initial_pick_state=PICK_ATOMS,
                          resolve_asl_aliases=False):
    """
    Post the atom selection dialog. The user can make selections in
    that dialog and the resulting ASL string will be returned as a
    result of this function. If the user cancels then the empty string
    will be returned by this function. The description field is displayed
    in the upper section of the dialog to indicate to the user the purpose of
    displaying this dialog. The current_asl will be set in the dialog when
    it is first displayed and the user will be able to edit that in this
    dialog.
    :param description - description field string
    :param current_asl - current asl string
    :param initial_pick_state - initial pick state enum (default is PICK_ATOMS)
    :param bool resolve_asl_aliases  - True if we want to resolve asl aliases
    False otherwise. False is the default parameter
    """
    return _pymae.mae_post_asd(description, current_asl, initial_pick_state,
                               resolve_asl_aliases) 
#****************************************************************************
[docs]def warning(message):
    """
    Post a Maestro warning dialog with an OK button.  This displays
    the text specified in 'message' in the same dialog Maestro
    uses to display warnings/errors, ensuring a consistent look and feel.
    """
    return _pymae.mae_dialog_warning(message) 
#****************************************************************************
[docs]def info(message):
    """
    Post a Maestro informational dialog with an OK button.  This displays
    the text specified in 'message' in the same dialog Maestro
    uses for displaying information, ensuring a consistent look and feel.
    """
    return _pymae.mae_dialog_info(message) 
#****************************************************************************
[docs]def question(question, button1="OK", button2="Cancel"):
    """
    Post a Maestro question dialog - the same dialog Maestro
    uses for displaying questions, ensuring a consistent look and feel.
    Arguments are:
      question:  Question text
      button1:   Defaults to OK, but you can pass in what you like
      button2:   Defaults to Cancel, but you can pass in what you like
    Returns:
    maestro.BUTTON1 or maestro.BUTTON2, depending on which button
    was pressed.
    """
    status = _pymae.mae_dialog_question(question, button1, button2)
    if (status == 0):
        return BUTTON1
    else:
        return BUTTON2 
#####################################################################
# Private module functions for periodic and workspace-changed callbacks
#####################################################################
def _destroy_tk_widgets():
    """
    This function should not be called from within a user script!
    """
    global _tk_widgets
    i = len(_tk_widgets)
    while i > 0:
        _tk_widgets[i - 1].destroy()
        i = i - 1
def _invoke_periodic_callbacks():
    """ This function should not be called from within a user script! """
    _callbacks['periodic'].invoke()
def _invoke_hover_callbacks(arg):
    """ This function should not be called from within a user script! """
    _callbacks['hover'].invoke(arg)
def _invoke_workspace_changed_callbacks(arg):
    """ This function should not be called from within a user script! """
    _callbacks['workspace_changed'].invoke(arg)
def _invoke_job_incorporation_callbacks(*args):
    """ This function should not be called from within a user script! """
    _callbacks['job_incorporation'].invokeJobIncorporation(*args)
def _invoke_pick_callback(atom1, atom2=None):
    """ This function should not be called from within a user script! """
    if _pick_callback:
        if atom2 is None:  # Atom picking
            _pick_callback(atom1)
        else:  # Bond picking
            _pick_callback(atom1, atom2)
[docs]def invoke_picking_loss_callback():
    """
    Notify the current pick widget that it lost its picking rights.  Note that
    this function will not stop the picking itself.
    """
    global _picking_loss_callback
    if _picking_loss_callback:
        try:
            _picking_loss_callback()
        except RuntimeError:
            # The callback no longer exists. May happen when the Qt C++ object
            # for the checkbox was destroyed.
            _picking_loss_callback = None 
[docs]def invoke_picking_default_callback():
    """
    Notify the current default notification function that Maestro has
    switched to default picking.
    """
    global _picking_default_callback
    if _picking_default_callback:
        try:
            _picking_default_callback()
        except RuntimeError:
            # The callback no longer exists. May happen if the script didn't
            # remove the callback before exiting.
            _picking_default_callback = None 
def _invoke_pick_lasso_callback(asl):
    """ This function should not be called from within a user script! """
    if _pick_lasso_callback:
        _pick_lasso_callback(asl)
def _invoke_pick_lasso_object_callback(object):
    """ This function should not be called from within a user script! """
    if _pick_lasso_object_callback:
        _pick_lasso_object_callback(object)
def _invoke_pick_asl_callback(asl):
    """ This function should not be called from within a user script! """
    if _pick_asl_callback:
        _pick_asl_callback(asl)
def _invoke_right_click_callbacks(x, y, atom_num):
    """ This function should not be called from within a user script! """
    _callbacks['right_click'].invoke(x, y, atom_num)
def _invoke_level_of_detail_callbacks():
    """ This function should not be called from within a user script! """
    _callbacks['level_of_detail'].invoke()
def _invoke_command_callbacks(cmd):
    """ This function should not be called from within a user script! """
    _callbacks['command'].invoke(cmd)
def _invoke_project_close_callbacks():
    """ This function should not be called from within a user script! """
    _callbacks['project_close'].invoke()
def _invoke_project_rename_callbacks():
    """ This function should not be called from within a user script! """
    _callbacks['project_rename'].invoke()
def _invoke_project_update_callbacks():
    """ This function should not be called from within a user script """
    _callbacks['project_update'].invoke()
def _invoke_workspace_bounding_box_callbacks(r00, r01, r02, r03, r10, r11, r12,
                                             r13, r20, r21, r22, r23, r30, r31,
                                             r32, r33):
    """ This function should not be called from within a user script! """
    _callbacks['workspace_bounding_box'].invoke(r00, r01, r02, r03, r10, r11,
                                                r12, r13, r20, r21, r22, r23,
                                                r30, r31, r32, r33)
def _invoke_rotation_callback(coords_str):
    """ This function should not be called from within a user script """
    # Convert the coordinates string into a list of [x,y,z] values:
    coords = list(map(float, coords_str.split(",")))
    coords_by_3 = [coords[i:i + 3] for i in range(0, len(coords), 3)]
    _rotation_cb(coords_by_3)
def _invoke_rotation_removed_callback():
    """ This function should not be called from within a user script """
    _rotation_removed_cb()
def _invoke_translation_callback(coords_str):
    """ This function should not be called from within a user script """
    # Convert the coordinates string into a list of [x,y,z] values:
    coords = list(map(float, coords_str.split(",")))
    coords_by_3 = [coords[i:i + 3] for i in range(0, len(coords), 3)]
    _translation_cb(coords_by_3)
def _invoke_translation_removed_callback():
    """ This function should not be called from within a user script """
    _translation_removed_cb()
#****************************************************************************
[docs]def get_directory(which_directory, preferences_file_name=""):
    """
    Return the full path for the specified directory.
    If which_directory is valid but it cannot, it raises a StandardError.
    :param which_directory: specifies the directory to get back.
    :param preferences_file_name: only applies when PREFERENCES is specified.
                                  In the PREFERENCES case if
                                  preferences_file_name is not specified, then
                                  just the absolute path to the preferences
                                  directory is returned. This is the default.
                                  If preferences_file_name is specified, then
                                  return a string which is the preferences dir
                                  + directory separator + preferences_file_name.
    Valid values for which_directory are defined at the top of this
    module and are:
    PREFERENCES         - Maestro preferences (for this version of Maestro)
    TEMPORARY_DATA      - Temporary data directory
    TEMPORARY_PROJECTS  - Directory where temporary (scratch) projects are put
    If an invalid which_directory is specified, then a ValueError is thrown.
    """
    dir = ""
    if which_directory == TEMPORARY_DATA:
        try:
            dir = _pymae.mae_get_temp_location()
        except Exception:
            raise Exception('Unable to lookup the value for the '
                            'TEMPORARY_DATA directory')
        else:
            return dir
    elif which_directory == TEMPORARY_PROJECTS:
        try:
            dir = _pymae.mae_get_temp_project_location()
        except KeyError:
            raise Exception('Unable to lookup the value for the '
                            'TEMPORARY_PROJECTS directory')
        else:
            return dir
    elif which_directory == PREFERENCES:
        # Default is to pass "" and will return just the directory
        dir = _pymae.mae_get_preferences_location(preferences_file_name)
        if not dir:
            raise Exception('Unable to get PREFERENCES directory')
        else:
            return dir
    else:
        raise ValueError('%s is not a valid value for which_directory' %
                         which_directory) 
###########################################################################
# Camera view functions
###########################################################################
[docs]def workspace_get_coordinate_center():
    """
    Returns a list of three floats representing the current Workspace
    center of rotation.
    """
    (x, y, z) = _pymae.mae_get_workspace_coordinate_center()
    ret_list = []
    ret_list.append(x)
    ret_list.append(y)
    ret_list.append(z)
    return ret_list 
[docs]def workspace_get_translation():
    """
    Returns a list of three floats representing the current Workspace
    translation vector
    """
    (x, y, z) = _pymae.mae_get_workspace_translation()
    ret_list = []
    ret_list.append(x)
    ret_list.append(y)
    ret_list.append(z)
    return ret_list 
[docs]def workspace_get_viewing_volume():
    """
    Returns a list of six floats representing the current Workspace
    viewing volume
    """
    (left, right, top, bottom, near,
     far) = _pymae.mae_get_workspace_viewing_volume()
    ret_list = []
    ret_list.append(left)
    ret_list.append(right)
    ret_list.append(top)
    ret_list.append(bottom)
    ret_list.append(near)
    ret_list.append(far)
    return ret_list 
[docs]def workspace_get_view_matrix():
    """
    Returns a list of 16 floats representing the current view matrix. Note
    that the final column of the matrix is always 0.0 as this doesn't actually
    include the translation - for that see workspace_get_translation()
    Also note that this matrix is returned in OpenGL order meaning
    it is column major notation.
    """
    (m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43,
     m44) = _pymae.mae_get_workspace_view_matrix()
    ret_list = []
    ret_list.append(m11)
    ret_list.append(m12)
    ret_list.append(m13)
    ret_list.append(m14)
    ret_list.append(m21)
    ret_list.append(m22)
    ret_list.append(m23)
    ret_list.append(m24)
    ret_list.append(m31)
    ret_list.append(m32)
    ret_list.append(m33)
    ret_list.append(m34)
    ret_list.append(m41)
    ret_list.append(m42)
    ret_list.append(m43)
    ret_list.append(m44)
    return ret_list 
[docs]def workspace_get_view_matrix_inverse():
    """
    Returns a list of 16 floats representing the current inverse view matrix.
    Note that the final column of the matrix is always 0.0 as this doesn't
    actually include the translation - for that see workspace_get_translation()
    """
    (m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43,
     m44) = _pymae.mae_get_workspace_view_matrix_inverse()
    ret_list = []
    ret_list.append(m11)
    ret_list.append(m12)
    ret_list.append(m13)
    ret_list.append(m14)
    ret_list.append(m21)
    ret_list.append(m22)
    ret_list.append(m23)
    ret_list.append(m24)
    ret_list.append(m31)
    ret_list.append(m32)
    ret_list.append(m33)
    ret_list.append(m34)
    ret_list.append(m41)
    ret_list.append(m42)
    ret_list.append(m43)
    ret_list.append(m44)
    return ret_list 
[docs]def workspace_get_view_matrix_inverse_no_center():
    """
    Returns a list of 16 floats representing the current inverse view matrix
    with no center.
    Note that the final column of the matrix is always 0.0 as this doesn't
    actually include the translation - for that see workspace_get_translation()
    """
    (m11, m12, m13, m14,
     m21, m22, m23, m24,
     m31, m32, m33, m34,
     m41, m42, m43, m44) = \
         
_pymae.mae_get_workspace_view_matrix_inverse_no_center()
    ret_list = []
    ret_list.append(m11)
    ret_list.append(m12)
    ret_list.append(m13)
    ret_list.append(m14)
    ret_list.append(m21)
    ret_list.append(m22)
    ret_list.append(m23)
    ret_list.append(m24)
    ret_list.append(m31)
    ret_list.append(m32)
    ret_list.append(m33)
    ret_list.append(m34)
    ret_list.append(m41)
    ret_list.append(m42)
    ret_list.append(m43)
    ret_list.append(m44)
    return ret_list 
[docs]def workspace_get_size_and_scale():
    """
    Return a tuple of width, height, scale
    width: width of the Workspace drawing area in pixels
    height: height of the Workspace drawing area in pixels
    scale:  ratio of window to world space.  The ratio is calculated for the
    width and for the height. The the smaller of these is returned (as this is
    what Maestro uses).
    """
    (width, height, scale) = \
            
_pymae.mae_get_workspace_size_and_scale()
    return (width, height, scale) 
[docs]def create_cone(x0, y0, z0, x1, y1, z1, r, g, b, radius, opacity, resolution):
    """
    Return the handle of a Maestro cone object using the given data
    """
    return _pymae.mae_create_cone(x0, y0, z0, x1, y1, z1, r, g, b, radius,
                                  opacity, resolution) 
[docs]def create_cylinder(x0,
                    y0,
                    z0,
                    x1,
                    y1,
                    z1,
                    r,
                    g,
                    b,
                    radius,
                    opacity,
                    resolution,
                    remove_endcaps=False):
    """
    Return the handle of a Maestro cylinder object using the given data
    """
    return _pymae.mae_create_cylinder(x0, y0, z0, x1, y1, z1, r, g, b, radius,
                                      opacity, resolution, remove_endcaps) 
[docs]def create_sphere(x,
                  y,
                  z,
                  r,
                  g,
                  b,
                  radius,
                  opacity,
                  resolution,
                  angle_dep_transparency=False):
    """
    Return the handle of a Maestro sphere object using the given data
    """
    return _pymae.mae_create_sphere(x, y, z, r, g, b, radius, opacity,
                                    resolution, angle_dep_transparency) 
[docs]def create_hemisphere(x,
                      y,
                      z,
                      direction_x,
                      direction_y,
                      direction_z,
                      r,
                      g,
                      b,
                      radius,
                      opacity,
                      resolution,
                      angle_dep_transparency=False):
    """
    Return the handle of a Maestro sphere object using the given data
    """
    return _pymae.mae_create_hemisphere(x, y, z, direction_x, direction_y,
                                        direction_z, r, g, b, radius, opacity,
                                        resolution, angle_dep_transparency) 
[docs]def set_glow_color(handle, r, g, b):
    """
    Set glow color on a graphics object.
    """
    return _pymae.mae_set_glow_color(handle, r, g, b) 
[docs]def set_is_glowing(handle, flag):
    """
    Set glowing flag on a graphics object.
    """
    return _pymae.mae_set_is_glowing(handle, flag) 
[docs]def create_torus(x0, y0, z0, x1, y1, z1, r, g, b, radius, tube_radius, opacity,
                 u_resolution, v_resolution):
    """
    Return the handle of a Maestro torus object using the given data
    """
    return _pymae.mae_create_torus(x0, y0, z0, x1, y1, z1, r, g, b, radius,
                                   tube_radius, opacity, u_resolution,
                                   v_resolution) 
[docs]def create_model_xyz_axes(cx, cy, cz, lx, ly, lz, r, g, b):
    """
    Return the handle of a Maestro xyz axes object using the given data
    """
    return _pymae.mae_create_model_xyz_axes(cx, cy, cz, lx, ly, lz, r, g, b) 
[docs]def create_polyhedron(vertices, faces, normals, r, g, b, opacity, style):
    """
    Return the handle of a Maestro polyhedron object using the given data
    """
    # Merge the vertices into a single array
    flattened_vertices = []
    for v in vertices:
        flattened_vertices.extend(v)
    # Merge the normals
    flattened_normals = []
    for n in normals:
        flattened_normals.extend(n)
    # Merge the faces
    flattened_faces = []
    for f in faces:
        flattened_faces.extend(f)
    num_vertices = len(vertices)
    num_faces = len(faces)
    num_vertices_per_face = len(flattened_faces) // num_faces
    return _pymae.mae_create_polyhedron(num_vertices, flattened_vertices,
                                        flattened_normals, num_faces,
                                        num_vertices_per_face, flattened_faces,
                                        r, g, b, opacity, style) 
[docs]def set_polyhedron_style(polyhedron, style):
    """
    Set the polyhedron style to either LINE or FILL
    :param polyhedron: A handle to a Maestro polyhedron, returned from
                       create_polyhedron()
    :param style: Whether to fill the polyhedron in or to leave it as
                  lines connecting vertices.
    :type  style: Choice, FILL or LINE
    """
    _pymae.mae_set_polyhedron_style(polyhedron, style) 
[docs]def create_polygon(vertices, r, g, b, opacity):
    """
    Return the handle of a Maestro polygon object using the given data
    """
    # Merge the vertices into a single array
    flattened_vertices = []
    for v in vertices:
        flattened_vertices.extend(v)
    num_vertices = len(vertices)
    return _pymae.mae_create_polygon(num_vertices, flattened_vertices, r, g, b,
                                     opacity) 
def _validate_rgb(r, g, b):
    """
    Validates the given color. Checks to make sure that r, g, and b and are
    in the range 0.0 to 1.0.
    :param r: The red component of the color
    :type r: float
    :param g: The green component of the color
    :type g: float
    :param b: The blue component of the color
    :type b: float
    """
    if any(v < 0. or v > 1. for v in (r, g, b)):
        raise ValueError(f"rgb must be between 0 and 1. got ({r}, {g}, {b})")
[docs]def create_parallelepiped(parallelepiped, r, g, b, line_width=1.0, style=0):
    """
    Return the handle of a Maestro parallelepiped object using the given data
    :param parallelepiped: The parallelepiped data to use to create the
                           Maestro object
    :type parallelepiped: maestro_ui.MM_GraphicsParallelepiped
    :param r: The red component of the color for the parallelepiped, in the
              range 0 to 1.
    :type r: float
    :param g: The green component of the color for the parallelepiped, in the
              range 0 to 1.
    :type g: float
    :param b: The blue component of the color for the parallelepiped, in the
              range 0 to 1.
    :type b: float
    :param line_width: The line width to use to draw the parallelepiped, > 0.
    :type line_width: float
    :param style: The line style to use to draw the parallelepiped. 0 is solid,
                  1 is short dashed lines, and 2 is dashed cylinders
    :type style: int
    """
    _validate_rgb(r, g, b)
    if line_width <= 0.:
        raise ValueError(f"line_width must be > 0, not {line_width}")
    if style < 0 or style > 2:
        raise ValueError(f"style must be between 0 and 2, not {style}")
    maestro_hub = maestro_ui.MaestroHub().instance()
    return maestro_hub.createParallelepiped(parallelepiped, r, g, b, line_width,
                                            style) 
[docs]def create_lines(segments, r, g, b, line_width=1.0, style=0):
    """
    Return the handle of a Maestro graphics object containing the given line
    segments
    :param segments: The line segments to use to create the Maestro object
    :type segments: [ maestro_ui.MM_GraphicsLineSegment ]
    :param r: The red component of the color for the segments, in the
              range 0 to 1.
    :type r: float
    :param g: The green component of the color for the segments, in the
              range 0 to 1.
    :type g: float
    :param b: The blue component of the color for the segments, in the
              range 0 to 1.
    :type b: float
    :param line_width: The line width to use to draw the segments, > 0.
    :type line_width: float
    :param style: The line style to use to draw the segments. 0 is solid,
                  1 is short dashed lines, and 2 is dashed cylinders
    :type style: int
    """
    _validate_rgb(r, g, b)
    if line_width <= 0.:
        raise ValueError(f"line_width must be > 0, not {line_width}")
    if style < 0 or style > 2:
        raise ValueError(f"style must be between 0 and 2, not {style}")
    maestro_hub = maestro_ui.MaestroHub().instance()
    return maestro_hub.createLineSegments(segments, r, g, b, line_width, style) 
[docs]def hide_object(handle):
    """
    Hide the 3D graphics object in Maestro with the given handle
    """
    _pymae.mae_hide_object(handle) 
[docs]def show_object(handle):
    """
    Show the 3D graphics object in Maestro with the given handle
    """
    _pymae.mae_show_object(handle) 
[docs]def remove_object(handle):
    """
    Remove the 3D graphics object in Maestro with the given handle
    """
    _pymae.mae_remove_object(handle) 
[docs]def set_coords(handle, x, y, z):
    """
    Set the coordinates (centroid) for the given handle
    """
    _pymae.mae_set_coords(handle, x, y, z) 
[docs]def set_coords1(handle, x, y, z):
    """
    Set the coordinates for the given handle.  Used for cylinders and
    cones.
    """
    _pymae.mae_set_coords1(handle, x, y, z) 
[docs]def set_coords2(handle, x, y, z):
    """
    Set the other coordinates for the given handle.  Used for cylinders and
    cones.
    """
    _pymae.mae_set_coords2(handle, x, y, z) 
[docs]def set_colors(handle, r, g, b, alpha):
    """
    Set the colors for the given handle
    """
    _pymae.mae_set_colors(handle, r, g, b, alpha) 
[docs]def set_radius(handle, radius):
    """
    Set the radius for the given handle
    """
    _pymae.mae_set_radius(handle, radius) 
[docs]def set_material(handle, material):
    """
    Set the material for the given handle
    """
    _pymae.mae_set_material(handle, material) 
[docs]def set_pick_id(handle, id):
    """
    Set the picking ID for the given handle.
    :param id: Pick id. None disables picking.
    :type id: int or NoneType
    """
    if id == 0:
        # Defend against accidental `for pick_id, obj in enumerate(objects):`
        raise ValueError("0 is not a pickable pick ID. "
                         "Pass None to make the handle unpickable.")
    elif id is None:
        id = 0
    _pymae.mae_set_pick_id(handle, id) 
[docs]def set_persistent_name(handle, name):
    """
    Set the persistent name for the given handle.
    """
    _pymae.mae_set_persistent_name(handle, name) 
[docs]def set_entry_id(handle, id):
    _pymae.mae_set_entry_id(handle, id) 
[docs]def set_pick_category(handle, pick_category):
    """
    Set the picking category for the given handle.  pick_category should be a
    string containing the category to pick (e.g. <script name>_spheres)
    """
    _pymae.mae_set_pick_category(handle, pick_category) 
[docs]def start_picking(help, callback_func, pick_category):
    """
    Start picking items in the picking category.
    :param pick_category: A picking category defined in mm_graphicspick.cxx
        string_pick_map.
    :type pick_category: str
    """
    global _pick_callback
    _pick_callback = callback_func
    func = "schrodinger.maestro.maestro._invoke_pick_callback"
    _pymae.mae_start_picking(func, help, pick_category) 
[docs]def add_pick_category_callback(callback_func, pick_category):
    """
    Add pick category callback
    :param callback_func: A callback function
    :type pick_category: function
    :param pick_category: A picking category defined in mm_graphicspick.cxx
    :type pick_category: str
    """
    func_str = callback_func.__module__ + '.' + callback_func.__name__
    _pymae.mae_add_pick_category_callback(func_str, pick_category) 
[docs]def remove_pick_category_callback(pick_category):
    """
    Remove pick category callback
    :param pick_category: A picking category defined in mm_graphicspick.cxx
    :type pick_category: str
    """
    _pymae.mae_remove_pick_category_callback(pick_category) 
[docs]def clear_pick_category_callbacks():
    """
    Clear all pick category callbacks
    """
    _pymae.mae_clear_pick_category_callbacks() 
[docs]def stop_picking():
    """
    Stop picking
    """
    _pymae.mae_stop_picking() 
[docs]def get_ligand_asl():
    """
    :deprecated maestro now uses the ligand keyword
    """
    warnings.warn("Maestro now uses the raw 'ligand' for identifying ligands.",
                  DeprecationWarning,
                  stacklevel=2)
    return "ligand" 
[docs]def set_project_command(name, command):
    """
    Sets a command to add to the project-open command script when the project
    is closed, or when a saved project scene is re-applied.
    :param name is typically the name of the invoking script
    :param command is a single Maestro command
    """
    _pymae.mae_set_project_command(name, command) 
[docs]def append_project_command(name, command):
    """
    Appends the given command to the set of commands to add to the
    project-open command script when the project is closed or project scene
    is re-applied.
    :param name is typically the name of the invoking script
    :param command is a single Maestro command
    """
    _pymae.mae_append_project_command(name, command) 
[docs]def delete_project_commands(name):
    """
    Deletes all of the project commands associated with the given name.
    :param name is typically the name of the invoking script
    """
    _pymae.mae_delete_project_commands(name) 
[docs]def get_maestro_toplevel_window():
    """
    Returns the maestro top level window from the list of available top level
    windows in the maestro application.  The exact window returned depends
    on whether docking is enabled or not.
    """
    maestro_docking_enabled = get_command_option("prefer", "dockingpanels")
    maestro_dock_location = get_command_option("prefer", "docklocation")
    top_level_widgets = QtWidgets.QApplication.instance().topLevelWidgets()
    maestro_toplevel = None
    for w in top_level_widgets:
        # main_maestro_window is the internal name of the main maestro
        # toplevel window
        if maestro_docking_enabled == "True" and \
               
maestro_dock_location == "mainwindow" and \
               
w.objectName() == "main_maestro_window":
            # Dock into main window in maestro
            maestro_toplevel = w
            break
        elif maestro_docking_enabled == "True" and \
                 
maestro_dock_location == "floating" and \
                 
w.objectName() == "dock_window":
            # Dock into free-floating window
            maestro_toplevel = w
            break
        elif maestro_docking_enabled == "False" and \
                 
w.objectName() == "main_maestro_window":
            # No docking - we expect client to create
            # free-standing window
            maestro_toplevel = w
            break
    if not maestro_toplevel:
        return None
    return maestro_toplevel 
[docs]def set_docking_configuration(w,
                              dockable,
                              dock_area=QtCore.Qt.RightDockWidgetArea):
    """
    :type w: QWidget
    :param w: Widget for which we need to set the docking configuration
    :type dockable:  bool
    :param dockable: Whether or not this widget is dockable
    :type dock_area:  Qt.QFlags
    :param dock_area: Area where the widget is to be docked (if
                      it is dockable)
    This routine takes the specified widget and configures the
    widget docking settings based on Maestro's preferences and
    whether the widget itself is dockable.
    """
    if dockable:
        toplevel = get_maestro_toplevel_window()
        if toplevel:
            toplevel.addDockWidget(dock_area, w)
            panel_name = w.objectName()
            tabify_docking_window(str(panel_name))
    else:
        # Panel is not dockable
        # Dock into normal window outside of maestro's main window
        pass 
[docs]def raise_main_window():
    """
    Raises the Maesto main window to the top of the parent widget's stack
    This function is used mainly by the python scripts which operates directly
    in Maestro workspace, so that maestro mainwindow is visible and stays on the
    top of the widget stack
    At present, this function is uncallable. Fix it, define
    getMaestroTopLevelWindow and remove the first line of the function
    """
    maemainwindow = get_maestro_toplevel_window()
    if maemainwindow:
        if maemainwindow.isMinimized():
            maemainwindow.show()
            maemainwindow.showNormal()
        else:
            maemainwindow.raise_() 
# ****************************************
[docs]def show_docking_panels_window():
    """
    Show the Docking Panels window.  There can be cases where we've
    added widgets to this panel, but it isn't visible and we want it to
    be visible.
    """
    _pymae.mae_show_docking_panels_window() 
# ****************************************
[docs]def sendemail(from_addr,
              to_addr,
              server,
              port,
              security,
              message,
              passwd="",
              debug=False):
    """
    Try to send an email.
    :param from_addr: The From Email address
    :type  from_addr: str
    :param to_addr: The address to which we are sending
    :type  to_addr: str
    :param server: SMTP server.  Only supports SMTP.
    :type  server: str
    :param port: Port to which we will attach
    :type  port: int
    :param security: What type of security if any.  Valid values are:
                     none, starttls, ssltls.
    :type  security: str
    :param message: Body of the message to send.
    :type  message: str
    :param passwd: Password.  If blank, then assumes no login is needed.
    :type  passwd: str
    :param debug: If True, debugging is printed.
    :type  debug: bool
    :return nothing
    """
    if debug:
        print("EMail: from=", from_addr)
        print("EMail: to=", to_addr)
        print("EMail: server=", server)
        print("EMail: pass=", passwd)
        print("EMail: port=", port)
        print("EMail: message=", message)
        print("EMail: security=", security)
    try:
        import smtplib
    except:
        print("Error importing smtplib module.  Could not send email.")
        return
    smtpserver = None
    if security == "starttls":
        if debug:
            print("EMail: Creating smtplib instance (starttls)")
        smtpserver = smtplib.SMTP(server, port, timeout=10)
        if debug:
            print("Email: connecting")
        smtpserver.ehlo()
        smtpserver.starttls()
        smtpserver.ehlo()
    elif security == "ssltls":
        if debug:
            print("EMail: Creating smtplib instance (ssltls)")
        smtpserver = smtplib.SMTP_SSL(server, port, timeout=10)
        if debug:
            print("Email: connecting")
        smtpserver.ehlo()
    elif security == "none":
        if debug:
            print("EMail: Creating smtplib instance (security: none)")
        smtpserver = smtplib.SMTP(server, port, timeout=10)
        if debug:
            print("Email: connecting")
        smtpserver.helo()
    else:
        if debug:
            print("EMail: %s is not a valid security value" % security)
        return
    if (len(passwd) > 0):
        if debug:
            print("Email: Logging in")
        smtpserver.login(from_addr, passwd)
    else:
        if debug:
            print("EMail: Assuming no need to log in")
    header = 'To:' + to_addr + '\n' + 'From: ' + from_addr + '\n' + \
             
'Subject: Maestro Automated Backup \n'
    msg = header + '\n' + message + '\n\n'
    if debug:
        print("EMail: Sending email")
    smtpserver.sendmail(from_addr, to_addr, msg)
    if debug:
        print('EMail: sent!')
    smtpserver.quit()
    if debug:
        print('EMail: quit session') 
[docs]def job_started(job_id):
    """
    Notify Maestro job monitoring that a job has been started.
    :param job_id: The job id returned from mmjob when the job was launched
    :type  job_id: str
    """
    _pymaecxx.mae_job_started(job_id) 
[docs]def tabify_docking_window(panel_name):
    """
    :param panel_name: Panel which needs to be docked under tab if there are
        existing docked panels.
    """
    _pymaecxx.mae_tabify_docking_window(panel_name) 
[docs]def get_main_window():
    """
    Gets main window instance
    :rtype: QMainWindow
    :returns: instance for Maestro's main window
    :raises: Exception if maestro main window not found
    """
    global _main_window
    if _main_window:
        return _main_window
    widget = get_widget_from_object_name("main_maestro_window")
    if widget is not None:
        _main_window = widget
        return _main_window
    raise Exception("Could not find main maestro window") 
def _ignore_callback(callback_type, callback_func):
    """
    Decorator to manage disconnecting a maestro callback, (if currently
    connected), and reconnecting after the function returns *or* excepts.
    This will only work with class-methods, not instance methods.  Use the
    context manager option for instance methods.
    :param callback_type: What callback you're working with, these are the keys
                          to the _callbacks dict.
    :type callback_type:  str
    :param callback_func: The method we want to disconnect (if connected)
                            from the given callback
    :type  callback_func: A function
    """
    def wrap(wrapped_func):
        def dec(*args, **kwargs):
            """
            This is the actual functionality encapsulating the decorated method
            """
            # Set the wait cursor, run the method, then restore the cursor
            func_registered = is_function_registered(callback_type,
                                                     callback_func)
            if func_registered:
                _callbacks[callback_type].remove(callback_func)
            try:
                response = wrapped_func(*args, **kwargs)
            finally:
                if func_registered:
                    _callbacks[callback_type].add(callback_func)
            return response
        return dec
    return wrap
#The following convenience wrappers of _ignore_callback could be done via
#function generation, but would be harder for people to read and discover.
[docs]def ignore_workspace_changed(callback_func):
    """
    A decorator for ignoring workspace changes.  Note: This will only work to
    ignore class-methods and global methods.  Instance-methods will need to
    use the context manager IgnoreWorkspaceChanged.
    Example::
        @maestro.ignore_workspace_changed(MyClass.my_ws_changed):
        def my_func():
            #Any WS changes inside this function won't trigger my_ws_changed,
            #even if it's been registered with workspace_changed_function_add
    """
    return _ignore_callback('workspace_changed', callback_func) 
[docs]def ignore_project_close(callback_func):
    """ See ignore_workspace_changed for docs/example """
    return _ignore_callback('project_close', callback_func) 
[docs]def ignore_project_update(callback_func):
    """ See ignore_workspace_changed for docs/example """
    return _ignore_callback('project_update', callback_func) 
class _IgnoreCallback:
    """
    This is a context manager to ignore maestro callbacks inside a with: block
    """
    def __init__(self, callback_type, callback_func):
        """
        :param callback_type: What callback you're working with, these are the
                              keys to the _callbacks dict.
        :type callback_type:  str
        :param callback_func: The method we want to disconnect (if connected)
                              from the given callback
        :type  callback_func: A function
        """
        self.callback_type = callback_type
        self.callback_func = callback_func
    def __enter__(self):
        self.func_registered = is_function_registered(self.callback_type,
                                                      self.callback_func)
        if self.func_registered:
            _callbacks[self.callback_type].remove(self.callback_func)
    def __exit__(self, exc_type, exc_value, exc_tb):
        if is_function_registered(self.callback_type, self.callback_func):
            return  #re-registered elsewhere
        if self.func_registered:
            _callbacks[self.callback_type].add(self.callback_func)
[docs]class IgnoreWorkspaceChanged(_IgnoreCallback):
    """
    Context manager to ignore Workspace Changed callbacks.
    Example::
        maestro.workspace_changed_function_add(self.my_ws_changed)
        with maestro.IgnoreWorkspaceChanged(self.my_ws_changed):
            #WS changes in this block won't trigger self.my_ws_changed
        #Outside of the block WS changes will again call self.my_ws_changed
    """
[docs]    def __init__(self, callback_func):
        """ See _IgnoreCallback.__init__ """
        _IgnoreCallback.__init__(self, 'workspace_changed', callback_func)  
[docs]class IgnoreProjectClose(_IgnoreCallback):
    """ See IgnoreWorkspaceChanged for docs/example. """
[docs]    def __init__(self, callback_func):
        _IgnoreCallback.__init__(self, 'project_close', callback_func)  
[docs]class IgnoreProjectUpdate(_IgnoreCallback):
    """ See IgnoreWorkspaceChanged for docs/example. """
[docs]    def __init__(self, callback_func):
        _IgnoreCallback.__init__(self, 'project_update', callback_func)  
[docs]def get_growth_space():
    """
    Returns the growth space from Maestro. This function assumes that the
    growth space has already been created through
    maestro.command("creategrowthspace")
    This growth space will not include any of the solvent-accessible spheres.
    See get_solvent_accessible_growth_space()
    :return: A list of spheres, with each sphere being a list: [x, y, z, radius]
    :rtype: list
    """
    return _pymaecxx.mae_get_growth_space() 
[docs]def get_solvent_accessible_growth_space():
    """
    Returns the solvent-accessible growth space from Maestro. This function
    assumes that the growth space has already been created through
    maestro.command("creategrowthspace")
    This growth space will not include any spheres which are not
    solvent-accessible.
    See get_growth_space()
    :return: A list of spheres, with each sphere being a list: [x, y, z, radius]
    :rtype: list
    """
    return _pymaecxx.mae_get_solvent_accessible_growth_space() 
[docs]def get_growth_space_sphere(sphere_id):
    """
    Returns the growth space sphere corresponding to the given id from Maestro.
    This function assumes that the growth space has already been created through
    maestro.command("creategrowthspace")
    :param sphere_id: The pick ID returned from maestro.start_picking()
    :type width: int
    :return: The sphere as a list [x, y, z, radius]
    :rtype: list
    """
    if sphere_id <= 0:
        raise ValueError("sphere_id must be > 0")
    return _pymaecxx.mae_get_growth_space_sphere(sphere_id) 
[docs]def set_rightclick_on_pick_category(pick_category, pymodule, pyfunc):
    """
    Set a right click handler on a specified pick category.
    When user right clicks on any graphics object which belongs
    from given pick category, a python function of a given module is called.
    :param pick_category: Pick category of graphics objects.
    :type pick_category: str
    :param pymodule: Python module name.
    :type pymodule: str
    :param pyfunc: Python function name.
    :type pyfunc: str
    """
    _pymaecxx.mae_set_rightclick_on_pick_category(pick_category, pymodule,
                                                  pyfunc) 
[docs]def compute_best_fit_line_params(x_points, y_points):
    """
    Utility function to compute best fit line parameters ( slope, intercept,
    co-relation coefficient ) from x and y points.
    :param x_points List of x coordinates.
    :param y_points List of y coordinates.
    """
    slope, intercept, corr_coeff, error_best_fit, error_corr_coeff = 0, 0, 0, 0, 0
    with warnings.catch_warnings():
        warnings.simplefilter("error")
        try:
            slope, intercept = numpy.polyfit(x_points, y_points, 1)
        except:
            error_best_fit = 1
        try:
            corr_coeff = numpy.corrcoef(x_points, y_points)[0, 1]
        except:
            error_corr_coeff = 1
    return slope, intercept, corr_coeff, error_best_fit, error_corr_coeff