import base64 import hashlib import io import struct import time import zipfile from vtk.vtkCommonCore import vtkTypeUInt32Array, vtkTypeInt32Array from vtk.vtkFiltersGeometry import vtkCompositeDataGeometryFilter, vtkGeometryFilter from vtk.vtkRenderingCore import vtkColorTransferFunction from vtk.vtkCommonDataModel import vtkDataObject from .enums import TextPosition def iteritems(d, **kwargs): return iter(d.items(**kwargs)) buffer = memoryview base64Encode = lambda x: base64.b64encode(x).decode('utf-8') # ----------------------------------------------------------------------------- # Array helpers # ----------------------------------------------------------------------------- arrayTypesMapping = [ ' ', # VTK_VOID 0 ' ', # VTK_BIT 1 'b', # VTK_CHAR 2 'B', # VTK_UNSIGNED_CHAR 3 'h', # VTK_SHORT 4 'H', # VTK_UNSIGNED_SHORT 5 'i', # VTK_INT 6 'I', # VTK_UNSIGNED_INT 7 'l', # VTK_LONG 8 'L', # VTK_UNSIGNED_LONG 9 'f', # VTK_FLOAT 10 'd', # VTK_DOUBLE 11 'L', # VTK_ID_TYPE 12 ' ', # VTK_STRING 13 ' ', # VTK_OPAQUE 14 ' ', # UNDEFINED 'l', # VTK_LONG_LONG 16 'L', # VTK_UNSIGNED_LONG_LONG 17 ] javascriptMapping = { 'b': 'Int8Array', 'B': 'Uint8Array', 'h': 'Int16Array', 'H': 'UInt16Array', 'i': 'Int32Array', 'I': 'Uint32Array', 'l': 'Int32Array', 'L': 'Uint32Array', 'f': 'Float32Array', 'd': 'Float64Array' } def hashDataArray(dataArray): return hashlib.md5(buffer(dataArray)).hexdigest() def getJSArrayType(dataArray): return javascriptMapping[arrayTypesMapping[dataArray.GetDataType()]] def zipCompression(name, data): with io.BytesIO() as in_memory: with zipfile.ZipFile(in_memory, mode="w") as zf: zf.writestr('data/%s' % name, data, zipfile.ZIP_DEFLATED) in_memory.seek(0) return in_memory.read() def dataTableToList(dataTable): dataType = arrayTypesMapping[dataTable.GetDataType()] elementSize = struct.calcsize(dataType) nbValues = dataTable.GetNumberOfValues() nbComponents = dataTable.GetNumberOfComponents() nbytes = elementSize * nbValues if dataType != ' ': with io.BytesIO(buffer(dataTable)) as stream: data = list(struct.unpack(dataType*nbValues ,stream.read(nbytes))) return [data[idx*nbComponents:(idx+1)*nbComponents] for idx in range(nbValues//nbComponents)] def getScalars(mapper, dataset): scalars = None cell_flag = 0 scalar_mode = mapper.GetScalarMode() array_access_mode = mapper.GetArrayAccessMode() array_id = mapper.GetArrayId() array_name = mapper.GetArrayName() pd = dataset.GetPointData() cd = dataset.GetCellData() fd = dataset.GetFieldData() if scalar_mode == 0: # VTK_SCALAR_MODE_DEFAULT scalars = pd.GetScalars() cell_flag = 0 if scalars is None: scalars = cd.GetScalars() cell_flag = 1 elif scalar_mode == 1: # VTK_SCALAR_MODE_USE_POINT_DATA scalars = pd.GetScalars() cell_flag = 0 elif scalar_mode == 2: # VTK_SCALAR_MODE_USE_CELL_DATA scalars = cd.GetScalars() cell_flag = 1 elif scalar_mode == 3: # VTK_SCALAR_MODE_USE_POINT_FIELD_DATA if array_access_mode == 0: # VTK_GET_ARRAY_BY_ID scalars = pd.GetAbstractArray(array_id) else: # VTK_GET_ARRAY_BY_NAME scalars = pd.GetAbstractArray(array_name) cell_flag = 0 elif scalar_mode == 4: # VTK_SCALAR_MODE_USE_CELL_FIELD_DATA if array_access_mode == 0: # VTK_GET_ARRAY_BY_ID scalars = cd.GetAbstractArray(array_id) else: # VTK_GET_ARRAY_BY_NAME scalars = cd.GetAbstractArray(array_name) cell_flag = 1 else: # VTK_SCALAR_MODE_USE_FIELD_DATA if array_access_mode == 0: # VTK_GET_ARRAY_BY_ID scalars = fd.GetAbstractArray(array_id) else: # VTK_GET_ARRAY_BY_NAME scalars = fd.GetAbstractArray(array_name) cell_flag = 2 return scalars, cell_flag def retrieveArrayName(mapper_instance, scalar_mode): colorArrayName = None try: ds = [deps for deps in mapper_instance['dependencies'] if deps['id'].endswith('dataset')][0] location = "pointData" if scalar_mode in (1, 3) else "cellData" for arrayMeta in ds['properties']['fields']: if arrayMeta["location"] == location and arrayMeta.get("registration", None) == "setScalars": colorArrayName = arrayMeta["name"] except Exception: pass return colorArrayName def linspace(start, stop, num): delta = (stop - start)/(num-1) return [start + i*delta for i in range(num)] # ----------------------------------------------------------------------------- # Convenience class for caching data arrays, storing computed sha sums, keeping # track of valid actors, etc... # ----------------------------------------------------------------------------- class SynchronizationContext(): def __init__(self, id_root=None, serialize_all_data_arrays=False, debug=False): self.serializeAllDataArrays = serialize_all_data_arrays self.dataArrayCache = {} self.lastDependenciesMapping = {} self.ingoreLastDependencies = False self.idRoot = id_root self.debugSerializers = debug self.debugAll = debug self.annotations = {} def getReferenceId(self, instance): if not self.idRoot or (hasattr(instance, 'IsA') and instance.IsA('vtkCamera')): return getReferenceId(instance) else: return self.idRoot + getReferenceId(instance) def addAnnotation(self, parent, prop, propId): if prop.GetClassName() == "vtkCornerAnnotation": annotation = { "id": propId, "viewport": parent.GetViewport(), "fontSize": prop.GetLinearFontScaleFactor() * 2, "fontFamily": prop.GetTextProperty().GetFontFamilyAsString(), "color": prop.GetTextProperty().GetColor(), **{pos.name: prop.GetText(pos.value) for pos in TextPosition} } if self.annotations is None: self.annotations = {propId: annotation} else: self.annotations.update({propId: annotation}) def getAnnotations(self): return list(self.annotations.values()) def setIgnoreLastDependencies(self, force): self.ingoreLastDependencies = force def cacheDataArray(self, pMd5, data): self.dataArrayCache[pMd5] = data def getCachedDataArray(self, pMd5, binary=False, compression=False): cacheObj = self.dataArrayCache[pMd5] array = cacheObj['array'] cacheTime = cacheObj['mTime'] if cacheTime != array.GetMTime(): if context.debugAll: print(' ***** ERROR: you asked for an old cache key! ***** ') if array.GetDataType() in (12, 16, 17): arraySize = array.GetNumberOfTuples() * array.GetNumberOfComponents() if array.GetDataType() in (12, 17): # IdType and unsigned long long need to be converted to Uint32 newArray = vtkTypeUInt32Array() else: # long long need to be converted to Int32 newArray = vtkTypeInt32Array() newArray.SetNumberOfTuples(arraySize) for i in range(arraySize): newArray.SetValue(i, -1 if array.GetValue(i) < 0 else array.GetValue(i)) pBuffer = buffer(newArray) else: pBuffer = buffer(array) if binary: # Convert the vtkUnsignedCharArray into a bytes object, required by # Autobahn websockets return pBuffer.tobytes() if not compression else zipCompression(pMd5, pBuffer.tobytes()) return base64Encode(pBuffer if not compression else zipCompression(pMd5, pBuffer.tobytes())) def checkForArraysToRelease(self, timeWindow=20): cutOffTime = time.time() - timeWindow shasToDelete = [] for sha in self.dataArrayCache: record = self.dataArrayCache[sha] array = record['array'] count = array.GetReferenceCount() if count == 1 and record['ts'] < cutOffTime: shasToDelete.append(sha) for sha in shasToDelete: del self.dataArrayCache[sha] def getLastDependencyList(self, idstr): lastDeps = [] if idstr in self.lastDependenciesMapping and not self.ingoreLastDependencies: lastDeps = self.lastDependenciesMapping[idstr] return lastDeps def setNewDependencyList(self, idstr, depList): self.lastDependenciesMapping[idstr] = depList def buildDependencyCallList(self, idstr, newList, addMethod, removeMethod): oldList = self.getLastDependencyList(idstr) calls = [] calls += [[addMethod, [wrapId(x)]] for x in newList if x not in oldList] calls += [[removeMethod, [wrapId(x)]] for x in oldList if x not in newList] self.setNewDependencyList(idstr, newList) return calls # ----------------------------------------------------------------------------- # Global variables # ----------------------------------------------------------------------------- SERIALIZERS = {} context = None # ----------------------------------------------------------------------------- # Global API # ----------------------------------------------------------------------------- def registerInstanceSerializer(name, method): global SERIALIZERS SERIALIZERS[name] = method # ----------------------------------------------------------------------------- def serializeInstance(parent, instance, instanceId, context, depth): instanceType = instance.GetClassName() serializer = SERIALIZERS[ instanceType] if instanceType in SERIALIZERS else None if serializer: return serializer(parent, instance, instanceId, context, depth) if context.debugSerializers: print('%s!!!No serializer for %s with id %s' % (pad(depth), instanceType, instanceId)) # ----------------------------------------------------------------------------- def initializeSerializers(): # Annotations registerInstanceSerializer('vtkCornerAnnotation', annotationSerializer) # Actors/viewProps registerInstanceSerializer('vtkImageSlice', genericProp3DSerializer) registerInstanceSerializer('vtkVolume', genericProp3DSerializer) registerInstanceSerializer('vtkOpenGLActor', genericActorSerializer) registerInstanceSerializer('vtkFollower', genericActorSerializer) registerInstanceSerializer('vtkPVLODActor', genericActorSerializer) # Mappers registerInstanceSerializer( 'vtkOpenGLPolyDataMapper', genericPolyDataMapperSerializer) registerInstanceSerializer( 'vtkCompositePolyDataMapper2', genericPolyDataMapperSerializer) registerInstanceSerializer('vtkDataSetMapper', genericPolyDataMapperSerializer) registerInstanceSerializer( 'vtkFixedPointVolumeRayCastMapper', genericVolumeMapperSerializer) registerInstanceSerializer( 'vtkSmartVolumeMapper', genericVolumeMapperSerializer) registerInstanceSerializer( 'vtkOpenGLImageSliceMapper', imageSliceMapperSerializer) registerInstanceSerializer( 'vtkOpenGLGlyph3DMapper', glyph3DMapperSerializer) # LookupTables/TransferFunctions registerInstanceSerializer('vtkLookupTable', lookupTableSerializer) registerInstanceSerializer( 'vtkPVDiscretizableColorTransferFunction', colorTransferFunctionSerializer) registerInstanceSerializer( 'vtkColorTransferFunction', colorTransferFunctionSerializer) # opacityFunctions registerInstanceSerializer( 'vtkPiecewiseFunction', piecewiseFunctionSerializer) # Textures registerInstanceSerializer('vtkOpenGLTexture', textureSerializer) # Property registerInstanceSerializer('vtkOpenGLProperty', propertySerializer) registerInstanceSerializer('vtkVolumeProperty', volumePropertySerializer) registerInstanceSerializer('vtkImageProperty', imagePropertySerializer) # Datasets registerInstanceSerializer('vtkPolyData', polydataSerializer) registerInstanceSerializer('vtkImageData', imageDataSerializer) registerInstanceSerializer( 'vtkStructuredGrid', mergeToPolydataSerializer) registerInstanceSerializer( 'vtkUnstructuredGrid', mergeToPolydataSerializer) registerInstanceSerializer( 'vtkMultiBlockDataSet', mergeToPolydataSerializer) # RenderWindows registerInstanceSerializer('vtkCocoaRenderWindow', renderWindowSerializer) registerInstanceSerializer( 'vtkXOpenGLRenderWindow', renderWindowSerializer) registerInstanceSerializer( 'vtkWin32OpenGLRenderWindow', renderWindowSerializer) registerInstanceSerializer('vtkEGLRenderWindow', renderWindowSerializer) registerInstanceSerializer('vtkOpenVRRenderWindow', renderWindowSerializer) registerInstanceSerializer( 'vtkGenericOpenGLRenderWindow', renderWindowSerializer) registerInstanceSerializer( 'vtkOSOpenGLRenderWindow', renderWindowSerializer) registerInstanceSerializer('vtkOpenGLRenderWindow', renderWindowSerializer) registerInstanceSerializer('vtkIOSRenderWindow', renderWindowSerializer) registerInstanceSerializer( 'vtkExternalOpenGLRenderWindow', renderWindowSerializer) # Renderers registerInstanceSerializer('vtkOpenGLRenderer', rendererSerializer) # Cameras registerInstanceSerializer('vtkOpenGLCamera', cameraSerializer) # ----------------------------------------------------------------------------- # Helper functions # ----------------------------------------------------------------------------- def pad(depth): padding = '' for _ in range(depth): padding += ' ' return padding # ----------------------------------------------------------------------------- def wrapId(idStr): return 'instance:${%s}' % idStr # ----------------------------------------------------------------------------- def getReferenceId(ref): if ref: try: return ref.__this__[1:17] except Exception: idStr = str(ref)[-12:-1] print('====> fallback ID %s for %s' % (idStr, ref)) return idStr return '0x0' # ----------------------------------------------------------------------------- dataArrayShaMapping = {} def digest(array): objId = getReferenceId(array) record = None if objId in dataArrayShaMapping: record = dataArrayShaMapping[objId] if record and record['mtime'] == array.GetMTime(): return record['sha'] record = { 'sha': hashDataArray(array), 'mtime': array.GetMTime() } dataArrayShaMapping[objId] = record return record['sha'] # ----------------------------------------------------------------------------- def getRangeInfo(array, component): r = array.GetRange(component) compRange = {} compRange['min'] = r[0] compRange['max'] = r[1] compRange['component'] = array.GetComponentName(component) return compRange # ----------------------------------------------------------------------------- def getArrayDescription(array, context): if not array: return None pMd5 = digest(array) context.cacheDataArray(pMd5, { 'array': array, 'mTime': array.GetMTime(), 'ts': time.time() }) root = {} root['hash'] = pMd5 root['vtkClass'] = 'vtkDataArray' root['name'] = array.GetName() root['dataType'] = getJSArrayType(array) root['numberOfComponents'] = array.GetNumberOfComponents() root['size'] = array.GetNumberOfComponents() * array.GetNumberOfTuples() root['ranges'] = [] if root['numberOfComponents'] > 1: for i in range(root['numberOfComponents']): root['ranges'].append(getRangeInfo(array, i)) root['ranges'].append(getRangeInfo(array, -1)) else: root['ranges'].append(getRangeInfo(array, 0)) return root # ----------------------------------------------------------------------------- def extractAllDataArrays(extractedFields, dataset, context): pointData = dataset.GetPointData() for id_arr in range(pointData.GetNumberOfArrays()): arrayMeta = getArrayDescription(pointData.GetArray(id_arr), context) if arrayMeta: arrayMeta['location'] = 'pointData' extractedFields.append(arrayMeta) cellData = dataset.GetCellData() for id_arr in range(cellData.GetNumberOfArrays()): arrayMeta = getArrayDescription(cellData.GetArray(id_arr), context) if arrayMeta: arrayMeta['location'] = 'cellData' extractedFields.append(arrayMeta) fieldData = dataset.GetCellData() for id_arr in range(fieldData.GetNumberOfArrays()): arrayMeta = getArrayDescription(fieldData.GetArray(id_arr), context) if arrayMeta: arrayMeta['location'] = 'fieldData' extractedFields.append(arrayMeta) # ----------------------------------------------------------------------------- def extractRequiredFields(extractedFields, parent, dataset, context, requestedFields=['Normals', 'TCoords']): # FIXME should evolve and support funky mapper which leverage many arrays if any(parent.IsA(cls) for cls in ['vtkMapper', 'vtkVolumeMapper', 'vtkImageSliceMapper', 'vtkTexture']): if parent.IsA("vtkAbstractMapper"): # GetScalars method should exists scalarVisibility = 1 if not hasattr(parent, "GetScalarVisibility") else parent.GetScalarVisibility() scalars, cell_flag = getScalars(parent, dataset) if context.serializeAllDataArrays: extractAllDataArrays(extractedFields, dataset, context) if scalars: for arrayMeta in extractedFields: if arrayMeta['name'] == scalars.GetName(): arrayMeta['registration'] = 'setScalars' elif scalars and scalarVisibility and not context.serializeAllDataArrays: arrayMeta = getArrayDescription(scalars, context) if cell_flag == 0: arrayMeta['location'] = 'pointData' elif cell_flag == 1: arrayMeta['location'] = 'cellData' else: raise NotImplementedError("Scalars on field data not handled") arrayMeta['registration'] = 'setScalars' extractedFields.append(arrayMeta) elif dataset.GetPointData().GetScalars(): arrayMeta = getArrayDescription(dataset.GetPointData().GetScalars(), context) arrayMeta['location'] = 'pointData' arrayMeta['registration'] = 'setScalars' extractedFields.append(arrayMeta) if parent.IsA("vtkGlyph3DMapper") and not context.serializeAllDataArrays: scaleArrayName = parent.GetInputArrayInformation(parent.SCALE).Get(vtkDataObject.FIELD_NAME()) if scaleArrayName is not None and scaleArrayName not in [field['name'] for field in extractedFields]: arrayMeta = getArrayDescription(dataset.GetPointData().GetAbstractArray(scaleArrayName), context) if arrayMeta is not None: arrayMeta['location'] = 'pointData' arrayMeta['registration'] = 'addArray' extractedFields.append(arrayMeta) scaleOrientationArrayName = parent.GetInputArrayInformation(parent.ORIENTATION).Get(vtkDataObject.FIELD_NAME()) if scaleOrientationArrayName is not None and scaleOrientationArrayName not in [field['name'] for field in extractedFields]: arrayMeta = getArrayDescription(dataset.GetPointData().GetAbstractArray(scaleOrientationArrayName), context) if arrayMeta is not None: arrayMeta['location'] = 'pointData' arrayMeta['registration'] = 'addArray' extractedFields.append(arrayMeta) # Normal handling if 'Normals' in requestedFields: normals = dataset.GetPointData().GetNormals() if normals: arrayMeta = getArrayDescription(normals, context) if arrayMeta: arrayMeta['location'] = 'pointData' arrayMeta['registration'] = 'setNormals' extractedFields.append(arrayMeta) # TCoord handling if 'TCoords' in requestedFields: tcoords = dataset.GetPointData().GetTCoords() if tcoords: arrayMeta = getArrayDescription(tcoords, context) if arrayMeta: arrayMeta['location'] = 'pointData' arrayMeta['registration'] = 'setTCoords' extractedFields.append(arrayMeta) # ----------------------------------------------------------------------------- # Concrete instance serializers # ----------------------------------------------------------------------------- def annotationSerializer(parent, prop, propId, context, depth): if context.debugSerializers: print('%s!!!Annotations are not handled directly by vtk.js but by bokeh model' % pad(depth)) context.addAnnotation(parent, prop, propId) return None def genericPropSerializer(parent, prop, popId, context, depth): # This kind of actor has two "children" of interest, a property and a # mapper (optionnaly a texture) mapperInstance = None propertyInstance = None calls = [] dependencies = [] mapper = None if not hasattr(prop, 'GetMapper'): if context.debugAll: print('This volume does not have a GetMapper method') else: mapper = prop.GetMapper() if mapper: mapperId = context.getReferenceId(mapper) mapperInstance = serializeInstance( prop, mapper, mapperId, context, depth + 1) if mapperInstance: dependencies.append(mapperInstance) calls.append(['setMapper', [wrapId(mapperId)]]) properties = None if hasattr(prop, 'GetProperty'): properties = prop.GetProperty() else: if context.debugAll: print('This image does not have a GetProperty method') if properties: propId = context.getReferenceId(properties) propertyInstance = serializeInstance( prop, properties, propId, context, depth + 1) if propertyInstance: dependencies.append(propertyInstance) calls.append(['setProperty', [wrapId(propId)]]) # Handle texture if any texture = None if hasattr(prop, 'GetTexture'): texture = prop.GetTexture() if texture: textureId = context.getReferenceId(texture) textureInstance = serializeInstance( prop, texture, textureId, context, depth + 1) if textureInstance: dependencies.append(textureInstance) calls.append(['addTexture', [wrapId(textureId)]]) return { 'parent': context.getReferenceId(parent), 'id': popId, 'type': prop.GetClassName(), 'properties': { # vtkProp 'visibility': prop.GetVisibility(), 'pickable': prop.GetPickable(), 'dragable': prop.GetDragable(), 'useBounds': prop.GetUseBounds(), }, 'calls': calls, 'dependencies': dependencies } # ----------------------------------------------------------------------------- def genericProp3DSerializer(parent, prop3D, prop3DId, context, depth): # This kind of actor has some position properties to add instance = genericPropSerializer(parent, prop3D, prop3DId, context, depth) if not instance: return instance['properties'].update({ # vtkProp3D 'origin': prop3D.GetOrigin(), 'position': prop3D.GetPosition(), 'scale': prop3D.GetScale(), 'orientation': prop3D.GetOrientation(), }) if prop3D.GetUserMatrix(): instance['properties'].update({ 'userMatrix': [prop3D.GetUserMatrix().GetElement(i%4,i//4) for i in range(16)], }) return instance # ----------------------------------------------------------------------------- def genericActorSerializer(parent, actor, actorId, context, depth): # may have texture and instance = genericProp3DSerializer(parent, actor, actorId, context, depth) if not instance: return # # actor may have a backface property instance (not used by vtkjs rendering) # # https://github.com/Kitware/vtk-js/issues/1545 # backfaceProperties = actor.GetBackfaceProperty() # if backfaceProperties: # backfacePropId = context.getReferenceId(backfaceProperties) # backPropertyInstance = serializeInstance( # actor, backfaceProperties, backfacePropId, context, depth + 1) # if backPropertyInstance: # instance['dependencies'].append(backPropertyInstance) # instance['calls'].append(['setBackfaceProperty', [wrapId(backfacePropId)]]) instance['properties'].update({ # vtkActor 'forceOpaque': actor.GetForceOpaque(), 'forceTranslucent': actor.GetForceTranslucent() }) if actor.IsA('vtkFollower'): camera = actor.GetCamera() cameraId = context.getReferenceId(camera) cameraInstance = serializeInstance( actor, camera, cameraId, context, depth + 1) if cameraInstance: instance['dependencies'].append(cameraInstance) instance['calls'].append(['setCamera', [wrapId(cameraId)]]) return instance # ----------------------------------------------------------------------------- def genericMapperSerializer(parent, mapper, mapperId, context, depth): # This kind of mapper requires us to get 2 items: input data and lookup # table dataObject = None dataObjectInstance = None lookupTableInstance = None calls = [] dependencies = [] if not hasattr(mapper, 'GetInputDataObject'): if context.debugAll: print('This mapper does not have GetInputDataObject method') else: for port in range(mapper.GetNumberOfInputPorts()): # Glyph3DMapper can define input data objects on 2 ports (input, source) dataObject = mapper.GetInputDataObject(port, 0) if dataObject: dataObjectId = '%s-dataset-%d' % (mapperId, port) if parent.IsA('vtkActor') and not mapper.IsA('vtkTexture'): # vtk-js actors can render only surfacic datasets # => we ensure to convert the dataset in polydata dataObjectInstance = mergeToPolydataSerializer( mapper, dataObject, dataObjectId, context, depth + 1) else: dataObjectInstance = serializeInstance( mapper, dataObject, dataObjectId, context, depth + 1) if dataObjectInstance: dependencies.append(dataObjectInstance) calls.append(['setInputData', [wrapId(dataObjectId), port]]) lookupTable = None if hasattr(mapper, 'GetLookupTable'): lookupTable = mapper.GetLookupTable() elif parent.IsA('vtkActor'): if context.debugAll: print('This mapper actor not have GetLookupTable method') if lookupTable: lookupTableId = context.getReferenceId(lookupTable) lookupTableInstance = serializeInstance( mapper, lookupTable, lookupTableId, context, depth + 1) if lookupTableInstance: dependencies.append(lookupTableInstance) calls.append(['setLookupTable', [wrapId(lookupTableId)]]) if dataObjectInstance: return { 'parent': context.getReferenceId(parent), 'id': mapperId, 'properties': {}, 'calls': calls, 'dependencies': dependencies } # ----------------------------------------------------------------------------- def genericPolyDataMapperSerializer(parent, mapper, mapperId, context, depth): instance = genericMapperSerializer(parent, mapper, mapperId, context, depth) if not instance: return instance['type'] = mapper.GetClassName() instance['properties'].update({ 'resolveCoincidentTopology': mapper.GetResolveCoincidentTopology(), 'renderTime': mapper.GetRenderTime(), 'arrayAccessMode': 1, # since we can't set mapper arrayId on vtkjs, we force acess mode by name and use retrieve name function 'scalarRange': mapper.GetScalarRange(), 'useLookupTableScalarRange': 1 if mapper.GetUseLookupTableScalarRange() else 0, 'scalarVisibility': mapper.GetScalarVisibility(), 'colorByArrayName': retrieveArrayName(instance, mapper.GetScalarMode()), 'colorMode': mapper.GetColorMode(), 'scalarMode': mapper.GetScalarMode(), 'interpolateScalarsBeforeMapping': 1 if mapper.GetInterpolateScalarsBeforeMapping() else 0 }) return instance # ----------------------------------------------------------------------------- def genericVolumeMapperSerializer(parent, mapper, mapperId, context, depth): instance = genericMapperSerializer(parent, mapper, mapperId, context, depth) if not instance: return imageSampleDistance = ( mapper.GetImageSampleDistance() if hasattr(mapper, 'GetImageSampleDistance') else 1 ) instance['type'] = mapper.GetClassName() instance['properties'].update({ 'sampleDistance': mapper.GetSampleDistance(), 'imageSampleDistance': imageSampleDistance, # 'maximumSamplesPerRay', 'autoAdjustSampleDistances': mapper.GetAutoAdjustSampleDistances(), 'blendMode': mapper.GetBlendMode(), }) return instance # ----------------------------------------------------------------------------- def glyph3DMapperSerializer(parent, mapper, mapperId, context, depth): instance = genericPolyDataMapperSerializer(parent, mapper, mapperId, context, depth) if not instance: return instance['type'] = mapper.GetClassName() instance['properties'].update({ 'orient': mapper.GetOrient(), 'orientationMode': mapper.GetOrientationMode(), 'scaling': mapper.GetScaling(), 'scaleFactor': mapper.GetScaleFactor(), 'scaleMode': mapper.GetScaleMode(), 'scaleArray': mapper.GetInputArrayInformation(mapper.SCALE).Get(vtkDataObject.FIELD_NAME()), 'orientationArray': mapper.GetInputArrayInformation(mapper.ORIENTATION).Get(vtkDataObject.FIELD_NAME()), }) return instance # ----------------------------------------------------------------------------- def textureSerializer(parent, texture, textureId, context, depth): instance = genericMapperSerializer(parent, texture, textureId, context, depth) if not instance: return instance['type'] = texture.GetClassName() instance['properties'].update({ 'interpolate': texture.GetInterpolate(), 'repeat': texture.GetRepeat(), 'edgeClamp': texture.GetEdgeClamp(), }) return instance # ----------------------------------------------------------------------------- def imageSliceMapperSerializer(parent, mapper, mapperId, context, depth): # On vtkjs side : vtkImageMapper connected to a vtkImageReslice filter instance = genericMapperSerializer(parent, mapper, mapperId, context, depth) if not instance: return instance['type'] = mapper.GetClassName() return instance # ----------------------------------------------------------------------------- def lookupTableSerializer(parent, lookupTable, lookupTableId, context, depth): # No children in this case, so no additions to bindings and return empty list # But we do need to add instance arrays = [] lookupTableRange = lookupTable.GetRange() lookupTableHueRange = [0.5, 0] if hasattr(lookupTable, 'GetHueRange'): try: lookupTable.GetHueRange(lookupTableHueRange) except Exception: pass lutSatRange = lookupTable.GetSaturationRange() # lutAlphaRange = lookupTable.GetAlphaRange() if lookupTable.GetTable(): arrayMeta = getArrayDescription(lookupTable.GetTable(), context) if arrayMeta: arrayMeta['registration'] = 'setTable' arrays.append(arrayMeta) return { 'parent': context.getReferenceId(parent), 'id': lookupTableId, 'type': lookupTable.GetClassName(), 'properties': { 'numberOfColors': lookupTable.GetNumberOfColors(), 'valueRange': lookupTableRange, 'range': lookupTableRange, 'hueRange': lookupTableHueRange, # 'alphaRange': lutAlphaRange, # Causes weird rendering artifacts on client 'saturationRange': lutSatRange, 'nanColor': lookupTable.GetNanColor(), 'belowRangeColor': lookupTable.GetBelowRangeColor(), 'aboveRangeColor': lookupTable.GetAboveRangeColor(), 'useAboveRangeColor': True if lookupTable.GetUseAboveRangeColor() else False, 'useBelowRangeColor': True if lookupTable.GetUseBelowRangeColor() else False, 'alpha': lookupTable.GetAlpha(), 'vectorSize': lookupTable.GetVectorSize(), 'vectorComponent': lookupTable.GetVectorComponent(), 'vectorMode': lookupTable.GetVectorMode(), 'indexedLookup': lookupTable.GetIndexedLookup(), }, 'arrays': arrays, } # ----------------------------------------------------------------------------- def lookupTableToColorTransferFunction(lookupTable): dataTable = lookupTable.GetTable() table = dataTableToList(dataTable) if table: ctf = vtkColorTransferFunction() tableRange = lookupTable.GetTableRange() points = linspace(*tableRange, num=len(table)) for x, rgba in zip(points, table): ctf.AddRGBPoint(x, *[x/255 for x in rgba[:3]]) return ctf # ----------------------------------------------------------------------------- def lookupTableSerializer2(parent, lookupTable, lookupTableId, context, depth): ctf = lookupTableToColorTransferFunction(lookupTable) if ctf: return colorTransferFunctionSerializer(parent, ctf, lookupTableId, context, depth) # ----------------------------------------------------------------------------- def propertySerializer(parent, propObj, propObjId, context, depth): representation = propObj.GetRepresentation() if hasattr( propObj, 'GetRepresentation') else 2 colorToUse = propObj.GetDiffuseColor() if hasattr( propObj, 'GetDiffuseColor') else [1, 1, 1] if representation == 1 and hasattr(propObj, 'GetColor'): colorToUse = propObj.GetColor() return { 'parent': context.getReferenceId(parent), 'id': propObjId, 'type': propObj.GetClassName(), 'properties': { 'representation': representation, 'diffuseColor': colorToUse, 'color': propObj.GetColor(), 'ambientColor': propObj.GetAmbientColor(), 'specularColor': propObj.GetSpecularColor(), 'edgeColor': propObj.GetEdgeColor(), 'ambient': propObj.GetAmbient(), 'diffuse': propObj.GetDiffuse(), 'specular': propObj.GetSpecular(), 'specularPower': propObj.GetSpecularPower(), 'opacity': propObj.GetOpacity(), 'interpolation': propObj.GetInterpolation(), 'edgeVisibility': 1 if propObj.GetEdgeVisibility() else 0, 'backfaceCulling': 1 if propObj.GetBackfaceCulling() else 0, 'frontfaceCulling': 1 if propObj.GetFrontfaceCulling() else 0, 'pointSize': propObj.GetPointSize(), 'lineWidth': propObj.GetLineWidth(), 'lighting': 1 if propObj.GetLighting() else 0, } } # ----------------------------------------------------------------------------- def volumePropertySerializer(parent, propObj, propObjId, context, depth): dependencies = [] calls = [] # TODO: for the moment only component 0 handle #OpactiyFunction ofun = propObj.GetScalarOpacity() if ofun: ofunId = context.getReferenceId(ofun) ofunInstance = serializeInstance( propObj, ofun, ofunId, context, depth + 1) if ofunInstance: dependencies.append(ofunInstance) calls.append(['setScalarOpacity', [0, wrapId(ofunId)]]) # ColorTranferFunction ctfun = propObj.GetRGBTransferFunction() if ctfun: ctfunId = context.getReferenceId(ctfun) ctfunInstance = serializeInstance( propObj, ctfun, ctfunId, context, depth + 1) if ctfunInstance: dependencies.append(ctfunInstance) calls.append(['setRGBTransferFunction', [0, wrapId(ctfunId)]]) calls += [ ['setScalarOpacityUnitDistance', [0, propObj.GetScalarOpacityUnitDistance(0)]], ['setComponentWeight', [0, propObj.GetComponentWeight(0)]], ['setUseGradientOpacity', [0, int(not propObj.GetDisableGradientOpacity())]], ] return { 'parent': context.getReferenceId(parent), 'id': propObjId, 'type': propObj.GetClassName(), 'properties': { 'independentComponents': propObj.GetIndependentComponents(), 'interpolationType': propObj.GetInterpolationType(), 'ambient': propObj.GetAmbient(), 'diffuse': propObj.GetDiffuse(), 'shade': propObj.GetShade(), 'specular': propObj.GetSpecular(0), 'specularPower': propObj.GetSpecularPower(), }, 'dependencies': dependencies, 'calls': calls, } # ----------------------------------------------------------------------------- def imagePropertySerializer(parent, propObj, propObjId, context, depth): calls = [] dependencies = [] lookupTable = propObj.GetLookupTable() if lookupTable: ctfun = lookupTableToColorTransferFunction(lookupTable) ctfunId = context.getReferenceId(ctfun) ctfunInstance = serializeInstance( propObj, ctfun, ctfunId, context, depth + 1) if ctfunInstance: dependencies.append(ctfunInstance) calls.append(['setRGBTransferFunction', [wrapId(ctfunId)]]) return { 'parent': context.getReferenceId(parent), 'id': propObjId, 'type': propObj.GetClassName(), 'properties': { 'interpolationType': propObj.GetInterpolationType(), 'colorWindow': propObj.GetColorWindow(), 'colorLevel': propObj.GetColorLevel(), 'ambient': propObj.GetAmbient(), 'diffuse': propObj.GetDiffuse(), 'opacity': propObj.GetOpacity(), }, 'dependencies': dependencies, 'calls': calls, } # ----------------------------------------------------------------------------- def imageDataSerializer(parent, dataset, datasetId, context, depth): datasetType = dataset.GetClassName() if hasattr(dataset, 'GetDirectionMatrix'): direction = [dataset.GetDirectionMatrix().GetElement(0, i) for i in range(9)] else: direction = [1, 0, 0, 0, 1, 0, 0, 0, 1] # Extract dataset fields arrays = [] extractRequiredFields(arrays, parent, dataset, context) return { 'parent': context.getReferenceId(parent), 'id': datasetId, 'type': datasetType, 'properties': { 'spacing': dataset.GetSpacing(), 'origin': dataset.GetOrigin(), 'dimensions': dataset.GetDimensions(), 'direction': direction, }, 'arrays': arrays } # ----------------------------------------------------------------------------- def polydataSerializer(parent, dataset, datasetId, context, depth): datasetType = dataset.GetClassName() if dataset and dataset.GetPoints(): properties = {} # Points points = getArrayDescription(dataset.GetPoints().GetData(), context) points['vtkClass'] = 'vtkPoints' properties['points'] = points # Verts if dataset.GetVerts() and dataset.GetVerts().GetData().GetNumberOfTuples() > 0: _verts = getArrayDescription(dataset.GetVerts().GetData(), context) properties['verts'] = _verts properties['verts']['vtkClass'] = 'vtkCellArray' # Lines if dataset.GetLines() and dataset.GetLines().GetData().GetNumberOfTuples() > 0: _lines = getArrayDescription(dataset.GetLines().GetData(), context) properties['lines'] = _lines properties['lines']['vtkClass'] = 'vtkCellArray' # Polys if dataset.GetPolys() and dataset.GetPolys().GetData().GetNumberOfTuples() > 0: _polys = getArrayDescription(dataset.GetPolys().GetData(), context) properties['polys'] = _polys properties['polys']['vtkClass'] = 'vtkCellArray' # Strips if dataset.GetStrips() and dataset.GetStrips().GetData().GetNumberOfTuples() > 0: _strips = getArrayDescription( dataset.GetStrips().GetData(), context) properties['strips'] = _strips properties['strips']['vtkClass'] = 'vtkCellArray' # Fields properties['fields'] = [] extractRequiredFields(properties['fields'], parent, dataset, context) return { 'parent': context.getReferenceId(parent), 'id': datasetId, 'type': datasetType, 'properties': properties } if context.debugAll: print('This dataset has no points!') # ----------------------------------------------------------------------------- def mergeToPolydataSerializer(parent, dataObject, dataObjectId, context, depth): dataset = None if dataObject.IsA('vtkCompositeDataSet'): gf = vtkCompositeDataGeometryFilter() gf.SetInputData(dataObject) gf.Update() dataset = gf.GetOutput() elif (dataObject.IsA('vtkUnstructuredGrid') or dataObject.IsA('vtkStructuredGrid') or dataObject.IsA('vtkImageData')): gf = vtkGeometryFilter() gf.SetInputData(dataObject) gf.Update() dataset = gf.GetOutput() else: dataset = dataObject return polydataSerializer(parent, dataset, dataObjectId, context, depth) # ----------------------------------------------------------------------------- def colorTransferFunctionSerializer(parent, instance, objId, context, depth): nodes = [] for i in range(instance.GetSize()): # x, r, g, b, midpoint, sharpness node = [0, 0, 0, 0, 0, 0] instance.GetNodeValue(i, node) nodes.append(node) return { 'parent': context.getReferenceId(parent), 'id': objId, 'type': instance.GetClassName(), 'properties': { 'clamping': 1 if instance.GetClamping() else 0, 'colorSpace': instance.GetColorSpace(), 'hSVWrap': 1 if instance.GetHSVWrap() else 0, # 'nanColor': instance.GetNanColor(), # Breaks client # 'belowRangeColor': instance.GetBelowRangeColor(), # Breaks client # 'aboveRangeColor': instance.GetAboveRangeColor(), # Breaks client # 'useAboveRangeColor': 1 if instance.GetUseAboveRangeColor() else 0, # 'useBelowRangeColor': 1 if instance.GetUseBelowRangeColor() else 0, 'allowDuplicateScalars': 1 if instance.GetAllowDuplicateScalars() else 0, 'alpha': instance.GetAlpha(), 'vectorComponent': instance.GetVectorComponent(), 'vectorSize': instance.GetVectorSize(), 'vectorMode': instance.GetVectorMode(), 'indexedLookup': instance.GetIndexedLookup(), 'nodes': nodes } } # ----------------------------------------------------------------------------- def piecewiseFunctionSerializer(parent, instance, objId, context, depth): nodes = [] for i in range(instance.GetSize()): # x, y, midpoint, sharpness node = [0, 0, 0, 0] instance.GetNodeValue(i, node) nodes.append(node) return { 'parent': context.getReferenceId(parent), 'id': objId, 'type': instance.GetClassName(), 'properties': { 'clamping': instance.GetClamping(), 'allowDuplicateScalars': instance.GetAllowDuplicateScalars(), 'nodes': nodes, } } # ----------------------------------------------------------------------------- def rendererSerializer(parent, instance, objId, context, depth): dependencies = [] viewPropIds = [] calls = [] # Camera camera = instance.GetActiveCamera() cameraId = context.getReferenceId(camera) cameraInstance = serializeInstance( instance, camera, cameraId, context, depth + 1) if cameraInstance: dependencies.append(cameraInstance) calls.append(['setActiveCamera', [wrapId(cameraId)]]) # View prop as representation containers viewPropCollection = instance.GetViewProps() for rpIdx in range(viewPropCollection.GetNumberOfItems()): viewProp = viewPropCollection.GetItemAsObject(rpIdx) viewPropId = context.getReferenceId(viewProp) viewPropInstance = serializeInstance( instance, viewProp, viewPropId, context, depth + 1) if viewPropInstance: dependencies.append(viewPropInstance) viewPropIds.append(viewPropId) calls += context.buildDependencyCallList('%s-props' % objId, viewPropIds, 'addViewProp', 'removeViewProp') return { 'parent': context.getReferenceId(parent), 'id': objId, 'type': instance.GetClassName(), 'properties': { 'background': instance.GetBackground(), 'background2': instance.GetBackground2(), 'viewport': instance.GetViewport(), # These commented properties do not yet have real setters in vtk.js # 'gradientBackground': instance.GetGradientBackground(), # 'aspect': instance.GetAspect(), # 'pixelAspect': instance.GetPixelAspect(), # 'ambient': instance.GetAmbient(), 'twoSidedLighting': instance.GetTwoSidedLighting(), 'lightFollowCamera': instance.GetLightFollowCamera(), 'layer': instance.GetLayer(), 'preserveColorBuffer': instance.GetPreserveColorBuffer(), 'preserveDepthBuffer': instance.GetPreserveDepthBuffer(), 'nearClippingPlaneTolerance': instance.GetNearClippingPlaneTolerance(), 'clippingRangeExpansion': instance.GetClippingRangeExpansion(), 'useShadows': instance.GetUseShadows(), 'useDepthPeeling': instance.GetUseDepthPeeling(), 'occlusionRatio': instance.GetOcclusionRatio(), 'maximumNumberOfPeels': instance.GetMaximumNumberOfPeels(), 'interactive': instance.GetInteractive(), }, 'dependencies': dependencies, 'calls': calls } # ----------------------------------------------------------------------------- def cameraSerializer(parent, instance, objId, context, depth): return { 'parent': context.getReferenceId(parent), 'id': objId, 'type': instance.GetClassName(), 'properties': { 'focalPoint': instance.GetFocalPoint(), 'position': instance.GetPosition(), 'viewUp': instance.GetViewUp(), 'clippingRange': instance.GetClippingRange(), } } # ----------------------------------------------------------------------------- def renderWindowSerializer(parent, instance, objId, context, depth): dependencies = [] rendererIds = [] rendererCollection = instance.GetRenderers() for rIdx in range(rendererCollection.GetNumberOfItems()): # Grab the next vtkRenderer renderer = rendererCollection.GetItemAsObject(rIdx) rendererId = context.getReferenceId(renderer) rendererInstance = serializeInstance( instance, renderer, rendererId, context, depth + 1) if rendererInstance: dependencies.append(rendererInstance) rendererIds.append(rendererId) calls = context.buildDependencyCallList( objId, rendererIds, 'addRenderer', 'removeRenderer') return { 'parent': context.getReferenceId(parent), 'id': objId, 'type': instance.GetClassName(), 'properties': { 'numberOfLayers': instance.GetNumberOfLayers() }, 'dependencies': dependencies, 'calls': calls, 'mtime': instance.GetMTime(), }