Source code for schrodinger.maestro.maestro

"""
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.

"""
from typing import List

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 rebuild_scripts_menu(): """ Rebuild the scripts menu (includes entries for both Knime Workflows and custom python scripts menus). This will re-read the scriptsx.mnu file from the user and common areas and display the contents in the Scripts menu. """ inst = maestro_ui.MaestroHub.instance() inst.emitRebuildScriptsMenu()
#****************************************************************************
[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 reset_workspace_transformations(): """ Reset the current Workspace transformations """ _pymae.mae_reset_workspace_transformations() return
[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_widget_from_object_name(object_name): """ Searches all windows (top-level widgets) and gets the widget from object name. :param object_name: The name of the object to be found. :type object_name: str :rtype: QWidget :returns: widget (if exists) with given object name. Otherwise, returns None. """ qapp = QtWidgets.QApplication.instance() for widget in qapp.topLevelWidgets(): if widget.objectName() == object_name: return widget return None
[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 get_growth_space_spheres(sphere_id: int) -> List[List[float]]: """ Returns the growth space spheres corresponding to the given id from Maestro. This function assumes that the growth space has already been created through maestro.command("creategrowthspace") The spheres will be the same ones which Maestro will highlight (e.g. it uses the same radius for selecting the spheres as for the hover effect). :param sphere_id: The pick ID returned from maestro.start_picking() :type width: int :return: The spheres as a list of lists of [x, y, z, radius] :rtype: List[List[float]] """ if sphere_id <= 0: raise ValueError("sphere_id must be > 0") return _pymaecxx.mae_get_growth_space_spheres(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 workspace_transform_local_scope(local_asl): """ Utility function for panels that use the Maestro command 'transform scope=local', which works operates on Workspace selection. See PANEL-8273 for details. :param local_asl: ASL defining the local scope for the transform :type local_asl: str """ command("workspaceselectionreplace {0}".format(local_asl)) command("transform scope=local") # MAE-36978 - Performing a translate sets the center in Maestro 11. # Otherwise we get an error. command('translate') command('transform scope=local') command("localcenter {0}".format(local_asl))
[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