1 2
import base64
2 2
import hashlib
3 2
import io
4 2
import struct
5 2
import sys
6 2
import time
7 2
import zipfile
8

9 2
from vtk.vtkCommonCore import vtkTypeUInt32Array, vtkTypeInt32Array
10 2
from vtk.vtkFiltersGeometry import vtkCompositeDataGeometryFilter
11 2
from vtk.vtkFiltersGeometry import vtkGeometryFilter
12 2
from vtk.vtkRenderingCore import vtkColorTransferFunction
13

14
# -----------------------------------------------------------------------------
15
# Python compatibility handling 2.6, 2.7, 3+
16
# -----------------------------------------------------------------------------
17

18 2
py3 = sys.version_info >= (3, 0)
19

20 2
if py3:
21 2
    def iteritems(d, **kwargs):
22 0
        return iter(d.items(**kwargs))
23
else:
24 0
    def iteritems(d, **kwargs):
25 0
        return d.iteritems(**kwargs)
26

27 2
if sys.version_info >= (2, 7):
28 2
    buffer = memoryview
29 2
    base64Encode = lambda x: base64.b64encode(x).decode('utf-8')
30
else:
31 0
    buffer = buffer
32 0
    base64Encode = lambda x: x.encode('base64')
33

34
# -----------------------------------------------------------------------------
35
# Array helpers
36
# -----------------------------------------------------------------------------
37

38 2
arrayTypesMapping = [
39
    ' ',  # VTK_VOID                0
40
    ' ',  # VTK_BIT                 1
41
    'b',  # VTK_CHAR                2
42
    'B',  # VTK_UNSIGNED_CHAR       3
43
    'h',  # VTK_SHORT               4
44
    'H',  # VTK_UNSIGNED_SHORT      5
45
    'i',  # VTK_INT                 6
46
    'I',  # VTK_UNSIGNED_INT        7
47
    'l',  # VTK_LONG                8
48
    'L',  # VTK_UNSIGNED_LONG       9
49
    'f',  # VTK_FLOAT               10
50
    'd',  # VTK_DOUBLE              11
51
    'L',  # VTK_ID_TYPE             12
52
    ' ',  # VTK_STRING              13
53
    ' ',  # VTK_OPAQUE              14
54
    ' ',  # UNDEFINED
55
    'l',  # VTK_LONG_LONG           16
56
    'L',  # VTK_UNSIGNED_LONG_LONG  17
57
]
58

59 2
javascriptMapping = {
60
    'b': 'Int8Array',
61
    'B': 'Uint8Array',
62
    'h': 'Int16Array',
63
    'H': 'UInt16Array',
64
    'i': 'Int32Array',
65
    'I': 'Uint32Array',
66
    'l': 'Int32Array',
67
    'L': 'Uint32Array',
68
    'f': 'Float32Array',
69
    'd': 'Float64Array'
70
}
71

72

73 2
def hashDataArray(dataArray):
74 2
    return hashlib.md5(buffer(dataArray)).hexdigest()
75

76

77 2
def getJSArrayType(dataArray):
78 2
    return javascriptMapping[arrayTypesMapping[dataArray.GetDataType()]]
79

80

81 2
def zipCompression(name, data):
82 2
    with io.BytesIO() as in_memory:
83 2
        with zipfile.ZipFile(in_memory, mode="w") as zf:
84 2
            zf.writestr('data/%s' % name,
85
                        data, zipfile.ZIP_DEFLATED)
86 2
        in_memory.seek(0)
87 2
        return in_memory.read()
88

89

90 2
def dataTableToList(dataTable):
91 0
    dataType = arrayTypesMapping[dataTable.GetDataType()]
92 0
    elementSize = struct.calcsize(dataType)
93 0
    nbValues = dataTable.GetNumberOfValues()
94 0
    nbComponents = dataTable.GetNumberOfComponents()
95 0
    nbytes = elementSize * nbValues
96 0
    if dataType != ' ':
97 0
        with io.BytesIO(buffer(dataTable)) as stream:
98 0
            data = list(struct.unpack(dataType*nbValues ,stream.read(nbytes)))
99 0
        return [data[idx*nbComponents:(idx+1)*nbComponents]
100
                    for idx in range(nbValues//nbComponents)]
101

102
# -----------------------------------------------------------------------------
103

104 2
def linspace(start, stop, num):
105 0
    delta = (stop - start)/(num-1)
106 0
    return [start + i*delta for i in range(num)]
107

108
# -----------------------------------------------------------------------------
109
# Convenience class for caching data arrays, storing computed sha sums, keeping
110
# track of valid actors, etc...
111
# -----------------------------------------------------------------------------
112

113

114 2
class SynchronizationContext():
115

116 2
    def __init__(self, id_root=None, debug=False):
117 2
        self.dataArrayCache = {}
118 2
        self.lastDependenciesMapping = {}
119 2
        self.ingoreLastDependencies = False
120 2
        self.idRoot = id_root
121 2
        self.debugSerializers = debug
122 2
        self.debugAll = debug
123

124 2
    def getReferenceId(self, instance):
125 2
        if not self.idRoot or (hasattr(instance, 'IsA') and instance.IsA('vtkCamera')):
126 2
            return getReferenceId(instance)
127
        else:
128 2
            return self.idRoot + getReferenceId(instance)
129

130 2
    def setIgnoreLastDependencies(self, force):
131 0
        self.ingoreLastDependencies = force
132

133 2
    def cacheDataArray(self, pMd5, data):
134 2
        self.dataArrayCache[pMd5] = data
135

136 2
    def getCachedDataArray(self, pMd5, binary=False, compression=False):
137 2
        cacheObj = self.dataArrayCache[pMd5]
138 2
        array = cacheObj['array']
139 2
        cacheTime = cacheObj['mTime']
140

141 2
        if cacheTime != array.GetMTime():
142 0
            if context.debugAll:
143 0
                print(' ***** ERROR: you asked for an old cache key! ***** ')
144

145 2
        if array.GetDataType() in (12, 16, 17):
146 2
            arraySize = array.GetNumberOfTuples() * array.GetNumberOfComponents()
147 2
            if array.GetDataType() in (12, 17):
148
                # IdType and unsigned long long need to be converted to Uint32
149 2
                newArray = vtkTypeUInt32Array()
150
            else:
151
                #  long long need to be converted to Int32
152 0
                newArray = vtkTypeInt32Array()
153 2
            newArray.SetNumberOfTuples(arraySize)
154 2
            for i in range(arraySize):
155 2
                newArray.SetValue(i, -1 if array.GetValue(i)
156
                                  < 0 else array.GetValue(i))
157 2
            pBuffer = buffer(newArray)
158
        else:
159 2
            pBuffer = buffer(array)
160

161 2
        if binary:
162
            # Convert the vtkUnsignedCharArray into a bytes object, required by
163
            # Autobahn websockets
164 2
            return pBuffer.tobytes() if not compression else zipCompression(pMd5, pBuffer.tobytes())
165

166 2
        return base64Encode(pBuffer if not compression else zipCompression(pMd5, pBuffer.tobytes()))
167

168 2
    def checkForArraysToRelease(self, timeWindow=20):
169 2
        cutOffTime = time.time() - timeWindow
170 2
        shasToDelete = []
171 2
        for sha in self.dataArrayCache:
172 2
            record = self.dataArrayCache[sha]
173 2
            array = record['array']
174 2
            count = array.GetReferenceCount()
175

176 2
            if count == 1 and record['ts'] < cutOffTime:
177 2
                shasToDelete.append(sha)
178

179 2
        for sha in shasToDelete:
180 2
            del self.dataArrayCache[sha]
181

182 2
    def getLastDependencyList(self, idstr):
183 2
        lastDeps = []
184 2
        if idstr in self.lastDependenciesMapping and not self.ingoreLastDependencies:
185 2
            lastDeps = self.lastDependenciesMapping[idstr]
186 2
        return lastDeps
187

188 2
    def setNewDependencyList(self, idstr, depList):
189 2
        self.lastDependenciesMapping[idstr] = depList
190

191 2
    def buildDependencyCallList(self, idstr, newList, addMethod, removeMethod):
192 2
        oldList = self.getLastDependencyList(idstr)
193

194 2
        calls = []
195 2
        calls += [[addMethod, [wrapId(x)]]
196
                  for x in newList if x not in oldList]
197 2
        calls += [[removeMethod, [wrapId(x)]]
198
                  for x in oldList if x not in newList]
199

200 2
        self.setNewDependencyList(idstr, newList)
201 2
        return calls
202

203
# -----------------------------------------------------------------------------
204
# Global variables
205
# -----------------------------------------------------------------------------
206

207 2
SERIALIZERS = {}
208 2
context = None
209

210
# -----------------------------------------------------------------------------
211
# Global API
212
# -----------------------------------------------------------------------------
213

214

215 2
def registerInstanceSerializer(name, method):
216
    global SERIALIZERS
217 2
    SERIALIZERS[name] = method
218

219
# -----------------------------------------------------------------------------
220

221

222 2
def serializeInstance(parent, instance, instanceId, context, depth):
223 2
    instanceType = instance.GetClassName()
224 2
    serializer = SERIALIZERS[
225
        instanceType] if instanceType in SERIALIZERS else None
226

227 2
    if serializer:
228 2
        return serializer(parent, instance, instanceId, context, depth)
229

230 2
    if context.debugSerializers:
231 0
        print('%s!!!No serializer for %s with id %s' %
232
              (pad(depth), instanceType, instanceId))
233

234
# -----------------------------------------------------------------------------
235

236

237 2
def initializeSerializers():
238
    # Actors/viewProps
239 2
    registerInstanceSerializer('vtkImageSlice', genericProp3DSerializer)
240 2
    registerInstanceSerializer('vtkVolume', genericProp3DSerializer)
241 2
    registerInstanceSerializer('vtkOpenGLActor', genericActorSerializer)
242 2
    registerInstanceSerializer('vtkFollower', genericActorSerializer)
243 2
    registerInstanceSerializer('vtkPVLODActor', genericActorSerializer)
244
    
245

246
    # Mappers
247 2
    registerInstanceSerializer(
248
        'vtkOpenGLPolyDataMapper', genericPolyDataMapperSerializer)
249 2
    registerInstanceSerializer(
250
        'vtkCompositePolyDataMapper2', genericPolyDataMapperSerializer)
251 2
    registerInstanceSerializer('vtkDataSetMapper', genericPolyDataMapperSerializer)
252 2
    registerInstanceSerializer(
253
        'vtkFixedPointVolumeRayCastMapper', genericVolumeMapperSerializer)
254 2
    registerInstanceSerializer(
255
        'vtkSmartVolumeMapper', genericVolumeMapperSerializer)
256 2
    registerInstanceSerializer(
257
        'vtkOpenGLImageSliceMapper', imageSliceMapperSerializer)
258

259
    # LookupTables/TransferFunctions
260 2
    registerInstanceSerializer('vtkLookupTable', lookupTableSerializer)
261 2
    registerInstanceSerializer(
262
        'vtkPVDiscretizableColorTransferFunction', colorTransferFunctionSerializer)
263 2
    registerInstanceSerializer(
264
        'vtkColorTransferFunction', colorTransferFunctionSerializer)
265

266
    # opacityFunctions
267 2
    registerInstanceSerializer(
268
        'vtkPiecewiseFunction', piecewiseFunctionSerializer)
269

270
    # Textures
271 2
    registerInstanceSerializer('vtkOpenGLTexture', textureSerializer)
272

273
    # Property
274 2
    registerInstanceSerializer('vtkOpenGLProperty', propertySerializer)
275 2
    registerInstanceSerializer('vtkVolumeProperty', volumePropertySerializer)
276 2
    registerInstanceSerializer('vtkImageProperty', imagePropertySerializer)
277

278
    # Datasets
279 2
    registerInstanceSerializer('vtkPolyData', polydataSerializer)
280 2
    registerInstanceSerializer('vtkImageData', imageDataSerializer)
281 2
    registerInstanceSerializer(
282
        'vtkStructuredGrid', mergeToPolydataSerializer)
283 2
    registerInstanceSerializer(
284
        'vtkUnstructuredGrid', mergeToPolydataSerializer)
285 2
    registerInstanceSerializer(
286
        'vtkMultiBlockDataSet', mergeToPolydataSerializer)
287

288
    # RenderWindows
289 2
    registerInstanceSerializer('vtkCocoaRenderWindow', renderWindowSerializer)
290 2
    registerInstanceSerializer(
291
        'vtkXOpenGLRenderWindow', renderWindowSerializer)
292 2
    registerInstanceSerializer(
293
        'vtkWin32OpenGLRenderWindow', renderWindowSerializer)
294 2
    registerInstanceSerializer('vtkEGLRenderWindow', renderWindowSerializer)
295 2
    registerInstanceSerializer('vtkOpenVRRenderWindow', renderWindowSerializer)
296 2
    registerInstanceSerializer(
297
        'vtkGenericOpenGLRenderWindow', renderWindowSerializer)
298 2
    registerInstanceSerializer(
299
        'vtkOSOpenGLRenderWindow', renderWindowSerializer)
300 2
    registerInstanceSerializer('vtkOpenGLRenderWindow', renderWindowSerializer)
301 2
    registerInstanceSerializer('vtkIOSRenderWindow', renderWindowSerializer)
302 2
    registerInstanceSerializer(
303
        'vtkExternalOpenGLRenderWindow', renderWindowSerializer)
304

305
    # Renderers
306 2
    registerInstanceSerializer('vtkOpenGLRenderer', rendererSerializer)
307

308
    # Cameras
309 2
    registerInstanceSerializer('vtkOpenGLCamera', cameraSerializer)
310

311

312
# -----------------------------------------------------------------------------
313
# Helper functions
314
# -----------------------------------------------------------------------------
315

316

317 2
def pad(depth):
318 0
    padding = ''
319 0
    for _ in range(depth):
320 0
        padding += '  '
321 0
    return padding
322

323
# -----------------------------------------------------------------------------
324

325

326 2
def wrapId(idStr):
327 2
    return 'instance:${%s}' % idStr
328

329
# -----------------------------------------------------------------------------
330

331

332 2
def getReferenceId(ref):
333 2
    if ref:
334 2
        try:
335 2
            return ref.__this__[1:17]
336 0
        except Exception:
337 0
            idStr = str(ref)[-12:-1]
338 0
            print('====> fallback ID %s for %s' % (idStr, ref))
339 0
            return idStr
340 2
    return '0x0'
341

342
# -----------------------------------------------------------------------------
343

344 2
dataArrayShaMapping = {}
345

346

347 2
def digest(array):
348 2
    objId = getReferenceId(array)
349

350 2
    record = None
351 2
    if objId in dataArrayShaMapping:
352 2
        record = dataArrayShaMapping[objId]
353

354 2
    if record and record['mtime'] == array.GetMTime():
355 2
        return record['sha']
356

357 2
    record = {
358
        'sha': hashDataArray(array),
359
        'mtime': array.GetMTime()
360
    }
361

362 2
    dataArrayShaMapping[objId] = record
363 2
    return record['sha']
364

365
# -----------------------------------------------------------------------------
366

367

368 2
def getRangeInfo(array, component):
369 2
    r = array.GetRange(component)
370 2
    compRange = {}
371 2
    compRange['min'] = r[0]
372 2
    compRange['max'] = r[1]
373 2
    compRange['component'] = array.GetComponentName(component)
374 2
    return compRange
375

376
# -----------------------------------------------------------------------------
377

378

379 2
def getArrayDescription(array, context):
380 2
    if not array:
381 2
        return None
382

383 2
    pMd5 = digest(array)
384 2
    context.cacheDataArray(pMd5, {
385
        'array': array,
386
        'mTime': array.GetMTime(),
387
        'ts': time.time()
388
    })
389

390 2
    root = {}
391 2
    root['hash'] = pMd5
392 2
    root['vtkClass'] = 'vtkDataArray'
393 2
    root['name'] = array.GetName()
394 2
    root['dataType'] = getJSArrayType(array)
395 2
    root['numberOfComponents'] = array.GetNumberOfComponents()
396 2
    root['size'] = array.GetNumberOfComponents() * array.GetNumberOfTuples()
397 2
    root['ranges'] = []
398 2
    if root['numberOfComponents'] > 1:
399 2
        for i in range(root['numberOfComponents']):
400 2
            root['ranges'].append(getRangeInfo(array, i))
401 2
        root['ranges'].append(getRangeInfo(array, -1))
402
    else:
403 2
        root['ranges'].append(getRangeInfo(array, 0))
404

405 2
    return root
406

407
# -----------------------------------------------------------------------------
408

409

410 2
def extractRequiredFields(extractedFields, parent, dataset, context, requestedFields=['Normals', 'TCoords']):
411
    # FIXME should evolve and support funky mapper which leverage many arrays
412 2
    if parent.IsA('vtkMapper') or parent.IsA('vtkVolumeMapper'):
413 2
        mapper = parent
414 2
        scalarVisibility = 1 if mapper.IsA('vtkVolumeMapper') else mapper.GetScalarVisibility()
415 2
        arrayAccessMode = mapper.GetArrayAccessMode()
416 2
        colorArrayName = mapper.GetArrayName() if arrayAccessMode == 1 else mapper.GetArrayId()
417
        # colorMode = mapper.GetColorMode()
418 2
        scalarMode = mapper.GetScalarMode()
419 2
        if scalarVisibility and scalarMode in (1, 3):
420 2
            arrayMeta = getArrayDescription(
421
                dataset.GetPointData().GetArray(colorArrayName), context)
422 2
            if arrayMeta:
423 0
                arrayMeta['location'] = 'pointData'
424 0
                extractedFields.append(arrayMeta)
425 2
            elif dataset.GetPointData().GetScalars():
426 2
                arrayMeta = getArrayDescription(
427
                    dataset.GetPointData().GetScalars(), context)
428 2
                arrayMeta['location'] = 'pointData'
429 2
                arrayMeta['registration'] = 'setScalars'
430 2
                extractedFields.append(arrayMeta)
431 2
        if scalarVisibility and scalarMode in (2, 4):
432 2
            arrayMeta = getArrayDescription(
433
                dataset.GetCellData().GetArray(colorArrayName), context)
434 2
            if arrayMeta:
435 0
                arrayMeta['location'] = 'cellData'
436 0
                extractedFields.append(arrayMeta)
437 2
            elif dataset.GetCellData().GetScalars():
438 2
                arrayMeta = getArrayDescription(
439
                    dataset.GetCellData().GetScalars(), context)
440 2
                arrayMeta['location'] = 'cellData'
441 2
                arrayMeta['registration'] = 'setScalars'
442 2
                extractedFields.append(arrayMeta)
443

444 2
    elif ((parent.IsA('vtkTexture') or parent.IsA('vtkImageSliceMapper'))
445
          and dataset.GetPointData().GetScalars()):
446 2
        arrayMeta = getArrayDescription(
447
            dataset.GetPointData().GetScalars(), context)
448 2
        arrayMeta['location'] = 'pointData'
449 2
        arrayMeta['registration'] = 'setScalars'
450 2
        extractedFields.append(arrayMeta)
451

452
    # Normal handling
453 2
    if 'Normals' in requestedFields:
454 2
        normals = dataset.GetPointData().GetNormals()
455 2
        if normals:
456 2
            arrayMeta = getArrayDescription(normals, context)
457 2
            if arrayMeta:
458 2
                arrayMeta['location'] = 'pointData'
459 2
                arrayMeta['registration'] = 'setNormals'
460 2
                extractedFields.append(arrayMeta)
461

462
    # TCoord handling
463 2
    if 'TCoords' in requestedFields:
464 2
        tcoords = dataset.GetPointData().GetTCoords()
465 2
        if tcoords:
466 2
            arrayMeta = getArrayDescription(tcoords, context)
467 2
            if arrayMeta:
468 2
                arrayMeta['location'] = 'pointData'
469 2
                arrayMeta['registration'] = 'setTCoords'
470 2
                extractedFields.append(arrayMeta)
471

472
# -----------------------------------------------------------------------------
473
# Concrete instance serializers
474
# -----------------------------------------------------------------------------
475

476 2
def genericPropSerializer(parent, prop, popId, context, depth):
477
    # This kind of actor has two "children" of interest, a property and a
478
    # mapper (optionnaly a texture)
479 2
    mapperInstance = None
480 2
    propertyInstance = None
481 2
    calls = []
482 2
    dependencies = []
483

484 2
    mapper = None
485 2
    if not hasattr(prop, 'GetMapper'):
486 0
        if context.debugAll:
487 0
            print('This volume does not have a GetMapper method')
488
    else:
489 2
        mapper = prop.GetMapper()
490
    
491 2
    if mapper:
492 2
        mapperId = context.getReferenceId(mapper)
493 2
        mapperInstance = serializeInstance(
494
            prop, mapper, mapperId, context, depth + 1)
495 2
        if mapperInstance:
496 2
            dependencies.append(mapperInstance)
497 2
            calls.append(['setMapper', [wrapId(mapperId)]])
498

499 2
    properties = None
500 2
    if hasattr(prop, 'GetProperty'):
501 2
        properties = prop.GetProperty()
502
    else:
503 0
        if context.debugAll:
504 0
            print('This image does not have a GetProperty method')
505

506 2
    if properties:
507 2
        propId = context.getReferenceId(properties)
508 2
        propertyInstance = serializeInstance(
509
            prop, properties, propId, context, depth + 1)
510 2
        if propertyInstance:
511 2
            dependencies.append(propertyInstance)
512 2
            calls.append(['setProperty', [wrapId(propId)]])
513
    
514
    # Handle texture if any
515 2
    texture = None
516 2
    if hasattr(prop, 'GetTexture'):
517 2
        texture = prop.GetTexture()
518

519 2
    if texture:
520 2
        textureId = context.getReferenceId(texture)
521 2
        textureInstance = serializeInstance(
522
            prop, texture, textureId, context, depth + 1)
523 2
        if textureInstance:
524 2
            dependencies.append(textureInstance)
525 2
            calls.append(['addTexture', [wrapId(textureId)]])
526

527 2
    return {
528
        'parent': context.getReferenceId(parent),
529
        'id': popId,
530
        'type': prop.GetClassName(),
531
        'properties': {
532
            # vtkProp
533
            'visibility': prop.GetVisibility(),
534
            'pickable': prop.GetPickable(),
535
            'dragable': prop.GetDragable(),
536
            'useBounds': prop.GetUseBounds(),  
537
        },
538
        'calls': calls,
539
        'dependencies': dependencies
540
    }
541

542
# -----------------------------------------------------------------------------
543

544

545 2
def genericProp3DSerializer(parent, prop3D, prop3DId, context, depth):
546
    # This kind of actor has some position properties to add
547 2
    instance = genericPropSerializer(parent, prop3D, prop3DId, context, depth)
548
    
549 2
    if not instance: return
550
    
551 2
    instance['properties'].update({
552
        # vtkProp3D
553
        'origin': prop3D.GetOrigin(),
554
        'position': prop3D.GetPosition(),
555
        'scale': prop3D.GetScale(),
556
        'orientation': prop3D.GetOrientation(),
557
    })
558
    
559 2
    if prop3D.GetUserMatrix():
560 0
        instance['properties'].update({
561
            'userMatrix': [prop3D.GetUserMatrix().GetElement(i%4,i//4) for i in range(16)],
562
        })
563 2
    return instance
564

565
# -----------------------------------------------------------------------------
566

567

568 2
def genericActorSerializer(parent, actor, actorId, context, depth):
569
    # may have texture and 
570 2
    instance = genericProp3DSerializer(parent, actor, actorId, context, depth)
571

572 2
    if not instance: return
573

574 2
    instance['properties'].update({
575
        # vtkActor
576
        'forceOpaque': actor.GetForceOpaque(),
577
        'forceTranslucent': actor.GetForceTranslucent()
578
    })
579

580 2
    if actor.IsA('vtkFollower'):
581 2
        camera = actor.GetCamera()
582 2
        cameraId = context.getReferenceId(camera)
583 2
        cameraInstance = serializeInstance(
584
            actor, camera, cameraId, context, depth + 1)
585 2
        if cameraInstance:
586 2
            instance['dependencies'].append(cameraInstance)
587 2
            instance['calls'].append(['setCamera', [wrapId(cameraId)]])
588

589 2
    return instance
590

591
# -----------------------------------------------------------------------------
592

593

594 2
def genericMapperSerializer(parent, mapper, mapperId, context, depth):
595
    # This kind of mapper requires us to get 2 items: input data and lookup
596
    # table
597 2
    dataObject = None
598 2
    dataObjectInstance = None
599 2
    lookupTableInstance = None
600 2
    calls = []
601 2
    dependencies = []
602

603 2
    if hasattr(mapper, 'GetInputDataObject'):
604 2
        dataObject = mapper.GetInputDataObject(0, 0)
605
    else:
606 0
        if context.debugAll:
607 0
            print('This mapper does not have GetInputDataObject method')
608

609 2
    if dataObject:
610 2
        dataObjectId = '%s-dataset' % mapperId
611 2
        if parent.IsA('vtkActor') and not mapper.IsA('vtkTexture'):
612
            # vtk-js actors can render only surfacic datasets
613
            # => we ensure to convert the dataset in polydata
614 2
            dataObjectInstance = mergeToPolydataSerializer(
615
                mapper, dataObject, dataObjectId, context, depth + 1)
616
        else:
617 2
            dataObjectInstance = serializeInstance(
618
                mapper, dataObject, dataObjectId, context, depth + 1)
619 2
        if dataObjectInstance:
620 2
            dependencies.append(dataObjectInstance)
621 2
            calls.append(['setInputData', [wrapId(dataObjectId)]])
622

623 2
    lookupTable = None
624

625 2
    if hasattr(mapper, 'GetLookupTable'):
626 2
        lookupTable = mapper.GetLookupTable()
627 2
    elif parent.IsA('vtkActor'):
628 0
        if context.debugAll:
629 0
            print('This mapper actor not have GetLookupTable method')
630

631 2
    if lookupTable:
632 2
        lookupTableId = context.getReferenceId(lookupTable)
633 2
        lookupTableInstance = serializeInstance(
634
            mapper, lookupTable, lookupTableId, context, depth + 1)
635 2
        if lookupTableInstance:
636 2
            dependencies.append(lookupTableInstance)
637 2
            calls.append(['setLookupTable', [wrapId(lookupTableId)]])
638

639 2
    if dataObjectInstance:
640 2
        return {
641
            'parent': context.getReferenceId(parent),
642
            'id': mapperId,
643
            'properties': {},
644
            'calls': calls,
645
            'dependencies': dependencies
646
        }
647

648
# -----------------------------------------------------------------------------
649

650

651 2
def genericPolyDataMapperSerializer(parent, mapper, mapperId, context, depth):
652 2
    instance = genericMapperSerializer(parent, mapper, mapperId, context, depth)
653

654 2
    if not instance: return
655

656 2
    colorArrayName = mapper.GetArrayName(
657
        ) if mapper.GetArrayAccessMode() == 1 else mapper.GetArrayId()
658

659 2
    instance['type'] = mapper.GetClassName()
660 2
    instance['properties'].update({
661
        'resolveCoincidentTopology': mapper.GetResolveCoincidentTopology(),
662
        'renderTime': mapper.GetRenderTime(),
663
        'arrayAccessMode': mapper.GetArrayAccessMode(),
664
        'scalarRange': mapper.GetScalarRange(),
665
        'useLookupTableScalarRange': 1 if mapper.GetUseLookupTableScalarRange() else 0,
666
        'scalarVisibility': mapper.GetScalarVisibility(),
667
        'colorByArrayName': colorArrayName,
668
        'colorMode': mapper.GetColorMode(),
669
        'scalarMode': mapper.GetScalarMode(),
670
        'interpolateScalarsBeforeMapping': 1 if mapper.GetInterpolateScalarsBeforeMapping() else 0
671
    })
672 2
    return instance
673

674
# -----------------------------------------------------------------------------
675

676

677 2
def genericVolumeMapperSerializer(parent, mapper, mapperId, context, depth):
678 2
    instance = genericMapperSerializer(parent, mapper, mapperId, context, depth)
679

680 2
    if not instance: return
681

682 2
    imageSampleDistance = (
683
        mapper.GetImageSampleDistance() 
684
        if hasattr(mapper, 'GetImageSampleDistance') 
685
        else 1
686
    )
687 2
    instance['type'] = mapper.GetClassName()
688 2
    instance['properties'].update({
689
        'sampleDistance': mapper.GetSampleDistance(),
690
        'imageSampleDistance': imageSampleDistance,
691
        # 'maximumSamplesPerRay',
692
        'autoAdjustSampleDistances': mapper.GetAutoAdjustSampleDistances(),
693
        'blendMode': mapper.GetBlendMode(),
694
    })
695 2
    return instance
696

697
# -----------------------------------------------------------------------------
698

699

700 2
def textureSerializer(parent, texture, textureId, context, depth):
701 2
    instance = genericMapperSerializer(parent, texture, textureId, context, depth)
702

703 2
    if not instance: return
704

705 2
    instance['type'] = texture.GetClassName()
706 2
    instance['properties'].update({
707
        'interpolate': texture.GetInterpolate(),
708
        'repeat': texture.GetRepeat(),
709
        'edgeClamp': texture.GetEdgeClamp(),
710
    })
711 2
    return instance
712

713
# -----------------------------------------------------------------------------
714

715

716 2
def imageSliceMapperSerializer(parent, mapper, mapperId, context, depth):
717
    # On vtkjs side : vtkImageMapper connected to a vtkImageReslice filter
718

719 0
    instance = genericMapperSerializer(parent, mapper, mapperId, context, depth)    
720

721 0
    if not instance: return
722

723 0
    instance['type'] = mapper.GetClassName()
724
    
725 0
    return instance
726

727
# -----------------------------------------------------------------------------
728

729

730 2
def lookupTableSerializer(parent, lookupTable, lookupTableId, context, depth):
731
    # No children in this case, so no additions to bindings and return empty list
732
    # But we do need to add instance
733 2
    arrays = []
734 2
    lookupTableRange = lookupTable.GetRange()
735

736 2
    lookupTableHueRange = [0.5, 0]
737 2
    if hasattr(lookupTable, 'GetHueRange'):
738 2
        try:
739 2
            lookupTable.GetHueRange(lookupTableHueRange)
740 2
        except Exception:
741 2
            pass
742

743 2
    lutSatRange = lookupTable.GetSaturationRange()
744
    # lutAlphaRange = lookupTable.GetAlphaRange()
745 2
    if lookupTable.GetTable():
746 2
        arrayMeta = getArrayDescription(lookupTable.GetTable(), context)
747 2
        if arrayMeta:
748 2
            arrayMeta['registration'] = 'setTable'
749 2
            arrays.append(arrayMeta)
750

751 2
    return {
752
        'parent': context.getReferenceId(parent),
753
        'id': lookupTableId,
754
        'type': lookupTable.GetClassName(),
755
        'properties': {
756
            'numberOfColors': lookupTable.GetNumberOfColors(),
757
            'valueRange': lookupTableRange,
758
            'range': lookupTableRange,
759
            'hueRange': lookupTableHueRange,
760
            # 'alphaRange': lutAlphaRange,  # Causes weird rendering artifacts on client
761
            'saturationRange': lutSatRange,
762
            'nanColor': lookupTable.GetNanColor(),
763
            'belowRangeColor': lookupTable.GetBelowRangeColor(),
764
            'aboveRangeColor': lookupTable.GetAboveRangeColor(),
765
            'useAboveRangeColor': True if lookupTable.GetUseAboveRangeColor() else False,
766
            'useBelowRangeColor': True if lookupTable.GetUseBelowRangeColor() else False,
767
            'alpha': lookupTable.GetAlpha(),
768
            'vectorSize': lookupTable.GetVectorSize(),
769
            'vectorComponent': lookupTable.GetVectorComponent(),
770
            'vectorMode': lookupTable.GetVectorMode(),
771
            'indexedLookup': lookupTable.GetIndexedLookup(),
772
        }, 
773
        'arrays': arrays,
774
    }
775

776
# -----------------------------------------------------------------------------
777

778

779 2
def lookupTableToColorTransferFunction(lookupTable):
780 0
    dataTable = lookupTable.GetTable()
781 0
    table = dataTableToList(dataTable)
782 0
    if table:
783 0
        ctf = vtkColorTransferFunction()
784 0
        tableRange = lookupTable.GetTableRange()
785 0
        points = linspace(*tableRange, num=len(table))
786 0
        for x, rgba in zip(points, table):
787 0
            ctf.AddRGBPoint(x, *[x/255 for x in rgba[:3]])
788 0
        return ctf
789

790
# -----------------------------------------------------------------------------
791

792

793 2
def lookupTableSerializer2(parent, lookupTable, lookupTableId, context, depth):
794 0
    ctf = lookupTableToColorTransferFunction(lookupTable)
795 0
    if ctf:
796 0
        return colorTransferFunctionSerializer(parent, ctf, lookupTableId, context, depth)
797

798
# -----------------------------------------------------------------------------
799

800

801 2
def propertySerializer(parent, propObj, propObjId, context, depth):
802 2
    representation = propObj.GetRepresentation() if hasattr(
803
        propObj, 'GetRepresentation') else 2
804 2
    colorToUse = propObj.GetDiffuseColor() if hasattr(
805
        propObj, 'GetDiffuseColor') else [1, 1, 1]
806 2
    if representation == 1 and hasattr(propObj, 'GetColor'):
807 0
        colorToUse = propObj.GetColor()
808

809 2
    return {
810
        'parent': context.getReferenceId(parent),
811
        'id': propObjId,
812
        'type': propObj.GetClassName(),
813
        'properties': {
814
            'representation': representation,
815
            'diffuseColor': colorToUse,
816
            'color': propObj.GetColor(),
817
            'ambientColor': propObj.GetAmbientColor(),
818
            'specularColor': propObj.GetSpecularColor(),
819
            'edgeColor': propObj.GetEdgeColor(),
820
            'ambient': propObj.GetAmbient(),
821
            'diffuse': propObj.GetDiffuse(),
822
            'specular': propObj.GetSpecular(),
823
            'specularPower': propObj.GetSpecularPower(),
824
            'opacity': propObj.GetOpacity(),
825
            'interpolation': propObj.GetInterpolation(),
826
            'edgeVisibility': 1 if propObj.GetEdgeVisibility() else 0,
827
            'backfaceCulling': 1 if propObj.GetBackfaceCulling() else 0,
828
            'frontfaceCulling': 1 if propObj.GetFrontfaceCulling() else 0,
829
            'pointSize': propObj.GetPointSize(),
830
            'lineWidth': propObj.GetLineWidth(),
831
            'lighting': 1 if propObj.GetLighting() else 0,
832
        }
833
    }
834

835
# -----------------------------------------------------------------------------
836

837 2
def volumePropertySerializer(parent, propObj, propObjId, context, depth):
838 2
    dependencies = []
839 2
    calls = []
840
    # TODO: for the moment only component 0 handle
841

842
    #OpactiyFunction
843 2
    ofun = propObj.GetScalarOpacity()
844 2
    if ofun:
845 2
        ofunId = context.getReferenceId(ofun)
846 2
        ofunInstance = serializeInstance(
847
            propObj, ofun, ofunId, context, depth + 1)
848 2
        if ofunInstance:
849 2
            dependencies.append(ofunInstance)
850 2
            calls.append(['setScalarOpacity', [0, wrapId(ofunId)]])
851

852
    # ColorTranferFunction
853 2
    ctfun = propObj.GetRGBTransferFunction()
854 2
    if ctfun:
855 2
        ctfunId = context.getReferenceId(ctfun)
856 2
        ctfunInstance = serializeInstance(
857
            propObj, ctfun, ctfunId, context, depth + 1)
858 2
        if ctfunInstance:
859 2
            dependencies.append(ctfunInstance)
860 2
            calls.append(['setRGBTransferFunction', [0, wrapId(ctfunId)]])
861

862 2
    calls += [
863
        ['setScalarOpacityUnitDistance', [0, propObj.GetScalarOpacityUnitDistance(0)]],
864
        ['setComponentWeight', [0, propObj.GetComponentWeight(0)]],
865
        ['setUseGradientOpacity', [0, int(not propObj.GetDisableGradientOpacity())]],
866
    ]
867

868 2
    return {
869
        'parent': context.getReferenceId(parent),
870
        'id': propObjId,
871
        'type': propObj.GetClassName(),
872
        'properties': {
873
            'independentComponents': propObj.GetIndependentComponents(),
874
            'interpolationType': propObj.GetInterpolationType(),
875
            'ambient': propObj.GetAmbient(),
876
            'diffuse': propObj.GetDiffuse(),
877
            'shade': propObj.GetShade(),
878
            'specular': propObj.GetSpecular(0),
879
            'specularPower': propObj.GetSpecularPower(),
880
        },
881
        'dependencies': dependencies,
882
        'calls': calls,
883
    }
884

885
# -----------------------------------------------------------------------------
886

887

888 2
def imagePropertySerializer(parent, propObj, propObjId, context, depth):
889 0
    calls = []
890 0
    dependencies = []
891

892 0
    lookupTable = propObj.GetLookupTable()
893 0
    if lookupTable: 
894 0
        ctfun = lookupTableToColorTransferFunction(lookupTable)
895 0
        ctfunId = context.getReferenceId(ctfun)
896 0
        ctfunInstance = serializeInstance(
897
            propObj, ctfun, ctfunId, context, depth + 1)
898 0
        if ctfunInstance:
899 0
            dependencies.append(ctfunInstance)
900 0
            calls.append(['setRGBTransferFunction', [wrapId(ctfunId)]])
901

902 0
    return {
903
        'parent': context.getReferenceId(parent),
904
        'id': propObjId,
905
        'type': propObj.GetClassName(),
906
        'properties': {
907
            'interpolationType': propObj.GetInterpolationType(),
908
            'colorWindow': propObj.GetColorWindow(),
909
            'colorLevel': propObj.GetColorLevel(),
910
            'ambient': propObj.GetAmbient(),
911
            'diffuse': propObj.GetDiffuse(),
912
            'opacity': propObj.GetOpacity(),
913
        },
914
        'dependencies': dependencies,
915
        'calls': calls,
916
    }
917

918
# -----------------------------------------------------------------------------
919

920

921 2
def imageDataSerializer(parent, dataset, datasetId, context, depth):
922 2
    datasetType = dataset.GetClassName()
923

924 2
    if hasattr(dataset, 'GetDirectionMatrix'):
925 2
        direction = [dataset.GetDirectionMatrix().GetElement(0, i)
926
                     for i in range(9)]
927
    else:
928 0
        direction = [1, 0, 0,
929
                     0, 1, 0,
930
                     0, 0, 1]
931

932
    # Extract dataset fields
933 2
    arrays = []
934 2
    extractRequiredFields(arrays, parent, dataset, context)
935

936 2
    return {
937
        'parent': context.getReferenceId(parent),
938
        'id': datasetId,
939
        'type': datasetType,
940
        'properties': {
941
            'spacing': dataset.GetSpacing(),
942
            'origin': dataset.GetOrigin(),
943
            'dimensions': dataset.GetDimensions(),
944
            'direction': direction,
945
        },
946
        'arrays': arrays
947
    }
948

949
# -----------------------------------------------------------------------------
950

951

952 2
def polydataSerializer(parent, dataset, datasetId, context, depth):
953 2
    datasetType = dataset.GetClassName()
954

955 2
    if dataset and dataset.GetPoints():
956 2
        properties = {}
957

958
        # Points
959 2
        points = getArrayDescription(dataset.GetPoints().GetData(), context)
960 2
        points['vtkClass'] = 'vtkPoints'
961 2
        properties['points'] = points
962

963
        # Verts
964 2
        if dataset.GetVerts() and dataset.GetVerts().GetData().GetNumberOfTuples() > 0:
965 0
            _verts = getArrayDescription(dataset.GetVerts().GetData(), context)
966 0
            properties['verts'] = _verts
967 0
            properties['verts']['vtkClass'] = 'vtkCellArray'
968

969
        # Lines
970 2
        if dataset.GetLines() and dataset.GetLines().GetData().GetNumberOfTuples() > 0:
971 0
            _lines = getArrayDescription(dataset.GetLines().GetData(), context)
972 0
            properties['lines'] = _lines
973 0
            properties['lines']['vtkClass'] = 'vtkCellArray'
974

975
        # Polys
976 2
        if dataset.GetPolys() and dataset.GetPolys().GetData().GetNumberOfTuples() > 0:
977 2
            _polys = getArrayDescription(dataset.GetPolys().GetData(), context)
978 2
            properties['polys'] = _polys
979 2
            properties['polys']['vtkClass'] = 'vtkCellArray'
980

981
        # Strips
982 2
        if dataset.GetStrips() and dataset.GetStrips().GetData().GetNumberOfTuples() > 0:
983 0
            _strips = getArrayDescription(
984
                dataset.GetStrips().GetData(), context)
985 0
            properties['strips'] = _strips
986 0
            properties['strips']['vtkClass'] = 'vtkCellArray'
987

988
        # Fields
989 2
        properties['fields'] = []
990 2
        extractRequiredFields(properties['fields'], parent, dataset, context)
991

992 2
        return {
993
            'parent': context.getReferenceId(parent),
994
            'id': datasetId,
995
            'type': datasetType,
996
            'properties': properties
997
        }
998

999 0
    if context.debugAll:
1000 0
        print('This dataset has no points!')
1001

1002
# -----------------------------------------------------------------------------
1003

1004

1005 2
def mergeToPolydataSerializer(parent, dataObject, dataObjectId, context, depth):
1006 2
    dataset = None
1007

1008 2
    if dataObject.IsA('vtkCompositeDataSet'):
1009 2
        gf = vtkCompositeDataGeometryFilter()
1010 2
        gf.SetInputData(dataObject)
1011 2
        gf.Update()
1012 2
        dataset = gf.GetOutput()
1013 2
    elif (dataObject.IsA('vtkUnstructuredGrid') or
1014
          dataObject.IsA('vtkStructuredGrid') or
1015
          dataObject.IsA('vtkImageData')):
1016 2
        gf = vtkGeometryFilter()
1017 2
        gf.SetInputData(dataObject)
1018 2
        gf.Update()
1019 2
        dataset = gf.GetOutput()
1020
    else:
1021 2
        dataset = parent.GetInput()
1022

1023 2
    return polydataSerializer(parent, dataset, dataObjectId, context, depth)
1024

1025
# -----------------------------------------------------------------------------
1026

1027

1028 2
def colorTransferFunctionSerializer(parent, instance, objId, context, depth):
1029 2
    nodes = []
1030

1031 2
    for i in range(instance.GetSize()):
1032
        # x, r, g, b, midpoint, sharpness
1033 2
        node = [0, 0, 0, 0, 0, 0]
1034 2
        instance.GetNodeValue(i, node)
1035 2
        nodes.append(node)
1036

1037 2
    return {
1038
        'parent': context.getReferenceId(parent),
1039
        'id': objId,
1040
        'type': instance.GetClassName(),
1041
        'properties': {
1042
            'clamping': 1 if instance.GetClamping() else 0,
1043
            'colorSpace': instance.GetColorSpace(),
1044
            'hSVWrap': 1 if instance.GetHSVWrap() else 0,
1045
            # 'nanColor': instance.GetNanColor(),                  # Breaks client
1046
            # 'belowRangeColor': instance.GetBelowRangeColor(),    # Breaks client
1047
            # 'aboveRangeColor': instance.GetAboveRangeColor(),    # Breaks client
1048
            # 'useAboveRangeColor': 1 if instance.GetUseAboveRangeColor() else 0,
1049
            # 'useBelowRangeColor': 1 if instance.GetUseBelowRangeColor() else 0,
1050
            'allowDuplicateScalars': 1 if instance.GetAllowDuplicateScalars() else 0,
1051
            'alpha': instance.GetAlpha(),
1052
            'vectorComponent': instance.GetVectorComponent(),
1053
            'vectorSize': instance.GetVectorSize(),
1054
            'vectorMode': instance.GetVectorMode(),
1055
            'indexedLookup': instance.GetIndexedLookup(),
1056
            'nodes': nodes
1057
        }
1058
    }
1059

1060
# -----------------------------------------------------------------------------
1061

1062

1063 2
def piecewiseFunctionSerializer(parent, instance, objId, context, depth):
1064 2
    nodes = []
1065

1066 2
    for i in range(instance.GetSize()):
1067
        # x, y, midpoint, sharpness
1068 2
        node = [0, 0, 0, 0]
1069 2
        instance.GetNodeValue(i, node)
1070 2
        nodes.append(node)
1071

1072 2
    return {
1073
        'parent': context.getReferenceId(parent),
1074
        'id': objId,
1075
        'type': instance.GetClassName(),
1076
        'properties': {
1077
            'clamping': instance.GetClamping(),
1078
            'allowDuplicateScalars': instance.GetAllowDuplicateScalars(),
1079
            'nodes': nodes,
1080
        }
1081
    }
1082
# -----------------------------------------------------------------------------
1083

1084

1085 2
def rendererSerializer(parent, instance, objId, context, depth):
1086 2
    dependencies = []
1087 2
    viewPropIds = []
1088 2
    calls = []
1089

1090
    # Camera
1091 2
    camera = instance.GetActiveCamera()
1092 2
    cameraId = context.getReferenceId(camera)
1093 2
    cameraInstance = serializeInstance(
1094
        instance, camera, cameraId, context, depth + 1)
1095 2
    if cameraInstance:
1096 2
        dependencies.append(cameraInstance)
1097 2
        calls.append(['setActiveCamera', [wrapId(cameraId)]])
1098

1099
    # View prop as representation containers
1100 2
    viewPropCollection = instance.GetViewProps()
1101 2
    for rpIdx in range(viewPropCollection.GetNumberOfItems()):
1102 2
        viewProp = viewPropCollection.GetItemAsObject(rpIdx)
1103 2
        viewPropId = context.getReferenceId(viewProp)
1104

1105 2
        viewPropInstance = serializeInstance(
1106
            instance, viewProp, viewPropId, context, depth + 1)
1107 2
        if viewPropInstance:
1108 2
            dependencies.append(viewPropInstance)
1109 2
            viewPropIds.append(viewPropId)
1110

1111 2
    calls += context.buildDependencyCallList('%s-props' %
1112
                                             objId, viewPropIds, 'addViewProp', 'removeViewProp')
1113

1114 2
    return {
1115
        'parent': context.getReferenceId(parent),
1116
        'id': objId,
1117
        'type': instance.GetClassName(),
1118
        'properties': {
1119
            'background': instance.GetBackground(),
1120
            'background2': instance.GetBackground2(),
1121
            'viewport': instance.GetViewport(),
1122
            # These commented properties do not yet have real setters in vtk.js
1123
            # 'gradientBackground': instance.GetGradientBackground(),
1124
            # 'aspect': instance.GetAspect(),
1125
            # 'pixelAspect': instance.GetPixelAspect(),
1126
            # 'ambient': instance.GetAmbient(),
1127
            'twoSidedLighting': instance.GetTwoSidedLighting(),
1128
            'lightFollowCamera': instance.GetLightFollowCamera(),
1129
            'layer': instance.GetLayer(),
1130
            'preserveColorBuffer': instance.GetPreserveColorBuffer(),
1131
            'preserveDepthBuffer': instance.GetPreserveDepthBuffer(),
1132
            'nearClippingPlaneTolerance': instance.GetNearClippingPlaneTolerance(),
1133
            'clippingRangeExpansion': instance.GetClippingRangeExpansion(),
1134
            'useShadows': instance.GetUseShadows(),
1135
            'useDepthPeeling': instance.GetUseDepthPeeling(),
1136
            'occlusionRatio': instance.GetOcclusionRatio(),
1137
            'maximumNumberOfPeels': instance.GetMaximumNumberOfPeels()
1138
        },
1139
        'dependencies': dependencies,
1140
        'calls': calls
1141
    }
1142

1143
# -----------------------------------------------------------------------------
1144

1145

1146 2
def cameraSerializer(parent, instance, objId, context, depth):
1147 2
    return {
1148
        'parent': context.getReferenceId(parent),
1149
        'id': objId,
1150
        'type': instance.GetClassName(),
1151
        'properties': {
1152
            'focalPoint': instance.GetFocalPoint(),
1153
            'position': instance.GetPosition(),
1154
            'viewUp': instance.GetViewUp(),
1155
            'clippingRange': instance.GetClippingRange(),
1156
        }
1157
    }
1158

1159
# -----------------------------------------------------------------------------
1160

1161

1162 2
def renderWindowSerializer(parent, instance, objId, context, depth):
1163 2
    dependencies = []
1164 2
    rendererIds = []
1165

1166 2
    rendererCollection = instance.GetRenderers()
1167 2
    for rIdx in range(rendererCollection.GetNumberOfItems()):
1168
        # Grab the next vtkRenderer
1169 2
        renderer = rendererCollection.GetItemAsObject(rIdx)
1170 2
        rendererId = context.getReferenceId(renderer)
1171 2
        rendererInstance = serializeInstance(
1172
            instance, renderer, rendererId, context, depth + 1)
1173 2
        if rendererInstance:
1174 2
            dependencies.append(rendererInstance)
1175 2
            rendererIds.append(rendererId)
1176

1177 2
    calls = context.buildDependencyCallList(
1178
        objId, rendererIds, 'addRenderer', 'removeRenderer')
1179

1180 2
    return {
1181
        'parent': context.getReferenceId(parent),
1182
        'id': objId,
1183
        'type': instance.GetClassName(),
1184
        'properties': {
1185
            'numberOfLayers': instance.GetNumberOfLayers()
1186
        },
1187
        'dependencies': dependencies,
1188
        'calls': calls,
1189
        'mtime': instance.GetMTime(),
1190
    }

Read our documentation on viewing source code .

Loading