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