123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842 |
- 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
|