475 lines
14 KiB
Python
475 lines
14 KiB
Python
""" an XML-RPC server to allow remote control of PyMol
|
|
|
|
Author: Greg Landrum (glandrum@users.sourceforge.net)
|
|
Created: January 2002
|
|
$LastChangedDate$
|
|
License: PyMol
|
|
Requires:
|
|
- a python xmlrpclib distribution containing the SimpleXMLRPCServer
|
|
module (1.0 or greater should be fine)
|
|
- python with threading enabled
|
|
|
|
RD Version: $Rev$
|
|
|
|
Modified 2013-04-17 Thomas Holder, Schrodinger, Inc.
|
|
"""
|
|
|
|
import sys
|
|
import xmlrpc.server as SimpleXMLRPCServer
|
|
import threading,os,tempfile
|
|
from pymol import cmd,cgo
|
|
|
|
# initial port to try for the server
|
|
_xmlPort=9123
|
|
# number of alternate ports to try if the first fails
|
|
_nPortsToTry=5
|
|
|
|
def rpcPing():
|
|
""" Used to establish whether or not the server is alive.
|
|
|
|
This is a good thing to call after establishing a connection just to
|
|
make sure that everything is ok.
|
|
|
|
Returns 1
|
|
|
|
"""
|
|
return 1
|
|
|
|
def rpcLabel(pos,labelText,id='lab1',color=(1,1,1)):
|
|
""" create a text label
|
|
|
|
Arguments:
|
|
pos: a 3 tuple with the position of the label
|
|
text: a string with the label
|
|
color: a 3 tuple with the color of the label. (1,1,1) is white
|
|
id: (OPTIONAL) the name of the object to be created
|
|
|
|
NOTE:
|
|
at the moment this is, how you say, a hack
|
|
|
|
"""
|
|
cmd.pseudoatom(id, label=repr(labelText), elem='C', pos=pos)
|
|
cmd.set_color("%s-color"%id,color)
|
|
cmd.color("%s-color"%id,id)
|
|
return 1
|
|
|
|
def rpcResetCGO(id):
|
|
""" removes a CGO from the local dictionary
|
|
|
|
"""
|
|
global cgoDict
|
|
if id=="*":
|
|
cgoDict={}
|
|
res = 1
|
|
elif id in cgoDict:
|
|
del(cgoDict[id])
|
|
res = 1
|
|
else:
|
|
res = 0
|
|
return res
|
|
|
|
def rpcSphere(pos,rad,color,id='cgo',extend=1,
|
|
transparent=0,transparency=0.5):
|
|
""" create a sphere
|
|
|
|
Arguments:
|
|
pos: a 3 tuple with the position of the sphere
|
|
rad: a float with the radius
|
|
color: a 3 tuple with the color of the sphere. (1,1,1) is white
|
|
id: (OPTIONAL) the name of the object to be created
|
|
extend: (OPTIONAL) if this is nonzero, the object will be cleared
|
|
before adding the new sphere. Otherwise the sphere is appended
|
|
to the ojbect
|
|
transparent: (OPTIONAL) sets the object to be transparent
|
|
transparency: (OPTIONAL) the percent transparency of the object
|
|
"""
|
|
r,g,b = color
|
|
x,y,z = pos
|
|
if extend:
|
|
obj = cgoDict.get(id,[])
|
|
else:
|
|
obj = []
|
|
if not transparent:
|
|
o = []
|
|
else:
|
|
o = [cgo.ALPHA,1-transparency]
|
|
o.extend([cgo.COLOR,r,g,b,cgo.SPHERE,x,y,z,rad])
|
|
obj.extend(o)
|
|
cgoDict[id] = obj
|
|
cmd.load_cgo(obj,id,1)
|
|
return 1
|
|
|
|
def rpcRenderCGO(cgoV,id='cgo',extend=1):
|
|
|
|
""" renders a CGO vector
|
|
|
|
Arguments:
|
|
cgoV: a vector of floats
|
|
id: (OPTIONAL) the name of the object to be created
|
|
extend: (OPTIONAL) if this is nonzero, the object will be cleared
|
|
before adding the new sphere. Otherwise the sphere is appended
|
|
to the ojbect
|
|
"""
|
|
if extend:
|
|
obj = cgoDict.get(id,[])
|
|
else:
|
|
obj = []
|
|
obj.extend(cgoV)
|
|
cmd.load_cgo(obj,id,1)
|
|
return 1
|
|
|
|
|
|
def rpcSpheres(sphereD,id='cgo',extend=1):
|
|
""" create a sphere
|
|
|
|
Arguments:
|
|
sphereD: a series of (pos,rad,color,transparent,transparency) tuples
|
|
id: (OPTIONAL) the name of the object to be created
|
|
extend: (OPTIONAL) if this is nonzero, the object will be cleared
|
|
before adding the new sphere. Otherwise the sphere is appended
|
|
to the ojbect
|
|
"""
|
|
if extend:
|
|
obj = cgoDict.get(id,[])
|
|
else:
|
|
obj = []
|
|
for pos,rad,color,transparent,transparency in sphereD:
|
|
r,g,b = color
|
|
x,y,z = pos
|
|
if not transparent:
|
|
o = []
|
|
else:
|
|
o = [cgo.ALPHA,1-transparency]
|
|
o.extend([cgo.COLOR,r,g,b,cgo.SPHERE,x,y,z,rad])
|
|
obj.extend(o)
|
|
cgoDict[id] = obj
|
|
cmd.load_cgo(obj,id,1)
|
|
return 1
|
|
|
|
def rpcCylinder(end1,end2,rad,color1,id='cgo',color2=None,extend=1,
|
|
transparent=0,transparency=0.5):
|
|
""" create a cylinder
|
|
|
|
Arguments:
|
|
end1: a 3 tuple with the position of end1 of the sphere
|
|
end2: a 3 tuple with the position of end1 of the sphere
|
|
rad: a float with the radius
|
|
color1: a 3 tuple with the color of end1 of the sphere. (1,1,1) is white
|
|
id: (OPTIONAL) the name of the object to be created
|
|
color2: (OPTIONAL) a 3 tuple with the color of end2 of the sphere. (1,1,1)
|
|
is white
|
|
extend: (OPTIONAL) if this is nonzero, the object will be cleared
|
|
before adding the new sphere. Otherwise the sphere is appended
|
|
to the ojbect
|
|
transparent: (OPTIONAL) sets the object to be transparent
|
|
transparency: (OPTIONAL) the percent transparency of the object
|
|
|
|
NOTE: the reason that color2 follows id is that I think clients are
|
|
going to be interested in setting the id more often than they are going
|
|
to care about the second color.
|
|
|
|
"""
|
|
global cgoDict
|
|
|
|
if color2 is None: color2 = color1
|
|
r1,g1,b1 = color1
|
|
r2,g2,b2 = color2
|
|
x1,y1,z1 = end1
|
|
x2,y2,z2 = end2
|
|
if extend:
|
|
obj = cgoDict.get(id,[])
|
|
else:
|
|
obj = []
|
|
if not transparent:
|
|
o = []
|
|
else:
|
|
o = [cgo.ALPHA,1-transparency]
|
|
o.extend([cgo.CYLINDER,x1,y1,z1,x2,y2,z2,rad,r1,g1,b1,r2,g2,b2,])
|
|
obj.extend(o)
|
|
cgoDict[id] = obj
|
|
cmd.load_cgo(obj,id,1)
|
|
return 1
|
|
|
|
def rpcDeleteObject(objName):
|
|
""" deletes an object """
|
|
try:
|
|
cmd.delete(objName)
|
|
except:
|
|
res = 0
|
|
else:
|
|
res = 1
|
|
return res
|
|
|
|
def rpcDeleteAll():
|
|
""" deletes all objects """
|
|
res = cmd.delete('all')
|
|
if res is not None:
|
|
return res
|
|
else:
|
|
return ''
|
|
|
|
def colorObj(objName,colorScheme):
|
|
""" sets an molecule's color scheme
|
|
Arguments:
|
|
- objName: the object (molecule) to change
|
|
- colorScheme: name of the color scheme to use
|
|
for the object (should be either 'std' or one of the
|
|
color schemes defined in pymol.utils)
|
|
"""
|
|
if colorScheme:
|
|
if colorScheme == 'std':
|
|
# this is an adaptation of the cbag scheme from util.py, but
|
|
# with a gray carbon.
|
|
cmd.color("magenta","("+objName+")",quiet=1)
|
|
cmd.color("oxygen","(elem O and "+objName+")",quiet=1)
|
|
cmd.color("nitrogen","(elem N and "+objName+")",quiet=1)
|
|
cmd.color("sulfur","(elem S and "+objName+")",quiet=1)
|
|
cmd.color("hydrogen","(elem H and "+objName+")",quiet=1)
|
|
cmd.color("gray","(elem C and "+objName+")",quiet=1)
|
|
elif hasattr(utils,colorScheme):
|
|
fn = getattr(utils,colorScheme)
|
|
fn(objName,quiet=1)
|
|
res = 1
|
|
else:
|
|
res = 0
|
|
return res
|
|
|
|
def rpcLoadPDB(data,objName,colorScheme='',replace=1):
|
|
""" loads a molecule from a pdb string
|
|
|
|
Arguments:
|
|
data: the mol block
|
|
objName: name of the object to create
|
|
colorScheme: (OPTIONAL) name of the color scheme to use
|
|
for the molecule (should be either 'std' or one of the
|
|
color schemes defined in pymol.utils)
|
|
replace: (OPTIONAL) if an object with the same name already
|
|
exists, delete it before adding this one
|
|
|
|
|
|
"""
|
|
from pymol import util
|
|
if replace:
|
|
cmd.delete(objName)
|
|
res = cmd.read_pdbstr(data,objName)
|
|
colorObj(objName,colorScheme)
|
|
if res is not None:
|
|
return res
|
|
else:
|
|
return ''
|
|
|
|
|
|
def rpcLoadMolBlock(data,objName,colorScheme='',replace=1):
|
|
""" loads a molecule from a mol block
|
|
|
|
Arguments:
|
|
data: the mol block
|
|
objName: name of the object to create
|
|
colorScheme: (OPTIONAL) name of the color scheme to use
|
|
for the molecule (should be either 'std' or one of the
|
|
color schemes defined in pymol.utils)
|
|
replace: (OPTIONAL) if an object with the same name already
|
|
exists, delete it before adding this one
|
|
|
|
"""
|
|
from pymol import util
|
|
if replace:
|
|
cmd.delete(objName)
|
|
res = cmd.read_molstr(data,objName)
|
|
colorObj(objName,colorScheme)
|
|
if res is not None:
|
|
return res
|
|
else:
|
|
return ''
|
|
|
|
|
|
def rpcLoadFile(fileName,objName='',format='',colorScheme='',replace=1):
|
|
""" loads an object from a file
|
|
|
|
Arguments:
|
|
fileName: the file to load
|
|
objName: (OPTIONAL) name of the object to create
|
|
format: (OPTIONAL) the format of the input file
|
|
colorScheme: (OPTIONAL) name of the color scheme to use
|
|
for the object (should be either 'std' or one of the
|
|
color schemes defined in pymol.utils)
|
|
replace: (OPTIONAL) if an object with the same name already
|
|
exists, delete it before adding this one
|
|
"""
|
|
if not objName:
|
|
objName = fileName.split('.')[0]
|
|
if replace:
|
|
cmd.delete(objName)
|
|
res = cmd.load(fileName,objName,format=format)
|
|
colorObj(objName,colorScheme)
|
|
if res is not None:
|
|
return res
|
|
else:
|
|
return ''
|
|
|
|
|
|
def rpcLoadSurface(fileName,objName,format='',surfaceLevel=1.0):
|
|
""" loads surface data from a file and adds an isosurface
|
|
|
|
Arguments:
|
|
fileName: the file to load
|
|
objName: (OPTIONAL) name of the object to create
|
|
format: (OPTIONAL) the format of the input file
|
|
surfaceLevel: (OPTIONAL) the isosurface level
|
|
|
|
"""
|
|
if not objName:
|
|
objName = fileName.split('.')[0]
|
|
gridName = 'grid-%s'%objName
|
|
res = cmd.load(fileName,gridName,format='')
|
|
cmd.isosurface(objName,gridName,level=surfaceLevel)
|
|
if res is not None:
|
|
return res
|
|
else:
|
|
return ''
|
|
|
|
def rpcLoadSurfaceData(data,objName='surface',format='',surfaceLevel=1.0):
|
|
""" loads surface data from a string and adds an isosurface
|
|
|
|
Arguments:
|
|
data: the data to load
|
|
objName: (OPTIONAL) name of the object to create
|
|
format: (OPTIONAL) the format of the input file
|
|
surfaceLevel: (OPTIONAL) the isosurface level
|
|
|
|
"""
|
|
gridName = 'grid-%s'%objName
|
|
# it would be nice if we didn't have to go by way of the temporary file,
|
|
# but at the moment pymol will only read shapes from files
|
|
tempnm = tempfile.mktemp('.grd')
|
|
open(tempnm,'w+').write(data)
|
|
res = rpcLoadSurface(tempnm,objName,format='',surfaceLevel=surfaceLevel)
|
|
os.unlink(tempnm)
|
|
if res is not None:
|
|
return res
|
|
else:
|
|
return ''
|
|
|
|
|
|
def rpcRotate(vect,objName='',state=-1):
|
|
""" rotates objects
|
|
|
|
Arguments:
|
|
- vect: a sequence with x y and z rotations
|
|
- objName: (OPTIONAL) object to be rotated
|
|
- state: (OPTIONAL) if zero only visible states are rotated,
|
|
if -1 (the default), all states are rotated
|
|
|
|
"""
|
|
cmd.rotate('x',vect[0],objName,state=state)
|
|
cmd.rotate('y',vect[1],objName,state=state)
|
|
cmd.rotate('z',vect[2],objName,state=state)
|
|
return 1
|
|
|
|
def rpcGetNames(what='selections',enabledOnly=1):
|
|
""" returns the results of cmd.get_names(what) """
|
|
return cmd.get_names(what,enabled_only=enabledOnly)
|
|
|
|
def rpcIdAtom(what='all',mode=0):
|
|
""" returns the results of cmd.id_atom(what) """
|
|
return cmd.id_atom(what,mode=mode)
|
|
|
|
def rpcGetAtomCoords(what='all',state=0):
|
|
""" returns the results of cmd.get_atom_coords(what,state) """
|
|
return cmd.get_atom_coords(what,state=state)
|
|
|
|
|
|
def rpcHelp(what=''):
|
|
""" returns general help text or help on a particular command """
|
|
global serv
|
|
res = 'Command Not Found'
|
|
if not what:
|
|
res = list(serv.funcs.keys())
|
|
else:
|
|
funcs = serv.funcs
|
|
if what in funcs:
|
|
fn = funcs[what]
|
|
res = "Function: %s("%what
|
|
defs = fn.__defaults__
|
|
if defs:
|
|
code = fn.__code__
|
|
nDefs = len(defs)
|
|
args = []
|
|
i = -1
|
|
for i in range(code.co_argcount - nDefs):
|
|
args.append(code.co_varnames[i])
|
|
for j in range(nDefs):
|
|
vName = code.co_varnames[j+i+1]
|
|
args.append("%s=%s"%(vName,repr(defs[j])))
|
|
res += ','.join(args)
|
|
res += ')\n'
|
|
if fn.__doc__:
|
|
res += fn.__doc__
|
|
return res
|
|
|
|
|
|
def launch_XMLRPC(hostname='',port=_xmlPort,nToTry=_nPortsToTry):
|
|
""" launches the xmlrpc server into a separate thread
|
|
|
|
Arguments:
|
|
hostname: (OPTIONAL) name of the host for the server
|
|
(defaults to be the name of the localhost)
|
|
port: (OPTIONAL) the first port to try for the server
|
|
nToTry: (OPTIONAL) the number of possible ports to try
|
|
(in case the first can't be opened)
|
|
|
|
"""
|
|
if not hostname:
|
|
import os
|
|
hostname = os.environ.get('PYMOL_RPCHOST', 'localhost')
|
|
|
|
global cgoDict,serv
|
|
cgoDict = {}
|
|
for i in range(nToTry):
|
|
try:
|
|
serv = SimpleXMLRPCServer.SimpleXMLRPCServer((hostname,port+i),logRequests=0,
|
|
allow_none=True)
|
|
except:
|
|
serv = None
|
|
else:
|
|
break
|
|
if serv:
|
|
print('xml-rpc server running on host %s, port %d'%(hostname,port+i))
|
|
|
|
# import PyMOL API
|
|
from pymol import api
|
|
serv.register_instance(cmd)
|
|
|
|
# legacy stuff with unique names
|
|
serv.register_function(rpcPing,'ping')
|
|
serv.register_function(rpcResetCGO,'resetCGO')
|
|
serv.register_function(rpcRenderCGO,'renderCGO')
|
|
serv.register_function(rpcSphere,'sphere')
|
|
serv.register_function(rpcSpheres,'spheres')
|
|
serv.register_function(rpcCylinder,'cylinder')
|
|
serv.register_function(rpcDeleteObject,'deleteObject')
|
|
serv.register_function(rpcDeleteAll,'deleteAll')
|
|
serv.register_function(rpcLoadPDB,'loadPDB')
|
|
serv.register_function(rpcLoadMolBlock,'loadMolBlock')
|
|
serv.register_function(rpcLoadSurface,'loadSurface')
|
|
serv.register_function(rpcLoadSurfaceData,'loadSurfaceData')
|
|
serv.register_function(rpcLoadFile,'loadFile')
|
|
serv.register_function(rpcGetNames,'getNames')
|
|
serv.register_function(api.count_atoms,'countAtoms')
|
|
serv.register_function(rpcIdAtom,'idAtom')
|
|
serv.register_function(rpcHelp,'help')
|
|
serv.register_function(rpcGetAtomCoords,'getAtomCoords')
|
|
|
|
# legacy stuff, should be removed because overwrites API names!
|
|
serv.register_function(rpcLabel,'label') # pseudoatom
|
|
serv.register_function(rpcRotate,'rotate')
|
|
|
|
serv.register_introspection_functions()
|
|
t = threading.Thread(target=serv.serve_forever)
|
|
t.setDaemon(1)
|
|
t.start()
|
|
else:
|
|
print('xml-rpc server could not be started')
|
|
|
|
# vi:expandtab:smarttab:sw=2
|