Source code for schrodinger.graphics3d.torus

"""
Maestro 3D toruses.

The torus module allows creation and drawing of toruses.  Clients draw using
Group instances not through Torus instances.

Control over the center, radius, color, resolution and opacity of a torus are
provided. See the Torus class for more info.

To draw any number of toruses, create the Torus instance and add it to a Group
instance.  Then invoke the Group's draw() method.

Copyright Schrodinger, LLC. All rights reserved.

"""

# Contributors: Herc Silverstein, Scott MacHoffie, Matvey Adzhigirey, Lixing Fang

import copy
import math

import numpy

import schrodinger

from . import common
from .common import OPACITY_MAX
from .common import OPACITY_MIN
from .common import TRANSPARENCY_MAX
from .common import TRANSPARENCY_MIN
from .common import Group as TorusGroup  # noqa: F401

RESOLUTION_MIN = 2
RESOLUTION_MAX = 30
RESOLUTION_DEFAULT_U = 5
RESOLUTION_DEFAULT_V = 3

TRANSPARENCY_DEFAULT = 0.0  # for backwards-compatibility
OPACITY_DEFAULT = 1.0

# Constants used to calculate bounding box:
BOUNDING_BOX_INIT_VALUE = 100000000.0
EPSILON = 0.0001

maestro = schrodinger.get_maestro()


[docs]def vector_cross(X, Y): """ Returns the cross product of the len 3 vectors X and Y """ r = numpy.array([0.0, 0.0, 0.0]) r[0] = X[1] * Y[2] - X[2] * Y[1] r[1] = X[2] * Y[0] - X[0] * Y[2] r[2] = X[0] * Y[1] - X[1] * Y[0] return r
############################################################################# # CLASSES # #############################################################################
[docs]class TorusCore(common.Primitive): """ Base class for MaestroTorus. See doc string for __init__ for details on instantiation. Toruses should be added to a TorusGroup and drawing done via TorusGroup. See TorusGroup documentation. """ _blend = None _smooth_point = None _cull = None _lighting = None _lighting_model = None _shading_model = None
[docs] def __init__( self, x0=None, y0=None, z0=None, x1=None, y1=None, z1=None, color=None, r=None, g=None, b=None, # for backwards-compatability radius=None, tube_radius=None, transparency=None, # for backwards-compatability opacity=OPACITY_DEFAULT, resolution_u=RESOLUTION_DEFAULT_U, resolution_v=RESOLUTION_DEFAULT_V): """ Constructor requires: x0, y0, z0: coordinate specifying center of torus in Angstroms. x1, y1, z1: coordinate specifying projected center of torus in Angstroms. color: One of: Color object Color name (string) Tuble of (R, G, B) (each 0.0-1.0) radius: radius of the torus in Angstroms tube_radius: radius of the tube of the torus in Angstroms Optional arguments: opacity: 0.0 (transparent) through 1.0 (opaque) Defaults to 1.0 resolution_u: 2 to 30, resolution in the U direction of the surface Defaults to 5 resolution_v: 2 to 30, resolution in the V direction of the surface Defaults to 3 """ if x0 is None or y0 is None or z0 is None: raise ValueError( "Must specify x0, y0 and z0 values to define the torus center") elif x1 is None or y1 is None or z1 is None: raise ValueError( "Must specify x1, y1 and z1 values to define the torus projected center" ) elif radius is None: raise ValueError("Must specify a value for radius") elif tube_radius is None: raise ValueError("Must specify a value for tube radius") else: self.x0 = x0 self.y0 = y0 self.z0 = z0 self.x1 = x1 self.y1 = y1 self.z1 = z1 self.radius = radius self.tube_radius = tube_radius if color is not None: self.r, self.g, self.b = common.color_arg_to_rgb(color) elif r is not None and g is not None and b is not None: # for backwards-compatability self.r = float(r) self.g = float(g) self.b = float(b) else: raise ValueError("Must specify a color") # Clamp to range of 0.0 and 1.0, inclusive if transparency is not None: # for backwards-compatability if transparency < TRANSPARENCY_MIN: self.opacity = OPACITY_MAX elif transparency > TRANSPARENCY_MAX: self.opacity = OPACITY_MIN else: self.opacity = 1.0 - transparency else: # Use opacity if opacity < OPACITY_MIN: self.opacity = OPACITY_MIN elif opacity > OPACITY_MAX: self.opacity = OPACITY_MAX else: self.opacity = opacity # Clamp the resolution in U direction if resolution_u < RESOLUTION_MIN: self.resolution_u = RESOLUTION_MIN elif resolution_u > RESOLUTION_MAX: self.resolution_u = RESOLUTION_MAX else: self.resolution_u = resolution_u # Clamp the resolution in U direction if resolution_v < RESOLUTION_MIN: self.resolution_v = RESOLUTION_MIN elif resolution_v > RESOLUTION_MAX: self.resolution_v = RESOLUTION_MAX else: self.resolution_v = resolution_v return
def _calculateBoundingBox(self, mat): xyzmin = [] xyzmax = [] for k in range(6): xyzmin.append(BOUNDING_BOX_INIT_VALUE) xyzmax.append(-BOUNDING_BOX_INIT_VALUE) # Two points for defining the torus axis C = numpy.array([self.x0, self.y0, self.z0]) D = numpy.array([self.x1, self.y1, self.z1]) # CD is the vector between points C and D CD = D - C # We need to find bottom point A and top point B of the torus A = C - self.tube_radius * CD B = C + self.tube_radius * CD # AB is the vector between points A and B AB = B - A # Perform the vector operation # We have coordinates for two positions (A, B) which # define the membrane planes. We need to figure out a vector # perpendiclar to the vector AB and a series of points # in the planes in order to draw a polygon # Assign the size of the planes l = self.radius + self.tube_radius # noqa: E741 # C is the midpoint between the two points A and B # N is an arbitrary point we will project onto C to get # a perpendicular vector N = copy.copy(C) if math.fabs(AB[0]) < EPSILON and math.fabs(AB[1]) < EPSILON: N[0] += 10.0 elif math.fabs(AB[1]) < EPSILON and math.fabs(AB[2]) < EPSILON: N[1] += 10.0 elif math.fabs(AB[2]) < EPSILON and math.fabs(AB[0]) < EPSILON: N[2] += 10.0 else: N[0] += 10.0 # CN is a vector crosses AB at C CN = N - C # O is the projection of N onto AB. The vector O-N perpendicular # to A-B dot1 = numpy.dot(AB, AB) dot2 = numpy.dot(AB, CN) K = dot2 / dot1 O = C + numpy.dot(K, AB) # noqa: E741 ON = common.get_normalized(N - O) # P is perpendicular to both AB and ON P = common.get_normalized(vector_cross(AB, ON)) # Now we have everything we need to generate the planes plane1 = [] plane2 = [] # Op is used to generate a vertex at each corner of what will # be drawn as a square plane. Basically it's atom1+S, atom1+U, # atom1-S, atom1-U op = [[l, 0.0], [0.0, l], [-l, 0.0], [0.0, -l]] for i in range(0, 4): plane1.append(A + op[i][0] * ON + op[i][1] * P) plane2.append(B + op[i][0] * ON + op[i][1] * P) tmp = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] for i in range(4): for k in range(3): tmp[k + 3] = plane1[i][k] tmp[0] = mat[0][0] * tmp[3] + mat[0][1] * tmp[4] + mat[0][2] * tmp[ 5] + mat[0][3] tmp[1] = mat[1][0] * tmp[3] + mat[1][1] * tmp[4] + mat[1][2] * tmp[ 5] + mat[1][3] tmp[2] = mat[2][0] * tmp[3] + mat[2][1] * tmp[4] + mat[2][2] * tmp[ 5] + mat[2][3] for k in range(6): if xyzmin[k] > tmp[k]: xyzmin[k] = tmp[k] if xyzmax[k] < tmp[k]: xyzmax[k] = tmp[k] for i in range(4): for k in range(3): tmp[k + 3] = plane2[i][k] tmp[0] = mat[0][0] * tmp[3] + mat[0][1] * tmp[4] + mat[0][2] * tmp[ 5] + mat[0][3] tmp[1] = mat[1][0] * tmp[3] + mat[1][1] * tmp[4] + mat[1][2] * tmp[ 5] + mat[1][3] tmp[2] = mat[2][0] * tmp[3] + mat[2][1] * tmp[4] + mat[2][2] * tmp[ 5] + mat[2][3] for k in range(6): if xyzmin[k] > tmp[k]: xyzmin[k] = tmp[k] if xyzmax[k] < tmp[k]: xyzmax[k] = tmp[k] return (xyzmin, xyzmax)
[docs]class MaestroTorus(common._MaestroPrimitiveMixin, TorusCore): """ Class to create a 3D torus in Maestro. This torus will be drawn at the appropriate time in Maestro to interact properly with transparent objects. Toruses 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.torus as torus torus_grp = common.Group() st = maestro.workspace_get() for bond in st.bond: trs = torus.MaestroTorus( x0=bond.atom1.x, y0=bond.atom1.y, z0=bond.atom1.z, x1=bond.atom2.x, y1=bond.atom2.y, z1=bond.atom2.z, radius=0.1, tube_radius=0.5, resolution_u=7, resolution_v=5, opacity=0.8, color='red' ) # Add the primative to the container. torus_grp.add(trs) # MaestroTorus needs to be drawn. # No special callback like there is for Toruses is needed. torus_grp.show() # Hide the markers. torus_grp.hide() # Remove the markers and the callback. torus_grp.clear() maestro.workspace_draw_function_remove(torus_grp.draw) """
[docs] def __init__( self, x0=None, y0=None, z0=None, x1=None, y1=None, z1=None, color=None, r=None, g=None, b=None, # for backwards-compatability radius=None, tube_radius=None, transparency=None, # for backwards-compatability opacity=OPACITY_DEFAULT, resolution_u=RESOLUTION_DEFAULT_U, resolution_v=RESOLUTION_DEFAULT_V): """ Constructor requires: x0, y0, z0: coordinate specifying center of torus in Angstroms. x1, y1, z1: coordinate specifying projected center of torus in Angstroms. color: One of: Color object Color name (string) Tuble of (R, G, B) (each 0.0-1.0) radius: radius of the torus in Angstroms tube_radius: radius of the tube of the torus in Angstroms Optional arguments: opacity: 0.0 (transparent) through 1.0 (opaque) Defaults to 1.0 resolution_u: 2 to 30, resolution in the U direction of the surface Defaults to 5 resolution_v: 2 to 30, resolution in the V direction of the surface Defaults to 3 """ self.torus = None self._x0 = 0 self._y0 = 0 self._z0 = 0 self._x1 = 0 self._y1 = 0 self._z1 = 0 self._radius = 0 self._tube_radius = 0 TorusCore.__init__(self, x0, y0, z0, x1, y1, z1, color, r, g, b, radius, tube_radius, transparency, opacity, resolution_u, resolution_v) self.torus = maestro.create_torus(self.x0, self.y0, self.z0, self.x1, self.y1, self.z1, self.r, self.g, self.b, self.radius, self.tube_radius, self.opacity, self.resolution_u, self.resolution_v) maestro_objects = [self.torus] common.Primitive.__init__(self, maestro_objects)
# Helper functions
[docs] def setCoords(self): if self.torus: maestro.set_coords(self.torus, self._x0, self._y0, self._z0)
# Accessors def _getX0(self): return self._x0 def _setX0(self, value): self._x0 = value self.setCoords() def _getY0(self): return self._y0 def _setY0(self, value): self._y0 = value self.setCoords() def _getZ0(self): return self._z0 def _setZ0(self, value): self._z0 = value self.setCoords() def _getX1(self): return self._x1 def _setX1(self, value): self._x1 = value def _getY1(self): return self._y1 def _setY1(self, value): self._y1 = value def _getZ1(self): return self._z1 def _setZ1(self, value): self._z1 = value def _getRadius(self): return self._radius def _setRadius(self, value): self._radius = value if self.torus: maestro.set_radius(self.torus, self._radius) def _getTubeRadius(self): return self._tube_radius def _setTubeRadius(self, value): self._tube_radius = value x0 = property(_getX0, _setX0, doc="X0 coordinate") y0 = property(_getY0, _setY0, doc="Y0 coordinate") z0 = property(_getZ0, _setZ0, doc="Z0 coordinate") x1 = property(_getX1, _setX1, doc="X1 coordinate") y1 = property(_getY1, _setY1, doc="Y1 coordinate") z1 = property(_getZ1, _setZ1, doc="Z1 coordinate") radius = property(_getRadius, _setRadius, doc="Torus's radius") tube_radius = property(_getTubeRadius, _setTubeRadius, doc="Torus's tube_radius")