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/aserver.py ADDED
@@ -0,0 +1,359 @@
1
+ import urllib
2
+ import os
3
+ import time
4
+ import errno
5
+ import weakref
6
+ import base64
7
+ import json
8
+ import socket
9
+
10
+ #ASYNC
11
+ import asyncio
12
+ import aiohttp
13
+ from aiohttp import web
14
+ import logging
15
+
16
+ from urllib.parse import unquote
17
+
18
+ """
19
+ HTTP Server interface
20
+ """
21
+
22
+ def _get_viewer(ref):
23
+ #Get from weak reference, if deleted raise exception
24
+ lv = ref()
25
+ if not lv:
26
+ raise(Exception("Viewer not found"))
27
+ return lv
28
+
29
+ def _execute(lv, cmds):
30
+ if len(cmds) and cmds[0] == '_':
31
+ #base64 encoded commands or JSON state
32
+ cmds = str(base64.b64decode(cmds).decode('utf-8'))
33
+ #cmds = str(base64.b64decode(cmds), 'utf-8')
34
+
35
+ #Object to select can be provided in preceding angle brackets
36
+ selobj = None
37
+ if cmds[0] == '<':
38
+ pos = cmds.find('>')
39
+ selobj = lv.objects[cmds[1:pos]]
40
+ cmds = cmds[pos+1:]
41
+
42
+ #Execute commands via python API by preceding with '.'
43
+ done = False
44
+ if cmds[0] == '.':
45
+ attr = cmds.split()[0][1:]
46
+ pos = cmds.find(' ')
47
+ params = cmds[pos+1:]
48
+ if selobj:
49
+ #Call on Object
50
+ func = getattr(selobj, attr)
51
+ if func and callable(func):
52
+ func(params)
53
+ done = True
54
+ else:
55
+ #Call on Viewer
56
+ func = getattr(lv, attr)
57
+ if func and callable(func):
58
+ func(params)
59
+ done = True
60
+ elif cmds[0] == '$':
61
+ #Requests prefixed by '$' are sent
62
+ #from property collection controls
63
+ #format is $ID KEY VALUE
64
+ # - ID is the python id() of the properties object
65
+ # All properties collections are stored on their parent
66
+ # object using this id in the _collections dict
67
+ # - KEY is the property name key to set
68
+ # - VALUE is a json string containing the value to set
69
+ S = cmds.split()
70
+ target = S[0][1:]
71
+ if target in lv._collections:
72
+ #Get from _collections by id (weakref)
73
+ props = lv._collections[target]()
74
+ props[S[1]] = json.loads(S[2])
75
+ #Check for callback - if provided, call with updated props
76
+ func = getattr(props, 'callback')
77
+ if func and callable(func):
78
+ func(props)
79
+
80
+ #Default, call via lv.commands() scripting API
81
+ if not done:
82
+ if selobj:
83
+ selobj.select()
84
+ lv.commands(cmds)
85
+
86
+ headers = {'Access-Control-Allow-Origin' : '*',
87
+ 'x-colab-notebook-cache-control' : 'no-cache'} #Colab: disable offline access cache
88
+
89
+ def img_response(lv, query={}):
90
+ global headers
91
+ resp = None
92
+ if 'width' in query and 'height' in query:
93
+ resp = lv.jpeg(resolution=(int(query['width']), int(query['height'])))
94
+ elif 'width' in query:
95
+ resp = lv.jpeg(resolution=(int(query['width']), 0))
96
+ else:
97
+ resp = lv.jpeg()
98
+
99
+ #Ensure the response is valid before serving
100
+ if resp is not None:
101
+ return web.Response(body=resp, content_type='image/jpeg', headers=headers)
102
+ else:
103
+ return web.Response(text='', headers=headers)
104
+
105
+ async def handle_get(request):
106
+ global headers
107
+ lv = _get_viewer(request.app['viewer'])
108
+
109
+ #Default to empty OK 200 response
110
+ response = web.Response(text='', headers=headers)
111
+
112
+ #print(request.path)
113
+ #for q in request.query:
114
+ # print(q, request.query[q])
115
+
116
+ if request.path == '/':
117
+ #Index request returns full screen interactive view
118
+ lv = _get_viewer(request.app['viewer'])
119
+ w = lv.control.Window(align=None, wrapper=None, fullscreen=True)
120
+ code = lv.control.show(True, filename="")
121
+ response = web.Response(text=code, headers=headers, content_type='text/html')
122
+
123
+ elif request.path.startswith('/image'):
124
+ response = img_response(lv, request.query)
125
+
126
+ elif request.path.startswith('/command=') or request.path.startswith('/icommand='):
127
+ pos1 = request.path.find('=')
128
+ pos2 = request.path.find('?')
129
+ if pos2 < 0: pos2 = len(request.path)
130
+ cmds = unquote(request.path[pos1+1:pos2])
131
+
132
+ #Run viewer commands
133
+ _execute(lv, cmds)
134
+
135
+ #Serve image or just respond 200
136
+ if request.path.startswith('/icommand='):
137
+ response = img_response(lv, request.query)
138
+
139
+ elif request.path.startswith('/getstate'):
140
+ state = lv.app.getState()
141
+ response = web.Response(text=state, headers=headers, content_type='application/json')
142
+ elif request.path.startswith('/connect'):
143
+ if 'url' in request.query:
144
+ #Save first valid connection URL on the viewer
145
+ url = request.query['url']
146
+ if len(lv._url) == 0:
147
+ lv._url = url
148
+ uid = id(lv)
149
+ response = web.Response(text=str(uid), headers=headers)
150
+ elif request.path.startswith('/key='):
151
+ pos2 = request.path.find('&')
152
+ cmds = unquote(request.path[1:pos2])
153
+ lv.commands('key ' + cmds, True)
154
+ elif request.path.startswith('/mouse='):
155
+ pos2 = request.path.find('&')
156
+ cmds = unquote(request.path[1:pos2])
157
+ lv.commands('mouse ' + cmds, True)
158
+ elif request.path.startswith('/db'):
159
+ #Send the database
160
+ db = bytes(lv.app.serialize())
161
+ response = web.Response(body=db, headers=headers, content_type='application/octet-stream')
162
+ else:
163
+ #Serve other urls as files if available
164
+ #print("UNKNOWN - ", request.path)
165
+ path = request.path
166
+ if os.path.exists(path):
167
+ #OK to always serve files in cwd?
168
+ response = web.FileResponse(path)
169
+ else:
170
+ #Serve files from lavavu html dir
171
+ #print(' - not found in cwd')
172
+ if path[0] == '/': path = path[1:]
173
+ path2 = os.path.join(lv.htmlpath, path)
174
+ if os.path.exists(path2) and os.path.isfile(path2):
175
+ #print(' - found in htmlpath')
176
+ response = web.FileResponse(path2)
177
+
178
+ return response
179
+
180
+ async def handle_post(request):
181
+ lv = _get_viewer(request.app['viewer'])
182
+ #print("POST", request.path)
183
+ #text = await request.text()
184
+ text = ''
185
+ #Always interpret post data as commands
186
+ #(can perform other actions too based on path later if we want)
187
+ if request.body_exists:
188
+ body = await request.read()
189
+ #body = await request.text()
190
+
191
+ cmds = str(body, 'utf-8') #python3 only
192
+ #from urllib.parse import unquote
193
+ #data_string = unquote(body)
194
+ #cmds = str(data_string.decode('utf-8'))
195
+
196
+ #Run viewer commands
197
+ _execute(lv, cmds)
198
+
199
+ return web.Response(text='', headers=headers)
200
+
201
+
202
+ """
203
+ HTTP Server manager class
204
+ """
205
+ class Server(object):
206
+ def __init__(self, viewer, port=8080, ipv6=False, retries=100):
207
+ #Allows viewer to be garbage collected
208
+ self.viewer = weakref.ref(viewer)
209
+ self.ipv6 = ipv6
210
+ self.retries = retries
211
+ self._closing = False
212
+ self._lock = asyncio.Lock()
213
+
214
+ #Get port/socket before running server in synchronous code
215
+ self.socket, self.port = _listen(port, self.ipv6, self.retries)
216
+
217
+ async def run(self):
218
+ #Server run!
219
+ #Lock until the port is retrieved
220
+ async with self._lock:
221
+ await _serve(self.viewer, self.socket)
222
+
223
+ #ONLY NEED THIS IF NO LOOP EXISTS AND WE ARE MANAGING OUR OWN
224
+ #while not self._closing:
225
+ # await asyncio.sleep(3600) # sleep forever
226
+ # #await asyncio.sleep(1) # sleep forever
227
+ # print('_', end='')
228
+
229
+ #To stop, call cleanup (TODO: put this somewhere in closedown code)
230
+ #await runner.cleanup()
231
+
232
+ def _listen(port, ipv6, retries):
233
+ #Get port/socket before running server in synchronous code
234
+ #avoids race conditions over port number with subsequent code that
235
+ #tries to use server.port before it is confirmed/opened
236
+ hostidx = 0
237
+ for i in range(retries):
238
+ try:
239
+ hosts = []
240
+ socktype = socket.AF_INET
241
+ if ipv6:
242
+ hosts = ['::', 'localhost', '::1']
243
+ host = hosts[hostidx]
244
+ socktype = socket.AF_INET6
245
+ else:
246
+ hosts = ['0.0.0.0', 'localhost', '127.0.0.1']
247
+ host = hosts[hostidx]
248
+
249
+ #https://github.com/aio-libs/aiohttp/issues/1987#issuecomment-309401600
250
+ sock = socket.socket(socktype)
251
+ sock.bind((host, port))
252
+ # Aiohttp will call 'listen' inside.
253
+ # But it must be called before we actually use the port,
254
+ # any attempts to connect before the 'listen' call will
255
+ # be rejected.
256
+ sock.listen(128)
257
+ params = sock.getsockname()
258
+ port = params[1]
259
+ #print("Socket ready on host %s port %s" % (host, port))
260
+ return sock, port
261
+
262
+ except (Exception) as e:
263
+ #Try another port
264
+ if e.errno == errno.EADDRINUSE: #98
265
+ port += 1
266
+ #Try again...
267
+ elif e.errno == errno.EAFNOSUPPORT: #97 : Address family not supported by protocol
268
+ #Try next host name/address
269
+ hostidx += 1
270
+ if hostidx > 2:
271
+ #Try again without ipv6?
272
+ if ipv6:
273
+ ipv6 = False
274
+ else:
275
+ ipv6 = True
276
+ hostidx = 0
277
+ #Try again...
278
+ else:
279
+ print("Socket open failed: ", e, e.errno, host, port)
280
+
281
+ print("Failed to open socket, max retries reached")
282
+ return None, 0
283
+
284
+ async def _serve(viewer, sock):
285
+ try:
286
+ #Create web application manager
287
+ app = web.Application()
288
+ #https://github.com/aio-libs/aiohttp/issues/3287
289
+ #app.logger.manager.disable = 100
290
+ app.logger.setLevel(logging.CRITICAL)
291
+
292
+ #Store viewer
293
+ app["viewer"] = viewer
294
+ #Add routes
295
+ app.router.add_post('/{tail:.*}', handle_post),
296
+ app.router.add_get('/{tail:.*}', handle_get)
297
+
298
+ #Static routes? https://docs.aiohttp.org/en/stable/web_advanced.html
299
+ #app.add_routes([web.static('/', path_to_static_folder)])
300
+ #routes.static('/prefix', path_to_static_folder)
301
+
302
+ #app.add_routes([web.get('/', handler),
303
+ # web.post('/post', post_handler),
304
+ # web.put('/put', put_handler)])
305
+
306
+ #Can't use this, is blocking
307
+ #web.run_app(app, sock=sock)
308
+
309
+ # set up aiohttp - like run_app, but non-blocking - socket provided version
310
+ runner = aiohttp.web.AppRunner(app, access_log=None)
311
+ await runner.setup()
312
+ site = aiohttp.web_runner.SockSite(runner, sock=sock)
313
+ await site.start()
314
+
315
+ """
316
+ #This works but have to wait for the port to be allocated before using
317
+ # passing socket as above gets port in synchronous code
318
+
319
+ # set up aiohttp - like run_app, but non-blocking
320
+ runner = aiohttp.web.AppRunner(app)
321
+ await runner.setup()
322
+ site = aiohttp.web.TCPSite(runner, host=host, port=port, reuse_address=False)
323
+ await site.start()
324
+
325
+ #Get port from first entry in list of active connections
326
+ for s in runner.sites:
327
+ #print(s.name, s._port)
328
+ return s._port #Get actual port allocated
329
+ return 0
330
+ """
331
+
332
+ except (Exception) as e:
333
+ print("Server start failed: ", e)
334
+
335
+ #Ignore SIGPIPE altogether (does not apply on windows)
336
+ #import sys
337
+ #if sys.platform != 'win32':
338
+ # from signal import signal, SIGPIPE, SIG_IGN
339
+ # signal(SIGPIPE, SIG_IGN)
340
+
341
+ def serve(viewer, port=None, ipv6=False, retries=100):
342
+ s = Server(viewer, port, ipv6, retries)
343
+ #Attach to event loop
344
+ loop = asyncio.get_event_loop()
345
+ loop.create_task(s.run())
346
+ return s
347
+
348
+ """
349
+ Main entry point - run server and open browser interface
350
+ """
351
+ if __name__ == '__main__':
352
+ import lavavu
353
+ import asyncio
354
+ lv = lavavu.Viewer()
355
+
356
+ lv.browser()
357
+ lv.app.loop()
358
+ #lv.interactive()
359
+