init
This commit is contained in:
@@ -0,0 +1,842 @@
|
||||
r"""protocols is a module that contains a set of VTK Web related
|
||||
protocols that can be combined together to provide a flexible way to define
|
||||
very specific web application.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import os, sys, logging, types, inspect, traceback, re, base64, time
|
||||
|
||||
from vtkmodules.vtkWebCore import vtkWebInteractionEvent
|
||||
|
||||
from vtkmodules.web.errors import WebDependencyMissingError
|
||||
from vtkmodules.web.render_window_serializer import (
|
||||
serializeInstance,
|
||||
SynchronizationContext,
|
||||
getReferenceId,
|
||||
initializeSerializers,
|
||||
)
|
||||
|
||||
try:
|
||||
from wslink import schedule_callback
|
||||
from wslink import register as exportRpc
|
||||
from wslink.websocket import LinkProtocol
|
||||
except ImportError:
|
||||
raise WebDependencyMissingError()
|
||||
|
||||
# =============================================================================
|
||||
#
|
||||
# Base class for any VTK Web based protocol
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class vtkWebProtocol(LinkProtocol):
|
||||
def getApplication(self):
|
||||
return self.getSharedObject("app")
|
||||
|
||||
# no need for a setApplication anymore, but keep for compatibility
|
||||
def setApplication(self, app):
|
||||
pass
|
||||
|
||||
def mapIdToObject(self, id):
|
||||
"""
|
||||
Maps global-id for a vtkObject to the vtkObject instance. May return None if the
|
||||
id is not valid.
|
||||
"""
|
||||
id = int(id)
|
||||
if id <= 0:
|
||||
return None
|
||||
return self.getApplication().GetObjectIdMap().GetVTKObject(id)
|
||||
|
||||
def getGlobalId(self, obj):
|
||||
"""
|
||||
Return the id for a given vtkObject
|
||||
"""
|
||||
return self.getApplication().GetObjectIdMap().GetGlobalId(obj)
|
||||
|
||||
def freeObject(self, obj):
|
||||
"""
|
||||
Delete the given vtkObject from the objectIdMap. Returns true if delete succeeded.
|
||||
"""
|
||||
return self.getApplication().GetObjectIdMap().FreeObject(obj)
|
||||
|
||||
def freeObjectById(self, id):
|
||||
"""
|
||||
Delete the vtkObject corresponding to the given objectId from the objectIdMap.
|
||||
Returns true if delete succeeded.
|
||||
"""
|
||||
return self.getApplication().GetObjectIdMap().FreeObjectById(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
|
||||
"""
|
||||
v = self.mapIdToObject(vid)
|
||||
|
||||
if not v:
|
||||
# Use active view is none provided.
|
||||
v = self.getApplication().GetObjectIdMap().GetActiveObject("VIEW")
|
||||
if not v:
|
||||
raise Exception("no view provided: %s" % vid)
|
||||
|
||||
return v
|
||||
|
||||
def setActiveView(self, view):
|
||||
"""
|
||||
Set a vtkRenderWindow to be the active one
|
||||
"""
|
||||
self.getApplication().GetObjectIdMap().SetActiveObject("VIEW", view)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
#
|
||||
# Handle Mouse interaction on any type of view
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class vtkWebMouseHandler(vtkWebProtocol):
|
||||
@exportRpc("viewport.mouse.interaction")
|
||||
def mouseInteraction(self, event):
|
||||
"""
|
||||
RPC Callback for mouse interactions.
|
||||
"""
|
||||
view = self.getView(event["view"])
|
||||
|
||||
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)
|
||||
if "x" in event:
|
||||
pvevent.SetX(event["x"])
|
||||
if "y" in event:
|
||||
pvevent.SetY(event["y"])
|
||||
if "scroll" in event:
|
||||
pvevent.SetScroll(event["scroll"])
|
||||
if event["action"] == "dblclick":
|
||||
pvevent.SetRepeatCount(2)
|
||||
# pvevent.SetKeyCode(event["charCode"])
|
||||
retVal = self.getApplication().HandleInteractionEvent(view, pvevent)
|
||||
del pvevent
|
||||
|
||||
if event["action"] == "down":
|
||||
self.getApplication().InvokeEvent("StartInteractionEvent")
|
||||
|
||||
if event["action"] == "up":
|
||||
self.getApplication().InvokeEvent("EndInteractionEvent")
|
||||
|
||||
if retVal:
|
||||
self.getApplication().InvokeEvent("UpdateEvent")
|
||||
|
||||
return retVal
|
||||
|
||||
@exportRpc("viewport.mouse.zoom.wheel")
|
||||
def updateZoomFromWheel(self, event):
|
||||
if "Start" in event["type"]:
|
||||
self.getApplication().InvokeEvent("StartInteractionEvent")
|
||||
|
||||
renderWindow = self.getView(event["view"])
|
||||
if renderWindow and "spinY" in event:
|
||||
zoomFactor = 1.0 - event["spinY"] / 10.0
|
||||
|
||||
camera = renderWindow.GetRenderers().GetFirstRenderer().GetActiveCamera()
|
||||
fp = camera.GetFocalPoint()
|
||||
pos = camera.GetPosition()
|
||||
delta = [fp[i] - pos[i] for i in range(3)]
|
||||
camera.Zoom(zoomFactor)
|
||||
|
||||
pos2 = camera.GetPosition()
|
||||
camera.SetFocalPoint([pos2[i] + delta[i] for i in range(3)])
|
||||
renderWindow.Modified()
|
||||
|
||||
if "End" in event["type"]:
|
||||
self.getApplication().InvokeEvent("EndInteractionEvent")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
#
|
||||
# Basic 3D Viewport API (Camera + Orientation + CenterOfRotation
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class vtkWebViewPort(vtkWebProtocol):
|
||||
@exportRpc("viewport.camera.reset")
|
||||
def resetCamera(self, viewId):
|
||||
"""
|
||||
RPC callback to reset camera.
|
||||
"""
|
||||
view = self.getView(viewId)
|
||||
renderer = view.GetRenderers().GetFirstRenderer()
|
||||
renderer.ResetCamera()
|
||||
|
||||
self.getApplication().InvalidateCache(view)
|
||||
self.getApplication().InvokeEvent("UpdateEvent")
|
||||
|
||||
return str(self.getGlobalId(view))
|
||||
|
||||
@exportRpc("viewport.axes.orientation.visibility.update")
|
||||
def updateOrientationAxesVisibility(self, viewId, showAxis):
|
||||
"""
|
||||
RPC callback to show/hide OrientationAxis.
|
||||
"""
|
||||
view = self.getView(viewId)
|
||||
# FIXME seb: view.OrientationAxesVisibility = (showAxis if 1 else 0);
|
||||
|
||||
self.getApplication().InvalidateCache(view)
|
||||
self.getApplication().InvokeEvent("UpdateEvent")
|
||||
|
||||
return str(self.getGlobalId(view))
|
||||
|
||||
@exportRpc("viewport.axes.center.visibility.update")
|
||||
def updateCenterAxesVisibility(self, viewId, showAxis):
|
||||
"""
|
||||
RPC callback to show/hide CenterAxesVisibility.
|
||||
"""
|
||||
view = self.getView(viewId)
|
||||
# FIXME seb: view.CenterAxesVisibility = (showAxis if 1 else 0);
|
||||
|
||||
self.getApplication().InvalidateCache(view)
|
||||
self.getApplication().InvokeEvent("UpdateEvent")
|
||||
|
||||
return str(self.getGlobalId(view))
|
||||
|
||||
@exportRpc("viewport.camera.update")
|
||||
def updateCamera(self, view_id, focal_point, view_up, position, forceUpdate=True):
|
||||
view = self.getView(view_id)
|
||||
|
||||
camera = view.GetRenderers().GetFirstRenderer().GetActiveCamera()
|
||||
camera.SetFocalPoint(focal_point)
|
||||
camera.SetViewUp(view_up)
|
||||
camera.SetPosition(position)
|
||||
|
||||
if forceUpdate:
|
||||
self.getApplication().InvalidateCache(view)
|
||||
self.getApplication().InvokeEvent("UpdateEvent")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
#
|
||||
# Provide Image delivery mechanism (deprecated - will be removed in VTK 10+)
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class vtkWebViewPortImageDelivery(vtkWebProtocol):
|
||||
@exportRpc("viewport.image.render")
|
||||
def stillRender(self, options):
|
||||
"""
|
||||
RPC Callback to render a view and obtain the rendered image.
|
||||
"""
|
||||
beginTime = int(round(time.time() * 1000))
|
||||
view = self.getView(options["view"])
|
||||
size = [view.GetSize()[0], view.GetSize()[1]]
|
||||
# use existing size, overridden only if options["size"] is set.
|
||||
resize = size != options.get("size", size)
|
||||
if resize:
|
||||
size = options["size"]
|
||||
if size[0] > 0 and size[1] > 0:
|
||||
view.SetSize(size)
|
||||
t = 0
|
||||
if options and "mtime" in options:
|
||||
t = options["mtime"]
|
||||
quality = 100
|
||||
if options and "quality" in options:
|
||||
quality = options["quality"]
|
||||
localTime = 0
|
||||
if options and "localTime" in options:
|
||||
localTime = options["localTime"]
|
||||
reply = {}
|
||||
app = self.getApplication()
|
||||
if t == 0:
|
||||
app.InvalidateCache(view)
|
||||
reply["image"] = app.StillRenderToString(view, t, quality)
|
||||
# Check that we are getting image size we have set. If not, wait until we
|
||||
# do. The render call will set the actual window size.
|
||||
tries = 10
|
||||
while resize and list(view.GetSize()) != size and size != [0, 0] and tries > 0:
|
||||
app.InvalidateCache(view)
|
||||
reply["image"] = app.StillRenderToString(view, t, quality)
|
||||
tries -= 1
|
||||
|
||||
reply["stale"] = app.GetHasImagesBeingProcessed(view)
|
||||
reply["mtime"] = app.GetLastStillRenderToMTime()
|
||||
reply["size"] = [view.GetSize()[0], view.GetSize()[1]]
|
||||
reply["format"] = "jpeg;base64"
|
||||
reply["global_id"] = str(self.getGlobalId(view))
|
||||
reply["localTime"] = localTime
|
||||
|
||||
endTime = int(round(time.time() * 1000))
|
||||
reply["workTime"] = endTime - beginTime
|
||||
|
||||
return reply
|
||||
|
||||
|
||||
# =============================================================================
|
||||
#
|
||||
# Provide publish-based Image delivery mechanism
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class vtkWebPublishImageDelivery(vtkWebProtocol):
|
||||
def __init__(self, decode=True):
|
||||
super(vtkWebPublishImageDelivery, self).__init__()
|
||||
self.trackingViews = {}
|
||||
self.lastStaleTime = 0
|
||||
self.staleHandlerCount = 0
|
||||
self.deltaStaleTimeBeforeRender = 0.5 # 0.5s
|
||||
self.decode = decode
|
||||
self.viewsInAnimations = []
|
||||
self.targetFrameRate = 30.0
|
||||
self.minFrameRate = 12.0
|
||||
self.maxFrameRate = 30.0
|
||||
|
||||
def pushRender(self, vId, ignoreAnimation=False):
|
||||
if vId not in self.trackingViews:
|
||||
return
|
||||
|
||||
if not self.trackingViews[vId]["enabled"]:
|
||||
return
|
||||
|
||||
if not ignoreAnimation and len(self.viewsInAnimations) > 0:
|
||||
return
|
||||
|
||||
if "originalSize" not in self.trackingViews[vId]:
|
||||
view = self.getView(vId)
|
||||
self.trackingViews[vId]["originalSize"] = list(view.GetSize())
|
||||
|
||||
if "ratio" not in self.trackingViews[vId]:
|
||||
self.trackingViews[vId]["ratio"] = 1
|
||||
|
||||
ratio = self.trackingViews[vId]["ratio"]
|
||||
mtime = self.trackingViews[vId]["mtime"]
|
||||
quality = self.trackingViews[vId]["quality"]
|
||||
size = [int(s * ratio) for s in self.trackingViews[vId]["originalSize"]]
|
||||
|
||||
reply = self.stillRender(
|
||||
{"view": vId, "mtime": mtime, "quality": quality, "size": size}
|
||||
)
|
||||
stale = reply["stale"]
|
||||
if reply["image"]:
|
||||
# depending on whether the app has encoding enabled:
|
||||
if self.decode:
|
||||
reply["image"] = base64.standard_b64decode(reply["image"])
|
||||
|
||||
reply["image"] = self.addAttachment(reply["image"])
|
||||
reply["format"] = "jpeg"
|
||||
# save mtime for next call.
|
||||
self.trackingViews[vId]["mtime"] = reply["mtime"]
|
||||
# echo back real ID, instead of -1 for 'active'
|
||||
reply["id"] = vId
|
||||
self.publish("viewport.image.push.subscription", reply)
|
||||
if stale:
|
||||
self.lastStaleTime = time.time()
|
||||
if self.staleHandlerCount == 0:
|
||||
self.staleHandlerCount += 1
|
||||
schedule_callback(
|
||||
self.deltaStaleTimeBeforeRender, lambda: self.renderStaleImage(vId)
|
||||
)
|
||||
else:
|
||||
self.lastStaleTime = 0
|
||||
|
||||
def renderStaleImage(self, vId):
|
||||
self.staleHandlerCount -= 1
|
||||
|
||||
if self.lastStaleTime != 0:
|
||||
delta = time.time() - self.lastStaleTime
|
||||
if delta >= self.deltaStaleTimeBeforeRender:
|
||||
self.pushRender(vId)
|
||||
else:
|
||||
self.staleHandlerCount += 1
|
||||
schedule_callback(
|
||||
self.deltaStaleTimeBeforeRender - delta + 0.001,
|
||||
lambda: self.renderStaleImage(vId),
|
||||
)
|
||||
|
||||
def animate(self):
|
||||
if len(self.viewsInAnimations) == 0:
|
||||
return
|
||||
|
||||
nextAnimateTime = time.time() + 1.0 / self.targetFrameRate
|
||||
for vId in self.viewsInAnimations:
|
||||
self.pushRender(vId, True)
|
||||
|
||||
nextAnimateTime -= time.time()
|
||||
|
||||
if self.targetFrameRate > self.maxFrameRate:
|
||||
self.targetFrameRate = self.maxFrameRate
|
||||
|
||||
if nextAnimateTime < 0:
|
||||
if nextAnimateTime < -1.0:
|
||||
self.targetFrameRate = 1
|
||||
if self.targetFrameRate > self.minFrameRate:
|
||||
self.targetFrameRate -= 1.0
|
||||
schedule_callback(0.001, lambda: self.animate())
|
||||
else:
|
||||
if self.targetFrameRate < self.maxFrameRate and nextAnimateTime > 0.005:
|
||||
self.targetFrameRate += 1.0
|
||||
schedule_callback(nextAnimateTime, lambda: self.animate())
|
||||
|
||||
@exportRpc("viewport.image.animation.fps.max")
|
||||
def setMaxFrameRate(self, fps=30):
|
||||
self.maxFrameRate = fps
|
||||
|
||||
@exportRpc("viewport.image.animation.fps.get")
|
||||
def getCurrentFrameRate(self):
|
||||
return self.targetFrameRate
|
||||
|
||||
@exportRpc("viewport.image.animation.start")
|
||||
def startViewAnimation(self, viewId="-1"):
|
||||
sView = self.getView(viewId)
|
||||
realViewId = str(self.getGlobalId(sView))
|
||||
|
||||
self.viewsInAnimations.append(realViewId)
|
||||
if len(self.viewsInAnimations) == 1:
|
||||
self.animate()
|
||||
|
||||
@exportRpc("viewport.image.animation.stop")
|
||||
def stopViewAnimation(self, viewId="-1"):
|
||||
sView = self.getView(viewId)
|
||||
realViewId = str(self.getGlobalId(sView))
|
||||
|
||||
if realViewId in self.viewsInAnimations:
|
||||
self.viewsInAnimations.remove(realViewId)
|
||||
|
||||
@exportRpc("viewport.image.push")
|
||||
def imagePush(self, options):
|
||||
sView = self.getView(options["view"])
|
||||
realViewId = str(self.getGlobalId(sView))
|
||||
# Make sure an image is pushed
|
||||
self.getApplication().InvalidateCache(sView)
|
||||
self.pushRender(realViewId)
|
||||
|
||||
# Internal function since the reply[image] is not
|
||||
# JSON(serializable) it can not be an RPC one
|
||||
def stillRender(self, options):
|
||||
"""
|
||||
RPC Callback to render a view and obtain the rendered image.
|
||||
"""
|
||||
beginTime = int(round(time.time() * 1000))
|
||||
view = self.getView(options["view"])
|
||||
size = view.GetSize()[0:2]
|
||||
resize = size != options.get("size", size)
|
||||
if resize:
|
||||
size = options["size"]
|
||||
if size[0] > 10 and size[1] > 10:
|
||||
view.SetSize(size)
|
||||
t = 0
|
||||
if options and "mtime" in options:
|
||||
t = options["mtime"]
|
||||
quality = 100
|
||||
if options and "quality" in options:
|
||||
quality = options["quality"]
|
||||
localTime = 0
|
||||
if options and "localTime" in options:
|
||||
localTime = options["localTime"]
|
||||
reply = {}
|
||||
app = self.getApplication()
|
||||
if t == 0:
|
||||
app.InvalidateCache(view)
|
||||
if self.decode:
|
||||
stillRender = app.StillRenderToString
|
||||
else:
|
||||
stillRender = app.StillRenderToBuffer
|
||||
reply_image = stillRender(view, t, quality)
|
||||
|
||||
# Check that we are getting image size we have set if not wait until we
|
||||
# do. The render call will set the actual window size.
|
||||
tries = 10
|
||||
while resize and list(view.GetSize()) != size and size != [0, 0] and tries > 0:
|
||||
app.InvalidateCache(view)
|
||||
reply_image = stillRender(view, t, quality)
|
||||
tries -= 1
|
||||
|
||||
if (
|
||||
not resize
|
||||
and options
|
||||
and ("clearCache" in options)
|
||||
and options["clearCache"]
|
||||
):
|
||||
app.InvalidateCache(view)
|
||||
reply_image = stillRender(view, t, quality)
|
||||
|
||||
reply["stale"] = app.GetHasImagesBeingProcessed(view)
|
||||
reply["mtime"] = app.GetLastStillRenderToMTime()
|
||||
reply["size"] = view.GetSize()[0:2]
|
||||
reply["memsize"] = reply_image.GetDataSize() if reply_image else 0
|
||||
reply["format"] = "jpeg;base64" if self.decode else "jpeg"
|
||||
reply["global_id"] = str(self.getGlobalId(view))
|
||||
reply["localTime"] = localTime
|
||||
if self.decode:
|
||||
reply["image"] = reply_image
|
||||
else:
|
||||
# Convert the vtkUnsignedCharArray into a bytes object, required by Autobahn websockets
|
||||
reply["image"] = memoryview(reply_image).tobytes() if reply_image else None
|
||||
|
||||
endTime = int(round(time.time() * 1000))
|
||||
reply["workTime"] = endTime - beginTime
|
||||
|
||||
return reply
|
||||
|
||||
@exportRpc("viewport.image.push.observer.add")
|
||||
def addRenderObserver(self, viewId):
|
||||
sView = self.getView(viewId)
|
||||
if not sView:
|
||||
return {"error": "Unable to get view with id %s" % viewId}
|
||||
|
||||
realViewId = str(self.getGlobalId(sView))
|
||||
|
||||
if not realViewId in self.trackingViews:
|
||||
observerCallback = lambda *args, **kwargs: self.pushRender(realViewId)
|
||||
startCallback = lambda *args, **kwargs: self.startViewAnimation(realViewId)
|
||||
stopCallback = lambda *args, **kwargs: self.stopViewAnimation(realViewId)
|
||||
tag = self.getApplication().AddObserver("UpdateEvent", observerCallback)
|
||||
tagStart = self.getApplication().AddObserver(
|
||||
"StartInteractionEvent", startCallback
|
||||
)
|
||||
tagStop = self.getApplication().AddObserver(
|
||||
"EndInteractionEvent", stopCallback
|
||||
)
|
||||
# TODO do we need self.getApplication().AddObserver('ResetActiveView', resetActiveView())
|
||||
self.trackingViews[realViewId] = {
|
||||
"tags": [tag, tagStart, tagStop],
|
||||
"observerCount": 1,
|
||||
"mtime": 0,
|
||||
"enabled": True,
|
||||
"quality": 100,
|
||||
}
|
||||
else:
|
||||
# There is an observer on this view already
|
||||
self.trackingViews[realViewId]["observerCount"] += 1
|
||||
|
||||
self.pushRender(realViewId)
|
||||
return {"success": True, "viewId": realViewId}
|
||||
|
||||
@exportRpc("viewport.image.push.observer.remove")
|
||||
def removeRenderObserver(self, viewId):
|
||||
sView = self.getView(viewId)
|
||||
if not sView:
|
||||
return {"error": "Unable to get view with id %s" % viewId}
|
||||
|
||||
realViewId = str(self.getGlobalId(sView))
|
||||
|
||||
observerInfo = None
|
||||
if realViewId in self.trackingViews:
|
||||
observerInfo = self.trackingViews[realViewId]
|
||||
|
||||
if not observerInfo:
|
||||
return {"error": "Unable to find subscription for view %s" % realViewId}
|
||||
|
||||
observerInfo["observerCount"] -= 1
|
||||
|
||||
if observerInfo["observerCount"] <= 0:
|
||||
for tag in observerInfo["tags"]:
|
||||
self.getApplication().RemoveObserver(tag)
|
||||
del self.trackingViews[realViewId]
|
||||
|
||||
return {"result": "success"}
|
||||
|
||||
@exportRpc("viewport.image.push.quality")
|
||||
def setViewQuality(self, viewId, quality, ratio=1):
|
||||
sView = self.getView(viewId)
|
||||
if not sView:
|
||||
return {"error": "Unable to get view with id %s" % viewId}
|
||||
|
||||
realViewId = str(self.getGlobalId(sView))
|
||||
observerInfo = None
|
||||
if realViewId in self.trackingViews:
|
||||
observerInfo = self.trackingViews[realViewId]
|
||||
|
||||
if not observerInfo:
|
||||
return {"error": "Unable to find subscription for view %s" % realViewId}
|
||||
|
||||
observerInfo["quality"] = quality
|
||||
observerInfo["ratio"] = ratio
|
||||
|
||||
# Update image size right now!
|
||||
if "originalSize" in self.trackingViews[realViewId]:
|
||||
size = [
|
||||
int(s * ratio) for s in self.trackingViews[realViewId]["originalSize"]
|
||||
]
|
||||
if hasattr(sView, "SetSize"):
|
||||
sView.SetSize(size)
|
||||
else:
|
||||
sView.ViewSize = size
|
||||
|
||||
return {"result": "success"}
|
||||
|
||||
@exportRpc("viewport.image.push.original.size")
|
||||
def setViewSize(self, viewId, width, height):
|
||||
sView = self.getView(viewId)
|
||||
if not sView:
|
||||
return {"error": "Unable to get view with id %s" % viewId}
|
||||
|
||||
realViewId = str(self.getGlobalId(sView))
|
||||
observerInfo = None
|
||||
if realViewId in self.trackingViews:
|
||||
observerInfo = self.trackingViews[realViewId]
|
||||
|
||||
if not observerInfo:
|
||||
return {"error": "Unable to find subscription for view %s" % realViewId}
|
||||
|
||||
observerInfo["originalSize"] = [width, height]
|
||||
|
||||
return {"result": "success"}
|
||||
|
||||
@exportRpc("viewport.image.push.enabled")
|
||||
def enableView(self, viewId, enabled):
|
||||
sView = self.getView(viewId)
|
||||
if not sView:
|
||||
return {"error": "Unable to get view with id %s" % viewId}
|
||||
|
||||
realViewId = str(self.getGlobalId(sView))
|
||||
observerInfo = None
|
||||
if realViewId in self.trackingViews:
|
||||
observerInfo = self.trackingViews[realViewId]
|
||||
|
||||
if not observerInfo:
|
||||
return {"error": "Unable to find subscription for view %s" % realViewId}
|
||||
|
||||
observerInfo["enabled"] = enabled
|
||||
|
||||
return {"result": "success"}
|
||||
|
||||
@exportRpc("viewport.image.push.invalidate.cache")
|
||||
def invalidateCache(self, viewId):
|
||||
sView = self.getView(viewId)
|
||||
if not sView:
|
||||
return {"error": "Unable to get view with id %s" % viewId}
|
||||
|
||||
self.getApplication().InvalidateCache(sView)
|
||||
self.getApplication().InvokeEvent("UpdateEvent")
|
||||
return {"result": "success"}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
#
|
||||
# Provide Geometry delivery mechanism (WebGL) (deprecated - will be removed in VTK 10+)
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class vtkWebViewPortGeometryDelivery(vtkWebProtocol):
|
||||
@exportRpc("viewport.webgl.metadata")
|
||||
def getSceneMetaData(self, view_id):
|
||||
view = self.getView(view_id)
|
||||
data = self.getApplication().GetWebGLSceneMetaData(view)
|
||||
return data
|
||||
|
||||
@exportRpc("viewport.webgl.data")
|
||||
def getWebGLData(self, view_id, object_id, part):
|
||||
view = self.getView(view_id)
|
||||
data = self.getApplication().GetWebGLBinaryData(view, str(object_id), part - 1)
|
||||
return data
|
||||
|
||||
|
||||
# =============================================================================
|
||||
#
|
||||
# Provide File/Directory listing
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class vtkWebFileBrowser(vtkWebProtocol):
|
||||
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 that we should start with
|
||||
- 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.baseDirectory = basePath
|
||||
self.rootName = name
|
||||
self.pattern = re.compile(excludeRegex)
|
||||
self.gPattern = re.compile(groupRegex)
|
||||
|
||||
@exportRpc("file.server.directory.list")
|
||||
def listServerDirectory(self, relativeDir="."):
|
||||
"""
|
||||
RPC Callback to list a server directory relative to the basePath
|
||||
provided at start-up.
|
||||
"""
|
||||
path = [self.rootName]
|
||||
if len(relativeDir) > len(self.rootName):
|
||||
relativeDir = relativeDir[len(self.rootName) + 1 :]
|
||||
path += relativeDir.replace("\\", "/").split("/")
|
||||
|
||||
currentPath = os.path.join(self.baseDirectory, relativeDir)
|
||||
result = {
|
||||
"label": relativeDir,
|
||||
"files": [],
|
||||
"dirs": [],
|
||||
"groups": [],
|
||||
"path": path,
|
||||
}
|
||||
if relativeDir == ".":
|
||||
result["label"] = self.rootName
|
||||
for file in os.listdir(currentPath):
|
||||
if os.path.isfile(os.path.join(currentPath, file)) and not re.search(
|
||||
self.pattern, file
|
||||
):
|
||||
result["files"].append({"label": file, "size": -1})
|
||||
elif os.path.isdir(os.path.join(currentPath, file)) and not re.search(
|
||||
self.pattern, file
|
||||
):
|
||||
result["dirs"].append(file)
|
||||
|
||||
# Filter files to create groups
|
||||
files = result["files"]
|
||||
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 gName in groupIdx:
|
||||
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
|
||||
|
||||
|
||||
# =============================================================================
|
||||
#
|
||||
# Provide an updated geometry delivery mechanism which better matches the
|
||||
# client-side rendering capability we have in vtk.js
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class vtkWebLocalRendering(vtkWebProtocol):
|
||||
def __init__(self, **kwargs):
|
||||
super(vtkWebLocalRendering, self).__init__()
|
||||
initializeSerializers()
|
||||
self.context = SynchronizationContext()
|
||||
self.trackingViews = {}
|
||||
self.mtime = 0
|
||||
|
||||
# RpcName: getArray => viewport.geometry.array.get
|
||||
@exportRpc("viewport.geometry.array.get")
|
||||
def getArray(self, dataHash, binary=False):
|
||||
if binary:
|
||||
return self.addAttachment(self.context.getCachedDataArray(dataHash, binary))
|
||||
return self.context.getCachedDataArray(dataHash, binary)
|
||||
|
||||
# RpcName: addViewObserver => viewport.geometry.view.observer.add
|
||||
@exportRpc("viewport.geometry.view.observer.add")
|
||||
def addViewObserver(self, viewId):
|
||||
sView = self.getView(viewId)
|
||||
if not sView:
|
||||
return {"error": "Unable to get view with id %s" % viewId}
|
||||
|
||||
realViewId = self.getApplication().GetObjectIdMap().GetGlobalId(sView)
|
||||
|
||||
def pushGeometry(newSubscription=False):
|
||||
stateToReturn = self.getViewState(realViewId, newSubscription)
|
||||
stateToReturn["mtime"] = 0 if newSubscription else self.mtime
|
||||
self.mtime += 1
|
||||
return stateToReturn
|
||||
|
||||
if not realViewId in self.trackingViews:
|
||||
observerCallback = lambda *args, **kwargs: self.publish(
|
||||
"viewport.geometry.view.subscription", pushGeometry()
|
||||
)
|
||||
tag = self.getApplication().AddObserver("UpdateEvent", observerCallback)
|
||||
self.trackingViews[realViewId] = {"tags": [tag], "observerCount": 1}
|
||||
else:
|
||||
# There is an observer on this view already
|
||||
self.trackingViews[realViewId]["observerCount"] += 1
|
||||
|
||||
self.publish("viewport.geometry.view.subscription", pushGeometry(True))
|
||||
return {"success": True, "viewId": realViewId}
|
||||
|
||||
# RpcName: removeViewObserver => viewport.geometry.view.observer.remove
|
||||
@exportRpc("viewport.geometry.view.observer.remove")
|
||||
def removeViewObserver(self, viewId):
|
||||
sView = self.getView(viewId)
|
||||
if not sView:
|
||||
return {"error": "Unable to get view with id %s" % viewId}
|
||||
|
||||
realViewId = self.getApplication().GetObjectIdMap().GetGlobalId(sView)
|
||||
|
||||
observerInfo = None
|
||||
if realViewId in self.trackingViews:
|
||||
observerInfo = self.trackingViews[realViewId]
|
||||
|
||||
if not observerInfo:
|
||||
return {"error": "Unable to find subscription for view %s" % realViewId}
|
||||
|
||||
observerInfo["observerCount"] -= 1
|
||||
|
||||
if observerInfo["observerCount"] <= 0:
|
||||
for tag in observerInfo["tags"]:
|
||||
self.getApplication().RemoveObserver(tag)
|
||||
del self.trackingViews[realViewId]
|
||||
|
||||
return {"result": "success"}
|
||||
|
||||
# RpcName: getViewState => viewport.geometry.view.get.state
|
||||
@exportRpc("viewport.geometry.view.get.state")
|
||||
def getViewState(self, viewId, newSubscription=False):
|
||||
sView = self.getView(viewId)
|
||||
if not sView:
|
||||
return {"error": "Unable to get view with id %s" % viewId}
|
||||
|
||||
self.context.setIgnoreLastDependencies(newSubscription)
|
||||
|
||||
# Get the active view and render window, use it to iterate over renderers
|
||||
renderWindow = sView
|
||||
renderer = renderWindow.GetRenderers().GetFirstRenderer()
|
||||
camera = renderer.GetActiveCamera()
|
||||
renderWindowId = self.getApplication().GetObjectIdMap().GetGlobalId(sView)
|
||||
viewInstance = serializeInstance(
|
||||
None, renderWindow, renderWindowId, self.context, 1
|
||||
)
|
||||
viewInstance["extra"] = {
|
||||
"vtkRefId": getReferenceId(renderWindow),
|
||||
"centerOfRotation": camera.GetFocalPoint(),
|
||||
"camera": getReferenceId(camera),
|
||||
}
|
||||
|
||||
self.context.setIgnoreLastDependencies(False)
|
||||
self.context.checkForArraysToRelease()
|
||||
|
||||
if viewInstance:
|
||||
return viewInstance
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user