mirror of
https://github.com/OpenFOAM/ThirdParty-6.git
synced 2025-12-08 06:57:43 +00:00
2803 lines
113 KiB
Python
2803 lines
113 KiB
Python
r"""paraviewweb_protocols is a module that contains a set of ParaViewWeb related
|
|
protocols that can be combined together to provide a flexible way to define
|
|
very specific web application.
|
|
"""
|
|
|
|
import os, sys, logging, types, inspect, traceback, logging, re, json
|
|
from time import time
|
|
|
|
# import Twisted reactor for later callback
|
|
from twisted.internet import reactor
|
|
|
|
# import RPC annotation
|
|
from autobahn.wamp import register as exportRpc
|
|
|
|
# import paraview modules.
|
|
import paraview
|
|
|
|
from paraview import simple, servermanager
|
|
from paraview.servermanager import ProxyProperty, InputProperty
|
|
from paraview.web import helper
|
|
from vtk.web import protocols as vtk_protocols
|
|
from decorators import *
|
|
|
|
from vtkWebCorePython import vtkWebInteractionEvent
|
|
|
|
from vtk import vtkImageData
|
|
from vtk import vtkUnsignedCharArray
|
|
from vtk import vtkDataEncoder
|
|
|
|
# Needed for:
|
|
# vtkSMPVRepresentationProxy
|
|
# vtkSMTransferFunctionProxy
|
|
# vtkSMTransferFunctionManager
|
|
from vtkPVServerManagerRenderingPython import *
|
|
|
|
# Needed for:
|
|
# vtkSMProxyManager
|
|
from vtkPVServerManagerCorePython import *
|
|
|
|
# Needed for:
|
|
# vtkDataObject
|
|
from vtkCommonDataModelPython import *
|
|
|
|
# =============================================================================
|
|
#
|
|
# Base class for any ParaView based protocol
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebProtocol(vtk_protocols.vtkWebProtocol):
|
|
|
|
def __init__(self):
|
|
self.Application = None
|
|
self.coreServer = None
|
|
self.multiRoot = False
|
|
self.baseDirectory = ''
|
|
self.baseDirectoryMap = {}
|
|
|
|
def mapIdToProxy(self, id):
|
|
"""
|
|
Maps global-id for a proxy to the proxy instance. May return None if the
|
|
id is not valid.
|
|
"""
|
|
try:
|
|
id = int(id)
|
|
except:
|
|
return None
|
|
if id <= 0:
|
|
return None
|
|
return simple.servermanager._getPyProxy(\
|
|
simple.servermanager.ActiveConnection.Session.GetRemoteObject(id))
|
|
|
|
def getView(self, vid):
|
|
"""
|
|
Returns the view for a given view ID, if vid is None then return the
|
|
current active view.
|
|
:param vid: The view ID
|
|
:type vid: str
|
|
"""
|
|
view = self.mapIdToProxy(vid)
|
|
if not view:
|
|
# Use active view is none provided.
|
|
view = simple.GetActiveView()
|
|
|
|
if not view:
|
|
raise Exception("no view provided: " + str(vid))
|
|
|
|
return view
|
|
|
|
def debug(self, msg):
|
|
if self.debugMode == True:
|
|
print msg
|
|
|
|
def setBaseDirectory(self, basePath):
|
|
self.overrideDataDirKey = None
|
|
self.baseDirectory = ''
|
|
self.baseDirectoryMap = {}
|
|
self.multiRoot = False
|
|
|
|
if basePath.find('|') < 0:
|
|
if basePath.find('=') >= 0:
|
|
basePair = basePath.split('=')
|
|
if os.path.exists(basePair[1]):
|
|
self.baseDirectory = basePair[1]
|
|
self.overrideDataDirKey = basePair[0]
|
|
else:
|
|
self.baseDirectory = basePath
|
|
else:
|
|
baseDirs = basePath.split('|')
|
|
for baseDir in baseDirs:
|
|
basePair = baseDir.split('=')
|
|
if os.path.exists(basePair[1]):
|
|
self.baseDirectoryMap[basePair[0]] = basePair[1]
|
|
|
|
# Check if we ended up with just a single directory
|
|
bdKeys = self.baseDirectoryMap.keys()
|
|
if len(bdKeys) == 1:
|
|
self.baseDirectory = self.baseDirectoryMap[bdKeys[0]]
|
|
self.overrideDataDirKey = bdKeys[0]
|
|
self.baseDirectoryMap = {}
|
|
elif len(bdKeys) > 1:
|
|
self.multiRoot = True
|
|
|
|
def getAbsolutePath(self, relativePath):
|
|
absolutePath = None
|
|
|
|
if self.multiRoot == True:
|
|
relPathParts = relativePath.replace('\\', '/').split('/')
|
|
realBasePath = self.baseDirectoryMap[relPathParts[0]]
|
|
absolutePath = os.path.join(realBasePath, *relPathParts[1:])
|
|
else:
|
|
absolutePath = os.path.join(self.baseDirectory, relativePath)
|
|
|
|
return os.path.normpath(absolutePath)
|
|
|
|
def updateScalarBars(self, view=None, mode=1):
|
|
"""
|
|
Manage scalarbar state
|
|
|
|
view:
|
|
A view proxy or the current active view will be used.
|
|
|
|
mode:
|
|
HIDE_UNUSED_SCALAR_BARS = 0x01,
|
|
SHOW_USED_SCALAR_BARS = 0x02
|
|
"""
|
|
v = view or self.getView(-1)
|
|
lutMgr = vtkSMTransferFunctionManager()
|
|
lutMgr.UpdateScalarBars(v.SMProxy, mode)
|
|
|
|
def publish(self, topic, event):
|
|
if self.coreServer:
|
|
self.coreServer.publish(topic, event)
|
|
|
|
# =============================================================================
|
|
#
|
|
# Handle Mouse interaction on any type of view
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebMouseHandler(ParaViewWebProtocol):
|
|
|
|
# RpcName: mouseInteraction => viewport.mouse.interaction
|
|
@exportRpc("viewport.mouse.interaction")
|
|
def mouseInteraction(self, event):
|
|
"""
|
|
RPC Callback for mouse interactions.
|
|
"""
|
|
view = self.getView(event['view'])
|
|
|
|
if hasattr(view, 'UseInteractiveRenderingForScreenshots'):
|
|
if event["action"] == 'down':
|
|
view.UseInteractiveRenderingForScreenshots = 1
|
|
elif event["action"] == 'up':
|
|
view.UseInteractiveRenderingForScreenshots = 0
|
|
|
|
buttons = 0
|
|
if event["buttonLeft"]:
|
|
buttons |= vtkWebInteractionEvent.LEFT_BUTTON;
|
|
if event["buttonMiddle"]:
|
|
buttons |= vtkWebInteractionEvent.MIDDLE_BUTTON;
|
|
if event["buttonRight"]:
|
|
buttons |= vtkWebInteractionEvent.RIGHT_BUTTON;
|
|
|
|
modifiers = 0
|
|
if event["shiftKey"]:
|
|
modifiers |= vtkWebInteractionEvent.SHIFT_KEY
|
|
if event["ctrlKey"]:
|
|
modifiers |= vtkWebInteractionEvent.CTRL_KEY
|
|
if event["altKey"]:
|
|
modifiers |= vtkWebInteractionEvent.ALT_KEY
|
|
if event["metaKey"]:
|
|
modifiers |= vtkWebInteractionEvent.META_KEY
|
|
|
|
pvevent = vtkWebInteractionEvent()
|
|
pvevent.SetButtons(buttons)
|
|
pvevent.SetModifiers(modifiers)
|
|
pvevent.SetX(event["x"])
|
|
pvevent.SetY(event["y"])
|
|
#pvevent.SetKeyCode(event["charCode"])
|
|
retVal = self.getApplication().HandleInteractionEvent(view.SMProxy, pvevent)
|
|
del pvevent
|
|
|
|
return retVal
|
|
|
|
# =============================================================================
|
|
#
|
|
# Basic 3D Viewport API (Camera + Orientation + CenterOfRotation
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebViewPort(ParaViewWebProtocol):
|
|
|
|
# RpcName: resetCamera => viewport.camera.reset
|
|
@exportRpc("viewport.camera.reset")
|
|
def resetCamera(self, viewId):
|
|
"""
|
|
RPC callback to reset camera.
|
|
"""
|
|
view = self.getView(viewId)
|
|
simple.Render(view)
|
|
simple.ResetCamera(view)
|
|
try:
|
|
view.CenterOfRotation = view.CameraFocalPoint
|
|
except:
|
|
pass
|
|
|
|
self.getApplication().InvalidateCache(view.SMProxy)
|
|
|
|
return view.GetGlobalIDAsString()
|
|
|
|
# RpcName: updateOrientationAxesVisibility => viewport.axes.orientation.visibility.update
|
|
@exportRpc("viewport.axes.orientation.visibility.update")
|
|
def updateOrientationAxesVisibility(self, viewId, showAxis):
|
|
"""
|
|
RPC callback to show/hide OrientationAxis.
|
|
"""
|
|
view = self.getView(viewId)
|
|
view.OrientationAxesVisibility = (showAxis if 1 else 0);
|
|
|
|
self.getApplication().InvalidateCache(view.SMProxy)
|
|
|
|
return view.GetGlobalIDAsString()
|
|
|
|
# RpcName: updateCenterAxesVisibility => viewport.axes.center.visibility.update
|
|
@exportRpc("viewport.axes.center.visibility.update")
|
|
def updateCenterAxesVisibility(self, viewId, showAxis):
|
|
"""
|
|
RPC callback to show/hide CenterAxesVisibility.
|
|
"""
|
|
view = self.getView(viewId)
|
|
view.CenterAxesVisibility = (showAxis if 1 else 0);
|
|
|
|
self.getApplication().InvalidateCache(view.SMProxy)
|
|
|
|
return view.GetGlobalIDAsString()
|
|
|
|
# RpcName: updateCamera => viewport.camera.update
|
|
@exportRpc("viewport.camera.update")
|
|
def updateCamera(self, view_id, focal_point, view_up, position):
|
|
view = self.getView(view_id)
|
|
|
|
view.CameraFocalPoint = focal_point
|
|
view.CameraViewUp = view_up
|
|
view.CameraPosition = position
|
|
self.getApplication().InvalidateCache(view.SMProxy)
|
|
|
|
@exportRpc("viewport.camera.get")
|
|
def getCamera(self, view_id):
|
|
view = self.getView(view_id)
|
|
return {
|
|
focal: list(view.CameraFocalPoint),
|
|
up: list(view.CameraViewUp),
|
|
position: list(view.CameraPosition)
|
|
}
|
|
|
|
@exportRpc("viewport.size.update")
|
|
def updateSize(self, view_id, width, height):
|
|
view = self.getView(view_id)
|
|
view.ViewSize = [ width, height ]
|
|
|
|
# =============================================================================
|
|
#
|
|
# Provide Image delivery mechanism
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebViewPortImageDelivery(ParaViewWebProtocol):
|
|
|
|
# RpcName: stillRender => viewport.image.render
|
|
@exportRpc("viewport.image.render")
|
|
def stillRender(self, options):
|
|
"""
|
|
RPC Callback to render a view and obtain the rendered image.
|
|
"""
|
|
beginTime = int(round(time() * 1000))
|
|
view = self.getView(options["view"])
|
|
size = [view.ViewSize[0], view.ViewSize[1]]
|
|
resize = size != options.get("size", size)
|
|
if resize:
|
|
size = options["size"]
|
|
view.ViewSize = size
|
|
t = 0
|
|
if options and options.has_key("mtime"):
|
|
t = options["mtime"]
|
|
quality = 100
|
|
if options and options.has_key("quality"):
|
|
quality = options["quality"]
|
|
localTime = 0
|
|
if options and options.has_key("localTime"):
|
|
localTime = options["localTime"]
|
|
reply = {}
|
|
app = self.getApplication()
|
|
reply["image"] = app.StillRenderToString(view.SMProxy, t, quality)
|
|
|
|
# Check that we are getting image size we have set if not wait until we
|
|
# do.
|
|
tries = 10;
|
|
while resize and list(app.GetLastStillRenderImageSize()) != size \
|
|
and size != [0, 0] and tries > 0:
|
|
app.InvalidateCache(view.SMProxy)
|
|
reply["image"] = app.StillRenderToString(view.SMProxy, t, quality)
|
|
tries -= 1
|
|
|
|
reply["stale"] = app.GetHasImagesBeingProcessed(view.SMProxy)
|
|
reply["mtime"] = app.GetLastStillRenderToStringMTime()
|
|
reply["size"] = [view.ViewSize[0], view.ViewSize[1]]
|
|
reply["format"] = "jpeg;base64"
|
|
reply["global_id"] = view.GetGlobalIDAsString()
|
|
reply["localTime"] = localTime
|
|
|
|
endTime = int(round(time() * 1000))
|
|
reply["workTime"] = (endTime - beginTime)
|
|
|
|
return reply
|
|
|
|
|
|
# =============================================================================
|
|
#
|
|
# Provide Geometry delivery mechanism (WebGL)
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebViewPortGeometryDelivery(ParaViewWebProtocol):
|
|
|
|
def __init__(self):
|
|
super(ParaViewWebViewPortGeometryDelivery, self).__init__()
|
|
|
|
self.dataCache = {}
|
|
|
|
# RpcName: getSceneMetaData => viewport.webgl.metadata
|
|
@exportRpc("viewport.webgl.metadata")
|
|
def getSceneMetaData(self, view_id):
|
|
view = self.getView(view_id);
|
|
data = self.getApplication().GetWebGLSceneMetaData(view.SMProxy)
|
|
return data
|
|
|
|
# RpcName: getWebGLData => viewport.webgl.data
|
|
@exportRpc("viewport.webgl.data")
|
|
def getWebGLData(self, view_id, object_id, part):
|
|
view = self.getView(view_id)
|
|
data = self.getApplication().GetWebGLBinaryData(view.SMProxy, str(object_id), part-1)
|
|
return data
|
|
|
|
# RpcName: getCachedWebGLData => viewport.webgl.cached.data
|
|
@exportRpc("viewport.webgl.cached.data")
|
|
def getCachedWebGLData(self, sha):
|
|
if sha not in self.dataCache:
|
|
return { 'success': False, 'reason': 'Key %s not in data cache' % sha }
|
|
return { 'success': True, 'data': self.dataCache[sha] }
|
|
|
|
# RpcName: getSceneMetaDataAllTimesteps => viewport.webgl.metadata.alltimesteps
|
|
@exportRpc("viewport.webgl.metadata.alltimesteps")
|
|
def getSceneMetaDataAllTimesteps(self, view_id=-1):
|
|
animationScene = simple.GetAnimationScene()
|
|
timeKeeper = animationScene.TimeKeeper
|
|
tsVals = timeKeeper.TimestepValues.GetData()
|
|
currentTime = timeKeeper.Time
|
|
|
|
oldCache = self.dataCache
|
|
self.dataCache = {}
|
|
returnToClient = {}
|
|
|
|
view = self.getView(view_id);
|
|
animationScene.GoToFirst()
|
|
|
|
# Iterate over all the timesteps, building up a list of unique shas
|
|
for i in xrange(len(tsVals)):
|
|
simple.Render()
|
|
|
|
mdString = self.getApplication().GetWebGLSceneMetaData(view.SMProxy)
|
|
timestepMetaData = json.loads(mdString)
|
|
objects = timestepMetaData['Objects']
|
|
|
|
# Iterate over the objects in the scene
|
|
for obj in objects:
|
|
sha = obj['md5']
|
|
objId = obj['id']
|
|
numParts = obj['parts']
|
|
|
|
if sha not in self.dataCache:
|
|
if sha in oldCache:
|
|
self.dataCache[sha] = oldCache[sha]
|
|
else:
|
|
transparency = obj['transparency']
|
|
layer = obj['layer']
|
|
partData = []
|
|
|
|
# Ask for the binary data for each part of this object
|
|
for part in xrange(numParts):
|
|
partNumber = part
|
|
data = self.getApplication().GetWebGLBinaryData(view.SMProxy, str(objId), partNumber)
|
|
partData.append(data)
|
|
|
|
# Now add object, with all its binary parts, to the cache
|
|
self.dataCache[sha] = { 'md5': sha,
|
|
'id': objId,
|
|
'numParts': numParts,
|
|
'transparency': transparency,
|
|
'layer': layer,
|
|
'partsList': partData }
|
|
|
|
returnToClient[sha] = { 'id': objId, 'numParts': numParts }
|
|
|
|
# Now move time forward
|
|
animationScene.GoToNext()
|
|
|
|
# Set the time back to where it was when all timesteps were requested
|
|
timeKeeper.Time = currentTime
|
|
animationScene.AnimationTime = currentTime
|
|
simple.Render()
|
|
|
|
return { 'success': True, 'metaDataList': returnToClient }
|
|
|
|
|
|
# =============================================================================
|
|
#
|
|
# Time management
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebTimeHandler(ParaViewWebProtocol):
|
|
|
|
def __init__(self):
|
|
super(ParaViewWebTimeHandler, self).__init__()
|
|
# setup animation scene
|
|
self.scene = simple.GetAnimationScene()
|
|
simple.GetTimeTrack()
|
|
self.scene.PlayMode = "Snap To TimeSteps"
|
|
self.playing = False
|
|
self.playTime = 0.1 # Time in second
|
|
|
|
def nextPlay(self):
|
|
self.updateTime('next')
|
|
if self.playing:
|
|
reactor.callLater(self.playTime, self.nextPlay)
|
|
|
|
# RpcName: updateTime => pv.vcr.action
|
|
@exportRpc("pv.vcr.action")
|
|
def updateTime(self,action):
|
|
view = simple.GetRenderView()
|
|
animationScene = simple.GetAnimationScene()
|
|
currentTime = view.ViewTime
|
|
|
|
if action == "next":
|
|
animationScene.GoToNext()
|
|
if currentTime == view.ViewTime:
|
|
animationScene.GoToFirst()
|
|
if action == "prev":
|
|
animationScene.GoToPrevious()
|
|
if currentTime == view.ViewTime:
|
|
animationScene.GoToLast()
|
|
if action == "first":
|
|
animationScene.GoToFirst()
|
|
if action == "last":
|
|
animationScene.GoToLast()
|
|
|
|
timestep = list(animationScene.TimeKeeper.TimestepValues).index(animationScene.TimeKeeper.Time)
|
|
self.publish("pv.time.change", { 'time': float(view.ViewTime), 'timeStep': timestep } )
|
|
|
|
return view.ViewTime
|
|
|
|
@exportRpc("pv.time.index.set")
|
|
def setTimeStep(self, timeIdx):
|
|
anim = simple.GetAnimationScene()
|
|
anim.TimeKeeper.Time = anim.TimeKeeper.TimestepValues[timeIdx]
|
|
return anim.TimeKeeper.Time
|
|
|
|
@exportRpc("pv.time.index.get")
|
|
def getTimeStep(self):
|
|
anim = simple.GetAnimationScene()
|
|
return list(anim.TimeKeeper.TimestepValues).index(anim.TimeKeeper.Time)
|
|
|
|
@exportRpc("pv.time.value.set")
|
|
def setTimeValue(self, t):
|
|
anim = simple.GetAnimationScene()
|
|
|
|
try:
|
|
step = list(anim.TimeKeeper.TimestepValues).index(t)
|
|
anim.TimeKeeper.Time = anim.TimeKeeper.TimestepValues[step]
|
|
except:
|
|
print 'Try to update time with', t, 'but value not found in the list'
|
|
|
|
return anim.TimeKeeper.Time
|
|
|
|
@exportRpc("pv.time.value.get")
|
|
def getTimeValue(self):
|
|
anim = simple.GetAnimationScene()
|
|
return anim.TimeKeeper.Time
|
|
|
|
@exportRpc("pv.time.values")
|
|
def getTimeValues(self):
|
|
return list(simple.GetAnimationScene().TimeKeeper.TimestepValues)
|
|
|
|
@exportRpc("pv.time.play")
|
|
def play(self, deltaT=0.1):
|
|
if not self.playing:
|
|
self.playTime = deltaT
|
|
self.playing = True
|
|
self.nextPlay()
|
|
|
|
@exportRpc("pv.time.stop")
|
|
def stop(self):
|
|
self.playing = False
|
|
|
|
# =============================================================================
|
|
#
|
|
# Color management
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebColorManager(ParaViewWebProtocol):
|
|
|
|
def __init__(self, pathToColorMaps=None):
|
|
super(ParaViewWebColorManager, self).__init__()
|
|
self.colorMapNames = []
|
|
if pathToColorMaps is not None:
|
|
self.pathToLuts = pathToColorMaps
|
|
else:
|
|
module_path = os.path.abspath(__file__)
|
|
path = os.path.dirname(module_path)
|
|
self.pathToLuts = os.path.join(path, '..', 'ColorMaps.xml')
|
|
|
|
# Rather than keeping a map of these xml strings in memory, it's very
|
|
# quick to just scan the file when we need a color map. This is a
|
|
# convenience function to read the colormaps file and find the one
|
|
# associated with a particular name, then return it as xml text. Also
|
|
# builds a list of preset color names and stores them in an instance
|
|
# variable.
|
|
def findColorMapText(self, colorMapName):
|
|
content = None
|
|
self.colorMapNames = []
|
|
with open(self.pathToLuts, 'r') as fd:
|
|
content = fd.read()
|
|
if content is not None:
|
|
colorMapMatcher = re.compile('(<ColorMap\s+.+?(?=</ColorMap>)</ColorMap>)', re.DOTALL)
|
|
nameMatcher = re.compile('name="([^"]+)"')
|
|
iterator = colorMapMatcher.finditer(content)
|
|
for match in iterator:
|
|
colorMap = match.group(1)
|
|
m = nameMatcher.search(colorMap)
|
|
if m:
|
|
mapName = m.group(1)
|
|
self.colorMapNames.append(mapName)
|
|
if mapName == colorMapName:
|
|
return colorMap
|
|
|
|
return None
|
|
|
|
# RpcName: getScalarBarVisibilities => pv.color.manager.scalarbar.visibility.get
|
|
@exportRpc("pv.color.manager.scalarbar.visibility.get")
|
|
def getScalarBarVisibilities(self, proxyIdList):
|
|
"""
|
|
Returns whether or not each specified scalar bar is visible.
|
|
"""
|
|
visibilities = {}
|
|
for proxyId in proxyIdList:
|
|
proxy = self.mapIdToProxy(proxyId)
|
|
if proxy is not None:
|
|
rep = simple.GetRepresentation(proxy)
|
|
view = self.getView(-1)
|
|
visibilities[proxyId] = vtkSMPVRepresentationProxy.IsScalarBarVisible(rep.SMProxy, view.SMProxy)
|
|
|
|
return visibilities
|
|
|
|
# RpcName: setScalarBarVisibilities => pv.color.manager.scalarbar.visibility.set
|
|
@exportRpc("pv.color.manager.scalarbar.visibility.set")
|
|
def setScalarBarVisibilities(self, proxyIdMap):
|
|
"""
|
|
Sets the visibility of the scalar bar corresponding to each specified
|
|
proxy. The representation for each proxy is found using the
|
|
filter/source proxy id and the current view.
|
|
"""
|
|
visibilities = {}
|
|
for proxyId in proxyIdMap:
|
|
proxy = self.mapIdToProxy(proxyId)
|
|
if proxy is not None:
|
|
rep = simple.GetDisplayProperties(proxy)
|
|
view = self.getView(-1)
|
|
vtkSMPVRepresentationProxy.SetScalarBarVisibility(rep.SMProxy,
|
|
view.SMProxy,
|
|
proxyIdMap[proxyId])
|
|
visibilities[proxyId] = vtkSMPVRepresentationProxy.IsScalarBarVisible(rep.SMProxy,
|
|
view.SMProxy)
|
|
|
|
# Render to get scalar bars in correct position when doing local rendering (webgl)
|
|
simple.Render()
|
|
|
|
return visibilities
|
|
|
|
# RpcName: rescaleTransferFunction => pv.color.manager.rescale.transfer.function
|
|
@exportRpc("pv.color.manager.rescale.transfer.function")
|
|
def rescaleTransferFunction(self, options):
|
|
"""
|
|
Rescale the color transfer function to fit either the data range,
|
|
the data range over time, or to a custom range, for the array by
|
|
which the representation is currently being colored.
|
|
"""
|
|
type = options['type']
|
|
proxyId = options['proxyId']
|
|
proxy = self.mapIdToProxy(proxyId)
|
|
rep = simple.GetRepresentation(proxy)
|
|
|
|
status = { 'success': False }
|
|
|
|
if type == 'time':
|
|
status['success'] = \
|
|
vtkSMPVRepresentationProxy.RescaleTransferFunctionToDataRangeOverTime(rep.SMProxy)
|
|
elif type == 'data':
|
|
extend = False
|
|
if 'extend' in options:
|
|
extend = options['extend']
|
|
status['success'] = \
|
|
vtkSMPVRepresentationProxy.RescaleTransferFunctionToDataRange(rep.SMProxy, extend)
|
|
elif type == 'custom':
|
|
rangemin = float(options['min'])
|
|
rangemax = float(options['max'])
|
|
extend = False
|
|
if 'extend' in options:
|
|
extend = options['extend']
|
|
lookupTable = rep.LookupTable
|
|
if lookupTable is not None:
|
|
status['success'] = \
|
|
vtkSMTransferFunctionProxy.RescaleTransferFunction(lookupTable.SMProxy,
|
|
rangemin,
|
|
rangemax,
|
|
extend)
|
|
|
|
if status['success']:
|
|
currentRange = self.getCurrentScalarRange(proxyId)
|
|
status['range'] = currentRange
|
|
|
|
return status
|
|
|
|
# RpcName: getCurrentScalarRange => pv.color.manager.scalar.range.get
|
|
@exportRpc("pv.color.manager.scalar.range.get")
|
|
def getCurrentScalarRange(self, proxyId):
|
|
proxy = self.mapIdToProxy(proxyId)
|
|
rep = simple.GetRepresentation(proxy)
|
|
|
|
lookupTable = rep.LookupTable
|
|
cMin = lookupTable.RGBPoints[0]
|
|
cMax = lookupTable.RGBPoints[-4]
|
|
|
|
return { 'min': cMin, 'max': cMax }
|
|
|
|
# RpcName: colorBy => pv.color.manager.color.by
|
|
@exportRpc("pv.color.manager.color.by")
|
|
def colorBy(self, representation, colorMode, arrayLocation='POINTS', arrayName='', vectorMode='Magnitude', vectorComponent=0, rescale=False):
|
|
"""
|
|
Choose the array to color by, and optionally specify magnitude or a
|
|
vector component in the case of vector array.
|
|
"""
|
|
locationMap = { 'POINTS': vtkDataObject.POINT, 'CELLS': vtkDataObject.CELL }
|
|
repProxy = self.mapIdToProxy(representation)
|
|
lutProxy = repProxy.LookupTable
|
|
|
|
if colorMode == 'SOLID':
|
|
# No array just diffuse color
|
|
repProxy.ColorArrayName = ''
|
|
else:
|
|
# Select data array
|
|
vtkSMPVRepresentationProxy.SetScalarColoring(repProxy.SMProxy, arrayName, locationMap[arrayLocation])
|
|
lut = repProxy.LookupTable
|
|
lut.VectorMode = str(vectorMode)
|
|
lut.VectorComponent = int(vectorComponent)
|
|
|
|
# FIXME: This should happen once at the time the lookup table is created
|
|
# Also, the False is the default, but we need it here to avoid the
|
|
# ambiguous call error
|
|
if rescale or (lut != lutProxy):
|
|
vtkSMPVRepresentationProxy.RescaleTransferFunctionToDataRange(repProxy.SMProxy, arrayName, locationMap[arrayLocation], False)
|
|
|
|
self.updateScalarBars()
|
|
|
|
simple.Render()
|
|
|
|
# RpcName: setOpacityFunctionPoints => pv.color.manager.opacity.points.set
|
|
@exportRpc("pv.color.manager.opacity.points.set")
|
|
def setOpacityFunctionPoints(self, arrayName, pointArray):
|
|
lutProxy = simple.GetColorTransferFunction(arrayName)
|
|
pwfProxy = simple.GetOpacityTransferFunction(arrayName)
|
|
|
|
# Use whatever the current scalar range is for this array
|
|
cMin = lutProxy.RGBPoints[0]
|
|
cMax = lutProxy.RGBPoints[-4]
|
|
|
|
# Scale and bias the x values, which come in between 0.0 and 1.0, to the
|
|
# current scalar range
|
|
for i in range(len(pointArray) / 4):
|
|
idx = i * 4
|
|
x = pointArray[idx]
|
|
pointArray[idx] = (x * (cMax - cMin)) + cMin
|
|
|
|
# Set the Points property to scaled and biased points array
|
|
pwfProxy.Points = pointArray
|
|
|
|
simple.Render()
|
|
|
|
# RpcName: getRgbPoints => pv.color.manager.rgb.points.get
|
|
@exportRpc("pv.color.manager.rgb.points.get")
|
|
def getRgbPoints(self, arrayName):
|
|
"""
|
|
return {
|
|
"mode": "categorical" | "continuous",
|
|
"categorical": {
|
|
"scalars": [ "1", "2", "3", ... ],
|
|
"annotations": [ "a1", "a2", "a3", ... ],
|
|
"colors": [ [r1, g1, b1], [r2, g2, b2], [r3, g3, b3], ... ]
|
|
},
|
|
"continuous": {
|
|
"scalars": [ 1.203, 17.976 ],
|
|
"color": [ [r1, g1, b1], [r2, g2, b2] ]
|
|
}
|
|
}
|
|
"""
|
|
|
|
lutProxy = simple.GetColorTransferFunction(arrayName)
|
|
|
|
# First, set the current coloring mode
|
|
colorInfo = {}
|
|
colorInfo['mode'] = 'categorical' if lutProxy.InterpretValuesAsCategories else 'continuous'
|
|
|
|
# Now build up the continuous coloring information
|
|
continuousInfo = {}
|
|
|
|
l = lutProxy.RGBPoints.GetData()
|
|
scalars = l[0:len(l):4]
|
|
continuousInfo['scalars'] = [ float(s) for s in scalars ]
|
|
|
|
reds = l[1:len(l):4]
|
|
greens = l[2:len(l):4]
|
|
blues = l[3:len(l):4]
|
|
continuousInfo['colors'] = [ list(a) for a in zip(reds, greens, blues) ]
|
|
|
|
colorInfo['continuous'] = continuousInfo
|
|
|
|
# Finally, build up the categorical coloring information
|
|
categoricalInfo = {}
|
|
|
|
rgbs = lutProxy.IndexedColors
|
|
reds = rgbs[0:len(rgbs):3]
|
|
greens = rgbs[1:len(rgbs):3]
|
|
blues = rgbs[2:len(rgbs):3]
|
|
categoricalInfo['colors'] = [ list(a) for a in zip(reds, greens, blues) ]
|
|
|
|
l = lutProxy.Annotations
|
|
scalars = l[0:len(l):2]
|
|
categoricalInfo['scalars'] = [ float(s) for s in scalars ]
|
|
categoricalInfo['annotations'] = l[1:len(l):2]
|
|
|
|
### If the numbers of categorical colors and scalars do not match,
|
|
### then this is probably because you already had a categorical color
|
|
### scheme set up when you applied a new indexed preset.
|
|
numColors = len(categoricalInfo['colors'])
|
|
numScalars = len(categoricalInfo['scalars'])
|
|
if numColors != numScalars:
|
|
# More colors than scalars means we applied a preset colormap with
|
|
# more colors than what we had set up already, so I want to add
|
|
# scalars and annotations.
|
|
if numColors > numScalars:
|
|
nextValue = 0
|
|
|
|
# Another special case here is that there were no categorical
|
|
# scalars/colors before the preset was applied, and we will need
|
|
# to insert a single space character (' ') annotation at the start
|
|
# of the list to make the scalarbar appear.
|
|
if numScalars == 0:
|
|
categoricalInfo['scalars'].append(nextValue)
|
|
categoricalInfo['annotations'].append(' ')
|
|
|
|
nextValue = categoricalInfo['scalars'][-1] + 1
|
|
|
|
for i in xrange((numColors - numScalars) - 1):
|
|
categoricalInfo['scalars'].append(nextValue)
|
|
categoricalInfo['annotations'].append('')
|
|
nextValue += 1
|
|
|
|
# More scalars than colors means we applied a preset colormap with
|
|
# *fewer* colors than what had already, so I want to add fake colors
|
|
# for any non-empty annotations I have after the end of the color list
|
|
elif numScalars > numColors:
|
|
newScalars = categoricalInfo['scalars'][0:numColors]
|
|
newAnnotations = categoricalInfo['annotations'][0:numColors]
|
|
|
|
for i in xrange(numColors, numScalars):
|
|
if categoricalInfo['annotations'][i] != '':
|
|
newScalars.append(categoricalInfo['scalars'][i])
|
|
newAnnotations.append(categoricalInfo['annotations'][i])
|
|
categoricalInfo['colors'].append([0.5, 0.5, 0.5])
|
|
|
|
categoricalInfo['scalars'] = newScalars
|
|
categoricalInfo['annotations'] = newAnnotations
|
|
|
|
# Now that we have made the number of indexed colors and associated
|
|
# scalars match, the final step in this special case is to apply the
|
|
# matched up props we just computed so that server and client ui are
|
|
# in sync.
|
|
idxColorsProperty = []
|
|
annotationsProperty = []
|
|
|
|
for aIdx in xrange(len(categoricalInfo['scalars'])):
|
|
annotationsProperty.append(str(categoricalInfo['scalars'][aIdx]))
|
|
annotationsProperty.append(str(categoricalInfo['annotations'][aIdx]))
|
|
idxColorsProperty.extend(categoricalInfo['colors'][aIdx])
|
|
|
|
lutProxy.Annotations = annotationsProperty
|
|
lutProxy.IndexedColors = idxColorsProperty
|
|
|
|
simple.Render
|
|
|
|
colorInfo['categorical'] = categoricalInfo
|
|
|
|
return colorInfo
|
|
|
|
# RpcName: setRgbPoints => pv.color.manager.rgb.points.set
|
|
@exportRpc("pv.color.manager.rgb.points.set")
|
|
def setRgbPoints(self, arrayName, rgbInfo):
|
|
lutProxy = simple.GetColorTransferFunction(arrayName)
|
|
|
|
colorMode = rgbInfo['mode']
|
|
continuousInfo = rgbInfo['continuous']
|
|
categoricalInfo = rgbInfo['categorical']
|
|
|
|
# First make sure the continous mode properties are set
|
|
continuousScalars = continuousInfo['scalars']
|
|
continuousColors = continuousInfo['colors']
|
|
|
|
rgbPoints = []
|
|
for idx in xrange(len(continuousScalars)):
|
|
scalar = continuousScalars[idx]
|
|
rgb = continuousColors[idx]
|
|
rgbPoints.append(float(scalar))
|
|
rgbPoints.extend(rgb)
|
|
lutProxy.RGBPoints = rgbPoints
|
|
|
|
# Now make sure the categorical mode properties are set
|
|
annotations = categoricalInfo['annotations']
|
|
categoricalScalars = categoricalInfo['scalars']
|
|
categoricalColors = categoricalInfo['colors']
|
|
|
|
annotationsProperty = []
|
|
idxColorsProperty = []
|
|
for aIdx in xrange(len(annotations)):
|
|
annotationsProperty.append(str(categoricalScalars[aIdx]))
|
|
annotationsProperty.append(str(annotations[aIdx]))
|
|
idxColorsProperty.extend(categoricalColors[aIdx])
|
|
|
|
lutProxy.Annotations = annotationsProperty
|
|
lutProxy.IndexedColors = idxColorsProperty
|
|
|
|
# Finally, set the coloring mode property
|
|
if colorMode == 'continuous': # continuous coloring
|
|
lutProxy.InterpretValuesAsCategories = 0
|
|
else: # categorical coloring
|
|
lutProxy.InterpretValuesAsCategories = 1
|
|
|
|
simple.Render();
|
|
|
|
# RpcName: getLutImage => pv.color.manager.lut.image.get
|
|
@exportRpc("pv.color.manager.lut.image.get")
|
|
def getLutImage(self, representation, numSamples, customRange=None):
|
|
repProxy = self.mapIdToProxy(representation)
|
|
lut = repProxy.LookupTable.GetClientSideObject()
|
|
|
|
dataRange = customRange
|
|
if not dataRange:
|
|
dataRange = lut.GetRange()
|
|
|
|
delta = (dataRange[1] - dataRange[0]) / float(numSamples)
|
|
|
|
colorArray = vtkUnsignedCharArray()
|
|
colorArray.SetNumberOfComponents(3)
|
|
colorArray.SetNumberOfTuples(numSamples)
|
|
|
|
rgb = [ 0, 0, 0 ]
|
|
for i in range(numSamples):
|
|
lut.GetColor(dataRange[0] + float(i) * delta, rgb)
|
|
r = int(round(rgb[0] * 255))
|
|
g = int(round(rgb[1] * 255))
|
|
b = int(round(rgb[2] * 255))
|
|
colorArray.SetTuple3(i, r, g, b)
|
|
|
|
# Add the color array to an image data
|
|
imgData = vtkImageData()
|
|
imgData.SetDimensions(numSamples, 1, 1)
|
|
aIdx = imgData.GetPointData().SetScalars(colorArray)
|
|
|
|
# Use the vtk data encoder to base-64 encode the image as png, using no compression
|
|
encoder = vtkDataEncoder()
|
|
b64Str = encoder.EncodeAsBase64Png(imgData, 0)
|
|
|
|
return { 'range': dataRange, 'image': b64Str }
|
|
|
|
@exportRpc("pv.color.manager.lut.image.all")
|
|
def getLutImages(self, numSamples):
|
|
colorArray = vtkUnsignedCharArray()
|
|
colorArray.SetNumberOfComponents(3)
|
|
colorArray.SetNumberOfTuples(numSamples)
|
|
|
|
pxm = simple.servermanager.ProxyManager()
|
|
lutProxy = pxm.NewProxy('lookup_tables', 'PVLookupTable')
|
|
lut = lutProxy.GetClientSideObject()
|
|
dataRange = lut.GetRange()
|
|
delta = (dataRange[1] - dataRange[0]) / float(numSamples)
|
|
|
|
# Add the color array to an image data
|
|
imgData = vtkImageData()
|
|
imgData.SetDimensions(numSamples, 1, 1)
|
|
imgData.GetPointData().SetScalars(colorArray)
|
|
|
|
# Use the vtk data encoder to base-64 encode the image as png, using no compression
|
|
encoder = vtkDataEncoder()
|
|
|
|
# Result container
|
|
result = {}
|
|
|
|
# Loop over all presets
|
|
self.findColorMapText('')
|
|
for name in self.colorMapNames:
|
|
colorMapText = self.findColorMapText(name)
|
|
vtkSMTransferFunctionProxy.ApplyColorMap(lutProxy, colorMapText)
|
|
rgb = [ 0, 0, 0 ]
|
|
for i in range(numSamples):
|
|
lut.GetColor(dataRange[0] + float(i) * delta, rgb)
|
|
r = int(round(rgb[0] * 255))
|
|
g = int(round(rgb[1] * 255))
|
|
b = int(round(rgb[2] * 255))
|
|
colorArray.SetTuple3(i, r, g, b)
|
|
|
|
result[name] = encoder.EncodeAsBase64Png(imgData, 0)
|
|
|
|
simple.Delete(lutProxy)
|
|
|
|
return result
|
|
|
|
# RpcName: setSurfaceOpacity => pv.color.manager.surface.opacity.set
|
|
@exportRpc("pv.color.manager.surface.opacity.set")
|
|
def setSurfaceOpacity(self, representation, enabled):
|
|
repProxy = self.mapIdToProxy(representation)
|
|
lutProxy = repProxy.LookupTable
|
|
|
|
lutProxy.EnableOpacityMapping = enabled
|
|
|
|
simple.Render()
|
|
|
|
# RpcName: getSurfaceOpacity => pv.color.manager.surface.opacity.get
|
|
@exportRpc("pv.color.manager.surface.opacity.get")
|
|
def getSurfaceOpacity(self, representation):
|
|
repProxy = self.mapIdToProxy(representation)
|
|
lutProxy = repProxy.LookupTable
|
|
|
|
return lutProxy.EnableOpacityMapping
|
|
|
|
# RpcName: selectColorMap => pv.color.manager.select.preset
|
|
@exportRpc("pv.color.manager.select.preset")
|
|
def selectColorMap(self, representation, paletteName):
|
|
"""
|
|
Choose the color map preset to use when coloring by an array.
|
|
"""
|
|
repProxy = self.mapIdToProxy(representation)
|
|
colorMapText = self.findColorMapText(paletteName)
|
|
|
|
if colorMapText is not None:
|
|
lutProxy = repProxy.LookupTable
|
|
if lutProxy is not None:
|
|
vtkSMTransferFunctionProxy.ApplyColorMap(lutProxy.SMProxy, colorMapText)
|
|
simple.Render()
|
|
return { 'result': 'success' }
|
|
else:
|
|
return { 'result': 'Representation proxy ' + representation + ' is missing lookup table' }
|
|
|
|
return { 'result': 'preset ' + paletteName + ' not found' }
|
|
|
|
# RpcName: listColorMapNames => pv.color.manager.list.preset
|
|
@exportRpc("pv.color.manager.list.preset")
|
|
def listColorMapNames(self):
|
|
"""
|
|
List the names of all color map presets available on the server. This
|
|
list will contain the names of any presets you provided in the file you
|
|
supplied to the constructor of this protocol.
|
|
"""
|
|
self.findColorMapText('')
|
|
return self.colorMapNames
|
|
|
|
# =============================================================================
|
|
#
|
|
# Proxy management protocol
|
|
#
|
|
# =============================================================================
|
|
class ParaViewWebProxyManager(ParaViewWebProtocol):
|
|
|
|
VTK_DATA_TYPES = [ 'void', # 0
|
|
'bit', # 1
|
|
'char', # 2
|
|
'unsigned_char', # 3
|
|
'short', # 4
|
|
'unsigned_short', # 5
|
|
'int', # 6
|
|
'unsigned_int', # 7
|
|
'long', # 8
|
|
'unsigned_long', # 9
|
|
'float', # 10
|
|
'double', # 11
|
|
'id_type', # 12
|
|
'unspecified', # 13
|
|
'unspecified', # 14
|
|
'signed_char' ] # 15
|
|
|
|
def __init__(self, allowedProxiesFile=None, baseDir=None, fileToLoad=None, allowUnconfiguredReaders=True):
|
|
"""
|
|
- basePath: specify the base directory (or directories) that we should start with, if this
|
|
parameter takes the form: "name1=path1|name2=path2|...", then we will treat this as the
|
|
case where multiple data directories are required. In this case, each top-level directory
|
|
will be given the name associated with the directory in the argument.
|
|
"""
|
|
super(ParaViewWebProxyManager, self).__init__()
|
|
self.debugMode = False
|
|
self.domainFunctionMap = { "vtkSMBooleanDomain": booleanDomainDecorator,
|
|
"vtkSMProxyListDomain": proxyListDomainDecorator,
|
|
"vtkSMIntRangeDomain": numberRangeDomainDecorator,
|
|
"vtkSMDoubleRangeDomain": numberRangeDomainDecorator,
|
|
"vtkSMArrayRangeDomain": numberRangeDomainDecorator,
|
|
"vtkSMRepresentationTypeDomain": stringListDomainDecorator,
|
|
"vtkSMStringListDomain": stringListDomainDecorator,
|
|
"vtkSMArrayListDomain": arrayListDomainDecorator,
|
|
"vtkSMArraySelectionDomain": arraySelectionDomainDecorator,
|
|
"vtkSMEnumerationDomain": enumerationDomainDecorator,
|
|
"vtkSMCompositeTreeDomain": treeDomainDecorator }
|
|
self.alwaysIncludeProperties = [ 'CubeAxesVisibility' ]
|
|
self.alwaysExcludeProperties = [ 'LookupTable', 'Texture', 'ColorArrayName',
|
|
'Representations', 'HiddenRepresentations',
|
|
'HiddenProps', 'UseTexturedBackground',
|
|
'BackgroundTexture', 'KeyLightWarmth',
|
|
'KeyLightIntensity', 'KeyLightElevation',
|
|
'KeyLightAzimuth', 'FileName' ]
|
|
self.propertyTypeMap = { 'vtkSMIntVectorProperty': 'int',
|
|
'vtkSMDoubleVectorProperty': 'float',
|
|
'vtkSMStringVectorProperty': 'str',
|
|
'vtkSMProxyProperty': 'proxy',
|
|
'vtkSMInputProperty': 'proxy' }
|
|
self.allowedProxies = {}
|
|
self.hintsMap = { 'PropertyWidgetDecorator': { 'ClipScalarsDecorator': clipScalarDecorator,
|
|
'GenericDecorator': genericDecorator },
|
|
'Widget': { 'multi_line': multiLineDecorator } }
|
|
|
|
self.setBaseDirectory(baseDir)
|
|
self.allowUnconfiguredReaders = allowUnconfiguredReaders
|
|
|
|
# If there was a proxy list file, use it, otherwise use the default
|
|
if allowedProxiesFile:
|
|
self.readAllowedProxies(allowedProxiesFile)
|
|
else:
|
|
module_path = os.path.abspath(__file__)
|
|
path = os.path.dirname(module_path)
|
|
proxyFile = os.path.join(path, 'defaultProxies.json')
|
|
self.readAllowedProxies(proxyFile)
|
|
|
|
if fileToLoad:
|
|
self.open(fileToLoad)
|
|
|
|
self.simpleTypes = [int, float, list, str]
|
|
self.view = simple.GetRenderView()
|
|
simple.SetActiveView(self.view)
|
|
simple.Render()
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Read the configured proxies json file into a dictionary and process.
|
|
#--------------------------------------------------------------------------
|
|
def readAllowedProxies(self, filepath):
|
|
self.availableList = {}
|
|
self.allowedProxies = {}
|
|
with open(filepath, 'r') as fd:
|
|
configurationData = json.load(fd)
|
|
self.configureFiltersOrSources('sources', configurationData['sources'])
|
|
self.configureFiltersOrSources('filters', configurationData['filters'])
|
|
self.configureReaders(configurationData['readers'])
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Handle filters and sources sections of the proxies config file.
|
|
#--------------------------------------------------------------------------
|
|
def configureFiltersOrSources(self, key, flist):
|
|
list = []
|
|
self.availableList[key] = list
|
|
for item in flist:
|
|
if 'label' in item:
|
|
self.allowedProxies[item['label']] = item['name']
|
|
list.append(item['label'])
|
|
else:
|
|
self.allowedProxies[item['name']] = item['name']
|
|
list.append(item['name'])
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Handle the readers section of the proxies config file.
|
|
#--------------------------------------------------------------------------
|
|
def configureReaders(self, rlist):
|
|
self.readerFactoryMap = {}
|
|
for config in rlist:
|
|
readerName = config['name']
|
|
readerMethod = 'FileName'
|
|
if 'method' in config:
|
|
readerMethod = config['method']
|
|
for ext in config['extensions']:
|
|
self.readerFactoryMap[ext] = [ readerName, readerMethod ]
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Look higher up in XML heirarchy for attributes on a property (useful if
|
|
# a property is an exposed property).
|
|
#--------------------------------------------------------------------------
|
|
def extractParentAttributes(self, property, attributes):
|
|
proxy = None
|
|
name = ''
|
|
try:
|
|
proxy = property.SMProperty.GetParent()
|
|
name = proxy.GetPropertyName(property.SMProperty)
|
|
except:
|
|
try:
|
|
proxy = property.GetParent()
|
|
name = proxy.GetPropertyName(property)
|
|
except:
|
|
print 'ERROR: unable to get property parent for property ' + property.Name
|
|
return {}
|
|
|
|
xmlElement = servermanager.ActiveConnection.Session.GetProxyDefinitionManager().GetCollapsedProxyDefinition(proxy.GetXMLGroup(), proxy.GetXMLName(), None)
|
|
attrMap = {}
|
|
|
|
nbChildren = xmlElement.GetNumberOfNestedElements()
|
|
for i in range(nbChildren):
|
|
xmlChild = xmlElement.GetNestedElement(i)
|
|
elementName = xmlChild.GetName()
|
|
if elementName.endswith('Property'):
|
|
propName = xmlChild.GetAttribute('name')
|
|
if propName == name:
|
|
for attrName in attributes:
|
|
if xmlChild.GetAttribute(attrName):
|
|
attrMap[attrName] = xmlChild.GetAttribute(attrName)
|
|
return attrMap
|
|
|
|
#--------------------------------------------------------------------------
|
|
# If we have a proxy property, gather up the xml information from the
|
|
# possible proxy values it can take on.
|
|
#--------------------------------------------------------------------------
|
|
def getProxyListFromProperty(self, proxy, proxyPropElement):
|
|
propPropName = proxyPropElement.GetAttribute('name')
|
|
propInstance = proxy.GetProperty(propPropName)
|
|
nbChildren = proxyPropElement.GetNumberOfNestedElements()
|
|
foundPLDChild = False
|
|
proxyDefMgr = servermanager.ActiveConnection.Session.GetProxyDefinitionManager()
|
|
for i in range(nbChildren):
|
|
child = proxyPropElement.GetNestedElement(i)
|
|
if child.GetName() == 'ProxyListDomain':
|
|
domain = propInstance.GetDomain(child.GetAttribute('name'))
|
|
foundPLDChild = True
|
|
for j in range(domain.GetNumberOfProxies()):
|
|
subProxy = domain.GetProxy(j)
|
|
pelt = proxyDefMgr.GetCollapsedProxyDefinition(subProxy.GetXMLGroup(),
|
|
subProxy.GetXMLName(),
|
|
None)
|
|
self.processXmlElement(subProxy, pelt)
|
|
return foundPLDChild
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Gather information from the xml associated with a proxy and properties.
|
|
#--------------------------------------------------------------------------
|
|
def processXmlElement(self, proxy, xmlElement):
|
|
proxyId = proxy.GetGlobalIDAsString()
|
|
nbChildren = xmlElement.GetNumberOfNestedElements()
|
|
for i in range(nbChildren):
|
|
xmlChild = xmlElement.GetNestedElement(i)
|
|
name = xmlChild.GetName()
|
|
nameAttr = xmlChild.GetAttribute('name')
|
|
detailsKey = proxyId + ':' + str(nameAttr)
|
|
|
|
infoOnly = xmlChild.GetAttribute('information_only')
|
|
isInternal = xmlChild.GetAttribute('is_internal')
|
|
panelVis = xmlChild.GetAttribute('panel_visibility')
|
|
numElts = xmlChild.GetAttribute('number_of_elements')
|
|
|
|
size = -1
|
|
informationOnly = 0
|
|
internal = 0
|
|
|
|
# Check for attributes that might only exist on parent xml
|
|
parentQueryList = []
|
|
if not numElts:
|
|
parentQueryList.append('number_of_elements')
|
|
else:
|
|
size = int(numElts)
|
|
|
|
if not infoOnly:
|
|
parentQueryList.append('information_only')
|
|
else:
|
|
informationOnly = int(infoOnly)
|
|
|
|
if not isInternal:
|
|
parentQueryList.append('is_internal')
|
|
else:
|
|
internal = int(isInternal)
|
|
|
|
# Try to retrieve those attributes from parent xml
|
|
parentAttrs = {}
|
|
if len(parentQueryList) > 0:
|
|
propInstance = proxy.GetProperty(nameAttr)
|
|
if propInstance:
|
|
parentAttrs = self.extractParentAttributes(propInstance,
|
|
parentQueryList)
|
|
|
|
if 'number_of_elements' in parentAttrs and parentAttrs['number_of_elements']:
|
|
size = int(parentAttrs['number_of_elements'])
|
|
if 'information_only' in parentAttrs and parentAttrs['information_only']:
|
|
informationOnly = int(parentAttrs['information_only'])
|
|
if 'is_internal' in parentAttrs and parentAttrs['is_internal']:
|
|
internal = int(parentAttrs['is_internal'])
|
|
|
|
# Now decide whether we should filter out this property
|
|
if ((panelVis == 'never' or informationOnly == 1 or internal == 1) and nameAttr not in self.alwaysIncludeProperties) or nameAttr in self.alwaysExcludeProperties:
|
|
self.debug('Filtering out property ' + str(nameAttr) + ' because panelVis is never, infoOnly is 1, isInternal is 1, or ' + str(nameAttr) + ' is an ALWAYS EXCLUDE property')
|
|
continue
|
|
|
|
if name == 'Hints':
|
|
self.debug(" ((((((((((Got the hints))))))))) ")
|
|
for j in range(xmlChild.GetNumberOfNestedElements()):
|
|
hintChild = xmlChild.GetNestedElement(j)
|
|
if hintChild.GetName() == 'Visibility':
|
|
replaceInputAttr = hintChild.GetAttribute('replace_input')
|
|
if replaceInputAttr:
|
|
self.propertyDetailsMap['specialHints'] = { 'replaceInput': int(replaceInputAttr) }
|
|
self.debug(" Replace input value: " + str(replaceInputAttr))
|
|
elif name == 'ProxyProperty' or name == 'InputProperty':
|
|
self.debug(str(nameAttr) + ' is a proxy property')
|
|
if detailsKey not in self.propertyDetailsMap:
|
|
self.propertyDetailsMap[detailsKey] = {'type': name, 'panelVis': panelVis, 'size': size}
|
|
self.orderedNameList.append(detailsKey)
|
|
foundProxyListDomain = self.getProxyListFromProperty(proxy, xmlChild)
|
|
if foundProxyListDomain == True:
|
|
self.propertyDetailsMap[detailsKey]['size'] = 1
|
|
elif name.endswith('Property'):
|
|
# Add this property to the list that will be used both to
|
|
# order as well as to filter the properties retrieved earlier.
|
|
if detailsKey not in self.propertyDetailsMap:
|
|
self.propertyDetailsMap[detailsKey] = {'type': name, 'panelVis': panelVis, 'size': size}
|
|
self.orderedNameList.append(detailsKey)
|
|
else:
|
|
# Don't recurse on properties that might be within a
|
|
# PropertyGroup unless the PropertyGroup is itself within an
|
|
# ExposedProperties. Later we might want to make a note in the
|
|
# ui properties that the properties listed within here should be
|
|
# grouped
|
|
if name != 'PropertyGroup' or xmlChild.GetParent().GetName() == "ExposedProperties":
|
|
self.processXmlElement(proxy, xmlChild)
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Entry point for the xml processing methods.
|
|
#--------------------------------------------------------------------------
|
|
def getProxyXmlDefinitions(self, proxy):
|
|
self.orderedNameList = []
|
|
self.propertyDetailsMap = {}
|
|
self.propertyDetailsList = []
|
|
proxyDefMgr = servermanager.ActiveConnection.Session.GetProxyDefinitionManager()
|
|
xmlElement = proxyDefMgr.GetCollapsedProxyDefinition(proxy.GetXMLGroup(),
|
|
proxy.GetXMLName(),
|
|
None)
|
|
self.processXmlElement(proxy, xmlElement)
|
|
|
|
self.debug('Length of final ordered property list: ' + str(len(self.orderedNameList)))
|
|
for propName in self.orderedNameList:
|
|
propRec = self.propertyDetailsMap[propName]
|
|
self.debug('Property ' + str(propName) + ', type = ' + str(propRec['type']) + ', pvis = ' + str(propRec['panelVis']) + ', size = ' + str(propRec['size']))
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Helper function to fill in all the properties of a proxy. Delegates
|
|
# properties with specific domain types to other helpers.
|
|
#--------------------------------------------------------------------------
|
|
def fillPropertyList(self, proxy_id, propertyList, dependency=None):
|
|
proxy = self.mapIdToProxy(proxy_id)
|
|
if proxy:
|
|
for property in proxy.ListProperties():
|
|
propertyName = proxy.GetProperty(property).Name
|
|
displayName = proxy.GetProperty(property).GetXMLLabel()
|
|
if propertyName in ["Refresh", "Input"] or propertyName.__contains__("Info"):
|
|
continue
|
|
|
|
prop = proxy.GetProperty(property)
|
|
pythonProp = servermanager._wrap_property(proxy, prop)
|
|
|
|
propJson = { 'name': propertyName,
|
|
'id': proxy_id,
|
|
'label': displayName }
|
|
|
|
if dependency is not None:
|
|
propJson['dependency'] = dependency
|
|
|
|
if type(prop) == ProxyProperty or type(prop) == InputProperty:
|
|
proxyListDomain = prop.FindDomain('vtkSMProxyListDomain')
|
|
if proxyListDomain:
|
|
# Set the value of this ProxyProperty
|
|
propJson['value'] = prop.GetProxy(0).GetXMLLabel()
|
|
|
|
# Now recursively fill in properties of this proxy property
|
|
for i in range(proxyListDomain.GetNumberOfProxies()):
|
|
subProxy = proxyListDomain.GetProxy(i)
|
|
subProxyId = subProxy.GetGlobalIDAsString()
|
|
depStr = proxy_id + ':' + propertyName + ':' + subProxy.GetXMLLabel() + ':1'
|
|
self.fillPropertyList(subProxyId, propertyList, depStr)
|
|
else:
|
|
# The value of this ProxyProperty is the list of proxy ids
|
|
value = []
|
|
for i in range(prop.GetNumberOfProxies()):
|
|
p = prop.GetProxy(i)
|
|
value.append(p.GetGlobalIDAsString())
|
|
propJson['value'] = value
|
|
else:
|
|
# For everything but ProxyProperty, we should just be able to call GetData()
|
|
try:
|
|
propJson['value'] = proxy.GetProperty(property).GetData()
|
|
except AttributeError as attrErr:
|
|
print 'Property ' + propertyName + ' has no GetData() method, skipping'
|
|
continue
|
|
|
|
self.debug('Adding a property to the pre-sorted list: ' + str(propJson))
|
|
propertyList.append(propJson)
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Make a first pass over the properties, building up a couple of data
|
|
# structures we can use to reorder the properties to match the xml order.
|
|
#--------------------------------------------------------------------------
|
|
def reorderProperties(self, rootProxyId, proxyProperties):
|
|
self.getProxyXmlDefinitions(self.mapIdToProxy(rootProxyId))
|
|
|
|
propMap = {}
|
|
for prop in proxyProperties:
|
|
propMap[prop['id'] + ':' + prop['name']] = prop
|
|
|
|
orderedList = []
|
|
for name in self.orderedNameList:
|
|
if name in propMap:
|
|
orderedList.append(propMap[name])
|
|
|
|
return orderedList
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Convenience function to set a property value. Can be extended with other
|
|
# special cases as the need arises.
|
|
#--------------------------------------------------------------------------
|
|
def setProperty(self, property, propertyValue):
|
|
if propertyValue == 'vtkProcessId':
|
|
property.SetElement(0, propertyValue)
|
|
else:
|
|
property.SetData(propertyValue)
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Taken directly from helper.py, applies domains so we can see real values.
|
|
#--------------------------------------------------------------------------
|
|
def applyDomains(self, parentProxy, proxy_id):
|
|
"""
|
|
This function is used to apply the domains so that queries for
|
|
specific values like range min and max retrieve something useful.
|
|
"""
|
|
proxy = self.mapIdToProxy(proxy_id)
|
|
|
|
# Call recursively on each sub-proxy if any
|
|
for property_name in proxy.ListProperties():
|
|
prop = proxy.GetProperty(property_name)
|
|
if prop.IsA('vtkSMProxyProperty'):
|
|
try:
|
|
if len(prop.Available) and prop.GetNumberOfProxies() == 1:
|
|
listdomain = prop.GetDomain('proxy_list')
|
|
if listdomain:
|
|
for i in xrange(listdomain.GetNumberOfProxies()):
|
|
internal_proxy = listdomain.GetProxy(i)
|
|
self.applyDomains(parentProxy, internal_proxy.GetGlobalIDAsString())
|
|
except:
|
|
exc_type, exc_obj, exc_tb = sys.exc_info()
|
|
print "Unexpected error:", exc_type, " line: " , exc_tb.tb_lineno
|
|
|
|
# Reset all properties to leverage domain capabilities
|
|
for prop_name in proxy.ListProperties():
|
|
if prop_name == 'Input':
|
|
continue
|
|
try:
|
|
prop = proxy.GetProperty(prop_name)
|
|
iter = prop.NewDomainIterator()
|
|
iter.Begin()
|
|
while not iter.IsAtEnd():
|
|
domain = iter.GetDomain()
|
|
iter.Next()
|
|
|
|
try:
|
|
if domain.IsA('vtkSMBoundsDomain'):
|
|
domain.SetDomainValues(parentProxy.GetDataInformation().GetBounds())
|
|
except AttributeError as attrErr:
|
|
print 'Caught exception setting domain values in apply_domains:'
|
|
print attrErr
|
|
|
|
prop.ResetToDefault()
|
|
|
|
# Need to UnRegister to handle the ref count from the NewDomainIterator
|
|
iter.UnRegister(None)
|
|
except:
|
|
exc_type, exc_obj, exc_tb = sys.exc_info()
|
|
print "Unexpected error:", exc_type, " line: " , exc_tb.tb_lineno
|
|
|
|
proxy.UpdateVTKObjects()
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Helper function to gather information about the bounds, point, and cell
|
|
# data.
|
|
#--------------------------------------------------------------------------
|
|
def getDataInformation(self, proxy):
|
|
# The stuff in here works, but only after you have created the
|
|
# representation.
|
|
dataInfo = proxy.GetDataInformation()
|
|
|
|
# Get some basic statistics about the data
|
|
info = { 'bounds': dataInfo.DataInformation.GetBounds(),
|
|
'points': dataInfo.DataInformation.GetNumberOfPoints(),
|
|
'cells': dataInfo.DataInformation.GetNumberOfCells(),
|
|
'type': dataInfo.DataInformation.GetPrettyDataTypeString(),
|
|
'memory': dataInfo.DataInformation.GetMemorySize() }
|
|
|
|
arrayData = []
|
|
|
|
# Get information about point data arrays
|
|
pdInfo = proxy.GetPointDataInformation()
|
|
numberOfPointArrays = pdInfo.GetNumberOfArrays()
|
|
for idx in xrange(numberOfPointArrays):
|
|
array = pdInfo.GetArray(idx)
|
|
numComps = array.GetNumberOfComponents()
|
|
typeStr = ParaViewWebProxyManager.VTK_DATA_TYPES[array.GetDataType()]
|
|
data = { 'name': array.Name, 'size': numComps, 'location': 'POINTS', 'type': typeStr }
|
|
magRange = array.GetRange(-1)
|
|
rangeList = []
|
|
if numComps > 1:
|
|
rangeList.append({ 'name': 'Magnitude', 'min': magRange[0], 'max': magRange[1] })
|
|
for i in range(numComps):
|
|
ithrange = array.GetRange(i)
|
|
rangeList.append({ 'name': array.GetComponentName(i),
|
|
'min': ithrange[0],
|
|
'max': ithrange[1] })
|
|
else:
|
|
rangeList.append({ 'name': '', 'min': magRange[0], 'max': magRange[1] })
|
|
|
|
data['range'] = rangeList
|
|
arrayData.append(data)
|
|
|
|
# Get information about cell data arrays
|
|
cdInfo = proxy.GetCellDataInformation()
|
|
numberOfCellArrays = cdInfo.GetNumberOfArrays()
|
|
for idx in xrange(numberOfCellArrays):
|
|
array = cdInfo.GetArray(idx)
|
|
numComps = array.GetNumberOfComponents()
|
|
typeStr = ParaViewWebProxyManager.VTK_DATA_TYPES[array.GetDataType()]
|
|
data = { 'name': array.Name, 'size': numComps, 'location': 'CELLS', 'type': typeStr }
|
|
magRange = array.GetRange(-1)
|
|
rangeList = []
|
|
if numComps > 1:
|
|
rangeList.append({ 'name': 'Magnitude', 'min': magRange[0], 'max': magRange[1] })
|
|
for i in range(numComps):
|
|
ithrange = array.GetRange(i)
|
|
rangeList.append({ 'name': array.GetComponentName(i),
|
|
'min': ithrange[0],
|
|
'max': ithrange[1] })
|
|
else:
|
|
rangeList.append({ 'name': '', 'min': magRange[1], 'max': magRange[1] })
|
|
|
|
data['range'] = rangeList
|
|
arrayData.append(data)
|
|
|
|
# Get field data information
|
|
fdInfo = dataInfo.DataInformation.GetFieldDataInformation()
|
|
numFieldDataArrays = fdInfo.GetNumberOfArrays()
|
|
for idx in xrange(numFieldDataArrays):
|
|
array = fdInfo.GetArrayInformation(idx)
|
|
numComps = array.GetNumberOfComponents()
|
|
typeStr = ParaViewWebProxyManager.VTK_DATA_TYPES[array.GetDataType()]
|
|
data = { 'name': array.GetName(), 'size': numComps, 'location': 'FIELDS', 'type': typeStr }
|
|
rangeList = []
|
|
for i in range(numComps):
|
|
ithrange = array.GetComponentRange(i)
|
|
rangeList.append({ 'name': array.GetComponentName(i),
|
|
'min': ithrange[0],
|
|
'max': ithrange[1] })
|
|
data['range'] = rangeList
|
|
arrayData.append(data)
|
|
|
|
# Time data
|
|
timeKeeper = servermanager.ProxyManager().GetProxiesInGroup("timekeeper").values()[0]
|
|
tsVals = timeKeeper.TimestepValues
|
|
if tsVals:
|
|
if isinstance(tsVals, float):
|
|
tsVals = [ tsVals ]
|
|
tValStrings = []
|
|
for tsVal in tsVals:
|
|
tValStrings.append(str(tsVal))
|
|
info['time'] = tValStrings
|
|
else:
|
|
info['time'] = []
|
|
|
|
info['arrays'] = arrayData
|
|
return info
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Helper function to gather information about the coloring used by this
|
|
# representation proxy.
|
|
#--------------------------------------------------------------------------
|
|
def getColorInformation(self, proxy):
|
|
"""
|
|
Generates a block of color information, given a representation proxy.
|
|
|
|
colorBy: {
|
|
'representation': '652',
|
|
'scalarBar': 0,
|
|
'mode': 'color' # 'array'
|
|
'color': [ 0.5, 1.0, 1.0 ],
|
|
'array': [ 'POINTS', 'RTData', -1 ],
|
|
'colorMap': 'Blue to Red'
|
|
}
|
|
"""
|
|
colorInfo = { 'representation': proxy.GetGlobalIDAsString() }
|
|
if proxy.GetProperty('DiffuseColor'):
|
|
colorInfo['color'] = proxy.GetProperty('DiffuseColor').GetData()
|
|
|
|
if proxy.GetProperty('ColorArrayName'):
|
|
colorInfo['mode'] = 'array'
|
|
view = self.getView(-1)
|
|
sbVisible = vtkSMPVRepresentationProxy.IsScalarBarVisible(proxy.SMProxy,
|
|
view.SMProxy);
|
|
colorInfo['scalarBar'] = 1 if sbVisible else 0
|
|
lut = proxy.GetProperty('LookupTable').GetData()
|
|
component = -1
|
|
|
|
if lut and lut.GetProperty('VectorMode').GetData() == 'Component':
|
|
component = lut.GetProperty('VectorComponent').GetData()
|
|
|
|
colorArrayProp = proxy.GetProperty('ColorArrayName')
|
|
colorInfo['array'] = [colorArrayProp.GetAssociation(),
|
|
colorArrayProp.GetArrayName(),
|
|
component]
|
|
else:
|
|
colorInfo['mode'] = 'color'
|
|
colorInfo['scalarBar'] = 0
|
|
|
|
return colorInfo
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Helper function populate the ui structures list, which corresponds with
|
|
# with the properties list, element by element, and provides information
|
|
# about the ui elements needed to represent each property.
|
|
#--------------------------------------------------------------------------
|
|
def getUiProperties(self, proxyId, proxyProperties):
|
|
"""
|
|
Generates an array of objects, parallel to the properties, containing
|
|
the information needed to render ui for each property. Not all of the
|
|
following attributes will always be present.
|
|
|
|
ui : [ {
|
|
'name': 'Clip Type',
|
|
'advanced': 1, # 0
|
|
'doc': 'Documentation string for the property',
|
|
'dependency': '498:ClipFunction:Sphere:1',
|
|
'values': { 'Plane': '456', 'Box': '457', 'Scalar': '458', 'Sphere': '459' },
|
|
'type': 'int', # 'float', 'int', 'str', 'proxy', 'input', ...
|
|
'widget': 'textfield', # 'checkbox', 'textarea', 'list-1', 'list-n'
|
|
'size': -1, # -1, 0, 2, 3, 6
|
|
'range': [ { 'min': 0, 'max': 1 }, { 'min': 4, 'max': 7 }, ... ]
|
|
}, ...
|
|
]
|
|
"""
|
|
uiProps = []
|
|
|
|
for proxyProp in proxyProperties:
|
|
uiElt = {}
|
|
|
|
proxyId = proxyProp['id']
|
|
propertyName = proxyProp['name']
|
|
proxy = self.mapIdToProxy(proxyId)
|
|
prop = proxy.GetProperty(propertyName)
|
|
|
|
# Get the xml details we already parsed out for this property
|
|
xmlProps = self.propertyDetailsMap[proxyId + ':' + propertyName]
|
|
|
|
# Set a few defaults for the ui elements, which may be overridden
|
|
uiElt['size'] = xmlProps['size']
|
|
uiElt['widget'] = 'textfield'
|
|
uiElt['type'] = self.propertyTypeMap[prop.GetClassName()]
|
|
|
|
doc = prop.GetDocumentation()
|
|
if doc:
|
|
uiElt['doc'] = doc.GetDescription()
|
|
|
|
uiElt['name'] = proxyProp['label']
|
|
proxyProp.pop('label', None)
|
|
|
|
if 'dependency' in proxyProp:
|
|
uiElt['depends'] = proxyProp['dependency']
|
|
proxyProp.pop('dependency', None)
|
|
if xmlProps['panelVis'] == 'advanced':
|
|
uiElt['advanced'] = 1
|
|
else:
|
|
uiElt['advanced'] = 0
|
|
|
|
# Iterate over the property domains until we find the interesting one
|
|
domainIter = prop.NewDomainIterator()
|
|
domainIter.Begin()
|
|
foundDomain = False
|
|
while domainIter.IsAtEnd() == 0 and foundDomain == False:
|
|
nextDomain = domainIter.GetDomain()
|
|
domainName = nextDomain.GetClassName()
|
|
if domainName in self.domainFunctionMap:
|
|
domainFunction = self.domainFunctionMap[domainName]
|
|
foundDomain = domainFunction(proxyProp, xmlProps, uiElt, nextDomain)
|
|
self.debug('Processed ' + str(propertyName) + ' as domain ' + str(domainName))
|
|
domainIter.Next()
|
|
|
|
if foundDomain == False:
|
|
self.debug(' ~~~|~~~ ' + str(propertyName) + ' did not have recognized domain')
|
|
|
|
domainIter.FastDelete()
|
|
|
|
# Now get hints for the property and provide an opportunity to decorate
|
|
hints = prop.GetHints()
|
|
if hints:
|
|
numHints = hints.GetNumberOfNestedElements()
|
|
for idx in range(numHints):
|
|
hint = hints.GetNestedElement(idx)
|
|
if hint.GetName() in self.hintsMap:
|
|
hmap = self.hintsMap[hint.GetName()]
|
|
hintType = hint.GetAttribute('type')
|
|
if hintType and hintType in hmap:
|
|
# We're intereseted in decorating based on this hint
|
|
hintFunction = hmap[hintType]
|
|
hintFunction(prop, uiElt, hint)
|
|
|
|
uiProps.append(uiElt)
|
|
|
|
return uiProps
|
|
|
|
#--------------------------------------------------------------------------
|
|
# Helper function validates the string we get from the client to make sure
|
|
# it is one of the allowed proxies that has been set up on the server side.
|
|
#--------------------------------------------------------------------------
|
|
def validate(self, functionName):
|
|
if functionName not in self.allowedProxies:
|
|
return None
|
|
else:
|
|
return functionName.strip()
|
|
|
|
@exportRpc("pv.proxy.manager.create")
|
|
def create(self, functionName, parentId):
|
|
"""
|
|
Creates a new filter/source proxy as a child of the specified
|
|
parent proxy. Returns the proxy state for the newly created
|
|
proxy as a JSON object.
|
|
"""
|
|
name = self.validate(functionName)
|
|
|
|
if not name:
|
|
return { 'success': False,
|
|
'reason': '"' + functionName + '" was not valid and could not be evaluated' }
|
|
|
|
pid = parentId
|
|
parentProxy = self.mapIdToProxy(parentId)
|
|
if parentProxy:
|
|
simple.SetActiveSource(parentProxy)
|
|
else:
|
|
pid = '0'
|
|
|
|
# Create new source/filter
|
|
allowed = self.allowedProxies[name]
|
|
newProxy = paraview.simple.__dict__[allowed]()
|
|
|
|
# To make WebGL export work
|
|
simple.Show()
|
|
simple.Render()
|
|
|
|
try:
|
|
self.applyDomains(parentProxy, newProxy.GetGlobalIDAsString())
|
|
except Exception as inst:
|
|
print 'Caught exception applying domains:'
|
|
print inst
|
|
|
|
return self.get(newProxy.GetGlobalIDAsString())
|
|
|
|
@exportRpc("pv.proxy.manager.create.reader")
|
|
def open(self, relativePath):
|
|
"""
|
|
Open relative file paths, attempting to use the file extension to select
|
|
from the configured readers.
|
|
"""
|
|
fileToLoad = []
|
|
if type(relativePath) == list:
|
|
for file in relativePath:
|
|
fileToLoad.append(self.getAbsolutePath(file))
|
|
else:
|
|
fileToLoad.append(self.getAbsolutePath(relativePath))
|
|
|
|
# Get file extension and look for configured reader
|
|
idx = fileToLoad[0].rfind('.')
|
|
extension = fileToLoad[0][idx+1:]
|
|
|
|
# Check if we were asked to load a state file
|
|
if extension == 'pvsm':
|
|
simple.LoadState(fileToLoad[0])
|
|
simple.Render()
|
|
simple.ResetCamera()
|
|
|
|
return { 'success': True }
|
|
|
|
readerName = None
|
|
if extension in self.readerFactoryMap:
|
|
readerName = self.readerFactoryMap[extension][0]
|
|
customMethod = self.readerFactoryMap[extension][1]
|
|
|
|
# Open the file(s)
|
|
reader = None
|
|
if readerName:
|
|
kw = { customMethod: fileToLoad }
|
|
reader = paraview.simple.__dict__[readerName](**kw)
|
|
else:
|
|
if self.allowUnconfiguredReaders:
|
|
reader = simple.OpenDataFile(fileToLoad)
|
|
else:
|
|
return { 'success': False,
|
|
'reason': 'No configured reader found for ' + extension + ' files, and unconfigured readers are not enabled.' }
|
|
|
|
# Rename the reader proxy
|
|
name = fileToLoad[0].split("/")[-1]
|
|
if len(name) > 15:
|
|
name = name[:15] + '*'
|
|
simple.RenameSource(name, reader)
|
|
|
|
# Representation, view, and camera setup
|
|
simple.Show()
|
|
simple.Render()
|
|
simple.ResetCamera()
|
|
|
|
return { 'success': True, 'id': reader.GetGlobalIDAsString() }
|
|
|
|
@exportRpc("pv.proxy.manager.get")
|
|
def get(self, proxyId, ui=True):
|
|
"""
|
|
Returns the proxy state for the given proxyId as a JSON object.
|
|
"""
|
|
proxyProperties = []
|
|
proxyId = str(proxyId)
|
|
self.fillPropertyList(proxyId, proxyProperties)
|
|
proxyProperties = self.reorderProperties(proxyId, proxyProperties)
|
|
proxyJson = { 'id': proxyId, 'properties': proxyProperties }
|
|
|
|
# Perform costly request only when needed
|
|
if ui:
|
|
proxyJson['ui'] = self.getUiProperties(proxyId, proxyProperties)
|
|
|
|
if 'specialHints' in self.propertyDetailsMap:
|
|
proxyJson['hints'] = self.propertyDetailsMap['specialHints']
|
|
|
|
proxy = self.mapIdToProxy(proxyId)
|
|
|
|
if proxy.SMProxy.IsA('vtkSMRepresentationProxy') == 1:
|
|
colorInfo = self.getColorInformation(proxy)
|
|
proxyJson['colorBy'] = colorInfo
|
|
elif proxy.SMProxy.IsA('vtkSMSourceProxy') == 1:
|
|
dataInfo = self.getDataInformation(proxy)
|
|
proxyJson['data'] = dataInfo
|
|
|
|
return proxyJson
|
|
|
|
@exportRpc("pv.proxy.manager.find.id")
|
|
def findProxyId(self, groupName, proxyName):
|
|
proxyMgr = servermanager.ProxyManager()
|
|
proxy = proxyMgr.GetProxy(groupName, proxyName)
|
|
proxyId = proxy.SMProxy.GetGlobalIDAsString()
|
|
return proxyId
|
|
|
|
@exportRpc("pv.proxy.manager.update")
|
|
def update(self, propertiesList):
|
|
"""
|
|
Takes a list of properties and updates the corresponding proxies.
|
|
"""
|
|
failureList = []
|
|
for newPropObj in propertiesList:
|
|
try:
|
|
proxyId = str(newPropObj['id'])
|
|
proxy = self.mapIdToProxy(proxyId)
|
|
propertyName = servermanager._make_name_valid(newPropObj['name'])
|
|
propertyValue = helper.removeUnicode(newPropObj['value'])
|
|
proxyProperty = servermanager._wrap_property(proxy, proxy.GetProperty(propertyName))
|
|
self.setProperty(proxyProperty, propertyValue)
|
|
except:
|
|
failureList.append('Unable to set property for proxy ' + proxyId + ': ' + str(newPropObj))
|
|
|
|
if len(failureList) > 0:
|
|
return { 'success': False,
|
|
'errorList': failureList }
|
|
|
|
return { 'success': True }
|
|
|
|
@exportRpc("pv.proxy.manager.delete")
|
|
def delete(self, proxyId):
|
|
"""
|
|
Checks if the proxy can be deleted (it is not the input to another
|
|
proxy), and then deletes it if so. Returns True if the proxy was
|
|
deleted, False otherwise.
|
|
"""
|
|
proxy = self.mapIdToProxy(proxyId)
|
|
canDelete = True
|
|
pid = '0'
|
|
for proxyJson in self.list()['sources']:
|
|
if proxyJson['parent'] == proxyId:
|
|
canDelete = False
|
|
elif proxyJson['id'] == proxyId:
|
|
pid = proxyJson['parent']
|
|
if proxy is not None and canDelete is True:
|
|
simple.Delete(proxy)
|
|
self.updateScalarBars()
|
|
return { 'success': 1, 'id': pid }
|
|
return { 'success': 0, 'id': '0' }
|
|
|
|
@exportRpc("pv.proxy.manager.list")
|
|
def list(self, viewId=None):
|
|
"""
|
|
Returns the current proxy list, specifying for each proxy it's
|
|
name, id, and parent (input) proxy id. A 'parent' of '0' means
|
|
the proxy has no input.
|
|
|
|
{ 'view': '376',
|
|
'sources': [
|
|
{'name': 'disk_out_ref', 'id': '350', 'parent': '0', 'rep': '352', 'visible': 1 },
|
|
{'name': 'Contour0', 'id': '455', 'parent': '350', 'rep': '319', 'visible': 0 },
|
|
{'name': 'Clip0', 'id': '471', 'parent': '455', 'rep': '327', 'visible': 1 },
|
|
{'name': 'Calc0', 'id': '221', 'multiparent': 2,
|
|
'parent': '471', 'parent_1': '455', 'rep': '756', 'visible': 1 }
|
|
]
|
|
}
|
|
"""
|
|
proxies = servermanager.ProxyManager().GetProxiesInGroup("sources")
|
|
viewProxy = self.getView(viewId)
|
|
proxyObj = { 'view': viewProxy.GetGlobalIDAsString() }
|
|
proxyList = []
|
|
for key in proxies:
|
|
listElt = {}
|
|
listElt['name'] = key[0]
|
|
listElt['id'] = key[1]
|
|
proxy = proxies[key]
|
|
|
|
rep = simple.GetRepresentation(proxy=proxy, view=viewProxy)
|
|
listElt['rep'] = rep.GetGlobalIDAsString()
|
|
listElt['visible'] = rep.Visibility
|
|
listElt['parent'] = '0'
|
|
if hasattr(proxy, 'Input') and proxy.Input:
|
|
inputProp = proxy.Input
|
|
if hasattr(inputProp, 'GetNumberOfProxies'):
|
|
numProxies = inputProp.GetNumberOfProxies()
|
|
if numProxies > 1:
|
|
listElt['multiparent'] = numProxies
|
|
for inputIdx in range(numProxies):
|
|
proxyId = inputProp.GetProxy(inputIdx).GetGlobalIDAsString()
|
|
if inputIdx == 0:
|
|
listElt['parent'] = proxyId
|
|
else:
|
|
listElt['parent_%d' % inputIdx] = proxyId
|
|
elif numProxies == 1:
|
|
listElt['parent'] = inputProp.GetProxy(0).GetGlobalIDAsString()
|
|
else:
|
|
listElt['parent'] = inputProp.GetGlobalIDAsString()
|
|
|
|
proxyList.append(listElt)
|
|
|
|
proxyObj['sources'] = proxyList
|
|
return proxyObj
|
|
|
|
@exportRpc("pv.proxy.manager.available")
|
|
def available(self, typeOfProxy):
|
|
"""
|
|
Returns a list of the available sources or filters, depending on the
|
|
argument typeOfProxy, which can be either 'filters' or 'sources'. If
|
|
typeOfProxy is anything other than 'filter' or 'source', the empty
|
|
list will be returned.
|
|
|
|
Any attempt to create a source or filter not returned by this method
|
|
will result in an error response. The returned list has the following
|
|
form:
|
|
|
|
[ 'Wavelet', 'Cone', 'Sphere', ... ]
|
|
|
|
or
|
|
|
|
[ 'Contour', 'Clip', 'Slice', ... ]
|
|
|
|
"""
|
|
return self.availableList[typeOfProxy]
|
|
|
|
# =============================================================================
|
|
#
|
|
# Pipeline manager
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebPipelineManager(ParaViewWebProtocol):
|
|
|
|
def __init__(self, baseDir=None, fileToLoad=None):
|
|
super(ParaViewWebPipelineManager, self).__init__()
|
|
# Setup global variables
|
|
self.pipeline = helper.Pipeline('Kitware')
|
|
self.view = simple.GetRenderView()
|
|
self.baseDir = baseDir;
|
|
simple.SetActiveView(self.view)
|
|
simple.Render()
|
|
if fileToLoad and fileToLoad[-5:] != '.pvsm':
|
|
try:
|
|
self.openFile(fileToLoad)
|
|
except:
|
|
print "error loading..."
|
|
|
|
def getColorTransferFunction(self, arrayName):
|
|
proxyMgr = vtkSMProxyManager.GetProxyManager()
|
|
sessionProxyMgr = proxyMgr.GetActiveSessionProxyManager()
|
|
lutMgr = vtkSMTransferFunctionManager()
|
|
return lutMgr.GetColorTransferFunction(arrayName, sessionProxyMgr)
|
|
|
|
# RpcName: reloadPipeline => pv.pipeline.manager.reload
|
|
@exportRpc("pv.pipeline.manager.reload")
|
|
def reloadPipeline(self):
|
|
self.pipeline.clear()
|
|
pxm = simple.servermanager.ProxyManager()
|
|
|
|
# Fill tree structure (order-less)
|
|
for proxyInfo in pxm.GetProxiesInGroup("sources"):
|
|
id = str(proxyInfo[1])
|
|
proxy = helper.idToProxy(id)
|
|
parentId = helper.getParentProxyId(proxy)
|
|
self.pipeline.addNode(parentId, id)
|
|
|
|
return self.pipeline.getRootNode(self.getView(-1))
|
|
|
|
# RpcName: getPipeline => pv.pipeline.manager.pipeline.get
|
|
@exportRpc("pv.pipeline.manager.pipeline.get")
|
|
def getPipeline(self):
|
|
if self.pipeline.isEmpty():
|
|
return self.reloadPipeline()
|
|
return self.pipeline.getRootNode(self.getView(-1))
|
|
|
|
# RpcName: addSource => pv.pipeline.manager.proxy.add
|
|
@exportRpc("pv.pipeline.manager.proxy.add")
|
|
def addSource(self, algo_name, parent):
|
|
pid = str(parent)
|
|
parentProxy = helper.idToProxy(parent)
|
|
if parentProxy:
|
|
simple.SetActiveSource(parentProxy)
|
|
else:
|
|
pid = '0'
|
|
|
|
# Create new source/filter
|
|
cmdLine = 'simple.' + algo_name + '()'
|
|
newProxy = eval(cmdLine)
|
|
|
|
# Create its representation and render
|
|
simple.Show()
|
|
simple.Render()
|
|
simple.ResetCamera()
|
|
|
|
# Add node to pipeline
|
|
self.pipeline.addNode(pid, newProxy.GetGlobalIDAsString())
|
|
|
|
# Handle domains
|
|
helper.apply_domains(parentProxy, newProxy.GetGlobalIDAsString())
|
|
|
|
# Return the newly created proxy pipeline node
|
|
return helper.getProxyAsPipelineNode(newProxy.GetGlobalIDAsString(), self.getView(-1))
|
|
|
|
# RpcName: deleteSource => pv.pipeline.manager.proxy.delete
|
|
@exportRpc("pv.pipeline.manager.proxy.delete")
|
|
def deleteSource(self, proxy_id):
|
|
self.pipeline.removeNode(proxy_id)
|
|
proxy = helper.idToProxy(proxy_id)
|
|
simple.Delete(proxy)
|
|
simple.Render()
|
|
|
|
# RpcName: updateDisplayProperty => pv.pipeline.manager.proxy.representation.update
|
|
@exportRpc("pv.pipeline.manager.proxy.representation.update")
|
|
def updateDisplayProperty(self, options):
|
|
proxy = helper.idToProxy(options['proxy_id'])
|
|
rep = simple.GetDisplayProperties(proxy)
|
|
helper.updateProxyProperties(rep, options)
|
|
|
|
if options.has_key('ColorArrayName') and len(options['ColorArrayName']) > 0:
|
|
name = options['ColorArrayName']
|
|
type = options['ColorAttributeType']
|
|
|
|
if type == 'POINT_DATA':
|
|
attr_type = vtkDataObject.POINT
|
|
elif type == 'CELL_DATA':
|
|
attr_type = vtkDataObject.CELL
|
|
|
|
dataRepr = simple.GetRepresentation(proxy)
|
|
|
|
vtkSMPVRepresentationProxy.SetScalarColoring(dataRepr.SMProxy, name, attr_type)
|
|
vtkSMPVRepresentationProxy.RescaleTransferFunctionToDataRange(dataRepr.SMProxy, name, attr_type, True)
|
|
|
|
simple.Render()
|
|
|
|
# RpcName: pushState => pv.pipeline.manager.proxy.update
|
|
@exportRpc("pv.pipeline.manager.proxy.update")
|
|
def pushState(self, state):
|
|
proxy_type = None
|
|
for proxy_id in state:
|
|
if proxy_id in ['proxy', 'widget_source']:
|
|
proxy_type = proxy_id
|
|
continue
|
|
proxy = helper.idToProxy(proxy_id);
|
|
helper.updateProxyProperties(proxy, state[proxy_id])
|
|
simple.Render()
|
|
|
|
if proxy_type == 'proxy':
|
|
return helper.getProxyAsPipelineNode(state['proxy'], self.getView(-1))
|
|
elif proxy_type == 'widget_source':
|
|
proxy.UpdateWidget(proxy.Observed)
|
|
|
|
# RpcName: openFile => pv.pipeline.manager.file.open
|
|
@exportRpc("pv.pipeline.manager.file.open")
|
|
def openFile(self, path):
|
|
reader = simple.OpenDataFile(path)
|
|
simple.RenameSource( path.split("/")[-1], reader)
|
|
simple.Show()
|
|
simple.Render()
|
|
simple.ResetCamera()
|
|
|
|
# Add node to pipeline
|
|
self.pipeline.addNode('0', reader.GetGlobalIDAsString())
|
|
|
|
return helper.getProxyAsPipelineNode(reader.GetGlobalIDAsString(), self.getView(-1))
|
|
|
|
# RpcName: openRelativeFile => pv.pipeline.manager.file.ropen
|
|
@exportRpc("pv.pipeline.manager.file.ropen")
|
|
def openRelativeFile(self, relativePath):
|
|
fileToLoad = []
|
|
if type(relativePath) == list:
|
|
for file in relativePath:
|
|
fileToLoad.append(os.path.join(self.baseDir, file))
|
|
else:
|
|
fileToLoad.append(os.path.join(self.baseDir, relativePath))
|
|
|
|
reader = simple.OpenDataFile(fileToLoad)
|
|
name = fileToLoad[0].split("/")[-1]
|
|
if len(name) > 15:
|
|
name = name[:15] + '*'
|
|
simple.RenameSource(name, reader)
|
|
simple.Show()
|
|
simple.Render()
|
|
simple.ResetCamera()
|
|
|
|
# Add node to pipeline
|
|
self.pipeline.addNode('0', reader.GetGlobalIDAsString())
|
|
|
|
return helper.getProxyAsPipelineNode(reader.GetGlobalIDAsString(), self.getView(-1))
|
|
|
|
# RpcName: updateScalarbarVisibility => pv.pipeline.manager.scalarbar.visibility.update
|
|
@exportRpc("pv.pipeline.manager.scalarbar.visibility.update")
|
|
def updateScalarbarVisibility(self, options):
|
|
lutMgr = vtkSMTransferFunctionManager()
|
|
lutMap = {}
|
|
view = self.getView(-1)
|
|
if options:
|
|
for key, lut in options.iteritems():
|
|
visibility = lut['enabled']
|
|
if type(lut['name']) == unicode:
|
|
lut['name'] = str(lut['name'])
|
|
parts = key.split('_')
|
|
arrayName = parts[0]
|
|
numComps = int(parts[1])
|
|
|
|
lutProxy = self.getColorTransferFunction(arrayName)
|
|
barRep = servermanager._getPyProxy(lutMgr.GetScalarBarRepresentation(lutProxy, view.SMProxy))
|
|
|
|
if visibility == 1:
|
|
barRep.Visibility = 1
|
|
barRep.Enabled = 1
|
|
barRep.Title = arrayName
|
|
if numComps > 1:
|
|
barRep.ComponentTitle = 'Magnitude'
|
|
else:
|
|
barRep.ComponentTitle = ''
|
|
vtkSMScalarBarWidgetRepresentationProxy.PlaceInView(barRep.SMProxy, view.SMProxy)
|
|
else:
|
|
barRep.Visibility = 0
|
|
barRep.Enabled = 0
|
|
|
|
lutMap[key] = { 'lutId': lut['name'],
|
|
'name': arrayName,
|
|
'size': numComps,
|
|
'enabled': visibility }
|
|
return lutMap
|
|
|
|
# RpcName: updateScalarRange => pv.pipeline.manager.scalar.range.rescale
|
|
@exportRpc("pv.pipeline.manager.scalar.range.rescale")
|
|
def updateScalarRange(self, proxyId):
|
|
proxy = self.mapIdToProxy(proxyId);
|
|
dataRepr = simple.GetRepresentation(proxy)
|
|
vtkSMPVRepresentationProxy.RescaleTransferFunctionToDataRange(dataRepr.SMProxy, False)
|
|
|
|
# RpcName: setLutDataRange => pv.pipeline.manager.lut.range.update
|
|
@exportRpc("pv.pipeline.manager.lut.range.update")
|
|
def setLutDataRange(self, name, number_of_components, customRange):
|
|
lut = self.getColorTransferFunction(name)
|
|
vtkSMTransferFunctionProxy.RescaleTransferFunction(lut, customRange[0],
|
|
customRange[1], False)
|
|
|
|
# RpcName: getLutDataRange => pv.pipeline.manager.lut.range.get
|
|
@exportRpc("pv.pipeline.manager.lut.range.get")
|
|
def getLutDataRange(self, name, number_of_components):
|
|
lut = self.getColorTransferFunction(name)
|
|
rgbPoints = lut.GetProperty('RGBPoints')
|
|
return [ rgbPoints.GetElement(0),
|
|
rgbPoints.GetElement(rgbPoints.GetNumberOfElements() - 4) ]
|
|
|
|
|
|
# =============================================================================
|
|
#
|
|
# Key/Value Store Protocol
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebKeyValuePairStore(ParaViewWebProtocol):
|
|
|
|
def __init__(self):
|
|
super(ParaViewWebKeyValuePairStore, self).__init__()
|
|
self.keyValStore = {}
|
|
|
|
# RpcName: storeKeyPair => 'pv.keyvaluepair.store'
|
|
@exportRpc("pv.keyvaluepair.store")
|
|
def storeKeyPair(self, key, value):
|
|
self.keyValStore[key] = value
|
|
|
|
# RpcName: retrieveKeyPair => 'pv.keyvaluepair.retrieve'
|
|
@exportRpc("pv.keyvaluepair.retrieve")
|
|
def retrieveKeyPair(self, key):
|
|
if key in self.keyValStore:
|
|
return self.keyValStore[key]
|
|
else:
|
|
return None
|
|
|
|
|
|
# =============================================================================
|
|
#
|
|
# Save Data Protocol
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebSaveData(ParaViewWebProtocol):
|
|
|
|
def __init__(self, baseSavePath=''):
|
|
super(ParaViewWebSaveData, self).__init__()
|
|
|
|
savePath = baseSavePath
|
|
|
|
if baseSavePath.find('=') >= 0:
|
|
parts = baseSavePath.split('=')
|
|
savePath = parts[-1]
|
|
|
|
self.baseSavePath = savePath
|
|
|
|
self.imageExtensions = [ 'jpg', 'png' ]
|
|
self.dataExtensions = [ 'vtk', 'ex2', 'vtp', 'vti', 'vtu', 'vtm', 'vts', 'vtr', 'csv' ]
|
|
self.stateExtensions = [ 'pvsm' ]
|
|
|
|
# RpcName: saveData => 'pv.data.save'
|
|
@exportRpc("pv.data.save")
|
|
def saveData(self, filePath, options=None):
|
|
"""
|
|
Save some data on the server side. Can save data from a proxy, a
|
|
screenshot, or else the current state. Options may be different
|
|
depending on the type of data to be save. Saving a screenshot
|
|
can take an optional size array indicating the desired width and
|
|
height of the saved image. Saving data can take an optional proxy
|
|
id specifying which filter or source to save output from.
|
|
|
|
options = {
|
|
'proxyId': '426',
|
|
'size': [ <imgWidth>, <imgHeight> ]
|
|
}
|
|
|
|
"""
|
|
|
|
# make sure file path exists
|
|
fullPath = os.path.join(self.baseSavePath, filePath)
|
|
if not os.path.exists(os.path.dirname(fullPath)):
|
|
os.makedirs(os.path.dirname(fullPath))
|
|
|
|
# Get file extension and look for configured reader
|
|
idx = filePath.rfind('.')
|
|
extension = filePath[idx+1:]
|
|
|
|
# Now find out what kind of save it is, and do it
|
|
if extension in self.imageExtensions:
|
|
if options and 'size' in options:
|
|
# Get the active view
|
|
v = self.getView(-1)
|
|
|
|
# Keep track of current size
|
|
cw = v.ViewSize[0]
|
|
ch = v.ViewSize[1]
|
|
|
|
# Resize to desired screenshot size
|
|
v.ViewSize = [ int(str(options['size'][0])), int(str(options['size'][1])) ]
|
|
simple.Render()
|
|
|
|
# Save actual screenshot
|
|
simple.SaveScreenshot(fullPath)
|
|
|
|
# Put the size back the way we found it
|
|
v.ViewSize = [cw, ch]
|
|
simple.Render()
|
|
else:
|
|
simple.SaveScreenshot(fullPath)
|
|
elif extension in self.dataExtensions:
|
|
proxy = None
|
|
if options and 'proxyId' in options:
|
|
proxyId = str(options['proxyId'])
|
|
proxy = self.mapIdToProxy(proxyId)
|
|
simple.SaveData(fullPath, proxy)
|
|
elif extension in self.stateExtensions:
|
|
# simple.SaveState(fullPath) # FIXME: Fixed after 4.3.1
|
|
servermanager.SaveState(fullPath)
|
|
else:
|
|
msg = 'ERROR: Unrecognized extension (%s) in relative path: %s' % (extension, filePath)
|
|
print msg
|
|
return { 'success': False, 'message': msg }
|
|
|
|
return { 'success': True }
|
|
|
|
|
|
# =============================================================================
|
|
#
|
|
# Filter list
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebFilterList(ParaViewWebProtocol):
|
|
|
|
def __init__(self, filtersFile=None):
|
|
super(ParaViewWebFilterList, self).__init__()
|
|
self.filterFile = filtersFile
|
|
|
|
# RpcName: listFilters => pv.filters.list
|
|
@exportRpc("pv.filters.list")
|
|
def listFilters(self):
|
|
filterSet = []
|
|
if self.filterFile is None :
|
|
filterSet = [{
|
|
'name': 'Cone',
|
|
'icon': 'dataset',
|
|
'category': 'source'
|
|
},{
|
|
'name': 'Sphere',
|
|
'icon': 'dataset',
|
|
'category': 'source'
|
|
},{
|
|
'name': 'Wavelet',
|
|
'icon': 'dataset',
|
|
'category': 'source'
|
|
},{
|
|
'name': 'Clip',
|
|
'icon': 'clip',
|
|
'category': 'filter'
|
|
},{
|
|
'name': 'Slice',
|
|
'icon': 'slice',
|
|
'category': 'filter'
|
|
},{
|
|
'name': 'Contour',
|
|
'icon': 'contour',
|
|
'category': 'filter'
|
|
},{
|
|
'name': 'Threshold',
|
|
'icon': 'threshold',
|
|
'category': 'filter'
|
|
},{
|
|
'name': 'StreamTracer',
|
|
'icon': 'stream',
|
|
'category': 'filter'
|
|
},{
|
|
'name': 'WarpByScalar',
|
|
'icon': 'filter',
|
|
'category': 'filter'
|
|
}]
|
|
else :
|
|
with open(self.filterFile, 'r') as fd:
|
|
filterSet = json.loads(fd.read())
|
|
|
|
if servermanager.ActiveConnection.GetNumberOfDataPartitions() > 1:
|
|
filterSet.append({ 'name': 'D3', 'icon': 'filter', 'category': 'filter' })
|
|
|
|
return filterSet
|
|
|
|
|
|
# =============================================================================
|
|
#
|
|
# Remote file list @DEPRECATED
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebFileManager(ParaViewWebProtocol):
|
|
|
|
def __init__(self, defaultDirectoryToList):
|
|
super(ParaViewWebFileManager, self).__init__()
|
|
self.directory = defaultDirectoryToList
|
|
self.dirCache = None
|
|
|
|
# RpcName: listFiles => pv.files.list
|
|
@exportRpc("pv.files.list")
|
|
def listFiles(self):
|
|
if not self.dirCache:
|
|
self.dirCache = helper.listFiles(self.directory)
|
|
return self.dirCache
|
|
|
|
|
|
# =============================================================================
|
|
#
|
|
# Handle remote Connection
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebRemoteConnection(ParaViewWebProtocol):
|
|
|
|
# RpcName: connect => pv.remote.connect
|
|
@exportRpc("pv.remote.connect")
|
|
def connect(self, options):
|
|
"""
|
|
Creates a connection to a remote pvserver.
|
|
Expect an option argument which should override any of
|
|
those default properties::
|
|
|
|
{
|
|
'host': 'localhost',
|
|
'port': 11111,
|
|
'rs_host': None,
|
|
'rs_port': 11111
|
|
}
|
|
|
|
"""
|
|
ds_host = "localhost"
|
|
ds_port = 11111
|
|
rs_host = None
|
|
rs_port = 11111
|
|
|
|
|
|
if options:
|
|
if options.has_key("host"):
|
|
ds_host = options["host"]
|
|
if options.has_key("port"):
|
|
ds_port = options["port"]
|
|
if options.has_key("rs_host"):
|
|
rs_host = options["rs_host"]
|
|
if options.has_key("rs_port"):
|
|
rs_host = options["rs_port"]
|
|
|
|
simple.Connect(ds_host, ds_port, rs_host, rs_port)
|
|
|
|
# RpcName: reverseConnect => pv.remote.reverse.connect
|
|
@exportRpc("pv.remote.reverse.connect")
|
|
def reverseConnect(self, port=11111):
|
|
"""
|
|
Create a reverse connection to a server. Listens on port and waits for
|
|
an incoming connection from the server.
|
|
"""
|
|
simple.ReverseConnect(port)
|
|
|
|
# RpcName: pvDisconnect => pv.remote.disconnect
|
|
@exportRpc("pv.remote.disconnect")
|
|
def pvDisconnect(self, message):
|
|
"""Free the current active session"""
|
|
simple.Disconnect()
|
|
|
|
|
|
# =============================================================================
|
|
#
|
|
# Handle remote Connection at startup
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebStartupRemoteConnection(ParaViewWebProtocol):
|
|
|
|
connected = False
|
|
|
|
def __init__(self, dsHost = None, dsPort = 11111, rsHost=None, rsPort=22222, rcPort=-1):
|
|
super(ParaViewWebStartupRemoteConnection, self).__init__()
|
|
if not ParaViewWebStartupRemoteConnection.connected and dsHost:
|
|
ParaViewWebStartupRemoteConnection.connected = True
|
|
simple.Connect(dsHost, dsPort, rsHost, rsPort)
|
|
elif not ParaViewWebStartupRemoteConnection.connected and rcPort >= 0:
|
|
ParaViewWebStartupRemoteConnection.connected = True
|
|
simple.ReverseConnect(str(rcPort))
|
|
|
|
|
|
# =============================================================================
|
|
#
|
|
# Handle plugin loading at startup
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebStartupPluginLoader(ParaViewWebProtocol):
|
|
|
|
loaded = False
|
|
|
|
def __init__(self, plugins=None, pathSeparator=':'):
|
|
super(ParaViewWebStartupPluginLoader, self).__init__()
|
|
if not ParaViewWebStartupPluginLoader.loaded and plugins:
|
|
ParaViewWebStartupPluginLoader.loaded = True
|
|
for path in plugins.split(pathSeparator):
|
|
simple.LoadPlugin(path, ns=globals())
|
|
|
|
# =============================================================================
|
|
#
|
|
# Handle State Loading
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebStateLoader(ParaViewWebProtocol):
|
|
|
|
def __init__(self, state_path = None):
|
|
super(ParaViewWebStateLoader, self).__init__()
|
|
if state_path and state_path[-5:] == '.pvsm':
|
|
self.loadState(state_path)
|
|
|
|
# RpcName: loadState => pv.loader.state
|
|
@exportRpc("pv.loader.state")
|
|
def loadState(self, state_file):
|
|
"""
|
|
Load a state file and return the list of view ids
|
|
"""
|
|
simple.LoadState(state_file)
|
|
ids = []
|
|
for view in simple.GetRenderViews():
|
|
ids.append(view.GetGlobalIDAsString())
|
|
return ids
|
|
|
|
# =============================================================================
|
|
#
|
|
# Handle Server File Listing
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebFileListing(ParaViewWebProtocol):
|
|
|
|
def __init__(self, basePath, name, excludeRegex=r"^\.|~$|^\$", groupRegex=r"[0-9]+\."):
|
|
"""
|
|
Configure the way the WebFile browser will expose the server content.
|
|
- basePath: specify the base directory (or directories) that we should start with, if this
|
|
parameter takes the form: "name1=path1|name2=path2|...", then we will treat this as the
|
|
case where multiple data directories are required. In this case, each top-level directory
|
|
will be given the name associated with the directory in the argument.
|
|
- name: Name of that base directory that will show up on the web
|
|
- excludeRegex: Regular expression of what should be excluded from the list of files/directories
|
|
"""
|
|
self.setBaseDirectory(basePath)
|
|
|
|
self.rootName = self.overrideDataDirKey or name
|
|
self.pattern = re.compile(excludeRegex)
|
|
self.gPattern = re.compile(groupRegex)
|
|
pxm = simple.servermanager.ProxyManager()
|
|
self.directory_proxy = pxm.NewProxy('misc', 'ListDirectory')
|
|
self.fileList = simple.servermanager.VectorProperty(self.directory_proxy,self.directory_proxy.GetProperty('FileList'))
|
|
self.directoryList = simple.servermanager.VectorProperty(self.directory_proxy,self.directory_proxy.GetProperty('DirectoryList'))
|
|
|
|
def handleSingleRoot(self, baseDirectory, relativeDir, startPath=None):
|
|
path = startPath or [ self.rootName ]
|
|
if len(relativeDir) > len(self.rootName):
|
|
relativeDir = relativeDir[len(self.rootName)+1:]
|
|
path += relativeDir.replace('\\','/').split('/')
|
|
|
|
currentPath = os.path.normpath(os.path.join(baseDirectory, relativeDir))
|
|
normBase = os.path.normpath(baseDirectory)
|
|
|
|
if not currentPath.startswith(normBase):
|
|
print "### CAUTION =========================================="
|
|
print " Attempt to get to another root path ###"
|
|
print " => Requested:", relativeDir
|
|
print " => BaseDir:", normBase
|
|
print " => Computed path:", currentPath
|
|
print "### CAUTION =========================================="
|
|
currentPath = normBase
|
|
|
|
self.directory_proxy.List(currentPath)
|
|
|
|
# build file/dir lists
|
|
files = []
|
|
if len(self.fileList) > 1:
|
|
for f in self.fileList.GetData():
|
|
if not re.search(self.pattern, f):
|
|
files.append( { 'label': f })
|
|
elif len(self.fileList) == 1 and not re.search(self.pattern, self.fileList.GetData()):
|
|
files.append( { 'label': self.fileList.GetData() })
|
|
|
|
dirs = []
|
|
if len(self.directoryList) > 1:
|
|
for d in self.directoryList.GetData():
|
|
if not re.search(self.pattern, d):
|
|
dirs.append(d)
|
|
elif len(self.directoryList) == 1 and not re.search(self.pattern, self.directoryList.GetData()):
|
|
dirs.append(self.directoryList.GetData())
|
|
|
|
result = { 'label': relativeDir, 'files': files, 'dirs': dirs, 'groups': [], 'path': path }
|
|
if relativeDir == '.':
|
|
result['label'] = self.rootName
|
|
|
|
# Filter files to create groups
|
|
files.sort()
|
|
groups = result['groups']
|
|
groupIdx = {}
|
|
filesToRemove = []
|
|
for file in files:
|
|
fileSplit = re.split(self.gPattern, file['label'])
|
|
if len(fileSplit) == 2:
|
|
filesToRemove.append(file)
|
|
gName = '*.'.join(fileSplit)
|
|
if groupIdx.has_key(gName):
|
|
groupIdx[gName]['files'].append(file['label'])
|
|
else:
|
|
groupIdx[gName] = { 'files' : [file['label']], 'label': gName }
|
|
groups.append(groupIdx[gName])
|
|
for file in filesToRemove:
|
|
gName = '*.'.join(re.split(self.gPattern, file['label']))
|
|
if len(groupIdx[gName]['files']) > 1:
|
|
files.remove(file)
|
|
else:
|
|
groups.remove(groupIdx[gName])
|
|
|
|
return result
|
|
|
|
def handleMultiRoot(self, relativeDir):
|
|
if relativeDir == '.':
|
|
return { 'label': self.rootName, 'files': [], 'dirs': self.baseDirectoryMap.keys(), 'groups': [], 'path': [ self.rootName ] }
|
|
|
|
pathList = relativeDir.replace('\\', '/').split('/')
|
|
currentBaseDir = self.baseDirectoryMap[pathList[1]]
|
|
if len(pathList) == 2:
|
|
return self.handleSingleRoot(currentBaseDir, '.', pathList)
|
|
else: # must be greater than 2
|
|
return self.handleSingleRoot(currentBaseDir, '/'.join([pathList[0]] + pathList[2:]), pathList[0:2])
|
|
|
|
# RpcName: listServerDirectory => file.server.directory.list
|
|
@exportRpc("file.server.directory.list")
|
|
def listServerDirectory(self, relativeDir='.'):
|
|
"""
|
|
RPC Callback to list a server directory relative to the basePath
|
|
provided at start-up.
|
|
"""
|
|
if self.multiRoot == True:
|
|
return self.handleMultiRoot(relativeDir)
|
|
else:
|
|
return self.handleSingleRoot(self.baseDirectory, relativeDir)
|
|
|
|
# =============================================================================
|
|
#
|
|
# Handle Data Selection
|
|
#
|
|
# =============================================================================
|
|
from vtkPVClientServerCoreRenderingPython import *
|
|
from vtkCommonCorePython import *
|
|
|
|
class ParaViewWebSelectionHandler(ParaViewWebProtocol):
|
|
|
|
def __init__(self):
|
|
self.active_view = None
|
|
self.previous_interaction = -1
|
|
self.selection_type = -1
|
|
|
|
# RpcName: startSelection => pv.selection.start
|
|
@exportRpc("pv.selection.start")
|
|
def startSelection(self, viewId, selectionType):
|
|
"""
|
|
Method used to initialize an interactive selection
|
|
"""
|
|
self.active_view = self.getView(viewId)
|
|
if self.active_view.IsSelectionAvailable():
|
|
self.previous_interaction = self.active_view.InteractionMode
|
|
self.active_view.InteractionMode = vtkPVRenderView.INTERACTION_MODE_SELECTION
|
|
else:
|
|
self.active_view = None
|
|
|
|
# RpcName: endSelection => pv.selection.end
|
|
@exportRpc("pv.selection.end")
|
|
def endSelection(self, area, extract):
|
|
"""
|
|
Method used to finalize an interactive selection by providing
|
|
the [ startPointX, startPointY, endPointX, endPointY ] area
|
|
where (0,0) match the lower left corner of the pixel screen.
|
|
"""
|
|
if self.active_view:
|
|
self.active_view.InteractionMode = self.previous_interaction
|
|
representations = vtkCollection()
|
|
sources = vtkCollection()
|
|
if self.selection_type == 0:
|
|
self.active_view.SelectSurfacePoints(area, representations, sources, False)
|
|
elif self.selection_type == 1:
|
|
self.active_view.SelectSurfaceCells(area, representations, sources, False)
|
|
elif self.selection_type == 2:
|
|
self.active_view.SelectFrustumPoints(area, representations, sources, False)
|
|
elif self.selection_type == 3:
|
|
self.active_view.SelectFrustumCells(area, representations, sources, False)
|
|
else:
|
|
self.active_view.SelectSurfacePoints(area, representations, sources, False)
|
|
# Don't know what to do if more than one representation/source
|
|
if representations.GetNumberOfItems() == sources.GetNumberOfItems() and sources.GetNumberOfItems() == 1:
|
|
# We are good for selection
|
|
rep = servermanager._getPyProxy(representations.GetItemAsObject(0))
|
|
selection = servermanager._getPyProxy(sources.GetItemAsObject(0))
|
|
if extract:
|
|
extract = simple.ExtractSelection(Input=rep.Input, Selection=selection)
|
|
simple.Show(extract)
|
|
simple.Render()
|
|
else:
|
|
rep.Input.SMProxy.SetSelectionInput(0, selection.SMProxy, 0)
|
|
|
|
# =============================================================================
|
|
#
|
|
# Handle Data Export
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebExportData(ParaViewWebProtocol):
|
|
|
|
def __init__(self, basePath):
|
|
self.base_export_path = basePath
|
|
|
|
# RpcName: exportData => pv.export.data
|
|
@exportRpc("pv.export.data")
|
|
def exportData(self, proxy_id, path):
|
|
proxy = self.mapIdToProxy(proxy_id)
|
|
fullpath = str(os.path.join(self.base_export_path, str(path)))
|
|
if fullpath.index('.vtk') == -1:
|
|
fullpath += '.vtk'
|
|
parentDir = os.path.dirname(fullpath)
|
|
if not os.path.exists(parentDir):
|
|
os.makedirs(parentDir)
|
|
if proxy:
|
|
writer = simple.DataSetWriter(Input=proxy, FileName=fullpath)
|
|
writer.UpdatePipeline()
|
|
del writer
|
|
|
|
# =============================================================================
|
|
#
|
|
# Protocols useful only for testing purposes
|
|
#
|
|
# =============================================================================
|
|
|
|
class ParaViewWebTestProtocols(ParaViewWebProtocol):
|
|
|
|
# RpcName: clearAll => pv.test.reset
|
|
@exportRpc("pv.test.reset")
|
|
def clearAll(self):
|
|
simple.Disconnect()
|
|
simple.Connect()
|
|
view = simple.GetRenderView()
|
|
simple.SetActiveView(view)
|
|
simple.Render()
|
|
|
|
# RpcName: getColoringInfo => pv.test.color.info.get
|
|
@exportRpc("pv.test.color.info.get")
|
|
def getColoringInfo(self, proxyId):
|
|
proxy = self.mapIdToProxy(proxyId)
|
|
rep = simple.GetRepresentation(proxy)
|
|
lut = rep.LookupTable
|
|
arrayInfo = rep.GetArrayInformationForColorArray()
|
|
arrayName = arrayInfo.GetName()
|
|
|
|
return { 'arrayName': arrayName,
|
|
'vectorMode': lut.VectorMode,
|
|
'vectorComponent': lut.VectorComponent }
|
|
|
|
@exportRpc("pv.test.repr.get")
|
|
def getReprFromSource(self, srcProxyId):
|
|
proxy = self.mapIdToProxy(srcProxyId)
|
|
rep = simple.GetRepresentation(proxy)
|
|
return { 'reprProxyId': rep.GetGlobalIDAsString() }
|
|
|
|
|
|
# =============================================================================
|
|
#
|
|
# Handle Widget Representation
|
|
#
|
|
# =============================================================================
|
|
|
|
def _line_update_widget(self, widget):
|
|
widget.Point1WorldPosition = self.Point1;
|
|
widget.Point2WorldPosition = self.Point2;
|
|
|
|
def _line_widget_update(self, obj, event):
|
|
self.GetProperty('Point1').Copy(obj.GetProperty('Point1WorldPositionInfo'))
|
|
self.GetProperty('Point2').Copy(obj.GetProperty('Point2WorldPositionInfo'))
|
|
self.UpdateVTKObjects()
|
|
|
|
def _plane_update_widget(self, widget):
|
|
widget.GetProperty('OriginInfo').SetData(self.Origin)
|
|
widget.Origin = self.Origin
|
|
widget.Normal = self.Normal
|
|
widget.UpdateVTKObjects()
|
|
|
|
def _plane_widget_update(self, obj, event):
|
|
self.GetProperty('Origin').Copy(obj.GetProperty('OriginInfo'))
|
|
self.GetProperty('Normal').Copy(obj.GetProperty('NormalInfo'))
|
|
self.UpdateVTKObjects()
|
|
_hide_plane(obj)
|
|
|
|
def _draw_plane(obj,event):
|
|
obj.GetProperty('DrawPlane').SetElement(0,1)
|
|
obj.UpdateVTKObjects()
|
|
|
|
def _hide_plane(obj):
|
|
obj.GetProperty('DrawPlane').SetElement(0,0)
|
|
obj.UpdateVTKObjects()
|
|
|
|
class ParaViewWebWidgetManager(ParaViewWebProtocol):
|
|
|
|
# RpcName: addRuler => pv.widgets.ruler.add
|
|
@exportRpc("pv.widgets.ruler.add")
|
|
def addRuler(self, view_id=-1):
|
|
proxy = simple.Ruler(Point1=[-1.0, -1.0, -1.0], Point2=[1.0, 1.0, 1.0])
|
|
self.createWidgetRepresentation(proxy.GetGlobalID(), view_id)
|
|
return proxy.GetGlobalIDAsString()
|
|
|
|
# RpcName: createWidgetRepresentation => pv.widgets.representation.create
|
|
@exportRpc("pv.widgets.representation.create")
|
|
def createWidgetRepresentation(self, proxy_id, view_id):
|
|
proxy = self.mapIdToProxy(proxy_id)
|
|
view = self.getView(view_id)
|
|
widgetProxy = None
|
|
# Find the corresponding widget representation
|
|
if proxy.__class__.__name__ == 'Plane':
|
|
widgetProxy = self.CreateWidgetRepresentation(view, 'ImplicitPlaneWidgetRepresentation')
|
|
setattr(proxy.__class__, 'UpdateWidget', _plane_update_widget)
|
|
setattr(proxy.__class__, 'WidgetUpdate', _plane_widget_update)
|
|
widgetProxy.GetProperty('DrawPlane').SetElement(0, 0)
|
|
widgetProxy.GetProperty('PlaceFactor').SetElement(0, 1.0)
|
|
proxy.UpdateWidget(widgetProxy)
|
|
widgetProxy.AddObserver("StartInteractionEvent", _draw_plane)
|
|
proxy.Observed = widgetProxy
|
|
proxy.ObserverTag = widgetProxy.AddObserver("EndInteractionEvent", proxy.WidgetUpdate)
|
|
elif proxy.__class__.__name__ == 'Box':
|
|
widgetProxy = self.CreateWidgetRepresentation(view, 'BoxWidgetRepresentation')
|
|
elif proxy.__class__.__name__ == 'Handle':
|
|
widgetProxy = self.CreateWidgetRepresentation(view, 'HandleWidgetRepresentation')
|
|
elif proxy.__class__.__name__ == 'PointSource':
|
|
widgetProxy = self.CreateWidgetRepresentation(view, 'PointSourceWidgetRepresentation')
|
|
elif proxy.__class__.__name__ == 'LineSource' or proxy.__class__.__name__ == 'HighResolutionLineSource' :
|
|
widgetProxy = self.CreateWidgetRepresentation(view, 'LineSourceWidgetRepresentation')
|
|
setattr(proxy.__class__, 'UpdateWidget', _line_update_widget)
|
|
setattr(proxy.__class__, 'WidgetUpdate', _line_widget_update)
|
|
proxy.UpdateWidget(widgetProxy)
|
|
proxy.Observed = widgetProxy
|
|
proxy.ObserverTag = widgetProxy.AddObserver("EndInteractionEvent", proxy.WidgetUpdate)
|
|
elif proxy.__class__.__name__ == 'Line':
|
|
widgetProxy = self.CreateWidgetRepresentation(view, 'LineWidgetRepresentation')
|
|
setattr(proxy.__class__, 'UpdateWidget', _line_update_widget)
|
|
setattr(proxy.__class__, 'WidgetUpdate', _line_widget_update)
|
|
proxy.UpdateWidget(widgetProxy)
|
|
proxy.Observed = widgetProxy
|
|
proxy.ObserverTag = widgetProxy.AddObserver("EndInteractionEvent", proxy.WidgetUpdate)
|
|
elif proxy.__class__.__name__ in ['Distance', 'Ruler'] :
|
|
widgetProxy = self.CreateWidgetRepresentation(view, 'DistanceWidgetRepresentation')
|
|
setattr(proxy.__class__, 'UpdateWidget', _line_update_widget)
|
|
setattr(proxy.__class__, 'WidgetUpdate', _line_widget_update)
|
|
proxy.UpdateWidget(widgetProxy)
|
|
proxy.Observed = widgetProxy
|
|
proxy.ObserverTag = widgetProxy.AddObserver("EndInteractionEvent", proxy.WidgetUpdate)
|
|
elif proxy.__class__.__name__ == 'Sphere':
|
|
widgetProxy = self.CreateWidgetRepresentation(view, 'SphereWidgetRepresentation')
|
|
elif proxy.__class__.__name__ == 'Spline':
|
|
widgetProxy = self.CreateWidgetRepresentation(view, 'SplineWidgetRepresentation')
|
|
else:
|
|
print "No widget representation for %s" % proxy.__class__.__name__
|
|
|
|
return widgetProxy.GetGlobalIDAsString()
|
|
|
|
def CreateWidgetRepresentation(self, view, name):
|
|
proxy = simple.servermanager.CreateProxy("representations", name, None)
|
|
pythonWrap = simple.servermanager.rendering.__dict__[proxy.GetXMLName()]()
|
|
pythonWrap.UpdateVTKObjects()
|
|
view.Representations.append(pythonWrap)
|
|
pythonWrap.Visibility = 1
|
|
pythonWrap.Enabled = 1
|
|
return pythonWrap
|