"""
Module for placing markers in the Workspace.
"""
import _pymaecxx
import schrodinger
from schrodinger.structutils import color
maestro = schrodinger.get_maestro()
[docs]class Icons(object):
"""
Constants for marker icons
"""
NONE = _pymaecxx.MM_GRAPHICS_ICON_NONE
LOCK = _pymaecxx.MM_GRAPHICS_ICON_LOCK
SPRING = _pymaecxx.MM_GRAPHICS_ICON_SPRING
EYE = _pymaecxx.MM_GRAPHICS_ICON_EYE
EQUALS = _pymaecxx.MM_GRAPHICS_ICON_EQUALS
FRAGMARK = _pymaecxx.MM_GRAPHICS_ICON_FRAGMARK
CHECKMARK = _pymaecxx.MM_GRAPHICS_ICON_CHECKMARK
DIAMOND = _pymaecxx.MM_GRAPHICS_ICON_DIAMOND
RSI = _pymaecxx.MM_GRAPHICS_ICON_RSI
TRANSROT = _pymaecxx.MM_GRAPHICS_ICON_TRANSROT
TORSIONROTATE = _pymaecxx.MM_GRAPHICS_ICON_TORSIONROTATE
CHECK = _pymaecxx.MM_GRAPHICS_ICON_CHECK
SCISSORS = _pymaecxx.MM_GRAPHICS_ICON_SCISSORS
BREAK = _pymaecxx.MM_GRAPHICS_ICON_BREAK
DDRIVE = _pymaecxx.MM_GRAPHICS_ICON_DDRIVE
LONEPAIR = _pymaecxx.MM_GRAPHICS_ICON_LONEPAIR
SMAGGLASS = _pymaecxx.MM_GRAPHICS_ICON_SMAGGLASS
GMAGGLASS = _pymaecxx.MM_GRAPHICS_ICON_GMAGGLASS
MAGGLASS = _pymaecxx.MM_GRAPHICS_ICON_MAGGLASS
WMAGGLASS = _pymaecxx.MM_GRAPHICS_ICON_WMAGGLASS
IMAGGLASS = _pymaecxx.MM_GRAPHICS_ICON_IMAGGLASS
VMAGGLASS = _pymaecxx.MM_GRAPHICS_ICON_VMAGGLASS
EMAGGLASS = _pymaecxx.MM_GRAPHICS_ICON_EMAGGLASS
ARROWDOWN = _pymaecxx.MM_GRAPHICS_ICON_ARROWDOWN
SCANATOM = _pymaecxx.MM_GRAPHICS_ICON_SCANATOM
SCANDIST = _pymaecxx.MM_GRAPHICS_ICON_SCANDIST
SCANANGLE = _pymaecxx.MM_GRAPHICS_ICON_SCANANGLE
[docs]class Marker(object):
"""
Class to manipulate a set of markers in the Workspace by ASLs. The only
parameter that can be controlled is the color of the marker.
This marker is useful for showing a picked ligand or residue, for example.
"""
# A count of how many names we have generated. Used to ensure that the
# generated names are unique.
_name_count = 1
[docs] def __init__(self, name=None, asl="NOT all", color=(1, 1, 1)):
"""
Create a Marker object
:type name: str
:param name: The name of the marker group. Used for unique
identification of the marker within Maestro. By default
a unique string identifier is generated.
:type asl: str
:param asl: The ASL defining which atoms should be marked. By default
no atoms are marked.
:type color: tuple of 3 floats
:param red: The amount of red, green, and blue to use, each ranging
from 0.0 to 1.0. Default is white (1, 1, 1).
"""
if name is None:
# Generate a unique identifier for this Marker instance. Pad the
# count with zeroes to reduce the chance of the user picking the
# same name for their own marker.
name = "Marker-%08i" % self._name_count
self.__class__._name_count += 1
self._name = name
self.setColor(color)
if maestro:
maestro.command('markers %s %s %s' % (self._color, self._name, asl))
@property
def name(self):
"""
:return: Marker name
:rtype: str
"""
return self._name
[docs] def hide(self):
"""
Hide the markers
"""
if maestro:
maestro.command('hidemarkers %s' % self._name)
[docs] def show(self, asl=None):
"""
Show the markers. If a new ASL is specified, then the previous
markers get cleared and the new atoms are marked.
:type asl: str
:param asl: The ASL defining which atoms should be marked. This
replaces any previous ASL marked by this Marker instance.
"""
if maestro:
if asl:
self.hide()
maestro.command('markers %s %s %s' %
(self._color, self._name, asl))
maestro.command('showmarkers ' + self._name)
[docs] def setVisible(self, show):
"""
Show the marker if <show> is True; hide otherwise. Useful for hooking
up to a slot of a QCheckBox.
"""
if show:
self.show()
else:
self.hide()
[docs] def setAsl(self, asl):
"""
Update the ASL of this marker to the given string, without affecting
the visibility of it.
"""
maestro.command('markers %s %s %s' % (self._color, self._name, asl))
[docs] def setColor(self, color):
"""
:param color: The amount of red, green, and blue to use, each ranging
from 0.0 to 1.0.
:type color: tuple(float, float, float)
"""
red, green, blue = color
if any([x < 0.0 or x > 1.0 for x in color]):
raise ValueError("Color values must be between 0 and 1")
self._color = f'r={red:.2f} g={green:.2f} b={blue:.2f}'
class _BaseMarker(object):
"""
Base class for the marker classes. This class cannot be instantiated
directly.
"""
SETTINGS_VARS = {"color", "icon", "text", "alt_color", "highlight"}
def __init__(self):
# The variable declarations here are for reference purposes only (both
# for human readability and static code analyzers). This class cannot
# be instantiated directly.
self._color = None
self._icon = None
self._text = None
self._alt_color = None
self._atoms = None
self._highlight = None
self._handle = None
raise NotImplementedError
def _getEntryAtomNumbers(self, atoms):
"""
Retrieve entry ids and atom numbers by entry for a list of atoms
:param atoms: A list of atoms to retrieve data for
:type atoms: list(schrodinger.structure._StructureAtom)
:return: A list of tuples, where each tuple contains the entry id and
number-by-entry of a single atom.
:rtype: list
"""
atom_data = []
for cur_atom in atoms:
eid = cur_atom.entry_id
num_by_entry = cur_atom.number_by_entry
cur_atom_data = (eid, num_by_entry)
atom_data.append(cur_atom_data)
return atom_data
def _createMarker(self):
"""
Create a marker using the criteria currently set in instance variables
"""
raise NotImplementedError
def _getWorkspaceAtomNumbers(self):
"""
Get the workspace atom numbers for the atoms described in self._atoms
:return: A list of atom numbers relative to the workspace structure
:rtype: list
:raise NotInWorkspaceError: If an atom described in self._atoms is not
currently in the workspace.
"""
ws_atom_nums = []
ws_struc = maestro.workspace_get(copy=False)
for (eid, num_by_entry) in self._atoms:
for ws_atom in ws_struc.atom:
if (ws_atom.entry_id == eid and
ws_atom.number_by_entry == num_by_entry):
ws_atom_nums.append(ws_atom.index)
break
else:
err = ("Atom not found in workspace: entry %s, "
"number_by_entry %i" % (eid, num_by_entry))
raise NotInWorkspaceError(err)
return ws_atom_nums
def hide(self):
"""
Hide this marker.
"""
if self._handle is not None:
_pymaecxx.mae_remove_object(self._handle)
self._handle = None
def show(self):
"""
Show this marker.
"""
if self._handle is None:
self._createMarker()
def setVisible(self, show):
"""
If "show" is True, show the marker, otherwise hide it.
"""
if show:
self.show()
else:
self.hide()
def _colorToRgb(self, incolor):
"""
Given a color of any accepted format, return an RGB tuple (range 0-1).
If None is specified, color white is returned.
:return: Tuple of RGB values (range 0-1)
:rtype: tuple
"""
if isinstance(incolor, (int, str)):
outcolor = color.Color(incolor).rgb_float
return tuple(outcolor)
elif incolor is None:
return (1.0, 1.0, 1.0) # white
elif isinstance(incolor, color.Color):
return tuple(incolor.rgb_float)
elif isinstance(incolor, (tuple, list)):
r, g, b = incolor
if not (0 <= r <= 1 and 0 <= g <= 1 and 0 <= b <= 1):
raise ValueError("RGB values must be between 0.0 and 1.0")
return (r, g, b)
else:
raise TypeError("Unrecognized color")
def _altColorToRgb(self, alt_color):
"""
Given a color of any accepted format, return an RGB tuple (range 0-1).
If None is specified, None is returned.
:return: Tuple of RGB values (range 0-1) or None if `alt_color` is None
:rtype: tuple or None
"""
if alt_color is None:
return None
else:
return self._colorToRgb(alt_color)
def _getAltColor(self):
"""
Get the alternate color.
:return: List of RGB values (range 0-1)
:rtype: list
"""
if self._alt_color is None:
return self._color
else:
return self._alt_color
def setColor(self, marker_color):
"""
Change the marker and icon color
:param marker_color: The color of the marker. May be an RGB tuple,
color name, color index, or `schrodinger.structutils.color` instance.
If not given, white will be used.
:type marker_color: tuple, str, int, or `schrodinger.structutils.color`
"""
self._color = self._colorToRgb(marker_color)
self.update()
def setIcon(self, icon):
"""
Change the marker icon
:param icon: The icon to draw next to the marker. Should be one the
Icons.* constants. If not given, no icon will be drawn.
:type icon: int
"""
self._icon = icon
self.update()
def setText(self, text):
"""
Change the marker text
:param text: The text to display next to the marker. If not given, no
text will be displayed.
:type text: str
"""
self._text = text
self.update()
def setAltColor(self, alt_color):
"""
Change the text color
:param alt_color: The color of the text. May be an RGB tuple,
color name, color index, or `schrodinger.structutils.color` instance.
If not given, the marker color will be used.
:type alt_color: tuple, str, int, or `schrodinger.structutils.color`
"""
self._alt_color = self._altColorToRgb(alt_color)
self.update()
def setHighlight(self, highlight):
"""
Change whether the marker is highlighted. A highlighted marker is
indicated with thicker lines and is colored using `alt_color` instead
of `color`.
:param highlight: Whether the marker should be highlighted.
:type highlight: bool
"""
self._highlight = highlight
self.update()
def update(self):
"""
Update the marker in response to the workspace contents changing. If
any marked atom is not in the workspace, the marker will be deleted.
"""
self.hide()
try:
self._createMarker()
except NotInWorkspaceError:
self._handle = None
def getSettings(self):
"""
Get the current marker settings.
:return: A dictionary of the marker settings. The keys of this
dictionary will be `SETTING_VARS`.
:rtype: dict
"""
return {name: getattr(self, "_" + name) for name in self.SETTINGS_VARS}
def applySettings(self, settings):
"""
Apply the specified marker settings
:param settings: A dictionary of marker settings. The keys of this
dictionary must appear in `SETTINGS_VARS`. Note that not all keys need
to be specified.
:type settings: dict
"""
if not self.SETTINGS_VARS.issuperset(list(settings)):
raise ValueError("Unrecognized marker setting key")
for name, val in settings.items():
if name == "color":
val = self._colorToRgb(val)
elif name == "alt_color":
val = self._altColorToRgb(val)
setattr(self, "_" + name, val)
self.update()
@classmethod
def defaultSettings(cls):
"""
Get the default marker settings
:return: A dictionary of marker settings. The keys of this dictionary
will be `SETTING_VARS`.
:rtype: dict
"""
settings = dict.fromkeys(cls.SETTINGS_VARS)
settings["icon"] = Icons.NONE
settings["highlight"] = False
if "text" in settings:
settings["text"] = ""
return settings
[docs]class AtomMarker(_BaseMarker):
"""
Class for marking a single atom.
"""
SETTINGS_VARS = _BaseMarker.SETTINGS_VARS - {"text"}
[docs] def __init__(self,
atom,
marker_color=None,
icon=Icons.NONE,
alt_color=None,
highlight=False):
"""
:param atom: The atom to mark
:type atom: `schrodinger.structure._StructureAtom`
:param marker_color: The color of the marker. May be an RGB tuple,
color name, color index, or `schrodinger.structutils.color` instance.
If not given, white will be used.
:type marker_color: tuple, str, int, or `schrodinger.structutils.color`
:param icon: The icon to draw next to the marker. Should be one the
Icons.* constants. If not given, no icon will be drawn.
:type icon: int
:param alt_color: The alternate marker color. This color is always used
for text, and is used for the marker and icon when `highlight` is True.
If not given, `color` will be used.
:type alt_color: tuple, str, int, or `schrodinger.structutils.color`
:param highlight: Whether the marker should be highlighted. A
highlighted marker is indicated with thicker lines and is colored using
`alt_color` instead of `color`.
:type highlight: bool
"""
self._atoms = self._getEntryAtomNumbers([atom])
self._color = self._colorToRgb(marker_color)
self._alt_color = self._altColorToRgb(alt_color)
self._icon = icon
self._highlight = highlight
self._handle = None
self.update()
def _createMarker(self):
atoms = self._getWorkspaceAtomNumbers()
r, g, b = self._color
tr, tg, tb = self._getAltColor()
self._handle = _pymaecxx.mae_create_atom_marker(atoms[0], r, g, b, tr,
tg, tb, self._highlight,
self._icon)
[docs] def setText(self, text):
raise RuntimeError("Text cannot be set for atom markers")
[docs]class PairMarker(_BaseMarker):
"""
Class for marking two atoms. Useful for marking bonds and distances.
"""
[docs] def __init__(self,
atom1,
atom2,
marker_color=None,
icon=Icons.NONE,
text="",
alt_color=None,
highlight=False):
"""
:param atom1: The first atom to mark
:type atom1: `schrodinger.structure._StructureAtom`
:param atom2: The second atom to mark
:type atom2: `schrodinger.structure._StructureAtom`
:param marker_color: The color of the marker. May be an RGB tuple,
color name, color index, or `schrodinger.structutils.color` instance.
If not given, white will be used.
:type marker_color: tuple, str, int, or `schrodinger.structutils.color`
:param icon: The icon to draw next to the marker. Should be one the
Icons.* constants. If not given, no icon will be drawn.
:type icon: int
:param text: The text to display next to the marker. If not given, no
text will be displayed.
:type text: str
:param alt_color: The alternate marker color. This color is always used
for text, and is used for the marker and icon when `highlight` is True.
If not given, `color` will be used.
:type alt_color: tuple, str, int, or `schrodinger.structutils.color`
:param highlight: Whether the marker should be highlighted. A
highlighted marker is indicated with thicker lines and is colored using
`alt_color` instead of `color`.
:type highlight: bool
:note: Either an icon or text may be displayed on a marker, but not
both. If both are given, only the text will be shown.
"""
self._atoms = self._getEntryAtomNumbers([atom1, atom2])
self._color = self._colorToRgb(marker_color)
self._icon = icon
self._text = text
self._alt_color = self._altColorToRgb(alt_color)
self._highlight = highlight
self._handle = None
self.update()
def _createMarker(self):
atoms = self._getWorkspaceAtomNumbers()
r, g, b = self._color
tr, tg, tb = self._getAltColor()
self._handle = _pymaecxx.mae_create_atom_pair_marker(
atoms[0], atoms[1], r, g, b, tr, tg, tb, self._highlight,
self._text, self._icon)
[docs]class TripleMarker(_BaseMarker):
"""
Class for marking three atoms. Useful for marking bond angles.
"""
[docs] def __init__(self,
atom1,
atom2,
atom3,
marker_color=None,
icon=Icons.NONE,
text="",
alt_color=None,
highlight=False):
"""
:param atom1: The first atom to mark
:type atom1: `schrodinger.structure._StructureAtom`
:param atom2: The second atom to mark
:type atom2: `schrodinger.structure._StructureAtom`
:param atom3: The third atom to mark
:type atom3: `schrodinger.structure._StructureAtom`
:param marker_color: The color of the marker. May be an RGB tuple,
color name, color index, or `schrodinger.structutils.color` instance.
If not given, white will be used.
:type marker_color: tuple, str, int, or `schrodinger.structutils.color`
:param icon: The icon to draw next to the marker. Should be one the
Icons.* constants. If not given, no icon will be drawn.
:type icon: int
:param text: The text to display next to the marker. If not given, no
text will be displayed.
:type text: str
:param alt_color: The alternate marker color. This color is always used
for text, and is used for the marker and icon when `highlight` is True.
If not given, `color` will be used.
:type alt_color: tuple, str, int, or `schrodinger.structutils.color`
:param highlight: Whether the marker should be highlighted. A
highlighted marker is indicated with thicker lines and is colored using
`alt_color` instead of `color`.
:type highlight: bool
:note: Either an icon or text may be displayed on a marker, but not
both. If both are given, only the text will be shown.
"""
self._atoms = self._getEntryAtomNumbers([atom1, atom2, atom3])
self._color = self._colorToRgb(marker_color)
self._icon = icon
self._text = text
self._alt_color = self._altColorToRgb(alt_color)
self._highlight = highlight
self._handle = None
self.update()
def _createMarker(self):
atoms = self._getWorkspaceAtomNumbers()
r, g, b = self._color
tr, tg, tb = self._getAltColor()
self._handle = _pymaecxx.mae_create_atom_triple_marker(
atoms[0], atoms[1], atoms[2], r, g, b, tr, tg, tb, self._highlight,
self._text, self._icon)
[docs]class QuadMarker(_BaseMarker):
"""
Class for marking four atoms. Useful for marking dihedral angles.
"""
[docs] def __init__(self,
atom1,
atom2,
atom3,
atom4,
marker_color=None,
icon=Icons.NONE,
text="",
alt_color=None,
highlight=False):
"""
:param atom1: The first atom to mark
:type atom1: `schrodinger.structure._StructureAtom`
:param atom2: The second atom to mark
:type atom2: `schrodinger.structure._StructureAtom`
:param atom3: The third atom to mark
:type atom3: `schrodinger.structure._StructureAtom`
:param atom3: The fourth atom to mark
:type atom3: `schrodinger.structure._StructureAtom`
:param marker_color: The color of the marker. May be an RGB tuple,
color name, color index, or `schrodinger.structutils.color` instance.
If not given, white will be used.
:type marker_color: tuple, str, int, or `schrodinger.structutils.color`
:param icon: The icon to draw next to the marker. Should be one the
Icons.* constants. If not given, no icon will be drawn.
:type icon: int
:param text: The text to display next to the marker. If not given, no
text will be displayed.
:type text: str
:param alt_color: The alternate marker color. This color is always used
for text, and is used for the marker and icon when `highlight` is True.
If not given, `color` will be used.
:type alt_color: tuple, str, int, or `schrodinger.structutils.color`
:param highlight: Whether the marker should be highlighted. A
highlighted marker is indicated with thicker lines and is colored using
`alt_color` instead of `color`.
:type highlight: bool
:note: Either an icon or text may be displayed on a marker, but not
both. If both are given, only the text will be shown.
"""
self._atoms = self._getEntryAtomNumbers([atom1, atom2, atom3, atom4])
self._color = self._colorToRgb(marker_color)
self._icon = icon
self._text = text
self._alt_color = self._altColorToRgb(alt_color)
self._highlight = highlight
self._handle = None
self.update()
def _createMarker(self):
atoms = self._getWorkspaceAtomNumbers()
r, g, b = self._color
tr, tg, tb = self._getAltColor()
self._handle = _pymaecxx.mae_create_atom_quad_marker(
atoms[0], atoms[1], atoms[2], atoms[3], r, g, b, tr, tg, tb,
self._highlight, self._text, self._icon)
[docs]class LineMarker(_BaseMarker):
"""
Class for marking two arbitrary points. Useful for marking lines between
two graphics3d objects.
"""
[docs] def __init__(self,
coord1,
entry_id1,
coord2,
entry_id2,
marker_color=None,
alt_color=None,
line_width=1):
"""
:param coord1: The first coordinate to mark
:type coord1: List of int
:param coord1: The second coordinate to mark
:type coord1: List of int
:param entry_id1: Entry ID the first coordinate is associated with.
This is to simplify when the line marker should be
shown and hidden.
:type entry_id1: int
:param entry_id2: Entry ID the second coordinate is associated with.
:type entry_id2: int
:param marker_color: The color of the marker. May be an RGB tuple,
color name, color index, or `schrodinger.structutils.color` instance.
If not given, white will be used.
:type marker_color: tuple, str, int, or `schrodinger.structutils.color`
:param alt_color: The alternate marker color. This color is always used
for text, and is used for the marker and icon when `highlight` is True.
If not given, `color` will be used.
:type alt_color: tuple, str, int, or `schrodinger.structutils.color`
:param line_width: Width of the line
:type line_width: int
"""
self._color = self._colorToRgb(marker_color)
self._alt_color = self._altColorToRgb(alt_color)
self._line_width = line_width
self._coord1 = coord1
self._coord2 = coord2
self._entry_ids = {str(entry_id1), str(entry_id2)}
self._handle = None
self.update()
def _createMarker(self):
"""
Create the line marker according to given options.
"""
r, g, b = self._color
tr, tg, tb = self._getAltColor()
x1, y1, z1 = self._coord1
x2, y2, z2 = self._coord2
self._handle = _pymaecxx.mae_create_line_marker(x1, y1, z1, x2, y2, z2,
r, g, b, tr, tg, tb,
self._line_width)
[docs] def update(self):
"""
See parent class documentation.
Update the marker in response to the workspace contents changing. If
either one of the entries the marker vertices belong to is no longer
included in workspace, the marker will be deleted.
"""
self.hide()
workspace_eids = maestro.get_included_entry_ids()
if workspace_eids.issuperset(self._entry_ids):
self._createMarker()
else:
self._handle = None
[docs]class NotInWorkspaceError(Exception):
"""
An exception when trying to display a marker covering atoms that are not
currently in the workspace.
"""
# This class intentionally left blank