import json, re, os, inspect # Imports need to run a simple webserver from twisted.web import server, resource, http from twisted.web.resource import Resource from twisted.internet import reactor from twisted.web.static import File # import paraview modules. from paraview.web import wamp as pv_wamp from paraview.web import protocols as pv_protocols from paraview import simple from paraview import servermanager # import RPC annotation from autobahn.wamp import register as exportRpc try: import argparse except ImportError: # since Python 2.6 and earlier don't have argparse, we simply provide # the source for the same as _argparse and we use it instead. import _argparse as argparse # Module-level regex object won't have to be recompiled on each POST request # The first regex group is the RPC call, e.g., viewport.image.render # The second (optional) regex group is the view id. If not there, assume -1. endpoint = 'rpc-http' urlMatcher = re.compile(endpoint + '/([^/]+)') #urlMatcher = re.compile(endpoint + '/([^/]+)' + '(?:/([0-9]*))?') # ============================================================================= # Convenience method to parse out the rpc method name from the path. In order # to find the method name, the server expects the path to contain: # # /hpcmp/rpc.method.name # # ============================================================================= def extractRpcMethod(path): m = urlMatcher.search(path) if m: return m.group(1) #, m.group(2) else: return None # ============================================================================= # Custom visualization application session # ============================================================================= class _BasicServer(pv_wamp.PVServerProtocol): @staticmethod def add_arguments(parser): parser.add_argument("--host", type=str, default='localhost', help="the interface for the web-server to listen on (default: localhost)") parser.add_argument("--port", default=8080, type=int, help="Which port to listen on", dest="port") @staticmethod def configure(args): pass def initialize(self): # Set the base directory to the user's home area. baseDir = os.path.expanduser("~") # Bring used components self.registerVtkWebProtocol(pv_protocols.ParaViewWebMouseHandler()) self.registerVtkWebProtocol(pv_protocols.ParaViewWebViewPort()) self.registerVtkWebProtocol(pv_protocols.ParaViewWebViewPortImageDelivery()) self.registerVtkWebProtocol(pv_protocols.ParaViewWebFileListing(baseDir,baseDir)) #self.registerVtkWebProtocol(pv_protocols.ParaViewWebProxyManager(None,baseDir)) self.proxyManager = pv_protocols.ParaViewWebProxyManager(None,baseDir) self.registerVtkWebProtocol(self.proxyManager) self.colorManager = pv_protocols.ParaViewWebColorManager() self.registerVtkWebProtocol(self.colorManager) self.registerVtkWebProtocol(pv_protocols.ParaViewWebTimeHandler()) # The below dictionaries are keyed on global IDs obtained from # GetGlobalIDAsString() methods. # Each entry looks like: view, proxyId, repId, animScene self.views = {} # Each entry contains a single file proxy. self.proxies = {} # Each entry contains a single representation proxy. self.representations = {} def createView(self, fullPath): # Find the relative path for the call to proxyManager.open(relativePath). # TODO relativePath = fullPath # Create a new view. view = simple.CreateRenderView() viewId = int(view.GetGlobalIDAsString()) self.views[viewId] = (view, None, None, None) # Open the file. Throw an error if the file could not be read. response = self.proxyManager.open(relativePath) try: proxyId = int(response['id']) except ValueError: self.disposeView(viewId) return { "success": False, "error": response['reason'] } proxy = self.proxyManager.mapIdToProxy(proxyId) self.proxies[proxyId] = proxy # Find the corresponding representation proxy. rep = simple.GetRepresentation(proxy=proxy, view=view) repId = int(rep.GetGlobalIDAsString()) self.representations[repId] = rep # Create a new AnimationScene just for this view. # TODO animScene = None # Store the view metadata currently associated with the view's ID. self.views[viewId] = (view, proxyId, repId, animScene) # Hide all other proxies that for whatever reason are automatically added to this view. self._hideOtherViews(proxyId, view) self.activateView(viewId) # Return a JSON object containing the view ID, the proxy ID, and the representation ID. return { "viewId": viewId, "proxyId": proxyId, "repId": repId } def activateView(self, id): # TODO Add some error handling # Find the view. viewAssociations = self.views[id] view = viewAssociations[0] proxyId = viewAssociations[1] proxy = self.proxies[proxyId] # Make it the active view. # Render it. # TODO Why don't we need to hide the other proxies? #self._hideOtherViews(proxyId, view) simple.SetActiveView(view) simple.Show() simple.Render() return { "success": True } def refreshScalarBars(self, viewId): # Try to look up the view, throwing an error if the ID is not an # integer or if the ID was not a valid key for the dictionary. try: view = self.views[int(viewId)][0] except ValueError: error = "Invalid view ID:", viewId return { "success": False, "error": error } except LookupError: error = "View with ID", viewId, "not found" return { "success": False, "error": error } # Hide all of the irrelevant scalar bars/legends. simple.HideUnusedScalarBars(view) return { "success": True } def disposeView(self, id): # TODO Add some error handling. # TODO Figure out how to actually dispose the ParaView resources. viewAssociations = self.views[id] # Dispose the view itself self.views[id] = None # Dispose the proxies proxyId = viewAssociations[1] if proxyId: proxy = self.proxies[proxyId] # TODO self.proxies[proxyId] = None repId = viewAssociations[2] if repId: rep = self.representations[repId] # TODO self.representations[repId] = None animScene = viewAssociations[3] # TODO return { "success": True } def _hideOtherViews(self, proxyId, view): proxies = servermanager.ProxyManager().GetProxiesInGroup("sources") for key in proxies: if key[1] != proxyId: simple.Hide(proxies[key], view) return def changeTime(self, id, action): # Find the AnimationScene for the specified view. viewAssociations = self.views[id] view = viewAssociations[0] # Return an error result if necessary. if not view: return { "time": -1, "error": "Invalid view with id {0}.".format(id) } animationScene = viewAssociations[3] currentTime = view.ViewTime # Update the AnimationScene depending on the action string. # This code comes (almost) straight from the ParaView protocols python script. if action == "next": animationScene.GoToNext() if currentTime == view.ViewTime: animationScene.GoToFirst() elif action == "prev": animationScene.GoToPrevious() if currentTime == view.ViewTime: animationScene.GoToLast() elif action == "first": animationScene.GoToFirst() elif action == "last": animationScene.GoToLast() else: return { "time": -1, "error": "Invalid action {0}.".format(action) } # Return a JSON object that contains a "success" boolean and a "reason" string. return view.ViewTime # ============================================================================= # Simple web server endpoint handling POST requests to execute rpc methods # ============================================================================= class HttpRpcResource(resource.Resource, object): def __init__(self, serverProtocol): super(HttpRpcResource, self).__init__() self.functionMap = {} # Build the rpc method dictionary protocolList = serverProtocol.getVtkWebProtocols() protocolList.append(serverProtocol) # so the exit methods get "registered" for protocolObject in protocolList: test = lambda x: inspect.ismethod(x) or inspect.isfunction(x) for k in inspect.getmembers(protocolObject.__class__, test): proc = k[1] if "_wampuris" in proc.__dict__: pat = proc.__dict__["_wampuris"][0] if pat.is_endpoint(): uri = pat.uri() # TODO Remove this line print uri self.functionMap[uri] = (protocolObject, proc) # Register the custom server methods. self.functionMap["createView"] = (None, serverProtocol.createView) self.functionMap["activateView"] = (None, serverProtocol.activateView) self.functionMap["disposeView"] = (None, serverProtocol.disposeView) self.functionMap["refreshScalarBars"] = (None, serverProtocol.refreshScalarBars) def getChild(self, path, request): return self def render_GET(self, request): return "" def render_POST(self, request): payload = json.loads(request.content.getvalue()) args = payload['args'] methodName = extractRpcMethod(request.path) print methodName obj,func = self.functionMap[methodName] if obj: results = func(obj, *args) else: results = func(*args) return json.dumps(results) # ============================================================================= # Parse arguments, create protocol server, and start webserver # ============================================================================= if __name__ == "__main__": # Create argument parser parser = argparse.ArgumentParser(description="ParaView Web Visualizer") # Add arguments _BasicServer.add_arguments(parser) args = parser.parse_args() _BasicServer.configure(args) # Create the application session serverProtocol = _BasicServer(None) # Start up the web server web_resource = resource.Resource() web_resource.putChild(endpoint, HttpRpcResource(serverProtocol)) site = server.Site(web_resource) reactor.listenTCP(args.port, site, interface=args.host) reactor.run()