modules/pymol/__init__.py (410 lines of code) (raw):
'''
PyMOL Molecular Graphics System
Copyright (c) Schrodinger, Inc.
Supported ways to launch PyMOL:
If $PYMOL_PATH is a non-default location, it must be set and exported
before launching PyMOL.
From a terminal:
shell> python /path/to/pymol/__init__.py [args]
From a python main thread:
>>> # with GUI
>>> import pymol
>>> pymol.finish_launching()
>>> # without GUI
>>> import pymol
>>> pymol.finish_launching(['pymol', '-cq'])
'''
from __future__ import absolute_import
from __future__ import print_function
import os
import sys
import __main__
if __name__ == '__main__':
# PyMOL launched as "python pymol/__init__.py"
# or via execfile(".../pymol/__init__.py",...) from main
# or as "python -m pymol.__init__"
if 'pymol' not in sys.modules:
# "python /abc/pymol/__init__.py" will add /abc/pymol to PYTHONPATH
# (we don't want that), but not /abc and not the current directory (we
# want those)
pymol_base = os.path.dirname(os.path.realpath(__file__))
site_packages = os.path.dirname(pymol_base)
# remove /abc/pymol
if pymol_base in sys.path:
sys.path.remove(pymol_base)
# add /abc
if site_packages not in sys.path:
sys.path.insert(0, site_packages)
# add current directory
if '' not in sys.path:
sys.path.insert(0, '')
# arguments default to sys.argv... but also support execfile(...)
# from a terminal where the user could set pymol_argv
args = getattr(__main__, "pymol_argv", None)
# standard launch (consume main thread)
import pymol
pymol.launch(args)
# this should never be reached because PyMOL will exit the process
raise SystemExit
IS_PY2 = sys.version_info[0] == 2
IS_PY3 = sys.version_info[0] == 3
IS_WINDOWS = sys.platform.startswith('win')
IS_MACOS = sys.platform.startswith('darwin')
IS_LINUX = sys.platform.startswith('linux')
if IS_PY3:
# legacy string API, still used by Pmw for example
import string
for attr in ['capitalize', 'count', 'find', 'index', 'lower',
'replace', 'rstrip', 'split', 'strip', 'upper', 'zfill']:
setattr(string, attr, getattr(str, attr))
string.letters = string.ascii_letters
string.lowercase = string.ascii_lowercase
string.join = lambda words, sep=' ': sep.join(words)
string.atoi = int
import _thread as thread
else:
import thread
import copy
import threading
import re
import time
import traceback
import math
from . import invocation
def _init_internals(_pymol):
# Create a temporary object "stored" in the PyMOL global namespace
# for usage with evaluate based-commands such as alter
_pymol.stored = Scratch_Storage()
# Create a permanent object in the PyMOL global namespace
# that will be picked and unpickled along with the session
_pymol.session = Session_Storage()
# This global will be non-None if logging is active
# (global variable used for efficiency)
_pymol._log_file = None
# This global will be non-None if an external gui
# exists. It mainly exists so that events which occur
# in the Python thread can be handed off to the
# external GUI thread through one or more FIFO Queues
# (global variable used for efficiency)
_pymol._ext_gui = None
# lists of functions to call when saving and restoring pymol session objects
# The entry 'None' represents the PyMOL C-API function call
_pymol._session_save_tasks = [ None ]
_pymol._session_restore_tasks = [ None ]
# cached results (as a list):
# [ [size, (hash1, hash2, ... ), (inp1, inp2, ...), output],
# [size, (hash1, hash2, ... ), (inp1, inp2, ...), output],
# ... ]
_pymol._cache = []
# standard input reading thread
_pymol._stdin_reader_thread = None
# stored views
_pymol._view_dict = {}
_pymol._view_dict_sc = None
# stored scenes
_pymol._scene_dict_sc = None
_pymol._scene_counter = 1
_pymol._scene_quit_on_action = ''
# get us a private invocation pseudo-module
_pymol._invocation = Scratch_Storage()
_pymol._invocation.options = copy.deepcopy(invocation.options)
_pymol._invocation.get_user_config = invocation.get_user_config
_pymol._invocation.parse_args = invocation.parse_args
# these locks are to be shared by all PyMOL instances within a
# single Python interpeter
_pymol.lock_api = threading.RLock() # mutex for API calls from the outside
_pymol.lock_api_c = threading.RLock() # mutex for C management of python threads
_pymol.lock_api_status = threading.RLock() # mutex for PyMOL status info
_pymol.lock_api_glut = threading.RLock() # mutex for GLUT avoidance
_pymol.lock_api_data = threading.RLock() # mutex for internal data structures
def get_version_message(v=None):
'''
Get an informative product + version string
'''
if not v:
v = _cmd.get_version()
p = "PyMOL %s " % v[0]
p += "Incentive Product" if invocation.options.incentive_product else \
"Open-Source"
if v[5]:
p += ", svn rev %s" % v[5]
return p
def guess_pymol_path():
'''
Guess PYMOL_PATH from typical locations and return it as string.
'''
init_file = os.path.abspath(__file__)
pymol_path_candidates = [
# $PYMOL_PATH == <site-packages>/pymol/pymol_path
os.path.join(os.path.dirname(init_file), 'pymol_path'),
# $PYMOL_PATH/modules/pymol/__init__.py
re.sub(r"[\/\\]modules[\/\\]pymol[\/\\]__init__\.py[c]*$", "", init_file),
# /usr/share/pymol
os.path.join(sys.prefix, 'share', 'pymol'),
]
for pymol_path in pymol_path_candidates:
if os.path.isdir(pymol_path):
return pymol_path
return '.'
def setup_environ():
# guess PYMOL_PATH if unset
if 'PYMOL_PATH' not in os.environ:
os.environ['PYMOL_PATH'] = guess_pymol_path()
# other PyMOL variables
if 'PYMOL_DATA' not in os.environ:
os.environ['PYMOL_DATA'] = os.path.join(os.environ['PYMOL_PATH'], 'data')
if 'PYMOL_SCRIPTS' not in os.environ:
os.environ['PYMOL_SCRIPTS'] = os.path.join(os.environ['PYMOL_PATH'], 'scripts')
os.environ['TUT'] = os.path.join(os.environ['PYMOL_DATA'], 'tut')
# auto-detect bundled FREEMOL (if present)
if 'FREEMOL' not in os.environ:
for test_path in ['ext', 'freemol']:
test_path = os.path.join(os.environ['PYMOL_PATH'], test_path)
if os.path.isdir(test_path):
os.environ['FREEMOL'] = test_path
break
# include FREEMOL's libpy in sys.path (if present)
if 'FREEMOL' in os.environ:
freemol_libpy = os.path.join(os.environ['FREEMOL'], "libpy")
if os.path.isdir(freemol_libpy) and freemol_libpy not in sys.path:
sys.path.append(freemol_libpy)
# set Tcl/Tk environment if we ship it in ext/lib
pymol_path = os.environ['PYMOL_PATH']
for varname, dirname in [
('TCL_LIBRARY', 'tcl8.5'),
('TK_LIBRARY', 'tk8.5')]:
dirname = os.path.join(pymol_path, "ext", "lib", dirname)
if os.path.isdir(dirname):
os.environ[varname] = dirname
def exec_str(self, string):
'''
Execute string in "self" namespace (used from C)
'''
try:
exec(string, self.__dict__, self.__dict__)
except Exception:
traceback.print_exc()
return None
def exec_deferred(self):
'''
Execute the stuff from invocations.options.deferred
'''
try:
from socket import error as socket_error
except ImportError:
socket_error = None
print('import socket failed')
cmd = self.cmd
_pymol = cmd._pymol
# read from stdin (-p)
if self.invocation.options.read_stdin and not _pymol._stdin_reader_thread:
try:
t = _pymol._stdin_reader_thread = \
threading.Thread(target=cmd._parser.stdin_reader)
t.setDaemon(1)
t.start()
except:
traceback.print_exc()
# do the deferred stuff
try:
if cmd.ready():
cmd.config_mouse(quiet=1)
for a in self.invocation.options.deferred:
if a[0:4] == "_do_":
cmd.do(a[4:])
else:
cmd.load(a, quiet=0)
except CmdException as e:
print(e)
print(" Error: Argument processing aborted due to exception (above).")
except socket_error:
# this (should) only happen if we're opening a PWG file on startup
# and the port is busy. For now, simply bail...
cmd.wizard("message",["Socket.error: ","",
"\\999Assigned socket in use.","",
"\\779Is PyMOL already launched?","",
"\\966Shutting down..."])
cmd.refresh()
cmd.do("time.sleep(2);cmd.quit()")
def adapt_to_hardware(self):
'''
optimize for (or workaround) specific hardware
'''
cmd = self.cmd
vendor, renderer, version = cmd.get_renderer()
# Quadro cards don't support GL_BACK in stereo contexts
if vendor.startswith('NVIDIA'):
if 'Quadro' in renderer:
if invocation.options.show_splash:
print(" Adapting to Quadro hardware.")
cmd.set('stereo_double_pump_mono', 1)
elif vendor.startswith('Mesa'):
if renderer[0:18]=='Mesa GLX Indirect':
pass
elif vendor.startswith('ATI'):
if renderer[0:17] == 'FireGL2 / FireGL3': # obsolete ?
if invocation.options.show_splash:
print(" Adapting to FireGL hardware.")
cmd.set('line_width', 2, quiet=1)
if IS_WINDOWS:
if sys.getwindowsversion()[0] > 5:
# prevent color corruption by calling glFlush etc.
cmd.set('ati_bugs', 1)
if 'Radeon HD' in renderer:
if invocation.options.show_splash:
print(" Adjusting settings to improve performance for ATI cards.")
if cmd.get_setting_int("use_shaders")==0:
# limit frame rate to 30 fps to avoid ATI "jello"
# where screen updates fall way behind the user.
cmd.set("max_ups", 30)
elif vendor.startswith('Microsoft'):
if renderer[0:17] == 'GDI Generic':
cmd.set('light_count', 1)
cmd.set('spec_direct', 0.7)
elif vendor.startswith("Intel"):
if "Express" in renderer:
if invocation.options.show_splash:
print(" Disabling shaders for Intel Express graphics")
cmd.set("use_shaders", 0)
elif ".100." in version:
# 4.5.0 - Build *.20.100.*
# Driver update breaks lighting
cmd.set("precomputed_lighting", quiet=0)
elif (vendor == 'nouveau'
or ' R300 ' in vendor # V: X.Org R300 Project, R: Gallium 0.4 on ATI RV370
):
if invocation.options.show_splash:
print(" Detected blacklisted graphics driver. Disabling shaders.")
cmd.set("use_shaders", 0)
# find out how many processors we have, and adjust hash
# table size to reflect available RAM
try:
import multiprocessing
ncpu = multiprocessing.cpu_count()
if ncpu > 1:
cmd.set("max_threads", ncpu)
if invocation.options.show_splash:
print(" Detected %d CPU cores."%ncpu, end=' ')
print(" Enabled multithreaded rendering.")
except:
pass
# store our adapted state as default
cmd.reinitialize("store")
def launch_gui(self):
'''
Launch if requested:
- external GUI
'''
pymol_path = os.getenv('PYMOL_PATH', '')
try:
poll = IS_MACOS
if self.invocation.options.external_gui == 3:
if 'DISPLAY' not in os.environ:
os.environ['DISPLAY'] = ':0.0'
if self.invocation.options.external_gui in (1, 3):
__import__(self.invocation.options.gui)
sys.modules[self.invocation.options.gui].__init__(self, poll,
skin = self.invocation.options.skin)
# import plugin system
import pymol.plugins
except:
traceback.print_exc()
def prime_pymol():
'''
Set the current thread as the glutThread
'''
global glutThread
if not glutThread:
glutThread = thread.get_ident()
def _launch_no_gui():
import pymol2
p = pymol2.SingletonPyMOL()
p.start()
# TODO sufficient?
while (p.idle() or p.getRedisplay() or
invocation.options.keep_thread_alive or
cmd.get_modal_draw() or
cmd.get_setting_int('keep_alive') or
cmd._pymol._stdin_reader_thread is not None):
p.draw()
# TODO needed?
cmd.sync()
p.stop()
def launch(args=None, block_input_hook=0):
'''
Run PyMOL with args
Only returns if we are running pretend GLUT.
'''
if args is None:
args = sys.argv
invocation.parse_args(args)
if invocation.options.gui == 'pmg_qt':
if invocation.options.no_gui:
return _launch_no_gui()
elif invocation.options.testing:
return pymol._cmd.test2()
try:
from pmg_qt import pymol_qt_gui
sys.exit(pymol_qt_gui.execapp())
except ImportError:
print('Qt not available, using GLUT/Tk interface')
invocation.options.gui = 'pmg_tk'
prime_pymol()
_cmd.runpymol(_cmd._get_global_C_object(), block_input_hook)
def finish_launching(args=None):
'''
Start the PyMOL process in a thread
'''
global glutThreadObject
if cmd._COb is not None:
return
import pymol
# legacy
if args is None:
args = getattr(pymol, 'pymol_argv', None)
if args is None:
args = getattr(__main__, 'pymol_argv', sys.argv)
if True:
# run PyMOL in thread
invocation.options.keep_thread_alive = 1
cmd.reaper = threading.currentThread()
glutThreadObject = threading.Thread(target=launch,
args=(list(args), 1))
glutThreadObject.start()
e = threading.Event()
# wait for the C library to initialize
while cmd._COb is None:
e.wait(0.01)
# make sure symmetry module has time to start...
while not hasattr(pymol, 'xray'):
e.wait(0.01)
class CmdException(Exception):
'''
Exception type for PyMOL commands
'''
label = "Error"
def __init__(self, message='', label=None):
self.message = message
if message:
self.args = (message,)
if label:
self.label = label
def __str__(self):
return " %s: %s" % (self.label, self.message)
class IncentiveOnlyException(CmdException):
'''
Exception type for features that are not available in Open-Source PyMOL
'''
label = "Incentive-Only-Error"
def __init__(self, message=''):
if not message:
try:
funcname = sys._getframe(1).f_code.co_name
message = '"%s" is not available in Open-Source PyMOL' % (funcname,)
except:
message = 'Not available in Open-Source PyMOL'
message += '\n\n' \
' Please visit http://pymol.org if you are interested in the\n' \
' full featured "Incentive PyMOL" version.\n'
super(IncentiveOnlyException, self).__init__(message)
class Scratch_Storage:
'''
Generic namespace
'''
def __reduce__(self):
# for loading Python 3 (new-style class) pickle with Python 2
return (self.__class__, (), self.__dict__)
def get_unused_name(self, prefix='tmp'):
'''
Get an unused name from this namespace
'''
i = 1
while True:
name = prefix + str(i)
if not hasattr(self, name):
setattr(self, name, None)
return name
i += 1
class Session_Storage:
'''
Generic namespace
'''
def __reduce__(self):
# for loading Python 3 (new-style class) pickle with Python 2
return (self.__class__, (), self.__dict__)
def _colortype(cmd):
# backwards compatible color index type for iterate, which used
# to expose colors as RGB tuples
get_color_tuple = cmd.get_color_tuple
class Color(int):
def __getitem__(self, i):
return get_color_tuple(self)[i]
def __len__(self):
return 3
return Color
######### VARIABLES ############################
glutThread = 0
######### ENVIRONMENT ##########################
setup_environ()
# initialize instance-specific module/object internals
_init_internals(sys.modules[__name__])
# maximize responsiveness
sys.setcheckinterval(1)
# get X-window support (machine_get_clipboard)
if 'DISPLAY' in os.environ:
from .xwin import *
########## C MODULE ############################
import pymol._cmd
_cmd = sys.modules['pymol._cmd']
from . import cmd
cmd._COb = None
try:
import epymol
except ImportError:
pass
########## WORKAROUND TO PREVENT "import cmd" ##############################
# Previous versions of PyMOL did relative imports and thus allowd
# "import cmd" in pymol scripts to import the pymol.cmd module. To be more
# strict and for compatibility with python3 we use absolute imports now,
# which unfortunately will import an unrelated "cmd" module from the default
# python library, and even worse will corrupt the pymol namespace with it.
# The following causes an import error for "import cmd":
class _NoCmdFinder:
def find_spec(self, fullname, path=None, target=None):
if path is None and fullname == 'cmd':
msg = 'use "from pymol import cmd" instead of "import cmd"'
raise CmdException(msg)
return None
find_module = find_spec
sys.meta_path.insert(0, _NoCmdFinder())
########## LEGACY PRINT STATEMENT FOR PYMOL COMMAND LINE ###################
if IS_PY3:
def _print_statement(*args, **_):
'''Legacy Python-2-like print statement for the PyMOL command line'''
kw = {}
if args and args[0].startswith('>>'):
kw['file'] = eval(args[0][2:])
args = args[1:]
if args and not args[-1]:
kw['end'] = ' '
args = args[:-1]
args = [eval(a) for a in args]
print(*args, **kw)
cmd.extend('print', _print_statement)