"""
Maestro 3D hemispheres.
The hemisphere module allows creation of hemispheres. Clients add hemispheres
to Group instances, and Maesto draws them.
Control over the center, radius, color, resolution and opacity of a hemisphere
are provided. See the Hemisphere class for more info.
To add any number of hemispheres, create the Hemisphere instance and add it to
a Group instance. Hemispheres can be shown or hidden through the Group instance.
Copyright Schrodinger, LLC. All rights reserved.
"""
import numpy
import schrodinger
from . import common
from .common import OPACITY_MAX
from .common import OPACITY_MIN
from .common import TRANSPARENCY_MAX # noqa: F401
from .common import TRANSPARENCY_MIN # noqa: F401
from .common import Group # noqa: F401
maestro = schrodinger.get_maestro()
RESOLUTION_MIN = 4
RESOLUTION_MAX = 50
RESOLUTION_DEFAULT = 16
TRANSPARENCY_DEFAULT = 0.0 # for backwards-compatibility
OPACITY_DEFAULT = 1.0
# Constants used to calculate bounding box:
BOUNDING_BOX_INIT_VALUE = 100000000.0
#############################################################################
# CLASSES #
#############################################################################
[docs]class MaestroHemisphere(common._MaestroPrimitiveMixin, common.Primitive):
"""
Class to create a 3D hemisphere in Maestro. This hemisphere will be drawn
at the appropriate time in Maestro to interact properly with transparent
objects.
Hemispheres should be added to a Group and drawing done via the Group. See
Group documentation.
API Example::
import schrodinger.maestro.maestro as maestro
import schrodinger.graphics3d.common as common
import schrodinger.graphics3d.hemisphere as hemisphere
hemisphere_grp = common.Group()
st = maestro.workspace_get()
for at in st.atom:
hemisph = hemisphere.MaestroHemisphere(
center=(at.x, at.y, at.z)
radius=1.0,
resolution=20,
opacity=0.8,
color='red'
)
# Add the primative to the container.
hemisphere_grp.add(hemisph)
# Unlike Hemisphere MaestroHemisphere simply needs to be shown to be
# drawn.
# No special callback like there is for Hemispheres is needed.
hemisphere_grp.show()
# Hide the markers.
hemisphere_grp.hide()
# Remove the markers
hemisphere_grp.clear()
"""
[docs] def __init__(self,
center=None,
color=None,
direction=(0.0, 0.0, 1.0),
radius=None,
opacity=OPACITY_DEFAULT,
resolution=RESOLUTION_DEFAULT,
angle_dep_transparency=False):
"""
Constructor requires:
:param center: List of 3 Angstrom values indicating the center
coordinate of the icosahedron.
:type center: list(float, float, float)
color: One of:
Color object
Color name (string)
Tuple of (R, G, B) (each 0.0-1.0)
radius: radius of the hemisphere in Angstroms
Optional arguments:
opacity: 0.0 (transparent) through 1.0 (opaque)
Defaults to 1.0
resolution: 4 to 50
Defaults to 16
angle_dep_transparency: if true, then we modify the color of the
hemisphere based on the angle from the center
"""
# Need to set this first because it is used by helper functions when
# we set properties.
self.hemisphere = None
# These variables are all accessed when assign to .x, .y, or .z, so
# they need to be initialized here.
self._x = 0
self._y = 0
self._z = 0
if center is None:
raise ValueError(
"Must specify center value to define the hemisphere center")
elif radius is None:
raise ValueError("Must specify a value for radius")
else:
self.x, self.y, self.z = center
self.radius = radius
self.direction = direction
if color is not None:
self.r, self.g, self.b = common.color_arg_to_rgb(color)
else:
raise ValueError("Must specify a color")
# Clamp to range of 0.0 and 1.0, inclusive
if opacity < OPACITY_MIN:
self.opacity = OPACITY_MIN
elif opacity > OPACITY_MAX:
self.opacity = OPACITY_MAX
else:
self.opacity = opacity
# Clamp the resolution
if resolution < RESOLUTION_MIN:
self.resolution = RESOLUTION_MIN
elif resolution > RESOLUTION_MAX:
self.resolution = RESOLUTION_MAX
else:
self.resolution = resolution
self.angle_dep_transparency = angle_dep_transparency
self.hemisphere = maestro.create_hemisphere(
self.x, self.y, self.z, self.direction[0], self.direction[1],
self.direction[2], self.r, self.g, self.b, self.radius,
self.opacity, self.resolution, self.angle_dep_transparency)
maestro_objects = [self.hemisphere]
common.Primitive.__init__(self, maestro_objects)
def _calculateBoundingBox(self, mat):
xyzmin = []
xyzmax = []
for k in range(6):
xyzmin.append(BOUNDING_BOX_INIT_VALUE)
xyzmax.append(-BOUNDING_BOX_INIT_VALUE)
x = self.x
y = self.y
z = self.z
r = self.radius
vertex = [[x - r, y - r, z + r],
[x + r, y - r, z + r],
[x + r, y + r, z + r],
[x - r, y + r, z + r],
[x - r, y - r, z - r],
[x + r, y - r, z - r],
[x + r, y + r, z - r],
[x - r, y + r, z - r]] # yapf: disable
transform = numpy.matrix(mat)
tmp = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
for i in range(8):
for k in range(3):
tmp[k + 3] = vertex[i][k]
vec = numpy.array([tmp[3], tmp[4], tmp[5], 1])
out = transform.dot(vec)
for k in range(3):
tmp[k] = out[0, k]
xyzmin = numpy.minimum(xyzmin, tmp).tolist()
xyzmax = numpy.maximum(xyzmax, tmp).tolist()
return (xyzmin, xyzmax)
# Helper functions
[docs] def setCoords(self):
if self.hemisphere:
maestro.set_coords(self.hemisphere, self._x, self._y, self._z)
[docs] def setXYZCoords(self, x, y, z):
self._x = x
self._y = y
self._z = z
self.setCoords()
# Properties
@property
def x(self):
"""Get X coordinate"""
return self._x
@x.setter
def x(self, value):
self._x = value
self.setCoords()
@property
def y(self):
"""Get Y coordinate"""
return self._y
@y.setter
def y(self, value):
self._y = value
self.setCoords()
@property
def z(self):
"""Get Z coordinate"""
return self._z
@z.setter
def z(self, value):
self._z = value
self.setCoords()
@property
def radius(self):
"""Get hemisphere's radius in angstroms"""
return self._radius
@radius.setter
def radius(self, value):
if value <= 0.:
raise ValueError("Radius must be greater than 0.")
self._radius = value
if self.hemisphere:
maestro.set_radius(self.hemisphere, self._radius)