# Another simple example of how PyMOL can be controlled using a web browser # using Python's built-in web server capabilities try: import BaseHTTPServer except ImportError: import http.server as BaseHTTPServer import time import cgi import threading import traceback import os, sys, re from pymol import cmd from chempy.sdf import SDF # example 3D sd file input_sdf = os.environ['PYMOL_PATH']+"/test/dat/ligs3d.sdf" class SafeDict(dict): ''' we will need to synchronize access if we later adopt a multi-threaded approach ''' pass # global singleton class for holding server state ServerState = SafeDict() # we're not maintaining session tokens yet... default_token = 0 def write_table(out, table): out.write('\n') out.write('
\n') out.write('') out.write('\n') out.write('
') out.write('\n') out.write('
\n') out.write('\n') header = table['header'] for heading in header: out.write('') body = table['body'] for row in body: out.write('') for col in row: out.write('') out.write('\n') out.write('
') out.write(heading) out.write('
') out.write(col) out.write('
') out.write('\n') out.write('
') out.write('\n') _server = None def _shutdown(self_cmd=cmd): global _server if _server != None: _server.socket.close() self_cmd.quit() class PymolHandler(BaseHTTPServer.BaseHTTPRequestHandler): def log_message(self, format, *args): # nuke logging for the time being since it slows down PyMOL pass def do_css(self): self.send_response(200) self.send_header('Content-type','text/css') self.end_headers() self.wfile.write(''' table { border-width: 1px 1px 1px 1px; border-spacing: 1px; border-style: outset outset outset outset; border-color: gray gray gray gray; border-collapse: separate; background-color: gray; } table th { border-width: 1px 1px 1px 1px; padding: 2px 5px 2px 5px; border-style: inset inset inset inset; border-color: gray gray gray gray; background-color: lightblue; -moz-border-radius: 0px 0px 0px 0px; } table td { border-width: 1px 1px 1px 1px; padding: 2px 5px 2px 5px; border-style: inset inset inset inset; border-color: gray gray gray gray; background-color: white; -moz-border-radius: 0px 0px 0px 0px; } a:link {text-decoration: none; color: blue; } a:visited {text-decoration: none; color: blue; } a:active {text-decoration: none; color: blue; } ''') def do_js(self): self.send_response(200) self.send_header('Content-type','text/javascript') self.end_headers() self.wfile.write(''' function load(molid) { // unnecessary...but keeping it around for later... // this doesnt actually work anyway! document.forms['hidden'].method='get'; document.forms['hidden'].action='http://localhost:8080/load.pymol?test=1&molid=' + molid; document.forms['hidden'].submit(); } ''') def do_pymol(self): if "table.pymol" in self.path: # send table session = ServerState[default_token] self.send_response(200) self.send_header('Content-type', 'text/html') self.send_header('Cache-control', 'no-cache') self.send_header('Pragma', 'no-cache') self.end_headers() if 'table' not in session: self.wfile.write("

No table defined.

\n") else: write_table(self.wfile, session['table']) elif "quit.pymol" in self.path: self.wfile.write('

Quitting...

') self.wfile.flush() _shutdown() elif "load.pymol" in self.path: self.send_response(200) self.send_header('Content-type', 'text/html') self.send_header('Cache-control', 'no-cache') self.send_header('Pragma', 'no-cache') self.end_headers() mo = re.search("molid\=([A-Z0-9]+)",self.path) if mo: mol_id = mo.groups(1)[0] session = ServerState[default_token] mol_dict = session['data']['mol_dict'] self_cmd = session['cmd'] if mol_id in self_cmd.get_names('objects'): if mol_id in self_cmd.get_names('objects',enabled_only=1): self.wfile.write("

Disabling %s...

"%mol_id) self_cmd.disable(mol_id) else: self.wfile.write("

Enabling %s...

"%mol_id) self_cmd.enable(mol_id) else: self.wfile.write("

Loading %s...

"%mol_id) self_cmd.read_molstr(mol_dict[mol_id], mol_id) self_cmd.show_as("sticks",mol_id) else: self.wfile.write("

Error processing query: %s

"%self.path) else: # start page self.send_response(200) self.send_header('Content-type', 'text/html') self.send_header('Cache-control', 'no-cache') self.send_header('Pragma', 'no-cache') self.end_headers() self.wfile.write("

Unhandled PyMOL request

") self.wfile.flush() def do_GET(self): try: doc = self.path.split('?')[0] if doc.endswith('.pymol'): # PyMOL try: self.do_pymol() except: traceback.print_exc() elif doc.endswith('.js'): # Javascript self.do_js() elif doc.endswith('.css'): # Javascript self.do_css() elif doc.endswith('.html'): f = open('.'+self.path) # UNSAFE!!! self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(f.read()) f.close() except IOError: self.send_error(404,'File Not Found: %s' % self.path) def do_POST(self): # not currently used global rootnode try: ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) if ctype == 'multipart/form-data': query=cgi.parse_multipart(self.rfile, pdict) self.send_response(301) self.end_headers() upfilecontent = query.get('upfile') print("filecontent", upfilecontent[0]) self.wfile.write('POST OK.

'); self.wfile.write(upfilecontent[0]); except : pass def table_from_data(data): # pull MOLID to the far left col_id_list = ['MOLID'] + [x for x in data['col_id_list'] if x!='MOLID'] content = data['content'] # create the header fields header = [] for col_id in col_id_list: header.append(col_id) # create the body body = [] for row_id in data['row_id_list']: row = [] for col_id in col_id_list: if col_id == 'MOLID': text = content.get( (row_id,col_id),'') row.append(''%text + text + '') else: row.append( content.get( (row_id,col_id),'' )) body.append(row) return { 'header' : header, 'body' : body, } def data_from_sdf(sdf_file_path): mol_dict = {} row_id_list = [] row_id_dict = {} col_id_list = [] col_id_dict = {} # first pass, load the identifiers, MOL files, and tag names col = 0 sdf = SDF(sdf_file_path) while 1: rec = sdf.read() if not rec: break mol = rec.get('MOL') # get the unique identifier mol_id = mol[0].strip() # store the MOL record mol_dict[mol_id] = ''.join(mol) # add row (assuming mol_id is unique) row_id_list.append(mol_id) row_id_dict[mol_id] = None # add column (if new) for key in rec.kees: if key != 'MOL': if key not in col_id_dict: col_id_list.append(key) col_id_dict[key] = None # second pass, read the actual data into the table structure content = {} sdf = SDF(sdf_file_path) while 1: rec = sdf.read() if not rec: break mol_id = rec.get('MOL')[0].strip() for key in rec.kees: if key != 'MOL': content[ (mol_id,key) ] = rec.get(key)[0].strip() return { 'content' : content, 'row_id_list' : row_id_list, 'col_id_list' : col_id_list, 'mol_dict' : mol_dict } def open_browser(): import webbrowser time.sleep(1) webbrowser.open('http://localhost:8080/table.pymol') # import os # os.system('open http://localhost:8080/start.pymol') def main(): try: global _server _server = BaseHTTPServer.HTTPServer(('', 8080), PymolHandler) print('started httpserver...') _server.serve_forever() except KeyboardInterrupt: print('^C received, shutting down server') _server.socket.close() if __name__ == '__main__': print("this script must be run from within PyMOL") if __name__ == 'pymol': session = SafeDict() session['cmd'] = cmd # could replaced by instance instead of module session['data'] = data_from_sdf(input_sdf) session['table'] = table_from_data(session['data']) ServerState[default_token] = session t = threading.Thread(target=main) t.setDaemon(1) t.start() t = threading.Thread(target=open_browser) t.setDaemon(1) t.start() """ """