pyfock.Graphics

  1import numpy as np
  2from OpenGL.GLUT import *
  3from OpenGL.GLU import *
  4from OpenGL.GL import *
  5from . import Data
  6import sys
  7
  8# Global variables
  9xCoord, yCoord, zCoord, atomicNumber, totalNumAtoms = [], [], [], [], 0
 10bondStart, bondEnd, bondLengths = [], [], []
 11maxDistance, selectedAtom = 0.0, -1
 12_mouse_dragging, _previous_mouse_position = False, None
 13zoom_factor = 1.0  # New zoom variable
 14min_zoom, max_zoom = 0.1, 5.0  # Zoom limits
 15
 16def initializeGlobalVars(mol):
 17    global xCoord, yCoord, zCoord, atomicNumber, totalNumAtoms, maxDistance
 18    totalNumAtoms = mol.natoms
 19    xCoord = [mol.coords[i][0] for i in range(mol.natoms)]
 20    yCoord = [mol.coords[i][1] for i in range(mol.natoms)]
 21    zCoord = [mol.coords[i][2] for i in range(mol.natoms)]
 22    atomicNumber = [mol.Zcharges[i] for i in range(mol.natoms)]
 23    
 24    # Center molecule and calculate max distance
 25    com = [sum(xCoord)/totalNumAtoms, sum(yCoord)/totalNumAtoms, sum(zCoord)/totalNumAtoms]
 26    for i in range(totalNumAtoms):
 27        xCoord[i] -= com[0]; yCoord[i] -= com[1]; zCoord[i] -= com[2]
 28    
 29    maxDistance = max(np.sqrt(x**2 + y**2 + z**2) for x,y,z in zip(xCoord, yCoord, zCoord)) * 2 + 5
 30    calculateBonds()
 31
 32def calculateBonds():
 33    global bondStart, bondEnd, bondLengths
 34    bondStart, bondEnd, bondLengths = [], [], []
 35    for i in range(totalNumAtoms):
 36        for j in range(i+1, totalNumAtoms):
 37            dist = np.sqrt((xCoord[i]-xCoord[j])**2 + (yCoord[i]-yCoord[j])**2 + (zCoord[i]-zCoord[j])**2)
 38            if dist <= (float(Data.covalentRadius[atomicNumber[i]]) + float(Data.covalentRadius[atomicNumber[j]])):
 39                bondStart.append(i); bondEnd.append(j); bondLengths.append(dist)
 40
 41def update_projection():
 42    """Update the projection matrix based on current zoom factor"""
 43    global zoom_factor, maxDistance
 44    glMatrixMode(GL_PROJECTION)
 45    glLoadIdentity()
 46    
 47    # Apply zoom by adjusting the orthographic viewing volume
 48    view_size = (maxDistance/2) / zoom_factor
 49    glOrtho(-view_size, view_size, -view_size, view_size, -100, 100)
 50    
 51    glMatrixMode(GL_MODELVIEW)
 52
 53def zoom_in():
 54    """Zoom in function"""
 55    global zoom_factor
 56    zoom_factor = min(zoom_factor * 1.2, max_zoom)
 57    update_projection()
 58    glutPostRedisplay()
 59
 60def zoom_out():
 61    """Zoom out function"""
 62    global zoom_factor
 63    zoom_factor = max(zoom_factor / 1.2, min_zoom)
 64    update_projection()
 65    glutPostRedisplay()
 66
 67def mouse_wheel(wheel, direction, x, y):
 68    """Handle mouse wheel for zooming"""
 69    if direction > 0:
 70        zoom_in()
 71    else:
 72        zoom_out()
 73
 74def draw_sphere(xyz, radius, color):
 75    glPushMatrix()
 76    color = [c/255.0 for c in color] + [1.0]
 77    glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color)
 78    glMaterialfv(GL_FRONT, GL_SPECULAR, [1.0, 1.0, 1.0, 1.0])
 79    glMaterialf(GL_FRONT, GL_SHININESS, 30.0)
 80    glTranslate(*xyz)
 81    glutSolidSphere(radius, 20, 20)
 82    glPopMatrix()
 83
 84def cylinder_between(x1, y1, z1, x2, y2, z2, length, radius):
 85    v = [x2-x1, y2-y1, z2-z1]
 86    axis = (1, 0, 0) if np.hypot(v[0], v[1]) < 0.001 else np.cross(v, (0, 0, 1))
 87    angle = -np.arctan2(np.hypot(v[0], v[1]), v[2]) * 180/np.pi
 88    glPushMatrix()
 89    glTranslate(x1, y1, z1)
 90    glRotate(angle, *axis)
 91    gluCylinder(gluNewQuadric(), radius, radius, length, 10, 10)
 92    glPopMatrix()
 93
 94def display():
 95    global selectedAtom
 96    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
 97    
 98    # Draw atoms
 99    for i in range(totalNumAtoms):
100        draw_sphere([xCoord[i], yCoord[i], zCoord[i]], 
101                   float(Data.atomicRadius[atomicNumber[i]])/2, 
102                   Data.CPKcolorRGB[atomicNumber[i]])
103    
104    # Draw bonds
105    for i in range(len(bondStart)):
106        start, end = bondStart[i], bondEnd[i]
107        x1, y1, z1 = xCoord[start], yCoord[start], zCoord[start]
108        x2, y2, z2 = xCoord[end], yCoord[end], zCoord[end]
109        
110        # First half with start atom color
111        color = [c/255.0 for c in Data.CPKcolorRGB[atomicNumber[start]]] + [1.0]
112        glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color)
113        cylinder_between(x1, y1, z1, (x1+x2)/2, (y1+y2)/2, (z1+z2)/2, bondLengths[i]/2, 0.1)
114        
115        # Second half with end atom color
116        color = [c/255.0 for c in Data.CPKcolorRGB[atomicNumber[end]]] + [1.0]
117        glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color)
118        cylinder_between((x1+x2)/2, (y1+y2)/2, (z1+z2)/2, x2, y2, z2, bondLengths[i]/2, 0.1)
119    
120    # Display selected atom info and zoom level
121    if selectedAtom >= 0:
122        glDisable(GL_LIGHTING)
123        glColor3f(1, 1, 1)
124        glRasterPos3f(maxDistance/3/zoom_factor, maxDistance/3/zoom_factor, 0)
125        info = f"Atom {selectedAtom}: {Data.elementSymbols[atomicNumber[selectedAtom]]} ({xCoord[selectedAtom]:.3f}, {yCoord[selectedAtom]:.3f}, {zCoord[selectedAtom]:.3f})"
126        for char in info:
127            glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, ord(char))
128        glEnable(GL_LIGHTING)
129    
130    # Display zoom level
131    glDisable(GL_LIGHTING)
132    glColor3f(0.8, 0.8, 0.8)
133    glRasterPos3f(-maxDistance/2.5/zoom_factor, maxDistance/2.5/zoom_factor, 0)
134    zoom_info = f"Zoom: {zoom_factor:.2f}x"
135    for char in zoom_info:
136        glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, ord(char))
137    glEnable(GL_LIGHTING)
138    
139    glutSwapBuffers()
140
141def mouse_click(button, state, x, y):
142    global _mouse_dragging, _previous_mouse_position, selectedAtom
143    if button == GLUT_LEFT_BUTTON:
144        if state == GLUT_DOWN:
145            _mouse_dragging = True
146            _previous_mouse_position = (x, y)
147            
148            # Check for atom selection
149            viewport = glGetIntegerv(GL_VIEWPORT)
150            modelview = glGetDoublev(GL_MODELVIEW_MATRIX)
151            projection = glGetDoublev(GL_PROJECTION_MATRIX)
152            
153            winY = viewport[3] - y
154            _, _, winZ = gluUnProject(x, winY, 0.5, modelview, projection, viewport)
155            
156            min_dist, closest = float('inf'), -1
157            for i in range(totalNumAtoms):
158                winX, winY, _ = gluProject(xCoord[i], yCoord[i], zCoord[i], modelview, projection, viewport)
159                dist = np.sqrt((x - winX)**2 + (y - (viewport[3] - winY))**2)
160                if dist < 20 and dist < min_dist:  # 20 pixel tolerance
161                    min_dist, closest = dist, i
162            
163            selectedAtom = closest
164            print(f"Selected atom {selectedAtom}: {Data.elementSymbols[atomicNumber[selectedAtom]] if selectedAtom >= 0 else 'None'}")
165            glutPostRedisplay()
166        else:
167            _mouse_dragging = False
168
169def mouse_motion(x, y):
170    global _previous_mouse_position
171    if _mouse_dragging and _previous_mouse_position:
172        dx, dy = (x - _previous_mouse_position[0])/100.0, (y - _previous_mouse_position[1])/100.0
173        glRotatef(dx * 50, 0, 1, 0)
174        glRotatef(dy * 50, 1, 0, 0)
175        _previous_mouse_position = (x, y)
176        glutPostRedisplay()
177
178def keyboard(key, x, y):
179    global zoom_factor
180    if key == b'\x1b':  # Escape key
181        glutLeaveMainLoop()
182    elif key == b'+' or key == b'=':  # Plus key for zoom in
183        zoom_in()
184        print(f"Zoom: {zoom_factor:.2f}x")
185    elif key == b'-':  # Minus key for zoom out
186        zoom_out()
187        print(f"Zoom: {zoom_factor:.2f}x")
188    elif key == b'r' or key == b'R':  # Reset zoom
189        zoom_factor = 1.0
190        update_projection()
191        glutPostRedisplay()
192        print("Zoom reset to 1.0x")
193
194def special_keys(key, x, y):
195    """Handle special keys like Page Up/Down for zooming"""
196    if key == GLUT_KEY_PAGE_UP:
197        zoom_in()
198        print(f"Zoom: {zoom_factor:.2f}x")
199    elif key == GLUT_KEY_PAGE_DOWN:
200        zoom_out()
201        print(f"Zoom: {zoom_factor:.2f}x")
202
203def visualize(mol, title='PyFock | CrysX - 3D Viewer'):
204    initializeGlobalVars(mol)
205    
206    glutInit(sys.argv)
207    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
208    glutInitWindowSize(700, 700)
209    glutCreateWindow(title)
210    
211    glClearColor(0.1, 0.1, 0.1, 1.0)
212    glEnable(GL_DEPTH_TEST)
213    glEnable(GL_LIGHTING)
214    glEnable(GL_LIGHT0)
215    glLightfv(GL_LIGHT0, GL_POSITION, [10, 10, 10, 1])
216    glLightfv(GL_LIGHT0, GL_DIFFUSE, [1, 1, 1, 1])
217    
218    # Set initial projection with zoom support
219    update_projection()
220    glMatrixMode(GL_MODELVIEW)
221    glLoadIdentity()
222    
223    glutDisplayFunc(display)
224    glutMouseFunc(mouse_click)
225    glutMotionFunc(mouse_motion)
226    glutKeyboardFunc(keyboard)
227    glutSpecialFunc(special_keys)
228    
229    # Register mouse wheel callback if available
230    try:
231        glutMouseWheelFunc(mouse_wheel)
232    except:
233        pass  # Mouse wheel not supported in older PyOpenGL versions
234    
235    print("Controls:")
236    print("- Click atoms to see coordinates")
237    print("- Drag to rotate")
238    print("- Mouse wheel: Zoom in/out")
239    print("- '+' or '=': Zoom in")
240    print("- '-': Zoom out") 
241    print("- 'R': Reset zoom")
242    print("- Page Up/Down: Zoom in/out")
243    print("- ESC: Exit")
244    print(f"Current zoom: {zoom_factor:.2f}x")
245    
246    glutMainLoop()
zoom_factor = 1.0
def initializeGlobalVars(mol):
17def initializeGlobalVars(mol):
18    global xCoord, yCoord, zCoord, atomicNumber, totalNumAtoms, maxDistance
19    totalNumAtoms = mol.natoms
20    xCoord = [mol.coords[i][0] for i in range(mol.natoms)]
21    yCoord = [mol.coords[i][1] for i in range(mol.natoms)]
22    zCoord = [mol.coords[i][2] for i in range(mol.natoms)]
23    atomicNumber = [mol.Zcharges[i] for i in range(mol.natoms)]
24    
25    # Center molecule and calculate max distance
26    com = [sum(xCoord)/totalNumAtoms, sum(yCoord)/totalNumAtoms, sum(zCoord)/totalNumAtoms]
27    for i in range(totalNumAtoms):
28        xCoord[i] -= com[0]; yCoord[i] -= com[1]; zCoord[i] -= com[2]
29    
30    maxDistance = max(np.sqrt(x**2 + y**2 + z**2) for x,y,z in zip(xCoord, yCoord, zCoord)) * 2 + 5
31    calculateBonds()
def calculateBonds():
33def calculateBonds():
34    global bondStart, bondEnd, bondLengths
35    bondStart, bondEnd, bondLengths = [], [], []
36    for i in range(totalNumAtoms):
37        for j in range(i+1, totalNumAtoms):
38            dist = np.sqrt((xCoord[i]-xCoord[j])**2 + (yCoord[i]-yCoord[j])**2 + (zCoord[i]-zCoord[j])**2)
39            if dist <= (float(Data.covalentRadius[atomicNumber[i]]) + float(Data.covalentRadius[atomicNumber[j]])):
40                bondStart.append(i); bondEnd.append(j); bondLengths.append(dist)
def update_projection():
42def update_projection():
43    """Update the projection matrix based on current zoom factor"""
44    global zoom_factor, maxDistance
45    glMatrixMode(GL_PROJECTION)
46    glLoadIdentity()
47    
48    # Apply zoom by adjusting the orthographic viewing volume
49    view_size = (maxDistance/2) / zoom_factor
50    glOrtho(-view_size, view_size, -view_size, view_size, -100, 100)
51    
52    glMatrixMode(GL_MODELVIEW)

Update the projection matrix based on current zoom factor

def zoom_in():
54def zoom_in():
55    """Zoom in function"""
56    global zoom_factor
57    zoom_factor = min(zoom_factor * 1.2, max_zoom)
58    update_projection()
59    glutPostRedisplay()

Zoom in function

def zoom_out():
61def zoom_out():
62    """Zoom out function"""
63    global zoom_factor
64    zoom_factor = max(zoom_factor / 1.2, min_zoom)
65    update_projection()
66    glutPostRedisplay()

Zoom out function

def mouse_wheel(wheel, direction, x, y):
68def mouse_wheel(wheel, direction, x, y):
69    """Handle mouse wheel for zooming"""
70    if direction > 0:
71        zoom_in()
72    else:
73        zoom_out()

Handle mouse wheel for zooming

def draw_sphere(xyz, radius, color):
75def draw_sphere(xyz, radius, color):
76    glPushMatrix()
77    color = [c/255.0 for c in color] + [1.0]
78    glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color)
79    glMaterialfv(GL_FRONT, GL_SPECULAR, [1.0, 1.0, 1.0, 1.0])
80    glMaterialf(GL_FRONT, GL_SHININESS, 30.0)
81    glTranslate(*xyz)
82    glutSolidSphere(radius, 20, 20)
83    glPopMatrix()
def cylinder_between(x1, y1, z1, x2, y2, z2, length, radius):
85def cylinder_between(x1, y1, z1, x2, y2, z2, length, radius):
86    v = [x2-x1, y2-y1, z2-z1]
87    axis = (1, 0, 0) if np.hypot(v[0], v[1]) < 0.001 else np.cross(v, (0, 0, 1))
88    angle = -np.arctan2(np.hypot(v[0], v[1]), v[2]) * 180/np.pi
89    glPushMatrix()
90    glTranslate(x1, y1, z1)
91    glRotate(angle, *axis)
92    gluCylinder(gluNewQuadric(), radius, radius, length, 10, 10)
93    glPopMatrix()
def display():
 95def display():
 96    global selectedAtom
 97    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
 98    
 99    # Draw atoms
100    for i in range(totalNumAtoms):
101        draw_sphere([xCoord[i], yCoord[i], zCoord[i]], 
102                   float(Data.atomicRadius[atomicNumber[i]])/2, 
103                   Data.CPKcolorRGB[atomicNumber[i]])
104    
105    # Draw bonds
106    for i in range(len(bondStart)):
107        start, end = bondStart[i], bondEnd[i]
108        x1, y1, z1 = xCoord[start], yCoord[start], zCoord[start]
109        x2, y2, z2 = xCoord[end], yCoord[end], zCoord[end]
110        
111        # First half with start atom color
112        color = [c/255.0 for c in Data.CPKcolorRGB[atomicNumber[start]]] + [1.0]
113        glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color)
114        cylinder_between(x1, y1, z1, (x1+x2)/2, (y1+y2)/2, (z1+z2)/2, bondLengths[i]/2, 0.1)
115        
116        # Second half with end atom color
117        color = [c/255.0 for c in Data.CPKcolorRGB[atomicNumber[end]]] + [1.0]
118        glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color)
119        cylinder_between((x1+x2)/2, (y1+y2)/2, (z1+z2)/2, x2, y2, z2, bondLengths[i]/2, 0.1)
120    
121    # Display selected atom info and zoom level
122    if selectedAtom >= 0:
123        glDisable(GL_LIGHTING)
124        glColor3f(1, 1, 1)
125        glRasterPos3f(maxDistance/3/zoom_factor, maxDistance/3/zoom_factor, 0)
126        info = f"Atom {selectedAtom}: {Data.elementSymbols[atomicNumber[selectedAtom]]} ({xCoord[selectedAtom]:.3f}, {yCoord[selectedAtom]:.3f}, {zCoord[selectedAtom]:.3f})"
127        for char in info:
128            glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, ord(char))
129        glEnable(GL_LIGHTING)
130    
131    # Display zoom level
132    glDisable(GL_LIGHTING)
133    glColor3f(0.8, 0.8, 0.8)
134    glRasterPos3f(-maxDistance/2.5/zoom_factor, maxDistance/2.5/zoom_factor, 0)
135    zoom_info = f"Zoom: {zoom_factor:.2f}x"
136    for char in zoom_info:
137        glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, ord(char))
138    glEnable(GL_LIGHTING)
139    
140    glutSwapBuffers()
def mouse_click(button, state, x, y):
142def mouse_click(button, state, x, y):
143    global _mouse_dragging, _previous_mouse_position, selectedAtom
144    if button == GLUT_LEFT_BUTTON:
145        if state == GLUT_DOWN:
146            _mouse_dragging = True
147            _previous_mouse_position = (x, y)
148            
149            # Check for atom selection
150            viewport = glGetIntegerv(GL_VIEWPORT)
151            modelview = glGetDoublev(GL_MODELVIEW_MATRIX)
152            projection = glGetDoublev(GL_PROJECTION_MATRIX)
153            
154            winY = viewport[3] - y
155            _, _, winZ = gluUnProject(x, winY, 0.5, modelview, projection, viewport)
156            
157            min_dist, closest = float('inf'), -1
158            for i in range(totalNumAtoms):
159                winX, winY, _ = gluProject(xCoord[i], yCoord[i], zCoord[i], modelview, projection, viewport)
160                dist = np.sqrt((x - winX)**2 + (y - (viewport[3] - winY))**2)
161                if dist < 20 and dist < min_dist:  # 20 pixel tolerance
162                    min_dist, closest = dist, i
163            
164            selectedAtom = closest
165            print(f"Selected atom {selectedAtom}: {Data.elementSymbols[atomicNumber[selectedAtom]] if selectedAtom >= 0 else 'None'}")
166            glutPostRedisplay()
167        else:
168            _mouse_dragging = False
def mouse_motion(x, y):
170def mouse_motion(x, y):
171    global _previous_mouse_position
172    if _mouse_dragging and _previous_mouse_position:
173        dx, dy = (x - _previous_mouse_position[0])/100.0, (y - _previous_mouse_position[1])/100.0
174        glRotatef(dx * 50, 0, 1, 0)
175        glRotatef(dy * 50, 1, 0, 0)
176        _previous_mouse_position = (x, y)
177        glutPostRedisplay()
def keyboard(key, x, y):
179def keyboard(key, x, y):
180    global zoom_factor
181    if key == b'\x1b':  # Escape key
182        glutLeaveMainLoop()
183    elif key == b'+' or key == b'=':  # Plus key for zoom in
184        zoom_in()
185        print(f"Zoom: {zoom_factor:.2f}x")
186    elif key == b'-':  # Minus key for zoom out
187        zoom_out()
188        print(f"Zoom: {zoom_factor:.2f}x")
189    elif key == b'r' or key == b'R':  # Reset zoom
190        zoom_factor = 1.0
191        update_projection()
192        glutPostRedisplay()
193        print("Zoom reset to 1.0x")
def special_keys(key, x, y):
195def special_keys(key, x, y):
196    """Handle special keys like Page Up/Down for zooming"""
197    if key == GLUT_KEY_PAGE_UP:
198        zoom_in()
199        print(f"Zoom: {zoom_factor:.2f}x")
200    elif key == GLUT_KEY_PAGE_DOWN:
201        zoom_out()
202        print(f"Zoom: {zoom_factor:.2f}x")

Handle special keys like Page Up/Down for zooming

def visualize(mol, title='PyFock | CrysX - 3D Viewer'):
204def visualize(mol, title='PyFock | CrysX - 3D Viewer'):
205    initializeGlobalVars(mol)
206    
207    glutInit(sys.argv)
208    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
209    glutInitWindowSize(700, 700)
210    glutCreateWindow(title)
211    
212    glClearColor(0.1, 0.1, 0.1, 1.0)
213    glEnable(GL_DEPTH_TEST)
214    glEnable(GL_LIGHTING)
215    glEnable(GL_LIGHT0)
216    glLightfv(GL_LIGHT0, GL_POSITION, [10, 10, 10, 1])
217    glLightfv(GL_LIGHT0, GL_DIFFUSE, [1, 1, 1, 1])
218    
219    # Set initial projection with zoom support
220    update_projection()
221    glMatrixMode(GL_MODELVIEW)
222    glLoadIdentity()
223    
224    glutDisplayFunc(display)
225    glutMouseFunc(mouse_click)
226    glutMotionFunc(mouse_motion)
227    glutKeyboardFunc(keyboard)
228    glutSpecialFunc(special_keys)
229    
230    # Register mouse wheel callback if available
231    try:
232        glutMouseWheelFunc(mouse_wheel)
233    except:
234        pass  # Mouse wheel not supported in older PyOpenGL versions
235    
236    print("Controls:")
237    print("- Click atoms to see coordinates")
238    print("- Drag to rotate")
239    print("- Mouse wheel: Zoom in/out")
240    print("- '+' or '=': Zoom in")
241    print("- '-': Zoom out") 
242    print("- 'R': Reset zoom")
243    print("- Page Up/Down: Zoom in/out")
244    print("- ESC: Exit")
245    print(f"Current zoom: {zoom_factor:.2f}x")
246    
247    glutMainLoop()