Skip to content

Commit

Permalink
Merge branch 'bild_viewer'
Browse files Browse the repository at this point in the history
  • Loading branch information
sjackso committed May 30, 2019
2 parents 1845c07 + 56fd490 commit 095294e
Show file tree
Hide file tree
Showing 18 changed files with 1,095 additions and 97 deletions.
76 changes: 76 additions & 0 deletions src/athena/bildparser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from pathlib import Path
from collections import namedtuple

from athena import colorTable

from PySide2.QtGui import QColor, QVector3D as vec3d
from PySide2.Qt3DCore import Qt3DCore
from PySide2.Qt3DExtras import Qt3DExtras

Sphere = namedtuple( 'Sphere', 'color, x, y, z, r' )
Cylinder = namedtuple ( 'Cylinder', 'color, x1, y1, z1, x2, y2, z2, r' )
# We'll give defaults for r1, r2, and rho, which are optional in a file.
# This isn't perfect because the default for r2 should be r1*4. Parser
# code should watch for the case where r1 is given and r2 is not, and update
# the r2 value appropriately.
Arrow = namedtuple ( 'Arrow', 'color, x1, y1, z1, x2, y2, z2, r1, r2, rho', defaults=[0.1,0.4,0.75] )

class OutputDecorations:
def __init__(self, scale_factor):
self.colors = dict() # maps normalized bild strings to QColors
self.current_color = None
self.spheres = list()
self.cylinders = list()
self.arrows = list()
self.scale_factor = scale_factor / 3.2

def addColor( self, tokens ):
color_key = ' '.join(tokens)
if color_key not in self.colors:
if color_key in colorTable.colors:
self.colors[color_key] = QColor( *colorTable.colors[color_key] )
else:
self.colors[color_key] = QColor( *(float(x)*255 for x in tokens) )
self.current_color = self.colors[color_key]

def addSphere( self, tokens ):
self.spheres.append( Sphere( self.current_color, *(float(x)*(self.scale_factor) for x in tokens) ) )

def addCylinder( self, tokens ):
self.cylinders.append( Cylinder( self.current_color, *(float(x)*(self.scale_factor) for x in tokens) ) )

def addArrow( self, tokens ):
self.arrows.append( Arrow( self.current_color, *(float(x)*(self.scale_factor) for x in tokens) ) )

def debugSummary( self ):
pattern = 'parsed BILD: {0} unique colors, {1} spheres, {2} cylinders, {3} arrows' +\
'\n unknown keywords/counts: {4}' +\
'\n comment lines: {5}'
return pattern.format( len(self.colors), len(self.spheres), len(self.cylinders), len(self.arrows),
self.unknown_keyword_map, len(self.other_line_list) )


def parseBildFile( filename, scale_factor ):
results = OutputDecorations(scale_factor)
with open(filename,'r') as bild:
unknown_keyword_map = dict()
other_line_list = list()
for line in bild:
tokens = line.split()
token0 = tokens[0]
if( token0 == '.arrow' ):
results.addArrow( tokens[1:] )
elif( token0 == '.color' ):
results.addColor( tokens[1:] )
elif token0 == '.cylinder':
results.addCylinder( tokens[1:] )
elif token0 == '.sphere':
results.addSphere( tokens[1:] )
elif( tokens[0].startswith('.')):
v = unknown_keyword_map.get(tokens[0],0)
unknown_keyword_map[tokens[0]] = v + 1
else:
other_line_list.append(tokens)
results.unknown_keyword_map = unknown_keyword_map
results.other_line_list = other_line_list
return results
99 changes: 99 additions & 0 deletions src/athena/colorTable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# --- UCSF Chimera Copyright ---
# Copyright (c) 2000 Regents of the University of California.
# All rights reserved. This software provided pursuant to a
# license agreement containing restrictions on its disclosure,
# duplication and use. This notice must be embedded in or
# attached to all copies, including partial copies, of the
# software or any revisions or derivations thereof.
# --- UCSF Chimera Copyright ---
#
# $Id: colorTable.py 26655 2009-01-07 22:02:30Z pett $

colors = {
"aquamarine": (127, 255, 212),
"black": (0, 0, 0),
"blue": (0, 0, 255),
"brown": (165, 42, 42),
"chartreuse": (127, 255, 0),
"coral": (255, 127, 80),
"cornflower blue": (100, 149, 237),
"cyan": (0, 255, 255),
"dark cyan": (0, 139, 139),
"dark gray": (169, 169, 169),
"dark grey": (169, 169, 169),
"dark green": (0, 100, 0),
"dark khaki": (189, 183, 107),
"dark magenta": (139, 0, 139),
"dark olive green": (85, 107, 47),
"dark red": (139, 0, 0),
"dark slate blue": (72, 61, 139),
"dark slate gray": (47, 79, 79),
"dark slate grey": (47, 79, 79),
"deep pink": (255, 20, 147),
"deep sky blue": (0, 191, 255),
"dim gray": (105, 105, 105),
"dim grey": (105, 105, 105),
"dodger blue": (30, 144, 255),
"firebrick": (178, 34, 34),
"forest green": (34, 139, 34),
"gold": (255, 215, 0),
"goldenrod": (218, 165, 32),
"gray": (190, 190, 190),
"grey": (190, 190, 190),
"green": (0, 255, 0),
"hot pink": (255, 105, 180),
"khaki": (240, 230, 140),
"light blue": (173, 216, 230),
"light gray": (211, 211, 211),
"light grey": (211, 211, 211),
"light green": (144, 238, 144),
"light sea green": (32, 178, 170),
"lime green": (50, 205, 50),
"magenta": (255, 0, 255),
"medium blue": (50, 50, 205),
"medium purple": (147, 112, 219),
"navy blue": (0, 0, 128),
"olive drab": (107, 142, 35),
"orange red": (255, 69, 0),
"orange": (255, 127, 0),
"orchid": (218, 112, 214),
"pink": (255, 192, 203),
"plum": (221, 160, 221),
"purple": (160, 32, 240),
"red": (255, 0, 0),
"rosy brown": (188, 143, 143),
"salmon": (250, 128, 114),
"sandy brown": (244, 164, 96),
"sea green": (46, 139, 87),
"sienna": (160, 82, 45),
"sky blue": (135, 206, 235),
"slate gray": (112, 128, 144),
"slate grey": (112, 128, 144),
"spring green": (0, 255, 127),
"steel blue": (70, 130, 180),
"tan": (210, 180, 140),
"turquoise": (64, 224, 208),
"violet red": (208, 32, 144),
"white": (255, 255, 255),
"yellow": (255, 255, 0),
}

def getColorByName(name):
# can raise KeyError
import chimera
color = chimera.Color.lookup(name)
if not color:
r, g, b = colors[name]
color = chimera.MaterialColor(r/255.0, g/255.0, b/255.0)
color.save(name)
return color

def getTkColorByName(name):
# can raise KeyError
import chimera
color = chimera.Color.lookup(name)
if color:
rgb = tuple([int(255 * v + 0.5) for v in color.rgba()[:3]])
else:
rgb = colors[name]
return "#%02x%02x%02x" % rgb
106 changes: 106 additions & 0 deletions src/athena/decorations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import numpy as np

from PySide2.QtGui import QColor, QVector3D as vec3d
from PySide2.QtCore import QByteArray, Qt
from PySide2.Qt3DCore import Qt3DCore
from PySide2.Qt3DRender import Qt3DRender

from athena.bildparser import Sphere, Cylinder, Arrow
from athena import geom


class SphereDecorations(Qt3DCore.QEntity):

def __init__(self, parent, bildfile):
super().__init__(parent)
spherelist = bildfile.spheres
num_spheres = len(spherelist)

if num_spheres == 0: return

total_vertices = num_spheres
vertex_basetype = geom.basetypes.Float
if( total_vertices < 30000 ):
index_basetype = geom.basetypes.UnsignedShort
else:
index_basetype = geom.basetypes.UnsignedInt

vertex_nparr = np.zeros([total_vertices,7],dtype=geom.basetype_numpy_codes[vertex_basetype])

for idx, (color, x, y, z, r) in enumerate(spherelist):
if color is None: color = QColor('white')
vertex_nparr[idx,:] = x, y, z, r, color.redF(), color.greenF(), color.blueF()

self.geometry = Qt3DRender.QGeometry(self)

position_attrname = Qt3DRender.QAttribute.defaultPositionAttributeName()
radius_attrname = 'sphereRadius'
color_attrname = Qt3DRender.QAttribute.defaultColorAttributeName()

attrspecs = [geom.AttrSpec(position_attrname, column=0, numcols=3),
geom.AttrSpec(radius_attrname, column=3, numcols=1),
geom.AttrSpec(color_attrname, column=4, numcols=3)]

self.vtx_attrs = geom.buildVertexAttrs( self, vertex_nparr, attrspecs )
for va in self.vtx_attrs:
self.geometry.addAttribute(va)

# Create qt3d index buffer
index_nparr = np.arange(len(vertex_nparr),dtype=geom.basetype_numpy_codes[index_basetype])
self.indexAttr = geom.buildIndexAttr( self, index_nparr )
self.geometry.addAttribute(self.indexAttr)

self.renderer = Qt3DRender.QGeometryRenderer(parent)
self.renderer.setGeometry(self.geometry)
self.renderer.setPrimitiveType(Qt3DRender.QGeometryRenderer.Points)

self.addComponent(self.renderer)

class CylinderDecorations(Qt3DCore.QEntity):

def __init__(self, parent, bildfile):
super().__init__(parent)
# Draw the arrow bodies as cylinders too
cylinderlist = bildfile.cylinders + [ Cylinder(*x[:8]) for x in bildfile.arrows]
num_cylinders = len(cylinderlist)

if num_cylinders == 0: return

total_vertices = 2 * num_cylinders
vertex_basetype = geom.basetypes.Float
if( total_vertices < 30000 ):
index_basetype = geom.basetypes.UnsignedShort
else:
index_basetype = geom.basetypes.UnsignedInt

vertex_nparr = np.zeros([total_vertices,7],dtype=geom.basetype_numpy_codes[vertex_basetype])
for idx, (color, x1, y1, z1, x2, y2, z2, r) in enumerate(cylinderlist):
if color is None: color = QColor('white')
vertex_nparr[2*idx,:] = x1, y1, z1, r, color.redF(), color.greenF(), color.blueF()
vertex_nparr[2*idx+1,:] = x2, y2, z2, r, color.redF(), color.greenF(), color.blueF()

self.geometry = Qt3DRender.QGeometry(self)

position_attrname = Qt3DRender.QAttribute.defaultPositionAttributeName()
radius_attrname = 'radius'
color_attrname = Qt3DRender.QAttribute.defaultColorAttributeName()

attrspecs = [geom.AttrSpec(position_attrname, column=0, numcols=3),
geom.AttrSpec(radius_attrname, column=3, numcols=1),
geom.AttrSpec(color_attrname, column=4, numcols=3)]

self.vtx_attrs = geom.buildVertexAttrs( self, vertex_nparr, attrspecs )
for va in self.vtx_attrs:
self.geometry.addAttribute(va)

# Create qt3d index buffer
index_nparr = np.arange(len(vertex_nparr),dtype=geom.basetype_numpy_codes[index_basetype])
self.indexAttr = geom.buildIndexAttr( self, index_nparr )
self.geometry.addAttribute(self.indexAttr)

self.renderer = Qt3DRender.QGeometryRenderer(parent)
self.renderer.setGeometry(self.geometry)
self.renderer.setPrimitiveType(Qt3DRender.QGeometryRenderer.Lines)

self.addComponent(self.renderer)

Loading

0 comments on commit 095294e

Please sign in to comment.