"""
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 write_entries_from_project(filename,
which_entries,
Htreatment=None,
synchronize=True,
append=False,
props=True):
"""
Write entries from the project to a file. The parameters to this
method are:
:param filename: the name of the file which to write these entries to. The
suffix determines the format of the file using the same
rules as Structure.write()
:type filename: str
:param which_entries: entries to write 'All', 'Included' or 'Selected'
:type which_entries: str
:param Htreatment: If not None then hydrogens will be added to the
structures before they are written to the file using the
specified H-treatment. Allowed treatment names are:
All-atom with Osp3/Nsp3-Lp, All-atom with No-Lp,
Csp3 United-atom with S-Lp, Csp3 United-atom with No-Lp,
All-atom with S-Lp, C sp2/sp3 United-atom with No-Lp,
C sp2/sp3, N,O,S United-atom with No-Lp. The one you
almost certainly want is 'All-Atom with No-Lp'
:type Htreatment: str
:param synchronize: If True, first synchronize the Workspace with the
project. It is recommended this be done.
:type synchronize: bool
:param append: If True, append to the specified file. If False, overwrite.
:type append: bool
:param props: If True, CT-level properties should be written.
:type props: bool
If synchronization was done and the Maestro user had 'Prompt' mode active
then they may choose to cancel the operation. If they cancel then nothing
will be written and this method will return False, otherwise it returns
True.
"""
if synchronize:
ret = project_table_synchronize()
if not ret:
# User canceled - return now without doing anything more
return ret
# Get the project and the appropriate iterator
pt = project_table_get()
if which_entries == "all":
iter = pt.all_rows
elif which_entries == "selected":
iter = pt.selected_rows
elif which_entries == "included":
iter = pt.included_rows
else:
raise Exception("Unknown which parameter: %s" % which_entries)
if Htreatment is not None:
mm.mmhtreat_initialize(mm.error_handler)
treatment = mm.mmhtreat_get_treatment_index(Htreatment)
if treatment < 0:
raise Exception("%s: is an unknown Hydrogen Treatment" \
% Htreatment)
for i, row in enumerate(iter):
st = row.getStructure(props=props, workspace_sync=False)
st.retype()
if Htreatment is not None:
for iatom in st.atom:
mm.mmhtreat_hadd(treatment, st, iatom)
if i == 0:
if append:
st.append(filename)
else:
st.write(filename)
else:
st.append(filename)
if Htreatment is not None:
mm.mmhtreat_terminate()
return True
#####################################################################
# Tk event handling
#####################################################################
#****************************************************************************
[docs]def tk_toplevel_add(toplevel_widget):
"""
Notifies Maestro that a new Tk toplevel has been created and
should be displayed and share event processing with Maestro itself.
Note: this is the preferred way to run a Tkinter script from within
Maestro and should be called instead of using the
Tkinter.MainLoop() function.
"""
global _tk_widgets
# In case tk_toplevel_add() gets called on same widget twice:
if toplevel_widget in _tk_widgets:
return
_tk_widgets.append(toplevel_widget)
# Start Maestro event processing:
_pymae.mae_start_tk_events()
#****************************************************************************
[docs]def tk_toplevel_remove(toplevel_widget):
"""
Notifies Maestro that a Tk toplevel previously registered via
tk_toplevel_add() no longer needs to be displayed or have events
passed to it. Note: this function does not actually hide the
toplevel widget. The script should call destroy() on the widget
after remove_tk_toplevel() in order to hide the widget.
"""
global _tk_widgets
try:
_tk_widgets.remove(toplevel_widget)
except ValueError: # This widget was either never added or already removed
pass
else:
# After removing the widget from the list, decrement the script count
# and stop maestro TK events if this was the last Python script:
_pymae.mae_stop_tk_events()
#****************************************************************************
def _update_tk_widgets():
"""This function should not be called from within a user script!"""
global _tk_widgets
for wd in _tk_widgets:
wd.update_idletasks()
wd.update()
#****************************************************************************
def _release_tk_widget_grabs():
"""This function should not be called from within a user script!"""
# Release event-handling from all Python widgets:
global _tk_widgets
for wd in _tk_widgets:
try:
grab_wd = wd.grab_current()
except KeyError:
# grab_current() sometimes does not work as expected (Ev:58350)
grab_wd = None
if grab_wd:
grab_wd.grab_release()
#****************************************************************************
[docs]def atom_selection_dialog(description,
current_asl="",
initial_pick_state=PICK_ATOMS,
resolve_asl_aliases=False):
"""
Post the atom selection dialog. The user can make selections in
that dialog and the resulting ASL string will be returned as a
result of this function. If the user cancels then the empty string
will be returned by this function. The description field is displayed
in the upper section of the dialog to indicate to the user the purpose of
displaying this dialog. The current_asl will be set in the dialog when
it is first displayed and the user will be able to edit that in this
dialog.
:param description - description field string
:param current_asl - current asl string
:param initial_pick_state - initial pick state enum (default is PICK_ATOMS)
:param bool resolve_asl_aliases - True if we want to resolve asl aliases
False otherwise. False is the default parameter
"""
return _pymae.mae_post_asd(description, current_asl, initial_pick_state,
resolve_asl_aliases)
#****************************************************************************
[docs]def warning(message):
"""
Post a Maestro warning dialog with an OK button. This displays
the text specified in 'message' in the same dialog Maestro
uses to display warnings/errors, ensuring a consistent look and feel.
"""
return _pymae.mae_dialog_warning(message)
#****************************************************************************
[docs]def info(message):
"""
Post a Maestro informational dialog with an OK button. This displays
the text specified in 'message' in the same dialog Maestro
uses for displaying information, ensuring a consistent look and feel.
"""
return _pymae.mae_dialog_info(message)
#****************************************************************************
[docs]def question(question, button1="OK", button2="Cancel"):
"""
Post a Maestro question dialog - the same dialog Maestro
uses for displaying questions, ensuring a consistent look and feel.
Arguments are:
question: Question text
button1: Defaults to OK, but you can pass in what you like
button2: Defaults to Cancel, but you can pass in what you like
Returns:
maestro.BUTTON1 or maestro.BUTTON2, depending on which button
was pressed.
"""
status = _pymae.mae_dialog_question(question, button1, button2)
if (status == 0):
return BUTTON1
else:
return BUTTON2
#####################################################################
# Private module functions for periodic and workspace-changed callbacks
#####################################################################
def _destroy_tk_widgets():
"""
This function should not be called from within a user script!
"""
global _tk_widgets
i = len(_tk_widgets)
while i > 0:
_tk_widgets[i - 1].destroy()
i = i - 1
def _invoke_periodic_callbacks():
""" This function should not be called from within a user script! """
_callbacks['periodic'].invoke()
def _invoke_hover_callbacks(arg):
""" This function should not be called from within a user script! """
_callbacks['hover'].invoke(arg)
def _invoke_workspace_changed_callbacks(arg):
""" This function should not be called from within a user script! """
_callbacks['workspace_changed'].invoke(arg)
def _invoke_job_incorporation_callbacks(*args):
""" This function should not be called from within a user script! """
_callbacks['job_incorporation'].invokeJobIncorporation(*args)
def _invoke_pick_callback(atom1, atom2=None):
""" This function should not be called from within a user script! """
if _pick_callback:
if atom2 is None: # Atom picking
_pick_callback(atom1)
else: # Bond picking
_pick_callback(atom1, atom2)
[docs]def invoke_picking_loss_callback():
"""
Notify the current pick widget that it lost its picking rights. Note that
this function will not stop the picking itself.
"""
global _picking_loss_callback
if _picking_loss_callback:
try:
_picking_loss_callback()
except RuntimeError:
# The callback no longer exists. May happen when the Qt C++ object
# for the checkbox was destroyed.
_picking_loss_callback = None
[docs]def invoke_picking_default_callback():
"""
Notify the current default notification function that Maestro has
switched to default picking.
"""
global _picking_default_callback
if _picking_default_callback:
try:
_picking_default_callback()
except RuntimeError:
# The callback no longer exists. May happen if the script didn't
# remove the callback before exiting.
_picking_default_callback = None
def _invoke_pick_lasso_callback(asl):
""" This function should not be called from within a user script! """
if _pick_lasso_callback:
_pick_lasso_callback(asl)
def _invoke_pick_lasso_object_callback(object):
""" This function should not be called from within a user script! """
if _pick_lasso_object_callback:
_pick_lasso_object_callback(object)
def _invoke_pick_asl_callback(asl):
""" This function should not be called from within a user script! """
if _pick_asl_callback:
_pick_asl_callback(asl)
def _invoke_right_click_callbacks(x, y, atom_num):
""" This function should not be called from within a user script! """
_callbacks['right_click'].invoke(x, y, atom_num)
def _invoke_level_of_detail_callbacks():
""" This function should not be called from within a user script! """
_callbacks['level_of_detail'].invoke()
def _invoke_command_callbacks(cmd):
""" This function should not be called from within a user script! """
_callbacks['command'].invoke(cmd)
def _invoke_project_close_callbacks():
""" This function should not be called from within a user script! """
_callbacks['project_close'].invoke()
def _invoke_project_rename_callbacks():
""" This function should not be called from within a user script! """
_callbacks['project_rename'].invoke()
def _invoke_project_update_callbacks():
""" This function should not be called from within a user script """
_callbacks['project_update'].invoke()
def _invoke_workspace_bounding_box_callbacks(r00, r01, r02, r03, r10, r11, r12,
r13, r20, r21, r22, r23, r30, r31,
r32, r33):
""" This function should not be called from within a user script! """
_callbacks['workspace_bounding_box'].invoke(r00, r01, r02, r03, r10, r11,
r12, r13, r20, r21, r22, r23,
r30, r31, r32, r33)
def _invoke_rotation_callback(coords_str):
""" This function should not be called from within a user script """
# Convert the coordinates string into a list of [x,y,z] values:
coords = list(map(float, coords_str.split(",")))
coords_by_3 = [coords[i:i + 3] for i in range(0, len(coords), 3)]
_rotation_cb(coords_by_3)
def _invoke_rotation_removed_callback():
""" This function should not be called from within a user script """
_rotation_removed_cb()
def _invoke_translation_callback(coords_str):
""" This function should not be called from within a user script """
# Convert the coordinates string into a list of [x,y,z] values:
coords = list(map(float, coords_str.split(",")))
coords_by_3 = [coords[i:i + 3] for i in range(0, len(coords), 3)]
_translation_cb(coords_by_3)
def _invoke_translation_removed_callback():
""" This function should not be called from within a user script """
_translation_removed_cb()
#****************************************************************************
[docs]def get_directory(which_directory, preferences_file_name=""):
"""
Return the full path for the specified directory.
If which_directory is valid but it cannot, it raises a StandardError.
:param which_directory: specifies the directory to get back.
:param preferences_file_name: only applies when PREFERENCES is specified.
In the PREFERENCES case if
preferences_file_name is not specified, then
just the absolute path to the preferences
directory is returned. This is the default.
If preferences_file_name is specified, then
return a string which is the preferences dir
+ directory separator + preferences_file_name.
Valid values for which_directory are defined at the top of this
module and are:
PREFERENCES - Maestro preferences (for this version of Maestro)
TEMPORARY_DATA - Temporary data directory
TEMPORARY_PROJECTS - Directory where temporary (scratch) projects are put
If an invalid which_directory is specified, then a ValueError is thrown.
"""
dir = ""
if which_directory == TEMPORARY_DATA:
try:
dir = _pymae.mae_get_temp_location()
except Exception:
raise Exception('Unable to lookup the value for the '
'TEMPORARY_DATA directory')
else:
return dir
elif which_directory == TEMPORARY_PROJECTS:
try:
dir = _pymae.mae_get_temp_project_location()
except KeyError:
raise Exception('Unable to lookup the value for the '
'TEMPORARY_PROJECTS directory')
else:
return dir
elif which_directory == PREFERENCES:
# Default is to pass "" and will return just the directory
dir = _pymae.mae_get_preferences_location(preferences_file_name)
if not dir:
raise Exception('Unable to get PREFERENCES directory')
else:
return dir
else:
raise ValueError('%s is not a valid value for which_directory' %
which_directory)
###########################################################################
# Camera view functions
###########################################################################
[docs]def workspace_get_coordinate_center():
"""
Returns a list of three floats representing the current Workspace
center of rotation.
"""
(x, y, z) = _pymae.mae_get_workspace_coordinate_center()
ret_list = []
ret_list.append(x)
ret_list.append(y)
ret_list.append(z)
return ret_list
[docs]def workspace_get_translation():
"""
Returns a list of three floats representing the current Workspace
translation vector
"""
(x, y, z) = _pymae.mae_get_workspace_translation()
ret_list = []
ret_list.append(x)
ret_list.append(y)
ret_list.append(z)
return ret_list
[docs]def workspace_get_viewing_volume():
"""
Returns a list of six floats representing the current Workspace
viewing volume
"""
(left, right, top, bottom, near,
far) = _pymae.mae_get_workspace_viewing_volume()
ret_list = []
ret_list.append(left)
ret_list.append(right)
ret_list.append(top)
ret_list.append(bottom)
ret_list.append(near)
ret_list.append(far)
return ret_list
[docs]def workspace_get_view_matrix():
"""
Returns a list of 16 floats representing the current view matrix. Note
that the final column of the matrix is always 0.0 as this doesn't actually
include the translation - for that see workspace_get_translation()
Also note that this matrix is returned in OpenGL order meaning
it is column major notation.
"""
(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43,
m44) = _pymae.mae_get_workspace_view_matrix()
ret_list = []
ret_list.append(m11)
ret_list.append(m12)
ret_list.append(m13)
ret_list.append(m14)
ret_list.append(m21)
ret_list.append(m22)
ret_list.append(m23)
ret_list.append(m24)
ret_list.append(m31)
ret_list.append(m32)
ret_list.append(m33)
ret_list.append(m34)
ret_list.append(m41)
ret_list.append(m42)
ret_list.append(m43)
ret_list.append(m44)
return ret_list
[docs]def workspace_get_view_matrix_inverse():
"""
Returns a list of 16 floats representing the current inverse view matrix.
Note that the final column of the matrix is always 0.0 as this doesn't
actually include the translation - for that see workspace_get_translation()
"""
(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43,
m44) = _pymae.mae_get_workspace_view_matrix_inverse()
ret_list = []
ret_list.append(m11)
ret_list.append(m12)
ret_list.append(m13)
ret_list.append(m14)
ret_list.append(m21)
ret_list.append(m22)
ret_list.append(m23)
ret_list.append(m24)
ret_list.append(m31)
ret_list.append(m32)
ret_list.append(m33)
ret_list.append(m34)
ret_list.append(m41)
ret_list.append(m42)
ret_list.append(m43)
ret_list.append(m44)
return ret_list
[docs]def workspace_get_view_matrix_inverse_no_center():
"""
Returns a list of 16 floats representing the current inverse view matrix
with no center.
Note that the final column of the matrix is always 0.0 as this doesn't
actually include the translation - for that see workspace_get_translation()
"""
(m11, m12, m13, m14,
m21, m22, m23, m24,
m31, m32, m33, m34,
m41, m42, m43, m44) = \
_pymae.mae_get_workspace_view_matrix_inverse_no_center()
ret_list = []
ret_list.append(m11)
ret_list.append(m12)
ret_list.append(m13)
ret_list.append(m14)
ret_list.append(m21)
ret_list.append(m22)
ret_list.append(m23)
ret_list.append(m24)
ret_list.append(m31)
ret_list.append(m32)
ret_list.append(m33)
ret_list.append(m34)
ret_list.append(m41)
ret_list.append(m42)
ret_list.append(m43)
ret_list.append(m44)
return ret_list
[docs]def workspace_get_size_and_scale():
"""
Return a tuple of width, height, scale
width: width of the Workspace drawing area in pixels
height: height of the Workspace drawing area in pixels
scale: ratio of window to world space. The ratio is calculated for the
width and for the height. The the smaller of these is returned (as this is
what Maestro uses).
"""
(width, height, scale) = \
_pymae.mae_get_workspace_size_and_scale()
return (width, height, scale)
[docs]def create_cone(x0, y0, z0, x1, y1, z1, r, g, b, radius, opacity, resolution):
"""
Return the handle of a Maestro cone object using the given data
"""
return _pymae.mae_create_cone(x0, y0, z0, x1, y1, z1, r, g, b, radius,
opacity, resolution)
[docs]def create_cylinder(x0,
y0,
z0,
x1,
y1,
z1,
r,
g,
b,
radius,
opacity,
resolution,
remove_endcaps=False):
"""
Return the handle of a Maestro cylinder object using the given data
"""
return _pymae.mae_create_cylinder(x0, y0, z0, x1, y1, z1, r, g, b, radius,
opacity, resolution, remove_endcaps)
[docs]def create_sphere(x,
y,
z,
r,
g,
b,
radius,
opacity,
resolution,
angle_dep_transparency=False):
"""
Return the handle of a Maestro sphere object using the given data
"""
return _pymae.mae_create_sphere(x, y, z, r, g, b, radius, opacity,
resolution, angle_dep_transparency)
[docs]def create_hemisphere(x,
y,
z,
direction_x,
direction_y,
direction_z,
r,
g,
b,
radius,
opacity,
resolution,
angle_dep_transparency=False):
"""
Return the handle of a Maestro sphere object using the given data
"""
return _pymae.mae_create_hemisphere(x, y, z, direction_x, direction_y,
direction_z, r, g, b, radius, opacity,
resolution, angle_dep_transparency)
[docs]def set_glow_color(handle, r, g, b):
"""
Set glow color on a graphics object.
"""
return _pymae.mae_set_glow_color(handle, r, g, b)
[docs]def set_is_glowing(handle, flag):
"""
Set glowing flag on a graphics object.
"""
return _pymae.mae_set_is_glowing(handle, flag)
[docs]def create_torus(x0, y0, z0, x1, y1, z1, r, g, b, radius, tube_radius, opacity,
u_resolution, v_resolution):
"""
Return the handle of a Maestro torus object using the given data
"""
return _pymae.mae_create_torus(x0, y0, z0, x1, y1, z1, r, g, b, radius,
tube_radius, opacity, u_resolution,
v_resolution)
[docs]def create_model_xyz_axes(cx, cy, cz, lx, ly, lz, r, g, b):
"""
Return the handle of a Maestro xyz axes object using the given data
"""
return _pymae.mae_create_model_xyz_axes(cx, cy, cz, lx, ly, lz, r, g, b)
[docs]def create_polyhedron(vertices, faces, normals, r, g, b, opacity, style):
"""
Return the handle of a Maestro polyhedron object using the given data
"""
# Merge the vertices into a single array
flattened_vertices = []
for v in vertices:
flattened_vertices.extend(v)
# Merge the normals
flattened_normals = []
for n in normals:
flattened_normals.extend(n)
# Merge the faces
flattened_faces = []
for f in faces:
flattened_faces.extend(f)
num_vertices = len(vertices)
num_faces = len(faces)
num_vertices_per_face = len(flattened_faces) // num_faces
return _pymae.mae_create_polyhedron(num_vertices, flattened_vertices,
flattened_normals, num_faces,
num_vertices_per_face, flattened_faces,
r, g, b, opacity, style)
[docs]def set_polyhedron_style(polyhedron, style):
"""
Set the polyhedron style to either LINE or FILL
:param polyhedron: A handle to a Maestro polyhedron, returned from
create_polyhedron()
:param style: Whether to fill the polyhedron in or to leave it as
lines connecting vertices.
:type style: Choice, FILL or LINE
"""
_pymae.mae_set_polyhedron_style(polyhedron, style)
[docs]def create_polygon(vertices, r, g, b, opacity):
"""
Return the handle of a Maestro polygon object using the given data
"""
# Merge the vertices into a single array
flattened_vertices = []
for v in vertices:
flattened_vertices.extend(v)
num_vertices = len(vertices)
return _pymae.mae_create_polygon(num_vertices, flattened_vertices, r, g, b,
opacity)
def _validate_rgb(r, g, b):
"""
Validates the given color. Checks to make sure that r, g, and b and are
in the range 0.0 to 1.0.
:param r: The red component of the color
:type r: float
:param g: The green component of the color
:type g: float
:param b: The blue component of the color
:type b: float
"""
if any(v < 0. or v > 1. for v in (r, g, b)):
raise ValueError(f"rgb must be between 0 and 1. got ({r}, {g}, {b})")
[docs]def create_parallelepiped(parallelepiped, r, g, b, line_width=1.0, style=0):
"""
Return the handle of a Maestro parallelepiped object using the given data
:param parallelepiped: The parallelepiped data to use to create the
Maestro object
:type parallelepiped: maestro_ui.MM_GraphicsParallelepiped
:param r: The red component of the color for the parallelepiped, in the
range 0 to 1.
:type r: float
:param g: The green component of the color for the parallelepiped, in the
range 0 to 1.
:type g: float
:param b: The blue component of the color for the parallelepiped, in the
range 0 to 1.
:type b: float
:param line_width: The line width to use to draw the parallelepiped, > 0.
:type line_width: float
:param style: The line style to use to draw the parallelepiped. 0 is solid,
1 is short dashed lines, and 2 is dashed cylinders
:type style: int
"""
_validate_rgb(r, g, b)
if line_width <= 0.:
raise ValueError(f"line_width must be > 0, not {line_width}")
if style < 0 or style > 2:
raise ValueError(f"style must be between 0 and 2, not {style}")
maestro_hub = maestro_ui.MaestroHub().instance()
return maestro_hub.createParallelepiped(parallelepiped, r, g, b, line_width,
style)
[docs]def create_lines(segments, r, g, b, line_width=1.0, style=0):
"""
Return the handle of a Maestro graphics object containing the given line
segments
:param segments: The line segments to use to create the Maestro object
:type segments: [ maestro_ui.MM_GraphicsLineSegment ]
:param r: The red component of the color for the segments, in the
range 0 to 1.
:type r: float
:param g: The green component of the color for the segments, in the
range 0 to 1.
:type g: float
:param b: The blue component of the color for the segments, in the
range 0 to 1.
:type b: float
:param line_width: The line width to use to draw the segments, > 0.
:type line_width: float
:param style: The line style to use to draw the segments. 0 is solid,
1 is short dashed lines, and 2 is dashed cylinders
:type style: int
"""
_validate_rgb(r, g, b)
if line_width <= 0.:
raise ValueError(f"line_width must be > 0, not {line_width}")
if style < 0 or style > 2:
raise ValueError(f"style must be between 0 and 2, not {style}")
maestro_hub = maestro_ui.MaestroHub().instance()
return maestro_hub.createLineSegments(segments, r, g, b, line_width, style)
[docs]def hide_object(handle):
"""
Hide the 3D graphics object in Maestro with the given handle
"""
_pymae.mae_hide_object(handle)
[docs]def show_object(handle):
"""
Show the 3D graphics object in Maestro with the given handle
"""
_pymae.mae_show_object(handle)
[docs]def remove_object(handle):
"""
Remove the 3D graphics object in Maestro with the given handle
"""
_pymae.mae_remove_object(handle)
[docs]def set_coords(handle, x, y, z):
"""
Set the coordinates (centroid) for the given handle
"""
_pymae.mae_set_coords(handle, x, y, z)
[docs]def set_coords1(handle, x, y, z):
"""
Set the coordinates for the given handle. Used for cylinders and
cones.
"""
_pymae.mae_set_coords1(handle, x, y, z)
[docs]def set_coords2(handle, x, y, z):
"""
Set the other coordinates for the given handle. Used for cylinders and
cones.
"""
_pymae.mae_set_coords2(handle, x, y, z)
[docs]def set_colors(handle, r, g, b, alpha):
"""
Set the colors for the given handle
"""
_pymae.mae_set_colors(handle, r, g, b, alpha)
[docs]def set_radius(handle, radius):
"""
Set the radius for the given handle
"""
_pymae.mae_set_radius(handle, radius)
[docs]def set_material(handle, material):
"""
Set the material for the given handle
"""
_pymae.mae_set_material(handle, material)
[docs]def set_pick_id(handle, id):
"""
Set the picking ID for the given handle.
:param id: Pick id. None disables picking.
:type id: int or NoneType
"""
if id == 0:
# Defend against accidental `for pick_id, obj in enumerate(objects):`
raise ValueError("0 is not a pickable pick ID. "
"Pass None to make the handle unpickable.")
elif id is None:
id = 0
_pymae.mae_set_pick_id(handle, id)
[docs]def set_persistent_name(handle, name):
"""
Set the persistent name for the given handle.
"""
_pymae.mae_set_persistent_name(handle, name)
[docs]def set_entry_id(handle, id):
_pymae.mae_set_entry_id(handle, id)
[docs]def set_pick_category(handle, pick_category):
"""
Set the picking category for the given handle. pick_category should be a
string containing the category to pick (e.g. <script name>_spheres)
"""
_pymae.mae_set_pick_category(handle, pick_category)
[docs]def start_picking(help, callback_func, pick_category):
"""
Start picking items in the picking category.
:param pick_category: A picking category defined in mm_graphicspick.cxx
string_pick_map.
:type pick_category: str
"""
global _pick_callback
_pick_callback = callback_func
func = "schrodinger.maestro.maestro._invoke_pick_callback"
_pymae.mae_start_picking(func, help, pick_category)
[docs]def add_pick_category_callback(callback_func, pick_category):
"""
Add pick category callback
:param callback_func: A callback function
:type pick_category: function
:param pick_category: A picking category defined in mm_graphicspick.cxx
:type pick_category: str
"""
func_str = callback_func.__module__ + '.' + callback_func.__name__
_pymae.mae_add_pick_category_callback(func_str, pick_category)
[docs]def remove_pick_category_callback(pick_category):
"""
Remove pick category callback
:param pick_category: A picking category defined in mm_graphicspick.cxx
:type pick_category: str
"""
_pymae.mae_remove_pick_category_callback(pick_category)
[docs]def clear_pick_category_callbacks():
"""
Clear all pick category callbacks
"""
_pymae.mae_clear_pick_category_callbacks()
[docs]def stop_picking():
"""
Stop picking
"""
_pymae.mae_stop_picking()
[docs]def get_ligand_asl():
"""
:deprecated maestro now uses the ligand keyword
"""
warnings.warn("Maestro now uses the raw 'ligand' for identifying ligands.",
DeprecationWarning,
stacklevel=2)
return "ligand"
[docs]def set_project_command(name, command):
"""
Sets a command to add to the project-open command script when the project
is closed, or when a saved project scene is re-applied.
:param name is typically the name of the invoking script
:param command is a single Maestro command
"""
_pymae.mae_set_project_command(name, command)
[docs]def append_project_command(name, command):
"""
Appends the given command to the set of commands to add to the
project-open command script when the project is closed or project scene
is re-applied.
:param name is typically the name of the invoking script
:param command is a single Maestro command
"""
_pymae.mae_append_project_command(name, command)
[docs]def delete_project_commands(name):
"""
Deletes all of the project commands associated with the given name.
:param name is typically the name of the invoking script
"""
_pymae.mae_delete_project_commands(name)
[docs]def get_maestro_toplevel_window():
"""
Returns the maestro top level window from the list of available top level
windows in the maestro application. The exact window returned depends
on whether docking is enabled or not.
"""
maestro_docking_enabled = get_command_option("prefer", "dockingpanels")
maestro_dock_location = get_command_option("prefer", "docklocation")
top_level_widgets = QtWidgets.QApplication.instance().topLevelWidgets()
maestro_toplevel = None
for w in top_level_widgets:
# main_maestro_window is the internal name of the main maestro
# toplevel window
if maestro_docking_enabled == "True" and \
maestro_dock_location == "mainwindow" and \
w.objectName() == "main_maestro_window":
# Dock into main window in maestro
maestro_toplevel = w
break
elif maestro_docking_enabled == "True" and \
maestro_dock_location == "floating" and \
w.objectName() == "dock_window":
# Dock into free-floating window
maestro_toplevel = w
break
elif maestro_docking_enabled == "False" and \
w.objectName() == "main_maestro_window":
# No docking - we expect client to create
# free-standing window
maestro_toplevel = w
break
if not maestro_toplevel:
return None
return maestro_toplevel
[docs]def set_docking_configuration(w,
dockable,
dock_area=QtCore.Qt.RightDockWidgetArea):
"""
:type w: QWidget
:param w: Widget for which we need to set the docking configuration
:type dockable: bool
:param dockable: Whether or not this widget is dockable
:type dock_area: Qt.QFlags
:param dock_area: Area where the widget is to be docked (if
it is dockable)
This routine takes the specified widget and configures the
widget docking settings based on Maestro's preferences and
whether the widget itself is dockable.
"""
if dockable:
toplevel = get_maestro_toplevel_window()
if toplevel:
toplevel.addDockWidget(dock_area, w)
panel_name = w.objectName()
tabify_docking_window(str(panel_name))
else:
# Panel is not dockable
# Dock into normal window outside of maestro's main window
pass
[docs]def raise_main_window():
"""
Raises the Maesto main window to the top of the parent widget's stack
This function is used mainly by the python scripts which operates directly
in Maestro workspace, so that maestro mainwindow is visible and stays on the
top of the widget stack
At present, this function is uncallable. Fix it, define
getMaestroTopLevelWindow and remove the first line of the function
"""
maemainwindow = get_maestro_toplevel_window()
if maemainwindow:
if maemainwindow.isMinimized():
maemainwindow.show()
maemainwindow.showNormal()
else:
maemainwindow.raise_()
# ****************************************
[docs]def show_docking_panels_window():
"""
Show the Docking Panels window. There can be cases where we've
added widgets to this panel, but it isn't visible and we want it to
be visible.
"""
_pymae.mae_show_docking_panels_window()
# ****************************************
[docs]def sendemail(from_addr,
to_addr,
server,
port,
security,
message,
passwd="",
debug=False):
"""
Try to send an email.
:param from_addr: The From Email address
:type from_addr: str
:param to_addr: The address to which we are sending
:type to_addr: str
:param server: SMTP server. Only supports SMTP.
:type server: str
:param port: Port to which we will attach
:type port: int
:param security: What type of security if any. Valid values are:
none, starttls, ssltls.
:type security: str
:param message: Body of the message to send.
:type message: str
:param passwd: Password. If blank, then assumes no login is needed.
:type passwd: str
:param debug: If True, debugging is printed.
:type debug: bool
:return nothing
"""
if debug:
print("EMail: from=", from_addr)
print("EMail: to=", to_addr)
print("EMail: server=", server)
print("EMail: pass=", passwd)
print("EMail: port=", port)
print("EMail: message=", message)
print("EMail: security=", security)
try:
import smtplib
except:
print("Error importing smtplib module. Could not send email.")
return
smtpserver = None
if security == "starttls":
if debug:
print("EMail: Creating smtplib instance (starttls)")
smtpserver = smtplib.SMTP(server, port, timeout=10)
if debug:
print("Email: connecting")
smtpserver.ehlo()
smtpserver.starttls()
smtpserver.ehlo()
elif security == "ssltls":
if debug:
print("EMail: Creating smtplib instance (ssltls)")
smtpserver = smtplib.SMTP_SSL(server, port, timeout=10)
if debug:
print("Email: connecting")
smtpserver.ehlo()
elif security == "none":
if debug:
print("EMail: Creating smtplib instance (security: none)")
smtpserver = smtplib.SMTP(server, port, timeout=10)
if debug:
print("Email: connecting")
smtpserver.helo()
else:
if debug:
print("EMail: %s is not a valid security value" % security)
return
if (len(passwd) > 0):
if debug:
print("Email: Logging in")
smtpserver.login(from_addr, passwd)
else:
if debug:
print("EMail: Assuming no need to log in")
header = 'To:' + to_addr + '\n' + 'From: ' + from_addr + '\n' + \
'Subject: Maestro Automated Backup \n'
msg = header + '\n' + message + '\n\n'
if debug:
print("EMail: Sending email")
smtpserver.sendmail(from_addr, to_addr, msg)
if debug:
print('EMail: sent!')
smtpserver.quit()
if debug:
print('EMail: quit session')
[docs]def job_started(job_id):
"""
Notify Maestro job monitoring that a job has been started.
:param job_id: The job id returned from mmjob when the job was launched
:type job_id: str
"""
_pymaecxx.mae_job_started(job_id)
[docs]def tabify_docking_window(panel_name):
"""
:param panel_name: Panel which needs to be docked under tab if there are
existing docked panels.
"""
_pymaecxx.mae_tabify_docking_window(panel_name)
[docs]def get_main_window():
"""
Gets main window instance
:rtype: QMainWindow
:returns: instance for Maestro's main window
:raises: Exception if maestro main window not found
"""
global _main_window
if _main_window:
return _main_window
widget = get_widget_from_object_name("main_maestro_window")
if widget is not None:
_main_window = widget
return _main_window
raise Exception("Could not find main maestro window")
def _ignore_callback(callback_type, callback_func):
"""
Decorator to manage disconnecting a maestro callback, (if currently
connected), and reconnecting after the function returns *or* excepts.
This will only work with class-methods, not instance methods. Use the
context manager option for instance methods.
:param callback_type: What callback you're working with, these are the keys
to the _callbacks dict.
:type callback_type: str
:param callback_func: The method we want to disconnect (if connected)
from the given callback
:type callback_func: A function
"""
def wrap(wrapped_func):
def dec(*args, **kwargs):
"""
This is the actual functionality encapsulating the decorated method
"""
# Set the wait cursor, run the method, then restore the cursor
func_registered = is_function_registered(callback_type,
callback_func)
if func_registered:
_callbacks[callback_type].remove(callback_func)
try:
response = wrapped_func(*args, **kwargs)
finally:
if func_registered:
_callbacks[callback_type].add(callback_func)
return response
return dec
return wrap
#The following convenience wrappers of _ignore_callback could be done via
#function generation, but would be harder for people to read and discover.
[docs]def ignore_workspace_changed(callback_func):
"""
A decorator for ignoring workspace changes. Note: This will only work to
ignore class-methods and global methods. Instance-methods will need to
use the context manager IgnoreWorkspaceChanged.
Example::
@maestro.ignore_workspace_changed(MyClass.my_ws_changed):
def my_func():
#Any WS changes inside this function won't trigger my_ws_changed,
#even if it's been registered with workspace_changed_function_add
"""
return _ignore_callback('workspace_changed', callback_func)
[docs]def ignore_project_close(callback_func):
""" See ignore_workspace_changed for docs/example """
return _ignore_callback('project_close', callback_func)
[docs]def ignore_project_update(callback_func):
""" See ignore_workspace_changed for docs/example """
return _ignore_callback('project_update', callback_func)
class _IgnoreCallback:
"""
This is a context manager to ignore maestro callbacks inside a with: block
"""
def __init__(self, callback_type, callback_func):
"""
:param callback_type: What callback you're working with, these are the
keys to the _callbacks dict.
:type callback_type: str
:param callback_func: The method we want to disconnect (if connected)
from the given callback
:type callback_func: A function
"""
self.callback_type = callback_type
self.callback_func = callback_func
def __enter__(self):
self.func_registered = is_function_registered(self.callback_type,
self.callback_func)
if self.func_registered:
_callbacks[self.callback_type].remove(self.callback_func)
def __exit__(self, exc_type, exc_value, exc_tb):
if is_function_registered(self.callback_type, self.callback_func):
return #re-registered elsewhere
if self.func_registered:
_callbacks[self.callback_type].add(self.callback_func)
[docs]class IgnoreWorkspaceChanged(_IgnoreCallback):
"""
Context manager to ignore Workspace Changed callbacks.
Example::
maestro.workspace_changed_function_add(self.my_ws_changed)
with maestro.IgnoreWorkspaceChanged(self.my_ws_changed):
#WS changes in this block won't trigger self.my_ws_changed
#Outside of the block WS changes will again call self.my_ws_changed
"""
[docs] def __init__(self, callback_func):
""" See _IgnoreCallback.__init__ """
_IgnoreCallback.__init__(self, 'workspace_changed', callback_func)
[docs]class IgnoreProjectClose(_IgnoreCallback):
""" See IgnoreWorkspaceChanged for docs/example. """
[docs] def __init__(self, callback_func):
_IgnoreCallback.__init__(self, 'project_close', callback_func)
[docs]class IgnoreProjectUpdate(_IgnoreCallback):
""" See IgnoreWorkspaceChanged for docs/example. """
[docs] def __init__(self, callback_func):
_IgnoreCallback.__init__(self, 'project_update', callback_func)
[docs]def get_growth_space():
"""
Returns the growth space from Maestro. This function assumes that the
growth space has already been created through
maestro.command("creategrowthspace")
This growth space will not include any of the solvent-accessible spheres.
See get_solvent_accessible_growth_space()
:return: A list of spheres, with each sphere being a list: [x, y, z, radius]
:rtype: list
"""
return _pymaecxx.mae_get_growth_space()
[docs]def get_solvent_accessible_growth_space():
"""
Returns the solvent-accessible growth space from Maestro. This function
assumes that the growth space has already been created through
maestro.command("creategrowthspace")
This growth space will not include any spheres which are not
solvent-accessible.
See get_growth_space()
:return: A list of spheres, with each sphere being a list: [x, y, z, radius]
:rtype: list
"""
return _pymaecxx.mae_get_solvent_accessible_growth_space()
[docs]def get_growth_space_sphere(sphere_id):
"""
Returns the growth space sphere corresponding to the given id from Maestro.
This function assumes that the growth space has already been created through
maestro.command("creategrowthspace")
:param sphere_id: The pick ID returned from maestro.start_picking()
:type width: int
:return: The sphere as a list [x, y, z, radius]
:rtype: list
"""
if sphere_id <= 0:
raise ValueError("sphere_id must be > 0")
return _pymaecxx.mae_get_growth_space_sphere(sphere_id)
[docs]def 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 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