Source code for schrodinger.graphics3d.box

"""
Maestro 3D boxes.

The box module allows creation and drawing of boxes and parallelepipeds.
A box is a special case of a parallelepiped where angles between edges are 90°
and edges are parallel to the XYZ axes.

Control over the positioning, color, and opacity of a box are provided. See the
MaestroBos and MaestroParallelepiped classes for more info.

To draw any number of boxes, create the class instances, add them to a Group
instance then invoke the Group's draw() method.

Copyright Schrodinger, LLC. All rights reserved.
"""

import schrodinger

from . import common
from .common import FILL
from .common import LINE
from .common import OPACITY_MAX
from .common import OPACITY_MIN
from .common import Group  # noqa: F401

DASHED_LINE = 2
maestro = schrodinger.get_maestro()
try:
    from schrodinger.ui import maestro_ui
except ImportError:
    maestro_ui = None

OPACITY_DEFAULT = 1.0

# Constants used to calculate bounding box:
BOUNDING_BOX_INIT_VALUE = 100000000.0

#############################################################################
#                              CLASSES                                      #
#############################################################################


[docs]class MaestroParallelepiped(common.Primitive): """ Class for placing a parallelepiped into the Workspace. Can be used for drawing boxes that are not positioned with edges parallel to 3 axes. 3D objects should be added to a graphics3d.common.Group. See the graphics3d.common.Group documentation. See note on transparency in the module level docstring. API Example:: import schrodinger.maestro.maestro as maestro import schrodinger.graphics3d.common as common import schrodinger.graphics3d.box as box box_grp = common.Group() st = maestro.workspace_get() bx = box.Parallelepiped( points=[origin, xyz2, xyz3, xyz4] color='red', opacity=1.0, style=box.LINE ) # Add the primitive to the container. box_grp.add(bx) # Hide the markers. box_grp.hide() # Remove the markers and the callback. box_grp.clear() """
[docs] def __init__(self, points, color, opacity=OPACITY_DEFAULT, line_width=1.0, style=LINE): """ Constructor requires: points: List of 4 (x, y, z) coordinates, representing half of the corners of the box. First coordinate must be adjacent to each of the other 3 coordinates. color: One of: Color object Color name (string) Tuple of (R, G, B) (each 0.0-1.0) Optional arguments: opacity: 0.0 (invisible) through 1.0 (opaque) Defaults to 1.0 line_width: Line width. Default is 1.0. style: FILL, LINE or DASHED_LINE. Default is LINE. """ r, g, b = common.color_arg_to_rgb(color) # Clamp to range of 0.0 and 1.0, inclusive if opacity < OPACITY_MIN or opacity > OPACITY_MAX: raise ValueError( "Invalid opacity: %s (must be betwen 0.0 and 1.0)" % opacity) if style != FILL and style != LINE and style != DASHED_LINE: raise ValueError("Style must be one of: FILL, LINE, DASHED_LINE") if len(points) != 4: raise ValueError("Must specify 4 points") assert len(points) == 4 assert len(points[0]) == 3 points = [maestro_ui.MM_GraphicsVec3d(*xyz) for xyz in points] parallelepiped = maestro_ui.MM_GraphicsParallelepiped(points) # Create 8 vertices, for boundary calculation. Also used for # create_polyhedron() call. self.vertices = [ (p.x(), p.y(), p.z()) for p in parallelepiped.getPoints() ] if style == FILL: # Will be creating a polygon 3D object, with filled sides # Parallelepiped objects don't support side filling. faces = [ [0, 2, 4, 1], [0, 1, 5, 3], [0, 3, 6, 2], [2, 6, 7, 4], [1, 4, 7, 5], [3, 5, 7, 6], ] normals = [common.create_normal([self.vertices[i] for i in indices]) for indices in faces] # yapf: disable assert len(normals) == 6 obj = maestro.create_polyhedron(self.vertices, faces, normals, r, g, b, opacity, style) else: # Will be creating a parallelepiped 3D object, with lines. # Polygon objects don't support variable line widths. # style: 0=solid lines, 1=short dashed lines, 2=dashed cylinders if style == DASHED_LINE: style = 1 elif style == LINE: style = 0 obj = maestro.create_parallelepiped(parallelepiped, r, g, b, line_width, style) maestro_objects = [obj] 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) tmp = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] for xyz in self.vertices: for k in range(3): tmp[k + 3] = xyz[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 MaestroBox(MaestroParallelepiped): """ Class for placing a 3D box into the Workspace. Edges of the box will be aligned with X/Y/Z axes. For custom rotation, or to create "skewed" boxes, use MaestroParallelepiped class. 3D objects should be added to a graphics3d.common.Group. See the graphics3d.common.Group documentation. See note on transparency in the module level docstring. API Example:: import schrodinger.maestro.maestro as maestro import schrodinger.graphics3d.common as common import schrodinger.graphics3d.box as box box_grp = common.Group() st = maestro.workspace_get() bx = box.Box( center=(x, y, z), extents=(10, 10, 10), color='red', opacity=1.0, style=box.LINE ) # Add the primitive to the container. box_grp.add(bx) # Hide the markers. box_grp.hide() # Remove the markers and the callback. box_grp.clear() """
[docs] def __init__(self, center, extents, color, opacity=OPACITY_DEFAULT, line_width=1.0, style=LINE): """ Constructor requires: center: List of 3 Angstrom values indicating the center coordinate of the box. extents: List of 3 float values in Angstroms - x length, y length, z length. color: One of: Color object Color name (string) Tuple of (R, G, B) (each 0.0-1.0) Optional arguments: opacity: 0.0 (invisible) through 1.0 (opaque) Defaults to 1.0 line_width: Line width. Default is 1.0. style: FILL, LINE or DASHED_LINE. Default is LINE. """ xoff = 0.5 * extents[0] yoff = 0.5 * extents[1] zoff = 0.5 * extents[2] points = [ [center[0] - xoff, center[1] - yoff, center[2] - zoff], [center[0] + xoff, center[1] - yoff, center[2] - zoff], [center[0] - xoff, center[1] + yoff, center[2] - zoff], [center[0] - xoff, center[1] - yoff, center[2] + zoff], ] # yapf: disable super().__init__(points, color, opacity, line_width, style)