lavavu-osmesa 1.9.9__cp313-cp313-manylinux_2_28_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. lavavu/LavaVuPython.py +561 -0
  2. lavavu/_LavaVuPython.cpython-313-x86_64-linux-gnu.so +0 -0
  3. lavavu/__init__.py +15 -0
  4. lavavu/__main__.py +12 -0
  5. lavavu/amalgamate.py +15 -0
  6. lavavu/aserver.py +359 -0
  7. lavavu/control.py +1731 -0
  8. lavavu/convert.py +888 -0
  9. lavavu/dict.json +2528 -0
  10. lavavu/font.bin +0 -0
  11. lavavu/html/LavaVu-amalgamated.css +282 -0
  12. lavavu/html/OK-min.js +99 -0
  13. lavavu/html/baseviewer.js +307 -0
  14. lavavu/html/control.css +104 -0
  15. lavavu/html/control.js +340 -0
  16. lavavu/html/dat-gui-light-theme.css +68 -0
  17. lavavu/html/dat.gui.min.js +2 -0
  18. lavavu/html/draw.js +2259 -0
  19. lavavu/html/drawbox.js +1039 -0
  20. lavavu/html/emscripten-template.js +184 -0
  21. lavavu/html/emscripten.css +92 -0
  22. lavavu/html/favicon.ico +0 -0
  23. lavavu/html/gl-matrix-min.js +47 -0
  24. lavavu/html/gui.css +25 -0
  25. lavavu/html/menu.js +615 -0
  26. lavavu/html/server.js +226 -0
  27. lavavu/html/stats.min.js +5 -0
  28. lavavu/html/styles.css +58 -0
  29. lavavu/html/webview-template.html +43 -0
  30. lavavu/html/webview.html +43 -0
  31. lavavu/lavavu.py +6200 -0
  32. lavavu/osmesa/LavaVuPython.py +561 -0
  33. lavavu/osmesa/_LavaVuPython.cpython-313-x86_64-linux-gnu.so +0 -0
  34. lavavu/osmesa/__init__.py +0 -0
  35. lavavu/points.py +191 -0
  36. lavavu/server.py +343 -0
  37. lavavu/shaders/default.frag +14 -0
  38. lavavu/shaders/default.vert +17 -0
  39. lavavu/shaders/fontShader.frag +20 -0
  40. lavavu/shaders/fontShader.vert +18 -0
  41. lavavu/shaders/lineShader.frag +39 -0
  42. lavavu/shaders/lineShader.vert +26 -0
  43. lavavu/shaders/pointShader.frag +127 -0
  44. lavavu/shaders/pointShader.vert +53 -0
  45. lavavu/shaders/triShader.frag +153 -0
  46. lavavu/shaders/triShader.vert +49 -0
  47. lavavu/shaders/volumeShader.frag +400 -0
  48. lavavu/shaders/volumeShader.vert +5 -0
  49. lavavu/tracers.py +207 -0
  50. lavavu/vutils.py +211 -0
  51. lavavu_osmesa-1.9.9.dist-info/METADATA +323 -0
  52. lavavu_osmesa-1.9.9.dist-info/RECORD +65 -0
  53. lavavu_osmesa-1.9.9.dist-info/WHEEL +5 -0
  54. lavavu_osmesa-1.9.9.dist-info/entry_points.txt +2 -0
  55. lavavu_osmesa-1.9.9.dist-info/licenses/LICENSE.md +179 -0
  56. lavavu_osmesa-1.9.9.dist-info/top_level.txt +1 -0
  57. lavavu_osmesa.libs/libLLVM-17-51492e70.so +0 -0
  58. lavavu_osmesa.libs/libOSMesa-f6a8f160.so.8.0.0 +0 -0
  59. lavavu_osmesa.libs/libdrm-b0291a67.so.2.4.0 +0 -0
  60. lavavu_osmesa.libs/libffi-3a37023a.so.6.0.2 +0 -0
  61. lavavu_osmesa.libs/libglapi-520b284c.so.0.0.0 +0 -0
  62. lavavu_osmesa.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
  63. lavavu_osmesa.libs/libselinux-d0805dcb.so.1 +0 -0
  64. lavavu_osmesa.libs/libtinfo-3a2cb85b.so.6.1 +0 -0
  65. lavavu_osmesa.libs/libzstd-76b78bac.so.1.4.4 +0 -0
lavavu/control.py ADDED
@@ -0,0 +1,1731 @@
1
+ """
2
+ LavaVu python interface
3
+
4
+ Interactive HTML UI controls library
5
+ These functions can be called from the ControlFactory provided on
6
+
7
+ - lavavu.Viewer()
8
+ - lavavu.Object()
9
+ - lavavu.Properties()
10
+
11
+ Some work only on the viewer and some only to objects and some to both.
12
+ Each will provide a control that allows modifying visualisation properties or executing commands interactively
13
+ in an IPython notebook environment
14
+
15
+ Example
16
+ -------
17
+
18
+ Create a Button control to execute a rotation command:
19
+
20
+ >>> lv = lavavu.Viewer()
21
+ >>> lv.control.Button('rotate x 1')
22
+
23
+ Create a Range control to adjust the pointsize property:
24
+
25
+ >>> lv = lavavu.Viewer()
26
+ >>> pts = lv.points()
27
+ >>> pts.control.Range('pointsize', range=[1, 10])
28
+
29
+ For convenience, controls to set visualisation properties can often be created by calling control directly with the property name
30
+ This will create a control of the default type for for that property if one is defined, e.g.
31
+
32
+ >>> lv = lavavu.Viewer()
33
+ >>> pts = lv.points()
34
+ >>> pts.control('pointsize')
35
+ """
36
+
37
+ __all__ = ['Button', 'Checkbox', 'Colour', 'ColourMapList', 'ColourMaps', 'Command', 'Divider', 'DualRange', 'Entry', 'File', 'Filter', 'Gradient', 'List', 'Number', 'Number2D', 'Number3D', 'ObjectList', 'ObjectSelect', 'Panel', 'Range', 'Range2D', 'Range3D', 'Rotation', 'Tabs', 'TimeStepper', 'Window']
38
+
39
+ import os
40
+ import sys
41
+ import time
42
+ import datetime
43
+ import json
44
+ from vutils import is_ipython, is_notebook
45
+ import weakref
46
+ import string
47
+ import re
48
+ from random import Random
49
+
50
+ #Register of windows (viewer instances)
51
+ windows = []
52
+ #Window unique ids
53
+ winids = []
54
+ #Generate unique strings for controls and windows
55
+ id_random = Random() #Ensure we use our own default seed in case set in notebook
56
+ def gen_id(length=10):
57
+ alphabet = string.ascii_letters + string.digits
58
+ try:
59
+ return ''.join(id_random.choices(alphabet, k=length))
60
+ except:
61
+ #Python 3.5
62
+ return ''.join(id_random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(length))
63
+
64
+ basehtml = """
65
+ <html>
66
+
67
+ <head>
68
+ <title>LavaVu Interface</title>
69
+ <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
70
+ <meta name="viewport" content="width=device-width, initial-scale=1">
71
+
72
+ <style>
73
+ html, body, table, form {
74
+ padding: 0; margin: 0;
75
+ border: none;
76
+ font-size: 9pt;
77
+ font-family: sans-serif;
78
+ }
79
+ p {margin: 0px 2px; }
80
+
81
+ .canvas {
82
+ width: 100%; height: 100%;
83
+ }
84
+
85
+ canvas {
86
+ z-index: 0; margin: 0px; padding:0px;
87
+ border: none;
88
+ }
89
+
90
+ </style>
91
+
92
+ ---SCRIPTS---
93
+
94
+ </head>
95
+
96
+ <body onload="---INIT---" oncontextmenu="return false;">
97
+ ---CONTENT---
98
+ </body>
99
+
100
+ </html>
101
+ """
102
+
103
+ inlinehtml = """
104
+ ---SCRIPTS---
105
+ <div id="---ID---" style="display: block; width: ---WIDTH---px; height: ---HEIGHT---px;"></div>
106
+ ---CONTENT---
107
+
108
+ <script>
109
+ ---INIT---
110
+ </script>
111
+ """
112
+
113
+ #Some common elements (TODO: dynamically create these when not found)
114
+ hiddenhtml = """
115
+ <div id="progress" class="popup" style="display: none; width: 310px; height: 32px;">
116
+ <span id="progressmessage"></span><span id="progressstatus"></span>
117
+ <div id="progressbar" style="width: 300px; height: 10px; background: #58f;"></div>
118
+ </div>
119
+
120
+ <div id="hidden" style="display: none">
121
+ <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAPCAYAAAA2yOUNAAAAj0lEQVQokWNIjHT8/+zZs//Pnj37/+TJk/9XLp/+f+bEwf9HDm79v2Prqv9aKrz/GUYVEaeoMDMQryJXayWIoi0bFmFV1NWS+z/E1/Q/AwMDA0NVcez/LRsWoSia2luOUAADVcWx/xfO6/1/5fLp/1N7y//HhlmhKoCBgoyA/w3Vyf8jgyyxK4CBUF8zDAUAAJRXY0G1eRgAAAAASUVORK5CYII=" id="slider">
122
+ <canvas id="gradient" width="2048" height="1"></canvas>
123
+ <canvas id="palette" width="512" height="24" class="palette checkerboard"></canvas>
124
+ </div>
125
+ """
126
+
127
+ #Global data (script) - set from lavavu
128
+ jsglobals = ''
129
+
130
+ #Static HTML location
131
+ htmlpath = ""
132
+
133
+ def _isviewer(target):
134
+ """Return true if target is a viewer"""
135
+ #Can't use isinstance without class in scope
136
+ #return not hasattr(target, "parent")
137
+ return target.__class__.__name__ == 'Viewer' or any(['lavavu.Viewer' in str(b) for b in target.__class__.__bases__])
138
+
139
+ def _isobject(target):
140
+ """Return true if target is a lavavu object"""
141
+ return target.__class__.__name__ == 'Object'
142
+
143
+ def _getviewer(target):
144
+ """Return its viewer if target is vis object
145
+ otherwise just return target as if is a viewer"""
146
+ if not _isviewer(target):
147
+ return target.parent
148
+ return target
149
+
150
+ def _getproperty(target, propname):
151
+ """Return value of property from target
152
+ if not defined, return the default value for this property
153
+ """
154
+ _lv = _getviewer(target)
155
+ if propname in target:
156
+ return target[propname]
157
+ elif propname in _lv.properties:
158
+ #Get property default
159
+ prop = _lv.properties[propname]
160
+ return prop["default"]
161
+ else:
162
+ return None
163
+
164
+ _file_cache = dict()
165
+
166
+ def _webglcode(shaders, css, scripts, menu=True, lighttheme=True, stats=False):
167
+ """
168
+ Returns base WebGL code, by default using full WebGL renderer (draw.js)
169
+ Pass 'drawbox.js' for box interactor only
170
+ """
171
+ code = shaders
172
+ if menu:
173
+ scripts[1] += ['menu.js']
174
+ #Following scripts need require disabled
175
+ scripts[0] += ['dat.gui.min.js']
176
+ #Stats module for WebVR
177
+ if stats:
178
+ scripts[0] += ['stats.min.js']
179
+ if lighttheme:
180
+ css.append("dat-gui-light-theme.css")
181
+ #Some css tweaks
182
+ css.append("gui.css")
183
+
184
+ else:
185
+ #DAT.gui disabled
186
+ scripts[0] += ['!GUI_OFF']
187
+
188
+ #First scripts list needs require.js disabled
189
+ code += _getjslibs(['!REQUIRE_OFF'] + scripts[0] + ['!REQUIRE_ON'] + scripts[1])
190
+
191
+ code += _getcss(css)
192
+ code += '<script>\n' + jsglobals + '\n</script>\n'
193
+ return code
194
+
195
+ def _webglviewcode(shaderpath, menu=True, lighttheme=True):
196
+ """
197
+ Returns WebGL base code for an interactive visualisation window
198
+ """
199
+ jslibs = [['gl-matrix-min.js'], ['OK-min.js', 'draw.js']]
200
+ return _webglcode(_getshaders(shaderpath), ['styles.css'], jslibs, menu=menu, lighttheme=lighttheme)
201
+
202
+ def _webglboxcode(menu=True, lighttheme=True):
203
+ """
204
+ Returns WebGL base code for the bounding box interactor window
205
+ """
206
+ jslibs = [['gl-matrix-min.js'], ['OK-min.js', 'control.js', 'drawbox.js']]
207
+ return _webglcode('', ['control.css', 'styles.css'], jslibs, menu=menu, lighttheme=lighttheme)
208
+
209
+ def _connectcode(target):
210
+ """
211
+ Returns base code to open a connection only, for obtaining correct connection url
212
+ (Only required if no interaction windows have been opened yet)
213
+ """
214
+ #If connection via window already opened, we don't need this
215
+ if len(winids) == 0:
216
+ initjs = '\n_wi[0] = new WindowInteractor(0, {uid}, {port});\n;'.format(uid=id(target), port=target.port)
217
+ #return _readfilehtml('control.js') + initjs
218
+ s = '<script>\n' + _readfilehtml('control.js') + initjs + '</script>\n'
219
+ return s
220
+ else:
221
+ return ""
222
+
223
+ def _emscriptencode(menu=False, lighttheme=True):
224
+ """
225
+ Returns WebGL base code for an interactive visualisation window
226
+ """
227
+ jslibs = [[], ['emscripten.js', 'LavaVu.js']]
228
+ return _webglcode('', ['emscripten.css'], jslibs, menu=menu, lighttheme=lighttheme)
229
+
230
+ def _getcss(files=["styles.css"]):
231
+ #Load stylesheets to inline tag
232
+ return _filestohtml(files, tag="style")
233
+
234
+ def _getjslibs(files):
235
+ #Load a set of combined javascript libraries in script tags
236
+ return _filestohtml(files, tag="script")
237
+
238
+ def _filestohtml(files, tag="script"):
239
+ #Load a set of files into a string enclosed in specified html tag
240
+ code = '<' + tag + '>\n'
241
+ for f in files:
242
+ code += _readfilehtml(f)
243
+ code += '\n</' + tag + '>\n'
244
+ return code
245
+
246
+ def _readfilehtml(filename):
247
+ #HACK: Need to disable require.js to load libraries from inline script tags
248
+ if filename == "!REQUIRE_OFF":
249
+ return '_backup_define = window.define; window.define = undefined;'
250
+ elif filename == "!REQUIRE_ON":
251
+ return 'window.define = _backup_define; delete _backup_define;'
252
+ elif filename == "!GUI_OFF":
253
+ return 'window.dat = undefined;'
254
+
255
+ #Read a file from the htmlpath (or cached copy)
256
+ global _file_cache
257
+ if not filename in _file_cache:
258
+ _file_cache[filename] = _readfile(os.path.join(htmlpath, filename))
259
+ return _file_cache[filename]
260
+
261
+ def _readfile(filename):
262
+ #Read a text file and return contents
263
+ data = ""
264
+ with open(filename, 'r') as f:
265
+ data = f.read()
266
+ f.close()
267
+ return data
268
+
269
+ def _getshaders(path, shaders=['points', 'lines', 'triangles', 'volume']):
270
+ #Load combined shaders
271
+ src = '<script>\nvar shaders = {};\n'
272
+ sdict = {'points' : 'point', 'lines' : 'line', 'triangles' : 'tri', 'volume' : 'volume'};
273
+ for key in shaders:
274
+ vs = _readfile(os.path.join(path, sdict[key] + 'Shader.vert'))
275
+ fs = _readfile(os.path.join(path, sdict[key] + 'Shader.frag'))
276
+ vs = vs.replace('"', '\\"').replace('\n', '\\n')
277
+ fs = fs.replace('"', '\\"').replace('\n', '\\n')
278
+ #vs = re.sub("^\s*in ", 'attribute ', vs, flags=re.MULTILINE);
279
+ #vs = re.sub("^\s*out ", 'varying ', vs, flags=re.MULTILINE)
280
+ #fs = re.sub("^\s*in ", 'varying ', fs, flags=re.MULTILINE);
281
+ src += 'shaders["' + key + '-vs"] = "' + vs + '";'
282
+ src += 'shaders["' + key + '-fs"] = "' + fs + '";'
283
+ src += '\n</script>\n\n'
284
+ return src
285
+
286
+ def _getshaders_as_scripts(path, shaders=['points', 'lines', 'triangles', 'volume']):
287
+ #Load shaders into script tags, not compatible with colab for some reason
288
+ src = ''
289
+ sdict = {'points' : 'point', 'lines' : 'line', 'triangles' : 'tri', 'volume' : 'volume'};
290
+ for key in shaders:
291
+ src += '<script id="' + key + '-vs" type="x-shader/x-vertex">\n'
292
+ src += _readfile(os.path.join(path, sdict[key] + 'Shader.vert'))
293
+ src += '</script>\n\n'
294
+ src += '<script id="' + key + '-fs" type="x-shader/x-fragment">\n'
295
+ src += _readfile(os.path.join(path, sdict[key] + 'Shader.frag'))
296
+ src += '</script>\n\n'
297
+ return src
298
+
299
+ class _Action(object):
300
+ """Base class for an action triggered by a control
301
+
302
+ Default action is to run a command
303
+ """
304
+
305
+ def __init__(self, target, command=None, readproperty=None):
306
+ self.target = target
307
+ self.command = command
308
+ if not hasattr(self, "property"):
309
+ self.property = readproperty
310
+ self.lastvalue = 0
311
+ self.index = None
312
+
313
+ def script(self):
314
+ #Return script action for HTML export
315
+ if self.command is None or not len(self.command): return ""
316
+ #Run a command with a value argument
317
+ return self.command + ' ---VAL---'
318
+
319
+ class _PropertyAction(_Action):
320
+ """Property change action triggered by a control
321
+ """
322
+
323
+ def __init__(self, target, prop, command=None, index=None):
324
+ self.property = prop
325
+ #Default action after property set is redraw, can be set to provided
326
+ #if command is None:
327
+ # command = "redraw"
328
+ self.command = command
329
+ super(_PropertyAction, self).__init__(target, command)
330
+ self.index = index
331
+
332
+ def script(self):
333
+ #Return script action for HTML export
334
+ #Set a property
335
+ #Check for index (3D prop)
336
+ propset = self.property + '=---VAL---'
337
+ if self.index is not None:
338
+ propset = self.property + '[' + str(self.index) + ']=---VAL---'
339
+ # - Globally
340
+ script = ''
341
+ if _isviewer(self.target):
342
+ script = 'select; ' + propset
343
+ # - on an object selector, select the object
344
+ elif type(self.target).__name__ == 'ObjectSelect':
345
+ script = propset
346
+ # - on a properties collection
347
+ elif type(self.target).__name__ == 'Properties':
348
+ script = '$' + str(id(self.target)) + ' ' + self.property + ' ---VAL--- '
349
+ # - On an object
350
+ else:
351
+ script = '<' + self.target["name"] + '>' + propset
352
+ #Add any additional commands
353
+ return script + '; ' + super(_PropertyAction, self).script()
354
+
355
+
356
+ class _CommandAction(_Action):
357
+ """Command action triggered by a control, with object select before command
358
+ """
359
+
360
+ def __init__(self, target, command, readproperty):
361
+ self.command = command
362
+ super(_CommandAction, self).__init__(target, command, readproperty)
363
+
364
+ def script(self):
365
+ #Return script action for HTML export
366
+ #Set a property
367
+ # - Globally
368
+ script = ''
369
+ if _isviewer(self.target):
370
+ script = 'select; '
371
+ # - on an object selector, select the object
372
+ elif type(self.target).__name__ == 'ObjectSelect':
373
+ script = ''
374
+ # - On an object
375
+ else:
376
+ script = '<' + self.target["name"] + '>'
377
+ #Add the commands
378
+ return script + super(_CommandAction, self).script()
379
+
380
+ class _FilterAction(_PropertyAction):
381
+ """Filter property change action triggered by a control
382
+ """
383
+ def __init__(self, target, findex, prop, command=None):
384
+ self.findex = findex
385
+ if command is None: command = "redraw"
386
+ self.command = command
387
+ super(_FilterAction, self).__init__(target, prop)
388
+
389
+ def script(self):
390
+ #Return script action for HTML export
391
+ #Set a filter range
392
+ cmd = "filtermin" if self.property == "minimum" else "filtermax"
393
+ return 'select ' + self.target["name"] + '; ' + cmd + ' ' + str(self.findex) + ' ---VAL---'
394
+ #propset = "filters=" + json.dumps()
395
+ #script = 'select ' + self.target["name"] + '; ' + propset
396
+
397
+ class _HTML(object):
398
+ """A class to output HTML controls
399
+ """
400
+
401
+ #Parent class for container types
402
+ def __init__(self, label):
403
+ self.label = label
404
+ self.uniqueid()
405
+
406
+ def html(self):
407
+ """Return the HTML code"""
408
+ return ''
409
+
410
+ def uniqueid(self):
411
+ #Get a unique control identifier
412
+ self.id = gen_id()
413
+ self.elid = "lvctrl_" + self.id
414
+
415
+ def labelhtml(self):
416
+ #Default label
417
+ html = ""
418
+ if self.label:
419
+ html += '<p>' + self.label + ':</p>\n'
420
+ return html
421
+
422
+
423
+ class _Container(_HTML):
424
+ """A container for a set of controls
425
+ """
426
+ #Parent class for container types
427
+ def __init__(self, viewer, label=""):
428
+ self.viewer = viewer
429
+ self._content = []
430
+ self._current = 0
431
+ super(_Container, self).__init__(label)
432
+
433
+ def add(self, ctrl):
434
+ self._content.append(ctrl)
435
+ return ctrl
436
+
437
+ def controls(self):
438
+ return self.html()
439
+
440
+ def __iter__(self):
441
+ return self
442
+
443
+ def __next__(self):
444
+ if self._current > len(self._content)-1:
445
+ self._current = 0
446
+ raise StopIteration
447
+ else:
448
+ self._current += 1
449
+ return self._content[self._current-1]
450
+
451
+ def html(self):
452
+ html = self.labelhtml()
453
+ html += '<div style="padding:0px; margin: 0px; position: relative;" class="lvctrl">\n'
454
+ for i in range(len(self._content)):
455
+ html += self._content[i].controls()
456
+ html += '</div>\n'
457
+ return html
458
+
459
+ def scripts(self):
460
+ #Returns script dictionary
461
+ d = {}
462
+ for i in range(len(self._content)):
463
+ d.update(self._content[i].scripts())
464
+ return d
465
+
466
+ class Window(_Container):
467
+ """
468
+ Creates an interaction window with an image of the viewer frame
469
+ and webgl controller for rotation/translation
470
+
471
+ Parameters
472
+ ----------
473
+ align : str
474
+ Set to "left/right" to align viewer window, default is left. Set to None to skip.
475
+ wrapper : str
476
+ Set the style of the wrapper div, default is empty string so wrapper is enabled with no custom style
477
+ Set to None to disable wrapper
478
+ """
479
+ def __init__(self, viewer, resolution=None, align="left", wrapper="", fullscreen=False):
480
+ super(Window, self).__init__(viewer)
481
+ self.align = align
482
+ self.wrapper = wrapper
483
+ self.fullscreen = fullscreen
484
+ if resolution is None:
485
+ #Default to largest of 640x480 or output res / 2
486
+ resolution = [0,0]
487
+ resolution[0] = max(viewer.output_resolution[0]//2, 640)
488
+ resolution[1] = int(resolution[0] * viewer.output_resolution[1]/viewer.output_resolution[0])
489
+ elif isinstance(resolution, int):
490
+ #Passed interger - interpert as width
491
+ resolution = [resolution,0]
492
+
493
+ if resolution[1] == 0:
494
+ #Width only, set height based on output aspect ratio
495
+ resolution[1] = int(resolution[0] * viewer.output_resolution[1]/viewer.output_resolution[0])
496
+
497
+ viewer.output_resolution = resolution
498
+
499
+ def html(self):
500
+ #print(self.viewer["resolution"], self.viewer.output_resolution)
501
+ #w = self.viewer["resolution"][0]
502
+ #h = self.viewer["resolution"][1]
503
+ w = self.viewer.output_resolution[0]
504
+ h = self.viewer.output_resolution[1]
505
+ style = f'height: {h}px; width: {w}px; min-height: 50px; min-width: 50px; position: relative; display: inline-block;'
506
+ style_img = 'height: 100%; width: 100%; position: relative; margin: 0px; display: inline-block;'
507
+ if self.align is not None:
508
+ style += ' float: ' + self.align + ';'
509
+ if self.wrapper is not None:
510
+ style += ' margin-right: 10px;'
511
+ classes = ""
512
+ if self.fullscreen:
513
+ style += ' height: 100%; width: 100%;'
514
+ else:
515
+ classes = "resizer"
516
+ style +=" border: 1px solid #aaa;"
517
+
518
+ html = ""
519
+ html += '<div class="' + classes + '" style="' + style + '">\n'
520
+ #html += '<img id="imgtarget_---VIEWERID---" draggable=false style="margin: 0px; border: 1px solid #aaa; display: inline-block;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAQAAAAAYLlVAAAAPUlEQVR42u3OMQEAAAgDINe/iSU1xh5IQPamKgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLtwAMBsGqBDct9xQAAAABJRU5ErkJggg==">\n'
521
+ html += '<img id="imgtarget_---VIEWERID---" draggable=false style="' + style_img + '" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAQAAAAAYLlVAAAAPUlEQVR42u3OMQEAAAgDINe/iSU1xh5IQPamKgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLtwAMBsGqBDct9xQAAAABJRU5ErkJggg==">\n'
522
+ html += """
523
+ <div style="display: none; z-index: 200; position: absolute; top: 5px; right: 5px;">
524
+ <select onchange="_wi['---VIEWERID---'].box.mode = this.value;">
525
+ <option>Rotate</option>
526
+ <option>Translate</option>
527
+ <option>Zoom</option>
528
+ </select>
529
+ <input type="button" value="Reset" onclick="_wi['---VIEWERID---'].execute('reset');">
530
+ </div>"""
531
+ html += '</div>\n'
532
+
533
+ #Display any contained controls
534
+ if self.wrapper is not None:
535
+ html += '<div style="' + self.wrapper + '" class="lvctrl">\n'
536
+ html += super(Window, self).html()
537
+ if self.wrapper is not None:
538
+ html += '</div>\n'
539
+ #html += '<div style="clear: both;">\n'
540
+ return html
541
+
542
+ class Panel(Window):
543
+ """Creates a control panel with an interactive viewer window and a set of controls
544
+ placed to the left with the viewer aligned to the right
545
+ """
546
+ def __init__(self, *args, **kwargs):
547
+ super(Panel, self).__init__(*args, align="right", wrapper=None, **kwargs)
548
+
549
+ class Tabs(_Container):
550
+ """Creates a group of controls with tabs that can be shown or hidden
551
+
552
+ Parameters
553
+ ----------
554
+ buttons : boolean
555
+ Display the tab buttons for switching tabs
556
+ """
557
+ def __init__(self, target, buttons=True):
558
+ self.tabs = []
559
+ self.buttons = buttons
560
+ super(Tabs, self).__init__(target)
561
+
562
+ def tab(self, label=""):
563
+ """Add a new tab, any controls appending will appear in the new tab
564
+ Parameters
565
+ ----------
566
+ label : str
567
+ Label for the tab, if omitted will be blank
568
+ """
569
+ self.tabs.append(label)
570
+
571
+ def add(self, ctrl):
572
+ if not len(self.tabs): self.tab()
573
+ self._content.append(ctrl)
574
+ ctrl.tab = len(self.tabs)-1
575
+
576
+ def html(self):
577
+ html = """
578
+ <script>
579
+ function openTab_---ELID---(el, tabName) {
580
+ var i;
581
+ var x = document.getElementsByClassName("---ELID---");
582
+ for (i = 0; i < x.length; i++)
583
+ x[i].style.display = "none";
584
+ document.getElementById("---ELID---_" + tabName).style.display = "block";
585
+
586
+ tabs = document.getElementsByClassName("tab_---ELID---");
587
+ for (i = 0; i < x.length; i++)
588
+ tabs[i].className = tabs[i].className.replace(" lvseltab", "");
589
+ el.className += " lvseltab";
590
+ }
591
+ </script>
592
+ """
593
+ if self.buttons:
594
+ html += "<div class='lvtabbar'>\n"
595
+ for t in range(len(self.tabs)):
596
+ #Add header items
597
+ classes = 'lvbutton lvctrl tab_---ELID---'
598
+ if t == 0: classes += ' lvseltab'
599
+ html += '<a class="' + classes + '" onclick="openTab_---ELID---(this, this.innerHTML)">---LABEL---</a>'
600
+ html = html.replace('---LABEL---', self.tabs[t])
601
+ html += "</div>\n"
602
+ for t in range(len(self.tabs)):
603
+ #Add control wrappers
604
+ style = ''
605
+ if t != 0: style = 'display: none;'
606
+ html += '<div id="---ELID---_---LABEL---" style="' + style + '" class="lvtab lvctrl ---ELID---">\n'
607
+ for ctrl in self._content:
608
+ if ctrl.tab == t:
609
+ html += ctrl.controls()
610
+ html += '</div>\n'
611
+ html = html.replace('---LABEL---', self.tabs[t])
612
+ html = html.replace('---ELID---', self.elid)
613
+ return html
614
+
615
+ class _Control(_HTML):
616
+ """
617
+ _Control object
618
+
619
+ Parameters
620
+ ----------
621
+ target : Obj or Viewer
622
+ Add a control to an object to control object properties
623
+ Add a control to a viewer to control global proerties and run commands
624
+ property : str
625
+ Property to modify
626
+ command : str
627
+ Command to run
628
+ value : any type
629
+ Initial value of the controls
630
+ label : str
631
+ Descriptive label for the control
632
+ readproperty : str
633
+ Property to read control value from on update (but not modified)
634
+ """
635
+
636
+ def __init__(self, target, property=None, command=None, value=None, label=None, index=None, readproperty=None):
637
+ super(_Control, self).__init__(label)
638
+ self.label = label
639
+
640
+ #Can either set a property directly or run a command
641
+ self.property = readproperty
642
+ self.target = target
643
+ self.action = None
644
+ if property:
645
+ #Property set
646
+ self.action = _PropertyAction(target, property, command, index)
647
+ if label is None:
648
+ self.label = property.capitalize()
649
+ self.property = property
650
+ elif command:
651
+ #Command only
652
+ self.action = _CommandAction(target, command, readproperty)
653
+ if label is None:
654
+ self.label = command.capitalize()
655
+ else:
656
+ #Assume derived class will fill out the action, this is just a placeholder
657
+ self.action = _Action(target)
658
+
659
+ if not self.label:
660
+ self.label = ""
661
+
662
+ #Get value from target or default if not provided
663
+ if property is not None:
664
+ if value is None:
665
+ value = _getproperty(target, property)
666
+ else:
667
+ #2d/3D value?
668
+ if index is not None:
669
+ V = target[property]
670
+ V[index] = value
671
+ target[property][index] = V #Set the provided value
672
+ else:
673
+ target[property] = value #Set the provided value
674
+
675
+ #Append reload command from prop dict if no command provided
676
+ _lv = _getviewer(target)
677
+ if property in _lv.properties:
678
+ prop = _lv.properties[property]
679
+ #TODO: support higher reload values
680
+ cmd = ""
681
+ if prop["redraw"] > 1 and not "reload" in str(command):
682
+ cmd = "reload"
683
+ elif prop["redraw"] > 0 and not "redraw" in str(command):
684
+ cmd = "reload"
685
+ #Append if command exists
686
+ if command is None:
687
+ command = cmd
688
+ else:
689
+ #Pass the value to command if provided
690
+ command += " ---VAL--- ; " + cmd
691
+
692
+ self.value = value
693
+ self._value = value #Store passed initial value
694
+
695
+ def onchange(self):
696
+ return "_wi['---VIEWERID---'].do_action('" + str(self.id) + "', this.value, this);"
697
+
698
+ def show(self):
699
+ #Show only this control
700
+ html = '<div style="" class="lvctrl">\n'
701
+ html += self.html()
702
+ html += '</div>\n'
703
+
704
+ def html(self):
705
+ return self.controls()
706
+
707
+ def controls(self, type='number', attribs={}, onchange=""):
708
+ #Input control
709
+ html = '<input id="---ELID---_{0}" class="---ELID---" type="{0}"'.format(type)
710
+ #Set custom attribute for property controls
711
+ if not "step" in attribs or attribs["step"] is None:
712
+ attribs["step"] = "any"
713
+ #if self.value:
714
+ # attribs["step"] = abs(self.value) / 100.
715
+ #else:
716
+ # attribs["step"] = "any"
717
+ for key in attribs:
718
+ html += key + '="' + str(attribs[key]) + '" '
719
+ html += self.attribs()
720
+ html += 'value="' + str(self.value) + '" '
721
+ #Onchange event
722
+ onchange += self.onchange()
723
+ html += 'onchange="' + onchange + '" '
724
+ html += '>\n'
725
+ html = html.replace('---ELID---', self.elid)
726
+ return html
727
+
728
+ def attribs(self):
729
+ html = ""
730
+ if self.property:
731
+ #Set custom attribute for property controls
732
+ if _isobject(self.target):
733
+ html += 'data-target="' + str(self.target["name"]) + '" '
734
+ html += 'data-property="' + self.property + '" '
735
+ if self.action.index is not None:
736
+ html += 'data-index="' + str(self.action.index) + '" '
737
+ return html
738
+
739
+ def scripts(self):
740
+ #Returns script dictionary
741
+ return {self.id : self.action.script()}
742
+
743
+ class _MultiControl(_Control):
744
+ """
745
+ _Control object that holds multiple integrated Controls
746
+ """
747
+
748
+ def __init__(self, *args, **kwargs):
749
+ super(_MultiControl, self).__init__(*args, **kwargs)
750
+ self._content = []
751
+ self._current = 0
752
+
753
+ def add(self, ctrl):
754
+ self._content.append(ctrl)
755
+ return ctrl
756
+
757
+ def controls(self):
758
+ return self.html()
759
+
760
+ def __iter__(self):
761
+ return self
762
+
763
+ def __next__(self):
764
+ if self._current > len(self._content)-1:
765
+ self._current = 0
766
+ raise StopIteration
767
+ else:
768
+ self._current += 1
769
+ return self._content[self._current-1]
770
+
771
+ def html(self):
772
+ html = self.labelhtml()
773
+ for i in range(len(self._content)):
774
+ html += self._content[i].controls()
775
+ return html
776
+
777
+ def scripts(self):
778
+ #Returns script dictionary
779
+ d = {}
780
+ for i in range(len(self._content)):
781
+ d[self._content[i].id] = self._content[i].action.script()
782
+ return d
783
+
784
+ class Divider(_Control):
785
+ """A divider element
786
+ """
787
+
788
+ def controls(self):
789
+ return '<hr style="clear: both;">\n'
790
+
791
+ class Number(_Control):
792
+ """A basic numerical input control
793
+ """
794
+ def __init__(self, target=None, property=None, command=None, value=None, label=None, index=None, step=None, readproperty=None):
795
+ super(Number, self).__init__(target, property, command, value, label, index, readproperty)
796
+
797
+ #Get step defaults from prop dict
798
+ _lv = _getviewer(target)
799
+ if step is None and property is not None and property in _lv.properties:
800
+ prop = _lv.properties[property]
801
+ #Check for integer type, set default step to 1
802
+ T = prop["type"]
803
+ ctrl = prop["control"]
804
+ #Whole number step size?
805
+ if "integer" in T:
806
+ step = 1
807
+ elif len(ctrl) > 1 and len(ctrl[1]) == 3:
808
+ step = ctrl[1][2]
809
+ elif "real" in T:
810
+ r = 1.0
811
+ #Use given range if any provided
812
+ if len(ctrl) > 1 and len(ctrl[1]) > 1:
813
+ r = ctrl[1][1] - ctrl[1][0]
814
+ step = r / 100.0
815
+
816
+ self.step = step
817
+
818
+ def controls(self):
819
+ html = self.labelhtml()
820
+ attribs = {"step" : self.step}
821
+ html += super(Number, self).controls('number', attribs)
822
+ return html + '<br>\n'
823
+
824
+ class Number2D(_MultiControl):
825
+ """A set of two numeric controls for adjusting a 2D value
826
+ """
827
+ def __init__(self, target, property, label=None, step=None, *args, **kwargs):
828
+ super(Number2D, self).__init__(target, property, label=label, *args, **kwargs)
829
+ if self._value is None:
830
+ self._value = _getproperty(target, property)
831
+ self.ctrlX = self.add(Number(target=target, property=property, value=self._value[0], step=step, label="", index=0))
832
+ self.ctrlY = self.add(Number(target=target, property=property, value=self._value[1], step=step, label="", index=1))
833
+
834
+ def controls(self):
835
+ html = self.labelhtml() + self.ctrlX.controls() + self.ctrlY.controls()
836
+ return html.replace('<br>', '') + '<br>'
837
+
838
+ class Number3D(_MultiControl):
839
+ """A set of three numeric controls for adjusting a 3D value
840
+ """
841
+ def __init__(self, target, property, label=None, step=None, *args, **kwargs):
842
+ super(Number3D, self).__init__(target, property, label=label, *args, **kwargs)
843
+ if self._value is None:
844
+ self._value = _getproperty(target, property)
845
+ self.ctrlX = self.add(Number(target=target, property=property, value=self._value[0], step=step, label="", index=0))
846
+ self.ctrlY = self.add(Number(target=target, property=property, value=self._value[1], step=step, label="", index=1))
847
+ self.ctrlZ = self.add(Number(target=target, property=property, value=self._value[2], step=step, label="", index=2))
848
+
849
+ def controls(self):
850
+ html = self.labelhtml() + self.ctrlX.controls() + self.ctrlY.controls() + self.ctrlZ.controls()
851
+ return html.replace('<br>', '') + '<br>'
852
+
853
+ class Rotation(_MultiControl):
854
+ """A set of six buttons for adjusting a 3D rotation
855
+ """
856
+ def __init__(self, target, *args, **kwargs):
857
+ self.label="Rotation"
858
+ super(Rotation, self).__init__(target, *args, **kwargs)
859
+
860
+ self.ctrlX0 = self.add(Button(target=target, command="rotate x -1", label="-X"))
861
+ self.ctrlX1 = self.add(Button(target=target, command="rotate x 1", label="+X"))
862
+ self.ctrlY0 = self.add(Button(target=target, command="rotate y -1", label="-Y"))
863
+ self.ctrlY1 = self.add(Button(target=target, command="rotate y 1", label="+Y"))
864
+ self.ctrlZ0 = self.add(Button(target=target, command="rotate z -1", label="-Z"))
865
+ self.ctrlZ1 = self.add(Button(target=target, command="rotate z 1", label="+Z"))
866
+
867
+ def controls(self):
868
+ html = self.labelhtml() + self.ctrlX0.controls() + self.ctrlX1.controls() + self.ctrlY0.controls() + self.ctrlY1.controls() + self.ctrlZ0.controls() + self.ctrlZ1.controls()
869
+ return html.replace('<br>', '') + '<br>'
870
+
871
+
872
+ class Checkbox(_Control):
873
+ """A checkbox control for a boolean value
874
+ """
875
+ def labelhtml(self):
876
+ return '' #'<br>\n'
877
+
878
+ def controls(self):
879
+ attribs = {}
880
+ if self.value: attribs = {"checked" : "checked"}
881
+ html = "<label>\n"
882
+ html += super(Checkbox, self).controls('checkbox', attribs)
883
+ html += " " + self.label + "</label><br>\n"
884
+ return html
885
+
886
+ def onchange(self):
887
+ return "; _wi['---VIEWERID---'].do_action('" + str(self.id) + "', this.checked ? 1 : 0, this);"
888
+
889
+ class Range(_Control):
890
+ """A slider control for a range of values
891
+
892
+ Parameters
893
+ ----------
894
+ range : list or tuple
895
+ Min/max values for the range
896
+ """
897
+ def __init__(self, target=None, property=None, command=None, value=None, label=None, index=None, range=None, step=None, readproperty=None):
898
+ super(Range, self).__init__(target, property, command, value, label, index, readproperty)
899
+
900
+ #Get range & step defaults from prop dict
901
+ _lv = _getviewer(target)
902
+ defrange = [0., 1., 0.]
903
+ T = None
904
+ if property is not None and property in _lv.properties:
905
+ prop = _lv.properties[property]
906
+ #Check for integer type, set default step to 1
907
+ T = prop["type"]
908
+ if "integer" in T:
909
+ defrange[2] = 1
910
+ ctrl = prop["control"]
911
+ if len(ctrl) > 1 and len(ctrl[1]) == 3:
912
+ defrange = ctrl[1]
913
+
914
+ if range is None:
915
+ range = defrange[0:2]
916
+ if step is None:
917
+ step = defrange[2]
918
+
919
+ self.range = range
920
+ self.step = step
921
+ if not step:
922
+ #Whole number step size?
923
+ r = range[1] - range[0]
924
+ #Has no type from property, or type is not real: Assume a step size of 1 if range max-min > 5 and both are integers
925
+ if (T is None or not "real" in T) and (r >= 5 and range[0] - int(range[0]) == 0 and range[1] - int(range[1]) == 0):
926
+ self.step = 1
927
+ #Range is less than 5 or range values indicate real numbers
928
+ else:
929
+ self.step = r / 100.0
930
+
931
+ def controls(self):
932
+ attribs = {"min" : self.range[0], "max" : self.range[1], "step" : self.step}
933
+ html = self.labelhtml()
934
+ html += super(Range, self).controls('number', attribs, onchange='this.nextElementSibling.value=this.value; ')
935
+ html += super(Range, self).controls('range', attribs, onchange='this.previousElementSibling.value=this.value; ')
936
+ return html + '<br>\n'
937
+
938
+ class Button(_Control):
939
+ """A push button control to execute a defined command
940
+ """
941
+ def __init__(self, target, command, label=None):
942
+ super(Button, self).__init__(target, None, command, None, label)
943
+
944
+ def onchange(self):
945
+ return "_wi['---VIEWERID---'].do_action('" + str(self.id) + "', '', this);"
946
+
947
+ def labelhtml(self):
948
+ return ''
949
+
950
+ def controls(self):
951
+ html = self.labelhtml()
952
+ html = '<input class="---ELID---" type="button" value="' + str(self.label) + '" '
953
+ #Onclick event
954
+ html += 'onclick="' + self.onchange() + '" '
955
+ html += '><br>\n'
956
+ html = html.replace('---ELID---', self.elid)
957
+ return html
958
+
959
+ class Entry(_Control):
960
+ """A generic input control for string values
961
+ """
962
+ def controls(self):
963
+ html = self.labelhtml()
964
+ html += '<input class="---ELID---" type="text" value="" '
965
+ html += self.attribs()
966
+ html += ' onkeypress="if (event.keyCode == 13) { _wi[\'---VIEWERID---\'].do_action(\'---ID---\', this.value.trim(), this); };"><br>\n'
967
+ html = html.replace('---ELID---', self.elid)
968
+ return html.replace('---ID---', self.id)
969
+
970
+ class Command(_Control):
971
+ """A generic input control for executing command strings
972
+ """
973
+ def __init__(self, *args, **kwargs):
974
+ super(Command, self).__init__(command=" ", label="Command", *args, **kwargs)
975
+
976
+ def controls(self):
977
+ html = self.labelhtml()
978
+ html += """
979
+ <input class="---ELID---" type="text" value=""
980
+ onkeypress="if (event.keyCode == 13) { var cmd=this.value.trim();
981
+ _wi['---VIEWERID---'].do_action('---ID---', cmd ? cmd : 'repeat', this); this.value=''; };"><br>\n
982
+ """
983
+ html = html.replace('---ELID---', self.elid)
984
+ return html.replace('---ID---', self.id)
985
+
986
+ class File(_Control):
987
+ """A file picker control
988
+
989
+ Unfortunately there is no way to get the file path
990
+ """
991
+ def __init__(self, command="file", directory=False, multiple=False, accept="", *args, **kwargs):
992
+ self.options = ""
993
+ if directory:
994
+ self.options += 'webkitdirectory directory '
995
+ elif multiple:
996
+ self.options += 'multiple '
997
+ if accept:
998
+ #Comma separated file types list, e.g. image/*,audio/*,video/*,.pdf
999
+ self.options += 'accept="' + accept + '"'
1000
+
1001
+ super(File, self).__init__(command=command, label="Load File", *args, **kwargs)
1002
+
1003
+ def controls(self):
1004
+ html = self.labelhtml()
1005
+ html += """
1006
+ <div style="border:solid #aaa 1px; padding:10px; margin-bottom: 5px;">
1007
+ <input class="---ELID---" type="file" id="file_selector_---ELID---" ---OPTIONS--- name="files[]"/>
1008
+ <output id="list"></output>
1009
+ </div>
1010
+ <script>
1011
+ function fileSelected_---ELID---(event) {
1012
+ var output = [];
1013
+ for (var i = 0; i < event.target.files.length; i++) {
1014
+ var f = event.target.files.item(i);
1015
+ output.push('<li><strong>', escape(f.name), '</strong> (', f.type || 'n/a', ') - ',
1016
+ f.size, ' bytes, last modified: ',
1017
+ '</li>');
1018
+ _wi['---VIEWERID---'].do_action("---ID---", f.name);
1019
+ }
1020
+ document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
1021
+ }
1022
+ document.getElementById('file_selector_---ELID---').addEventListener('change', fileSelected_---ELID---, false);
1023
+ </script>
1024
+ """
1025
+ html = html.replace('---ELID---', self.elid)
1026
+ html = html.replace('---OPTIONS---', self.options)
1027
+ return html.replace('---ID---', self.id)
1028
+
1029
+ class List(_Control):
1030
+ """A list of predefined input values to set properties or run commands
1031
+
1032
+ Parameters
1033
+ ----------
1034
+ options: list
1035
+ List of the available value strings
1036
+ """
1037
+ def __init__(self, target, property=None, options=None, *args, **kwargs):
1038
+ #Get default options from prop dict
1039
+ if options is None:
1040
+ defoptions = []
1041
+ _lv = _getviewer(target)
1042
+ if property is not None and property in _lv.properties:
1043
+ prop = _lv.properties[property]
1044
+ ctrl = prop["control"]
1045
+ if len(ctrl) > 2 and len(ctrl[2]):
1046
+ defoptions = ctrl[2]
1047
+ options = defoptions
1048
+ self.options = options
1049
+ super(List, self).__init__(target, property, *args, **kwargs)
1050
+
1051
+ def controls(self):
1052
+ html = self.labelhtml()
1053
+ html += '<select class="---ELID---" id="---ELID---" value="" '
1054
+ html += self.attribs()
1055
+ html += 'onchange="' + self.onchange() + '">\n'
1056
+ for opt in self.options:
1057
+ #Each element of options list can be:
1058
+ # - dict {"label" : label, "value" : value, "selected" : True/False}
1059
+ # - list [value, label, selected]
1060
+ # - value only
1061
+ if isinstance(opt, dict):
1062
+ selected = "selected" if opt.selected else ""
1063
+ html += '<option value="' + str(opt["value"]) + '" ' + selected + '>' + opt["label"] + '</option>\n'
1064
+ elif isinstance(opt, list) or isinstance(opt, tuple):
1065
+ selected = "selected" if len(opt) > 2 and opt[2] else ""
1066
+ html += '<option value="' + str(opt[0]) + '" ' + selected + '>' + str(opt[1]) + '</option>\n'
1067
+ else:
1068
+ html += '<option>' + str(opt) + '</option>\n'
1069
+ html += '</select><br>\n'
1070
+ html = html.replace('---ELID---', self.elid)
1071
+ return html
1072
+
1073
+ class Colour(_Control):
1074
+ """A colour picker for setting colour properties
1075
+ """
1076
+ def __init__(self, *args, **kwargs):
1077
+ self.style = ""
1078
+ super(Colour, self).__init__(command="", *args, **kwargs)
1079
+
1080
+ def controls(self):
1081
+ html = self.labelhtml()
1082
+ html += """
1083
+ <div class="colourbg checkerboard" style="---STYLE---">
1084
+ <div id="---ELID---" ---ATTRIBS--- class="colour ---ELID---" onclick="
1085
+ var col = new Colour(this.style.backgroundColor);
1086
+ var offset = [this.getBoundingClientRect().left, this.getBoundingClientRect().top];
1087
+ var el = this;
1088
+ var savefn = function(val) {
1089
+ var c = new Colour(0);
1090
+ c.setHSV(val);
1091
+ el.style.backgroundColor = c.html();
1092
+ _wi['---VIEWERID---'].do_action('---ID---', c.html(), el);
1093
+ }
1094
+ el.picker = new ColourPicker(savefn);
1095
+ el.picker.pick(col, offset[0], offset[1]);">
1096
+ </div>
1097
+ </div>
1098
+ <script>
1099
+ var el = document.getElementById("---ELID---");
1100
+ //Set the initial colour
1101
+ var col = new Colour('---VALUE---');
1102
+ el.style.backgroundColor = col.html();
1103
+ </script>
1104
+ """
1105
+ html = html.replace('---VALUE---', str(self.value))
1106
+ html = html.replace('---ELID---', self.elid)
1107
+ html = html.replace('---ATTRIBS---', self.attribs())
1108
+ html = html.replace('---STYLE---', self.style)
1109
+ return html.replace('---ID---', self.id)
1110
+
1111
+ class ColourIndicator(Colour):
1112
+ """A small indicator for showing/setting the colour of the object
1113
+ """
1114
+ def __init__(self, *args, **kwargs):
1115
+ super(ColourIndicator, self).__init__(*args, **kwargs)
1116
+ #Custom div style:
1117
+ self.style="border: #888 1px solid; display: inline-block; width: 20px; height: 20px;"
1118
+ self.label = ""
1119
+ #Set the colour from cached data so at least the indicated colour is correct
1120
+ if not self.target["colour"]:
1121
+ self.target["colour"] = self.target.colour.toString()
1122
+
1123
+ class Gradient(_Control):
1124
+ """A colourmap editor
1125
+ """
1126
+ def __init__(self, target, *args, **kwargs):
1127
+ super(Gradient, self).__init__(target, property="colourmap", command="", *args, **kwargs)
1128
+ #Get and save the map id of target object
1129
+ if _isviewer(target):
1130
+ raise(Exception("Gradient control requires an Object target, not Viewer"))
1131
+ self.maps = target.parent.state["colourmaps"]
1132
+ self.map = None
1133
+ for m in self.maps:
1134
+ if m["name"] == self.value:
1135
+ self.map = m
1136
+ self.selected = -1;
1137
+
1138
+ def controls(self):
1139
+ html = self.labelhtml()
1140
+ html += """
1141
+ <canvas id="---ELID---_canvas" ---ATTRIBS--- width="512" height="24" class="palette checkerboard">
1142
+ </canvas>
1143
+ <script>
1144
+ var el = document.getElementById("---ELID---_canvas"); //Get the canvas
1145
+ //Store the maps
1146
+ el.colourmaps = ---COLOURMAPS---;
1147
+ el.currentmap = ---COLOURMAP---;
1148
+ el.selectedIndex = ---SELID---;
1149
+ if (!el.gradient) {
1150
+ //Create the gradient editor
1151
+ el.gradient = new GradientEditor(el, function(obj, id) {
1152
+ //Gradient updated
1153
+ //var colours = obj.palette.toJSON()
1154
+ el.currentmap = obj.palette.get(el.currentmap);
1155
+ _wi['---VIEWERID---'].do_action('---ID---', JSON.stringify(el.currentmap.colours));
1156
+ //_wi['---VIEWERID---'].do_action('---ID---', obj.palette.toJSON(), el);
1157
+
1158
+ //Update stored maps list by name
1159
+ if (el.selectedIndex >= 0)
1160
+ el.colourmaps[el.selectedIndex].colours = el.currentmap.colours; //el.gradient.palette.get();
1161
+ }
1162
+ , true); //Enable premultiply
1163
+ }
1164
+ //Load the initial colourmap
1165
+ el.gradient.read(el.currentmap.colours);
1166
+ </script>
1167
+ """
1168
+ mapstr = json.dumps(self.maps)
1169
+ html = html.replace('---COLOURMAPS---', mapstr)
1170
+ if self.map:
1171
+ html = html.replace('---COLOURMAP---', json.dumps(self.map))
1172
+ else:
1173
+ html = html.replace('---COLOURMAP---', '"black white"')
1174
+ html = html.replace('---SELID---', str(self.selected))
1175
+ html = html.replace('---ELID---', self.elid)
1176
+ html = html.replace('---ATTRIBS---', self.attribs())
1177
+ return html.replace('---ID---', self.id)
1178
+
1179
+ class ColourMapList(List):
1180
+ """A colourmap list selector, populated by the default colour maps
1181
+ """
1182
+ def __init__(self, target, selection=None, *args, **kwargs):
1183
+ if _isviewer(target):
1184
+ raise(Exception("ColourMapList control requires an Object target, not Viewer"))
1185
+ #Load maps list
1186
+ if selection is None:
1187
+ selection = target.parent.defaultcolourmaps()
1188
+ options = [''] + selection
1189
+ #Also add the matplotlib colourmaps if available
1190
+ try:
1191
+ #Load maps list
1192
+ import matplotlib
1193
+ import matplotlib.pyplot as plt
1194
+ sel = matplotlib.pyplot.colormaps()
1195
+ options += matplotlib.pyplot.colormaps()
1196
+ except:
1197
+ pass
1198
+
1199
+ #Preceding command with '.' calls via python API, allowing use of matplotlib maps
1200
+ super(ColourMapList, self).__init__(target, options=options, command=".colourmap", label="Load Colourmap", *args, **kwargs)
1201
+
1202
+ class ColourMaps(_MultiControl):
1203
+ """A colourmap list selector, populated by the available colour maps,
1204
+ combined with a colourmap editor for the selected colour map
1205
+ """
1206
+ def __init__(self, target, *args, **kwargs):
1207
+ #Load maps list
1208
+ if _isviewer(target):
1209
+ raise(Exception("ColourMaps control requires an Object target, not Viewer"))
1210
+ self.maps = target.parent.state["colourmaps"]
1211
+ options = [["", "None"]]
1212
+ sel = target["colourmap"]
1213
+ if sel is None: sel = ""
1214
+ for m in range(len(self.maps)):
1215
+ options.append([self.maps[m]["name"], self.maps[m]["name"]])
1216
+ #Mark selected
1217
+ if sel == self.maps[m]["name"]:
1218
+ options[-1].append(True)
1219
+ sel = m
1220
+
1221
+ super(ColourMaps, self).__init__(target, *args, **kwargs)
1222
+ self.list = self.add(List(target, options=options, command="", property="colourmap", label="", *args, **kwargs))
1223
+
1224
+ self.gradient = Gradient(target)
1225
+ self.gradient.selected = sel #gradient editor needs to know selection index
1226
+ self.gradient.label = self.label
1227
+ self.add(self.gradient) #Add control list
1228
+
1229
+ def controls(self):
1230
+ html = self.list.controls() + self.gradient.controls()
1231
+ html = html.replace('---PALLID---', str(self.gradient.elid))
1232
+ return html + '<br>'
1233
+
1234
+ class TimeStepper(Range):
1235
+ """A time step selection range control with up/down buttons
1236
+ """
1237
+ def __init__(self, viewer, *args, **kwargs):
1238
+ #Acts as a command setter with some additional controls
1239
+ super(TimeStepper, self).__init__(target=viewer, label="Timestep", command="timestep", readproperty="timestep", *args, **kwargs)
1240
+
1241
+ self.timesteps = viewer.timesteps()
1242
+ self.step = 1
1243
+ if len(self.timesteps):
1244
+ self.range = (self.timesteps[0], self.timesteps[-1])
1245
+ #Calculate step gap
1246
+ self.step = self.timesteps[1] - self.timesteps[0]
1247
+ else:
1248
+ self.range = (0,0)
1249
+ self.value = 0
1250
+
1251
+ def controls(self):
1252
+ html = Range.controls(self)
1253
+ #Note: unicode symbol escape must use double slash to be
1254
+ # passed through to javascript or python will process them
1255
+ html += """
1256
+ <script>
1257
+ var timer_---ELID--- = -1;
1258
+ function startTimer_---ELID---() {
1259
+ if (timer_---ELID--- >= 0) {
1260
+ if (timer_---ELID--- > 0)
1261
+ window.cancelAnimationFrame(timer_---ELID---);
1262
+ timer_---ELID--- = window.requestAnimationFrame(nextStep_---ELID---);
1263
+ }
1264
+ }
1265
+ function nextStep_---ELID---() {
1266
+ el = document.getElementById('---ELID---_number');
1267
+ if (el) {
1268
+ //Call again on image load - pass callback
1269
+ var V = _wi['---VIEWERID---'];
1270
+ if (!V.box.canvas.mouse.isdown && !V.box.zoomTimer && (!V.box.gui || V.box.gui.closed))
1271
+ V.execute("next", startTimer_---ELID---);
1272
+ else
1273
+ setTimeout(nextStep_---ELID---, 100);
1274
+ }
1275
+ }
1276
+ function playPause_---ELID---(btn) {
1277
+ if (timer_---ELID--- >= 0) {
1278
+ btn.value="\\u25BA";
1279
+ window.cancelAnimationFrame(timer_---ELID---);
1280
+ timer_---ELID--- = -1;
1281
+ } else {
1282
+ timer_---ELID--- = 0;
1283
+ startTimer_---ELID---();
1284
+ btn.value="\\u25FE";
1285
+ }
1286
+ }
1287
+ </script>
1288
+ """
1289
+ html += '<input type="button" style="width: 50px;" onclick="var el = document.getElementById(\'---ELID---_range\'); el.stepDown(); el.onchange()" value="&larr;" />'
1290
+ html += '<input type="button" style="width: 50px;" onclick="var el = document.getElementById(\'---ELID---_range\'); el.stepUp(); el.onchange()" value="&rarr;" />'
1291
+ html += '<input type="button" style="width: 60px;" onclick="playPause_---ELID---(this);" value="&#9658;" />'
1292
+ html = html.replace('---ELID---', self.elid)
1293
+ return html
1294
+
1295
+ class DualRange(_MultiControl):
1296
+ """A set of two range slider controls for adjusting a minimum and maximum range
1297
+
1298
+ Parameters
1299
+ ----------
1300
+ range : list or tuple
1301
+ Min/max values for the range
1302
+ """
1303
+ def __init__(self, target, properties, values=[None,None], label=None, range=(0.,1.), step=None):
1304
+ super(DualRange, self).__init__(target)
1305
+ self.label = label
1306
+
1307
+ self.ctrlmin = self.add(Range(target=target, property=properties[0], step=step, value=values[0], range=range, label=""))
1308
+ self.ctrlmax = self.add(Range(target=target, property=properties[1], step=step, value=values[1], range=range, label=""))
1309
+
1310
+ class Range2D(_MultiControl):
1311
+ """A set of two range slider controls for adjusting a 2D value
1312
+
1313
+ Parameters
1314
+ ----------
1315
+ range : list or tuple
1316
+ Min/max values for the ranges
1317
+ """
1318
+ def __init__(self, target, property, label=None, value=None, range=(0.,1.), step=None, *args, **kwargs):
1319
+ super(Range2D, self).__init__(target, property=property, command=None, value=value, label=label, *args, **kwargs)
1320
+
1321
+ if self._value is None:
1322
+ self._value = _getproperty(target, property)
1323
+
1324
+ self.ctrlX = self.add(Range(target=target, property=property, step=step, value=self._value[0], range=range, label="", index=0))
1325
+ self.ctrlY = self.add(Range(target=target, property=property, step=step, value=self._value[1], range=range, label="", index=1))
1326
+
1327
+ class Range3D(_MultiControl):
1328
+ """A set of three range slider controls for adjusting a 3D value
1329
+
1330
+ Parameters
1331
+ ----------
1332
+ range : list or tuple
1333
+ Min/max values for the ranges
1334
+ """
1335
+ def __init__(self, target, property, label=None, value=None, range=(0.,1.), step=None, *args, **kwargs):
1336
+ super(Range3D, self).__init__(target, property=property, command=None, value=value, label=label, *args, **kwargs)
1337
+
1338
+ if self._value is None:
1339
+ self._value = _getproperty(target, property)
1340
+
1341
+ self.ctrlX = self.add(Range(target=target, property=property, step=step, value=self._value[0], range=range, label="", index=0))
1342
+ self.ctrlY = self.add(Range(target=target, property=property, step=step, value=self._value[1], range=range, label="", index=1))
1343
+ self.ctrlZ = self.add(Range(target=target, property=property, step=step, value=self._value[2], range=range, label="", index=2))
1344
+
1345
+ class Filter(DualRange):
1346
+ """A set of two range slider controls for adjusting a minimum and maximum filter range
1347
+
1348
+ Parameters
1349
+ ----------
1350
+ range : list or tuple
1351
+ Min/max values for the filter range
1352
+ """
1353
+ def __init__(self, target, filteridx, label=None, range=None, step=None):
1354
+ self.label = label
1355
+ if len(target["filters"]) <= filteridx:
1356
+ print("Filter index out of range: ", filteridx, len(target["filters"]))
1357
+ return None
1358
+ self.filter = target["filters"][filteridx]
1359
+
1360
+ #Default label - data set name
1361
+ if label is None:
1362
+ label = self.filter['by'].capitalize()
1363
+
1364
+ #Get the default range limits from the matching data source
1365
+ fname = self.filter['by']
1366
+ if not target["data"] or not "minimum" in target["data"][fname]:
1367
+ #Attempt to update
1368
+ target.reload()
1369
+ target.parent.render()
1370
+
1371
+ if not target["data"]:
1372
+ raise(ValueError("target data list empty, can't use filter"))
1373
+ if not fname in target['data']:
1374
+ raise(ValueError(fname + " not found in target data list, can't use filter"))
1375
+ self.data = target["data"][fname]
1376
+ if not range:
1377
+ #self.range = (self.filter["minimum"], self.filter["maximum"])
1378
+ if self.filter["map"]:
1379
+ range = (0.,1.)
1380
+ else:
1381
+ range = (self.data["minimum"]*0.99, self.data["maximum"]*1.01)
1382
+
1383
+ #Setup DualRange using filter min/max
1384
+ super(Filter, self).__init__(_getviewer(target), properties=[None, None], values=[self.filter["minimum"], self.filter["maximum"]], label=label, range=range)
1385
+
1386
+ #Replace actions on the controls
1387
+ self.ctrlmin.action = _FilterAction(target, filteridx, "minimum")
1388
+ self.ctrlmax.action = _FilterAction(target, filteridx, "maximum")
1389
+
1390
+ class ObjectList(_MultiControl):
1391
+ """A set of checkbox controls for controlling visibility of all visualisation objects
1392
+ """
1393
+ def __init__(self, viewer, *args, **kwargs):
1394
+ super(ObjectList, self).__init__(viewer, label="Objects", *args, **kwargs)
1395
+ for obj in viewer.objects.list:
1396
+ self.add(ColourIndicator(obj, "colour"))
1397
+ self.add(Checkbox(obj, "visible", label=obj["name"]))
1398
+
1399
+ #TODO: broken
1400
+ class ObjectSelect(_Container):
1401
+ """A list selector of all visualisation objects that can be used to
1402
+ choose the target of a set of controls
1403
+
1404
+ Parameters
1405
+ ----------
1406
+ objects : list
1407
+ A list of objects to display, by default all available objects are added
1408
+ """
1409
+ def __init__(self, viewer, objects=None, *args, **kwargs):
1410
+ if not _isviewer(viewer):
1411
+ print("Can't add ObjectSelect control to an Object, must add to Viewer")
1412
+ return
1413
+ self.parent = viewer
1414
+ if objects is None:
1415
+ objects = viewer.objects.list
1416
+
1417
+ #Load maps list
1418
+ options = [(0, "None")]
1419
+ for o in range(len(objects)):
1420
+ obj = objects[o]
1421
+ options += [(o+1, obj["name"])]
1422
+
1423
+ #The list control
1424
+ self._list = List(target=viewer, label="", options=options, command="select", *args, **kwargs)
1425
+
1426
+ #Init container
1427
+ super(ObjectSelect, self).__init__(viewer) #, label="Objects", options=options, command="select", *args, **kwargs)
1428
+
1429
+ #Holds a control factory so controls can be added with this as a target
1430
+ self.control = _ControlFactory(self)
1431
+
1432
+ #def onchange(self):
1433
+ # #Update the control values on change
1434
+ # #return super(ObjectSelect, self).onchange()
1435
+ # return self._list.onchange()
1436
+
1437
+ def __contains__(self, key):
1438
+ #print "CONTAINS",key
1439
+ obj = self._list.action.lastvalue
1440
+ #print "OBJECT == ",obj,(key in self.parent.objects.list[obj-1])
1441
+ return obj > 0 and key in self.parent.objects.list[obj-1]
1442
+
1443
+ def __getitem__(self, key):
1444
+ #print "GETITEM",key
1445
+ obj = self._list.action.lastvalue
1446
+ if obj > 0:
1447
+ #Passthrough: Get from selected object
1448
+ return self.parent.objects.list[obj-1][key]
1449
+ return None
1450
+
1451
+ def __setitem__(self, key, value):
1452
+ obj = self._list.action.lastvalue
1453
+ #print "SETITEM",key,value
1454
+ if obj > 0:
1455
+ #Passtrough: Set on selected object
1456
+ self.parent.objects.list[obj-1][key] = value
1457
+
1458
+ #Undefined method call - pass call to target
1459
+ def __getattr__(self, key):
1460
+ #__getattr__ called if no attrib/method found
1461
+ def any_method(*args, **kwargs):
1462
+ #If member function exists on target, call it
1463
+ obj = self._list.action.lastvalue
1464
+ if obj > 0:
1465
+ method = getattr(self.parent.objects.list[obj-1], key, None)
1466
+ if method and callable(method):
1467
+ return method(*args, **kwargs)
1468
+ return any_method
1469
+
1470
+ def html(self):
1471
+ html = '<div style="border: #888 1px solid; display: inline-block; padding: 6px;" class="lvctrl">\n'
1472
+ html += self._list.controls()
1473
+ html += '<hr>\n'
1474
+ html += super(ObjectSelect, self).html()
1475
+ html += '</div>\n'
1476
+ return html
1477
+
1478
+ def controls(self):
1479
+ return self.html()
1480
+
1481
+ class _ControlFactory(object):
1482
+ """
1483
+ Create and manage sets of controls for interaction with a Viewer or Object
1484
+ Controls can run commands or change properties
1485
+ """
1486
+ #Creates a control factory used to generate controls for a specified target
1487
+ def __init__(self, target):
1488
+ self._target = weakref.ref(target)
1489
+ self.clear()
1490
+ self.interactor = False
1491
+ #self.output = ""
1492
+
1493
+ #Save types of all control/containers
1494
+ def all_subclasses(cls):
1495
+ return cls.__subclasses__() + [g for s in cls.__subclasses__() for g in all_subclasses(s)]
1496
+
1497
+ #_Control contructor shortcut methods
1498
+ #(allows constructing controls directly from the factory object)
1499
+ #Use a closure to define a new method to call constructor and add to controls
1500
+ def addmethod(constr):
1501
+ def method(*args, **kwargs):
1502
+ #Return the new control and add it to the list
1503
+ newctrl = constr(self._target(), *args, **kwargs)
1504
+ self.add(newctrl)
1505
+ return newctrl
1506
+ return method
1507
+
1508
+ self._control_types = all_subclasses(_Control)
1509
+ self._container_types = all_subclasses(_Container)
1510
+ for constr in self._control_types + self._container_types:
1511
+ key = constr.__name__
1512
+ method = addmethod(constr)
1513
+ #Set docstring (+ _Control docs)
1514
+ if constr in self._control_types:
1515
+ method.__doc__ = constr.__doc__ + _Control.__doc__
1516
+ else:
1517
+ method.__doc__ = constr.__doc__
1518
+ self.__setattr__(key, method)
1519
+
1520
+ def __call__(self, property, *args, **kwargs):
1521
+ """
1522
+ Calling with a property name creates the default control for that property
1523
+ """
1524
+ _lv = _getviewer(self._target())
1525
+ if property is not None and property in _lv.properties:
1526
+ #Get control info from prop dict
1527
+ prop = _lv.properties[property]
1528
+ T = prop["type"]
1529
+ ctrl = prop["control"]
1530
+ if len(ctrl) > 2 and len(ctrl[2]) > 1:
1531
+ #Has selections
1532
+ return self.List(property, *args, **kwargs)
1533
+ if "integer" in T or "real" in T:
1534
+ hasrange = len(ctrl) > 1 and len(ctrl[1]) == 3
1535
+ #Multi-dimensional?
1536
+ if "[2]" in T:
1537
+ return self.Range2D(property, *args, **kwargs) if hasrange else self.Number2D(property, *args, **kwargs)
1538
+ elif "[3]" in T:
1539
+ return self.Range3D(property, *args, **kwargs) if hasrange else self.Number3D(property, *args, **kwargs)
1540
+ else:
1541
+ return self.Range(property, *args, **kwargs) if hasrange else self.Number(property, *args, **kwargs)
1542
+ elif T == "string":
1543
+ return self.Entry(property, *args, **kwargs)
1544
+ elif T == "boolean":
1545
+ return self.Checkbox(property, *args, **kwargs)
1546
+ elif T == "colour":
1547
+ return self.Colour(property, *args, **kwargs)
1548
+ else:
1549
+ print("Unable to determine control type for property: " + property)
1550
+ print(prop)
1551
+
1552
+ def add(self, ctrl):
1553
+ """
1554
+ Add a control
1555
+ """
1556
+ #Just add to the parent lists if not a viewer
1557
+ if not _isviewer(self._target()):
1558
+ self._target().parent.control.add(ctrl)
1559
+ else:
1560
+ if type(ctrl) in self._container_types:
1561
+ #New container, further controls will be added to it
1562
+ self._containers.append(ctrl)
1563
+ else:
1564
+ #Normal control: add to active container
1565
+ self._containers[-1].add(ctrl)
1566
+
1567
+ return ctrl
1568
+
1569
+ def show(self, menu=True, filename=None, fallback=None):
1570
+ """
1571
+ Displays all added controls including viewer if any
1572
+
1573
+ Parameters
1574
+ ----------
1575
+ filename : str
1576
+ Filename to write output HTML
1577
+ If in a notebook this is not necessary as content is displayed inline
1578
+ If not provided and run outside IPython, defaults to "control.html"
1579
+ fallback : function
1580
+ A function which is called in place of the viewer display method when run outside IPython
1581
+ """
1582
+ #Show all controls in container
1583
+ target = self._target()
1584
+
1585
+ #Creates an interactor to connect javascript/html controls to IPython and viewer
1586
+ #if no viewer Window() created, it will be a windowless interactor
1587
+ if _isviewer(target):
1588
+ #Append the current viewer ref
1589
+ windows.append(target)
1590
+ #Get unique ID
1591
+ winids.append(gen_id())
1592
+ else:
1593
+ target.parent.control.show()
1594
+ return
1595
+
1596
+ viewerid = winids[-1]
1597
+
1598
+ #Generate the HTML and associated action JS
1599
+ html = "<form novalidate>"
1600
+ chtml = ""
1601
+ actions = {}
1602
+ for con in self._containers:
1603
+ html += con.html()
1604
+ actions.update(con.scripts())
1605
+ html += "</form>"
1606
+ #print('\n'.join([str(a)+":"+actions[a] for a in actions]))
1607
+
1608
+ #Auto-clear now content generated
1609
+ #Prevents doubling up if cell executed again
1610
+ self.clear()
1611
+
1612
+ #Set viewer id
1613
+ html = html.replace('---VIEWERID---', str(viewerid))
1614
+ #self.output += html
1615
+
1616
+ code = _webglboxcode(menu)
1617
+
1618
+ #Display HTML inline or export
1619
+ if is_notebook() and filename is None:
1620
+ if not target.server:
1621
+ raise(Exception("LavaVu HTTP Server must be active for interactive controls, set port= parameter to > 0"))
1622
+ """
1623
+ HTTP server mode interaction, rendering in separate render thread:
1624
+ - This should work in all notebook contexts, colab, jupyterlab etc
1625
+ - Only problem remaining is port access, from docker or cloud instances etc, need to forward port
1626
+ - jupyter-server-proxy packaged (available on pip) supports forwarding port via the jupyter server
1627
+ """
1628
+ from IPython.display import display,HTML
1629
+ #Interaction requires some additional js/css/webgl
1630
+ #Insert stylesheet, shaders and combined javascript libraries
1631
+ display(HTML(code))
1632
+
1633
+ #Try and prevent this getting cached
1634
+ timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
1635
+ html += "<!-- CREATION TIMESTAMP {0} -->".format(timestamp)
1636
+
1637
+ #Pass port and object id from server
1638
+ html += "<script>init('{0}');</script>".format(viewerid)
1639
+ actionjs = self.export_actions(actions, id(target), target.port)
1640
+ #Output the controls and start interactor
1641
+ display(HTML(actionjs + html))
1642
+
1643
+ else:
1644
+ #Export standalone html
1645
+ #return as string or write output to .html file in current directory
1646
+ template = basehtml
1647
+ template = template.replace('---SCRIPTS---', code + self.export_actions(actions)) #Process actions
1648
+ template = template.replace('---INIT---', 'init(\'{0}\');'.format(viewerid))
1649
+ template = template.replace('---CONTENT---', html)
1650
+ full_html = template
1651
+
1652
+ if filename == '':
1653
+ #Just return code
1654
+ return full_html
1655
+ else:
1656
+ #Write the file
1657
+ if filename is None:
1658
+ filename = "control.html"
1659
+ hfile = open(filename, "w")
1660
+ hfile.write(full_html)
1661
+ hfile.close()
1662
+
1663
+ if callable(fallback):
1664
+ fallback(target)
1665
+
1666
+ def export_actions(self, actions, uid=0, port=0, proxy=False):
1667
+ #Process actions
1668
+ topjs = '<script>\n'
1669
+ if port > 0:
1670
+ topjs += 'function init(viewerid) {{_wi[viewerid] = new WindowInteractor(viewerid, {uid}, {port});\n'.format(uid=uid, port=port)
1671
+ else:
1672
+ topjs += 'function init(viewerid) {{_wi[viewerid] = new WindowInteractor(viewerid, {uid});\n'.format(uid=uid)
1673
+
1674
+ topjs += '_wi[viewerid].actions = {\n'
1675
+
1676
+ actionjs = ''
1677
+ for act in actions:
1678
+ #Add to action functions to list, each takes the value of the control when called
1679
+ if len(actionjs):
1680
+ actionjs += ',\n'
1681
+ actionjs += ' "' + str(act) + '" : "' + actions[act] + '"';
1682
+
1683
+ #Add init and finish
1684
+ return topjs + actionjs + '};\n}\n</script>\n'
1685
+
1686
+ def redisplay(self):
1687
+ """Update the active viewer image if any
1688
+ Applies changes made in python to the viewer and forces a redisplay
1689
+ """
1690
+ if not is_notebook():
1691
+ return
1692
+ from IPython.display import display,HTML,Javascript
1693
+ display(Javascript(self.redisplay_call()))
1694
+
1695
+ def redisplay_call(self):
1696
+ """Update the active viewer image if any
1697
+ Applies changes made in python to the viewer and forces a redisplay
1698
+ """
1699
+ #Find matching viewer id, redisplay first match
1700
+ ids = self.active_viewers()
1701
+ return ';'.join('_wi["{0}"].redisplay()'.format(i) for i in ids)
1702
+
1703
+ def active_viewers(self):
1704
+ """Return matching active viewer IDs
1705
+ """
1706
+ #Find matching viewer ids, all that match
1707
+ ids = []
1708
+ for idx,obj in enumerate(windows):
1709
+ if obj == self._target():
1710
+ ids.append(winids[idx])
1711
+ return ids
1712
+
1713
+ def update(self):
1714
+ """Update the control values from current viewer data
1715
+ Applies changes made in python to the UI controls
1716
+ """
1717
+ #NOTE: to do this now, all we need is to trigger a get_state call from interactor by sending any command
1718
+ #TODO: currently only works for controllers with a window, other controls will not be updated
1719
+ if not is_notebook():
1720
+ return
1721
+ #Find matching viewer id, update all that match
1722
+ for idx,obj in enumerate(windows):
1723
+ #if obj == self._target():
1724
+ viewerid = winids[idx]
1725
+ from IPython.display import display,HTML
1726
+ display(HTML('<script>_wi["{0}"].execute(" ");</script>'.format(viewerid)))
1727
+
1728
+ def clear(self):
1729
+ #Initialise with a default control wrapper
1730
+ self._containers = [_Container(self)]
1731
+