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.
- lavavu/LavaVuPython.py +561 -0
- lavavu/_LavaVuPython.cpython-313-x86_64-linux-gnu.so +0 -0
- lavavu/__init__.py +15 -0
- lavavu/__main__.py +12 -0
- lavavu/amalgamate.py +15 -0
- lavavu/aserver.py +359 -0
- lavavu/control.py +1731 -0
- lavavu/convert.py +888 -0
- lavavu/dict.json +2528 -0
- lavavu/font.bin +0 -0
- lavavu/html/LavaVu-amalgamated.css +282 -0
- lavavu/html/OK-min.js +99 -0
- lavavu/html/baseviewer.js +307 -0
- lavavu/html/control.css +104 -0
- lavavu/html/control.js +340 -0
- lavavu/html/dat-gui-light-theme.css +68 -0
- lavavu/html/dat.gui.min.js +2 -0
- lavavu/html/draw.js +2259 -0
- lavavu/html/drawbox.js +1039 -0
- lavavu/html/emscripten-template.js +184 -0
- lavavu/html/emscripten.css +92 -0
- lavavu/html/favicon.ico +0 -0
- lavavu/html/gl-matrix-min.js +47 -0
- lavavu/html/gui.css +25 -0
- lavavu/html/menu.js +615 -0
- lavavu/html/server.js +226 -0
- lavavu/html/stats.min.js +5 -0
- lavavu/html/styles.css +58 -0
- lavavu/html/webview-template.html +43 -0
- lavavu/html/webview.html +43 -0
- lavavu/lavavu.py +6200 -0
- lavavu/osmesa/LavaVuPython.py +561 -0
- lavavu/osmesa/_LavaVuPython.cpython-313-x86_64-linux-gnu.so +0 -0
- lavavu/osmesa/__init__.py +0 -0
- lavavu/points.py +191 -0
- lavavu/server.py +343 -0
- lavavu/shaders/default.frag +14 -0
- lavavu/shaders/default.vert +17 -0
- lavavu/shaders/fontShader.frag +20 -0
- lavavu/shaders/fontShader.vert +18 -0
- lavavu/shaders/lineShader.frag +39 -0
- lavavu/shaders/lineShader.vert +26 -0
- lavavu/shaders/pointShader.frag +127 -0
- lavavu/shaders/pointShader.vert +53 -0
- lavavu/shaders/triShader.frag +153 -0
- lavavu/shaders/triShader.vert +49 -0
- lavavu/shaders/volumeShader.frag +400 -0
- lavavu/shaders/volumeShader.vert +5 -0
- lavavu/tracers.py +207 -0
- lavavu/vutils.py +211 -0
- lavavu_osmesa-1.9.9.dist-info/METADATA +323 -0
- lavavu_osmesa-1.9.9.dist-info/RECORD +65 -0
- lavavu_osmesa-1.9.9.dist-info/WHEEL +5 -0
- lavavu_osmesa-1.9.9.dist-info/entry_points.txt +2 -0
- lavavu_osmesa-1.9.9.dist-info/licenses/LICENSE.md +179 -0
- lavavu_osmesa-1.9.9.dist-info/top_level.txt +1 -0
- lavavu_osmesa.libs/libLLVM-17-51492e70.so +0 -0
- lavavu_osmesa.libs/libOSMesa-f6a8f160.so.8.0.0 +0 -0
- lavavu_osmesa.libs/libdrm-b0291a67.so.2.4.0 +0 -0
- lavavu_osmesa.libs/libffi-3a37023a.so.6.0.2 +0 -0
- lavavu_osmesa.libs/libglapi-520b284c.so.0.0.0 +0 -0
- lavavu_osmesa.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
- lavavu_osmesa.libs/libselinux-d0805dcb.so.1 +0 -0
- lavavu_osmesa.libs/libtinfo-3a2cb85b.so.6.1 +0 -0
- lavavu_osmesa.libs/libzstd-76b78bac.so.1.4.4 +0 -0
lavavu/points.py
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
"""
|
2
|
+
Warning! EXPERIMENTAL:
|
3
|
+
these features and functions are under development, will have bugs,
|
4
|
+
and may be heavily modified in the future
|
5
|
+
|
6
|
+
Tools for importing and working with point clouds
|
7
|
+
- OBJ loader, requires pywavefront
|
8
|
+
- PLY loader, requires plyfile
|
9
|
+
- LAS loader, requires laspy
|
10
|
+
|
11
|
+
These functions ported from my viz script repo here:
|
12
|
+
https://github.com/mivp/vizscripts/tree/master/pointcloud
|
13
|
+
More tools can be found there, including a ptx reader which handles transformations of multiple point clouds into one coord space
|
14
|
+
|
15
|
+
"""
|
16
|
+
import numpy
|
17
|
+
import os
|
18
|
+
import convert
|
19
|
+
|
20
|
+
def loadpointcloud(filename, subsample=1, dtype=numpy.float32, components=['x', 'y', 'z', 'red', 'green', 'blue', 'alpha']):
|
21
|
+
"""
|
22
|
+
Attempt to load the passed point cloud file based
|
23
|
+
Pick loader based on extension
|
24
|
+
|
25
|
+
Parameters
|
26
|
+
----------
|
27
|
+
filename, str
|
28
|
+
Full path to file to load
|
29
|
+
subsample, int
|
30
|
+
Subsample factor, sample every Nth point
|
31
|
+
dtype, numpy.dtype
|
32
|
+
Type of data
|
33
|
+
components, list
|
34
|
+
List of components per point
|
35
|
+
|
36
|
+
Returns
|
37
|
+
-------
|
38
|
+
vertices : array
|
39
|
+
numpy array x,y,z
|
40
|
+
colours : array
|
41
|
+
numpy array r,g,b or r,g,b,a
|
42
|
+
"""
|
43
|
+
fn, ext = os.path.splitext(filename)
|
44
|
+
ext = ext.lower()
|
45
|
+
if ext == '.xyz':
|
46
|
+
#Loop over lines in input file
|
47
|
+
count = 0
|
48
|
+
V = []
|
49
|
+
C = []
|
50
|
+
with open(filename, 'r') as file:
|
51
|
+
for line in file:
|
52
|
+
#Subsample?
|
53
|
+
count += 1
|
54
|
+
if subsample > 1 and count % subsample != 1: continue
|
55
|
+
|
56
|
+
if count % 10000 == 0:
|
57
|
+
print(count)
|
58
|
+
sys.stdout.flush()
|
59
|
+
|
60
|
+
#Read particle position
|
61
|
+
data = re.split(r'[,;\s]+', line.rstrip())
|
62
|
+
if len(data) < 3: continue
|
63
|
+
x = float(data[0])
|
64
|
+
y = float(data[1])
|
65
|
+
z = float(data[2])
|
66
|
+
|
67
|
+
V.append((x, y, z))
|
68
|
+
|
69
|
+
#R,G,B[,A] colour if provided
|
70
|
+
if len(data) >= 7:
|
71
|
+
C.append((int(data[3]), int(data[4]), int(data[5]), int(data[6])))
|
72
|
+
elif len(data) == 6:
|
73
|
+
C.append((int(data[3]), int(data[4]), int(data[5]), 255))
|
74
|
+
|
75
|
+
V = numpy.array(V)
|
76
|
+
C = numpy.array(C)
|
77
|
+
|
78
|
+
elif ext == '.xyzb':
|
79
|
+
inbytes = os.path.getsize(filename)
|
80
|
+
#All floats
|
81
|
+
#num = inbytes / (4 * 6) # x y z r g b
|
82
|
+
#All floats, rgba
|
83
|
+
#num = inbytes / (4 * 7) # x y z r g b a
|
84
|
+
#All doubles, rgba
|
85
|
+
#num = inbytes / (8 * 7) # x y z r g b a
|
86
|
+
#Float vert, byte colour
|
87
|
+
#num = inbytes / (4 * 3 + 4) # x y z r g b a
|
88
|
+
sz = numpy.dtype(dtype).itemsize
|
89
|
+
num = inbytes // (sz * len(components))
|
90
|
+
|
91
|
+
with open(filename, 'rb') as infile:
|
92
|
+
print("Importing " + str(num//subsample) + " points")
|
93
|
+
arr = numpy.fromfile(infile, dtype='float64')
|
94
|
+
infile.close()
|
95
|
+
|
96
|
+
arr = arr.astype(dtype).reshape(len(arr)//len(components), len(components))
|
97
|
+
if subsample > 1:
|
98
|
+
arr = arr[::subsample]
|
99
|
+
|
100
|
+
#Convert float rgba to uint32 colour
|
101
|
+
colours = None
|
102
|
+
if len(components) > 6:
|
103
|
+
colours = arr[:,3:len(components)]
|
104
|
+
colours *= 255
|
105
|
+
colours = colours.astype('uint8').ravel()
|
106
|
+
colours = colours.view(dtype='uint32')
|
107
|
+
|
108
|
+
return (arr[:,0:3], colours)
|
109
|
+
|
110
|
+
elif ext == '.obj':
|
111
|
+
print("Loading OBJ")
|
112
|
+
import pywavefront
|
113
|
+
scene = pywavefront.Wavefront(filename)
|
114
|
+
V = numpy.array(scene.vertices)
|
115
|
+
|
116
|
+
#SubSample
|
117
|
+
if subsample > 1:
|
118
|
+
V = V[::subsample]
|
119
|
+
print("Subsampled:",V.shape)
|
120
|
+
|
121
|
+
verts = V[:,0:3]
|
122
|
+
colours = V[:,3:] * 255
|
123
|
+
|
124
|
+
#Load positions and RGB
|
125
|
+
print("Creating visualisation ")
|
126
|
+
return (verts, colours)
|
127
|
+
|
128
|
+
elif ext == '.ply':
|
129
|
+
print("Loading PLY")
|
130
|
+
from plyfile import PlyData, PlyElement
|
131
|
+
plydata = PlyData.read(filename)
|
132
|
+
if plydata:
|
133
|
+
x = plydata['vertex']['x']
|
134
|
+
y = plydata['vertex']['y']
|
135
|
+
z = plydata['vertex']['z']
|
136
|
+
V = numpy.vstack((x,y,z)).reshape([3, -1]).transpose()
|
137
|
+
|
138
|
+
C = convert._get_PLY_colours(plydata.elements[0])
|
139
|
+
|
140
|
+
#SubSample
|
141
|
+
if subsample > 1:
|
142
|
+
V = V[::subsample]
|
143
|
+
if C is not None:
|
144
|
+
C = C[::subsample]
|
145
|
+
|
146
|
+
return (V, C)
|
147
|
+
|
148
|
+
elif ext == '.las' or ext == '.laz':
|
149
|
+
print("Loading LAS")
|
150
|
+
import laspy
|
151
|
+
infile = laspy.file.File(filename, mode="r")
|
152
|
+
'''
|
153
|
+
print(infile)
|
154
|
+
print(infile.point_format)
|
155
|
+
for spec in infile.point_format:
|
156
|
+
print(spec.name)
|
157
|
+
print(infile.header)
|
158
|
+
print(infile.header.dataformat_id)
|
159
|
+
|
160
|
+
print(infile.header.offset)
|
161
|
+
print(infile.header.scale)
|
162
|
+
'''
|
163
|
+
|
164
|
+
#Grab all of the points from the file.
|
165
|
+
#point_records = infile.points
|
166
|
+
#print(point_records)
|
167
|
+
#print(point_records.shape)
|
168
|
+
|
169
|
+
#Convert colours from short to uchar
|
170
|
+
if infile.red.dtype == numpy.uint16:
|
171
|
+
R = (infile.red / 255).astype(numpy.uint8)
|
172
|
+
G = (infile.green / 255).astype(numpy.uint8)
|
173
|
+
B = (infile.blue / 255).astype(numpy.uint8)
|
174
|
+
else:
|
175
|
+
R = infile.red
|
176
|
+
G = infile.green
|
177
|
+
B = infile.blue
|
178
|
+
|
179
|
+
ss = subsample
|
180
|
+
if ss > 1:
|
181
|
+
V = numpy.array([infile.x[::ss], infile.y[::ss], infile.z[::ss]])
|
182
|
+
C = numpy.array([R[::ss],G[::ss],B[::ss]])
|
183
|
+
return (V, C)
|
184
|
+
else:
|
185
|
+
V = numpy.array([infile.x, infile.y, infile.z])
|
186
|
+
C = numpy.array([R,G,B])
|
187
|
+
return (V, C)
|
188
|
+
|
189
|
+
else:
|
190
|
+
print("Unknown point cloud format, extension: ", ext)
|
191
|
+
|
lavavu/server.py
ADDED
@@ -0,0 +1,343 @@
|
|
1
|
+
import urllib
|
2
|
+
import os
|
3
|
+
import threading
|
4
|
+
import time
|
5
|
+
import errno
|
6
|
+
from functools import partial
|
7
|
+
import weakref
|
8
|
+
import base64
|
9
|
+
import json
|
10
|
+
import socket
|
11
|
+
|
12
|
+
from socketserver import ThreadingMixIn
|
13
|
+
from http.server import SimpleHTTPRequestHandler, HTTPServer
|
14
|
+
from urllib.parse import unquote
|
15
|
+
from urllib.parse import urlparse
|
16
|
+
from urllib.parse import parse_qs
|
17
|
+
|
18
|
+
"""
|
19
|
+
HTTP Server interface
|
20
|
+
"""
|
21
|
+
class LVRequestHandler(SimpleHTTPRequestHandler, object):
|
22
|
+
|
23
|
+
def __init__(self, viewer_weakref, *args, **kwargs):
|
24
|
+
#Used with partial() to provide the viewer object
|
25
|
+
try:
|
26
|
+
self._lv = viewer_weakref
|
27
|
+
super(LVRequestHandler, self).__init__(*args, **kwargs)
|
28
|
+
except (IOError) as e:
|
29
|
+
pass #Just ignore IO errors on server
|
30
|
+
if e.errno == errno.EPIPE:
|
31
|
+
# EPIPE error, ignore
|
32
|
+
pass
|
33
|
+
elif e.errno == errno.EPROTOTYPE:
|
34
|
+
# MacOS "Protocol wrong type for socket" error, ignore
|
35
|
+
pass
|
36
|
+
else:
|
37
|
+
raise e
|
38
|
+
|
39
|
+
def serveResponse(self, data, datatype):
|
40
|
+
try:
|
41
|
+
#Serve provided data, with error check for SIGPIPE (broken connection)
|
42
|
+
self.send_response(200)
|
43
|
+
self.send_header('Content-type', datatype)
|
44
|
+
self.send_header('Access-Control-Allow-Origin', '*')
|
45
|
+
self.send_header('x-colab-notebook-cache-control', 'no-cache') #Colab: disable offline access cache
|
46
|
+
self.end_headers()
|
47
|
+
if data:
|
48
|
+
self.wfile.write(data)
|
49
|
+
#This specific error sometimes occurs on windows, ConnectionError is the base class and covers a few more
|
50
|
+
#except (IOError,ConnectionAbortedError) as e:
|
51
|
+
# if isinstance(e,ConnectionAbortedError):
|
52
|
+
except (IOError,ConnectionError) as e:
|
53
|
+
if isinstance(e,ConnectionError):
|
54
|
+
pass
|
55
|
+
elif e.errno == errno.EPIPE:
|
56
|
+
# EPIPE error, ignore
|
57
|
+
pass
|
58
|
+
else:
|
59
|
+
raise e
|
60
|
+
|
61
|
+
def do_HEAD(self):
|
62
|
+
self.serveResponse(None, 'text/html')
|
63
|
+
|
64
|
+
def do_POST(self):
|
65
|
+
#Always interpret post data as commands
|
66
|
+
#(can perform other actions too based on self.path later if we want)
|
67
|
+
data_string = self.rfile.read(int(self.headers['Content-Length']))
|
68
|
+
self.serveResponse(b'', 'text/plain')
|
69
|
+
#cmds = str(data_string, 'utf-8') #python3 only
|
70
|
+
try: #Python3
|
71
|
+
from urllib.parse import unquote
|
72
|
+
data_string = unquote(data_string)
|
73
|
+
except: #Python2
|
74
|
+
from urllib import unquote
|
75
|
+
data_string = unquote(data_string).decode('utf8')
|
76
|
+
cmds = str(data_string.decode('utf-8'))
|
77
|
+
#Run viewer commands
|
78
|
+
self._execute(cmds)
|
79
|
+
|
80
|
+
def do_GET(self):
|
81
|
+
lv = self._get_viewer()
|
82
|
+
parsed = urlparse(self.path)
|
83
|
+
query = parse_qs(parsed.query)
|
84
|
+
|
85
|
+
def img_response():
|
86
|
+
resp = None
|
87
|
+
if 'width' in query and 'height' in query:
|
88
|
+
resp = lv.jpeg(resolution=(int(query['width'][0]), int(query['height'][0])))
|
89
|
+
elif 'width' in query:
|
90
|
+
resp = lv.jpeg(resolution=(int(query['width'][0]), 0))
|
91
|
+
else:
|
92
|
+
resp = lv.jpeg()
|
93
|
+
|
94
|
+
#Ensure the response is valid before serving
|
95
|
+
if resp is not None:
|
96
|
+
self.serveResponse(resp, 'image/jpeg')
|
97
|
+
|
98
|
+
if self.path.find('image') > 0:
|
99
|
+
img_response()
|
100
|
+
|
101
|
+
elif self.path.find('command=') > 0:
|
102
|
+
pos1 = self.path.find('=')
|
103
|
+
pos2 = self.path.find('?')
|
104
|
+
if pos2 < 0: pos2 = len(self.path)
|
105
|
+
cmds = unquote(self.path[pos1+1:pos2])
|
106
|
+
|
107
|
+
#Run viewer commands
|
108
|
+
self._execute(cmds)
|
109
|
+
|
110
|
+
#Serve image or just respond 200
|
111
|
+
if self.path.find('icommand=') > 0:
|
112
|
+
img_response()
|
113
|
+
else:
|
114
|
+
self.serveResponse(b'', 'text/plain')
|
115
|
+
|
116
|
+
elif self.path.find('getstate') > 0:
|
117
|
+
state = lv.app.getState()
|
118
|
+
self.serveResponse(bytearray(state, 'utf-8'), 'text/plain; charset=utf-8')
|
119
|
+
#self.serveResponse(bytearray(state, 'utf-8'), 'text/plain')
|
120
|
+
elif self.path.find('connect') > 0:
|
121
|
+
if 'url' in query:
|
122
|
+
#Save first valid connection URL on the viewer
|
123
|
+
url = query['url'][0]
|
124
|
+
if len(lv._url) == 0:
|
125
|
+
lv._url = url
|
126
|
+
uid = id(lv)
|
127
|
+
self.serveResponse(bytearray(str(uid), 'utf-8'), 'text/plain; charset=utf-8')
|
128
|
+
elif self.path.find('key=') > 0:
|
129
|
+
pos2 = self.path.find('&')
|
130
|
+
cmds = unquote(self.path[1:pos2])
|
131
|
+
lv.commands('key ' + cmds, True)
|
132
|
+
self.serveResponse(b'', 'text/plain')
|
133
|
+
elif self.path.find('mouse=') > 0:
|
134
|
+
pos2 = self.path.find('&')
|
135
|
+
cmds = unquote(self.path[1:pos2])
|
136
|
+
lv.commands('mouse ' + cmds, True)
|
137
|
+
self.serveResponse(b'', 'text/plain')
|
138
|
+
elif len(self.path) <= 1:
|
139
|
+
#Root requested, returns interactive view
|
140
|
+
w = lv.control.Window(align=None, wrapper=None)
|
141
|
+
code = lv.control.show(True, filename="")
|
142
|
+
self.serveResponse(bytearray(code, 'utf-8'), 'text/html; charset=utf-8')
|
143
|
+
else:
|
144
|
+
return SimpleHTTPRequestHandler.do_GET(self)
|
145
|
+
|
146
|
+
#Serve files from lavavu html dir
|
147
|
+
def translate_path(self, path):
|
148
|
+
lv = self._get_viewer()
|
149
|
+
if not os.path.exists(path):
|
150
|
+
#print(' - not found in cwd')
|
151
|
+
if path[0] == '/': path = path[1:]
|
152
|
+
path = os.path.join(lv.htmlpath, path)
|
153
|
+
if os.path.exists(path) and os.path.isfile(path):
|
154
|
+
#print(' - found in htmlpath')
|
155
|
+
return path
|
156
|
+
else:
|
157
|
+
#print(' - not found in htmlpath')
|
158
|
+
return SimpleHTTPRequestHandler.translate_path(self, self.path)
|
159
|
+
else:
|
160
|
+
return SimpleHTTPRequestHandler.translate_path(self, path)
|
161
|
+
|
162
|
+
#Stifle log output
|
163
|
+
def log_message(self, format, *args):
|
164
|
+
return
|
165
|
+
|
166
|
+
def _get_viewer(self):
|
167
|
+
#Get from weak reference, if deleted raise exception
|
168
|
+
lv = self._lv()
|
169
|
+
if not lv:
|
170
|
+
self._closing = True
|
171
|
+
raise(Exception("Viewer not found"))
|
172
|
+
return lv
|
173
|
+
|
174
|
+
def _execute(self, cmds):
|
175
|
+
lv = self._get_viewer()
|
176
|
+
|
177
|
+
if len(cmds) and cmds[0] == '_':
|
178
|
+
#base64 encoded commands or JSON state
|
179
|
+
cmds = str(base64.b64decode(cmds).decode('utf-8'))
|
180
|
+
#cmds = str(base64.b64decode(cmds), 'utf-8')
|
181
|
+
|
182
|
+
#Object to select can be provided in preceding angle brackets
|
183
|
+
selobj = None
|
184
|
+
if cmds[0] == '<':
|
185
|
+
pos = cmds.find('>')
|
186
|
+
selobj = lv.objects[cmds[1:pos]]
|
187
|
+
cmds = cmds[pos+1:]
|
188
|
+
|
189
|
+
#Execute commands via python API by preceding with '.'
|
190
|
+
done = False
|
191
|
+
if cmds[0] == '.':
|
192
|
+
attr = cmds.split()[0][1:]
|
193
|
+
pos = cmds.find(' ')
|
194
|
+
params = cmds[pos+1:]
|
195
|
+
if selobj:
|
196
|
+
#Call on Object
|
197
|
+
func = getattr(selobj, attr)
|
198
|
+
if func and callable(func):
|
199
|
+
func(params)
|
200
|
+
done = True
|
201
|
+
else:
|
202
|
+
#Call on Viewer
|
203
|
+
func = getattr(lv, attr)
|
204
|
+
if func and callable(func):
|
205
|
+
func(params)
|
206
|
+
done = True
|
207
|
+
elif cmds[0] == '$':
|
208
|
+
#Requests prefixed by '$' are sent
|
209
|
+
#from property collection controls
|
210
|
+
#format is $ID KEY VALUE
|
211
|
+
# - ID is the python id() of the properties object
|
212
|
+
# All properties collections are stored on their parent
|
213
|
+
# object using this id in the _collections dict
|
214
|
+
# - KEY is the property name key to set
|
215
|
+
# - VALUE is a json string containing the value to set
|
216
|
+
S = cmds.split()
|
217
|
+
target = S[0][1:]
|
218
|
+
if target in lv._collections:
|
219
|
+
#Get from _collections by id (weakref)
|
220
|
+
props = lv._collections[target]()
|
221
|
+
props[S[1]] = json.loads(S[2])
|
222
|
+
#Check for callback - if provided, call with updated props
|
223
|
+
func = getattr(props, 'callback')
|
224
|
+
if func and callable(func):
|
225
|
+
func(props)
|
226
|
+
|
227
|
+
#Default, call via lv.commands() scripting API
|
228
|
+
if not done:
|
229
|
+
if selobj:
|
230
|
+
selobj.select()
|
231
|
+
lv.commands(cmds)
|
232
|
+
|
233
|
+
#Optional thread per request version:
|
234
|
+
class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
|
235
|
+
pass
|
236
|
+
|
237
|
+
"""
|
238
|
+
HTTP Server manager class
|
239
|
+
"""
|
240
|
+
class Server(threading.Thread):
|
241
|
+
def __init__(self, viewer, port=None, ipv6=False, retries=100):
|
242
|
+
self.host = 0
|
243
|
+
if port is None:
|
244
|
+
port = 8080
|
245
|
+
self._closing = False
|
246
|
+
#Allow viewer to be garbage collected
|
247
|
+
self.viewer = weakref.ref(viewer)
|
248
|
+
self.port = port
|
249
|
+
self.retries = retries
|
250
|
+
self.maxretries = retries
|
251
|
+
self.ipv6 = ipv6
|
252
|
+
super(Server, self).__init__()
|
253
|
+
self.daemon = True #Place in background so will be closed on program exit
|
254
|
+
self._cv = threading.Condition()
|
255
|
+
|
256
|
+
def handle(self):
|
257
|
+
try:
|
258
|
+
httpd.handle_request()
|
259
|
+
except (socket.exception) as e:
|
260
|
+
#print(str(e))
|
261
|
+
pass
|
262
|
+
|
263
|
+
def run(self):
|
264
|
+
httpd = None
|
265
|
+
HTTPServer.allow_reuse_address = False
|
266
|
+
try:
|
267
|
+
# We "partially apply" our first argument to get the viewer object into LVRequestHandler
|
268
|
+
handler = partial(LVRequestHandler, self.viewer)
|
269
|
+
if self.ipv6:
|
270
|
+
HTTPServer.address_family = socket.AF_INET6
|
271
|
+
hosts = ['::', 'localhost', '::1']
|
272
|
+
host = hosts[self.host]
|
273
|
+
#httpd = HTTPServer((host, self.port), handler)
|
274
|
+
httpd = ThreadingHTTPServer((host, self.port), handler)
|
275
|
+
else:
|
276
|
+
HTTPServer.address_family = socket.AF_INET
|
277
|
+
hosts = ['0.0.0.0', 'localhost', '127.0.0.1']
|
278
|
+
host = hosts[self.host]
|
279
|
+
#httpd = HTTPServer((host, self.port), handler)
|
280
|
+
httpd = ThreadingHTTPServer(('0.0.0.0', self.port), handler)
|
281
|
+
|
282
|
+
#print("Server running on host %s port %s" % (host, self.port))
|
283
|
+
|
284
|
+
#Sync with starting thread here to ensure server thread has initialised before it continues
|
285
|
+
with self._cv:
|
286
|
+
self._cv.notifyAll()
|
287
|
+
|
288
|
+
# Handle requests
|
289
|
+
#print("Using port: ", self.port)
|
290
|
+
# A timeout is needed for server to check periodically if closing
|
291
|
+
httpd.timeout = 0.05 #50 millisecond timeout
|
292
|
+
while self.viewer() is not None and not self._closing:
|
293
|
+
httpd.handle_request()
|
294
|
+
|
295
|
+
except (Exception) as e:
|
296
|
+
self.retries -= 1
|
297
|
+
if self.retries < 1:
|
298
|
+
print("Failed to start server, max retries reached")
|
299
|
+
|
300
|
+
#Try another port
|
301
|
+
if e.errno == errno.EADDRINUSE: #98
|
302
|
+
self.port += 1
|
303
|
+
#Try again
|
304
|
+
self.run()
|
305
|
+
elif e.errno == errno.EAFNOSUPPORT: #97 : Address family not supported by protocol
|
306
|
+
#Try next host name/address
|
307
|
+
self.host += 1
|
308
|
+
if self.host > 2:
|
309
|
+
#Try again without ipv6?
|
310
|
+
if self.ipv6:
|
311
|
+
self.ipv6 = False
|
312
|
+
else:
|
313
|
+
self.ipv6 = True
|
314
|
+
self.host = 0
|
315
|
+
#Try again
|
316
|
+
self.run()
|
317
|
+
else:
|
318
|
+
print("Server start failed: ",e, e.errno, self.port)
|
319
|
+
|
320
|
+
def serve(viewer, port=None, ipv6=False, retries=100):
|
321
|
+
s = Server(viewer, port, ipv6, retries)
|
322
|
+
#Start the thread and wait for it to finish initialising
|
323
|
+
with s._cv:
|
324
|
+
s.start()
|
325
|
+
s._cv.wait()
|
326
|
+
return s
|
327
|
+
|
328
|
+
#Ignore SIGPIPE altogether (does not apply on windows)
|
329
|
+
import sys
|
330
|
+
if sys.platform != 'win32':
|
331
|
+
from signal import signal, SIGPIPE, SIG_IGN
|
332
|
+
signal(SIGPIPE, SIG_IGN)
|
333
|
+
|
334
|
+
"""
|
335
|
+
Main entry point - run server and open browser interface
|
336
|
+
"""
|
337
|
+
if __name__ == '__main__':
|
338
|
+
import lavavu
|
339
|
+
lv = lavavu.Viewer()
|
340
|
+
#lv.animate(1) #Required to show viewer window and handle mouse/keyboard events there too
|
341
|
+
lv.browser()
|
342
|
+
lv._thread.join() #Wait for server to quit
|
343
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
in vec4 aVertexPosition;
|
2
|
+
in vec4 aVertexColour;
|
3
|
+
in vec2 aVertexTexCoord;
|
4
|
+
uniform mat4 uMVMatrix;
|
5
|
+
uniform mat4 uPMatrix;
|
6
|
+
out vec4 vColour;
|
7
|
+
out vec2 vTexCoord;
|
8
|
+
flat out vec4 vFlatColour;
|
9
|
+
|
10
|
+
void main(void)
|
11
|
+
{
|
12
|
+
gl_Position = uPMatrix * uMVMatrix * aVertexPosition;
|
13
|
+
vColour = aVertexColour;
|
14
|
+
vTexCoord = aVertexTexCoord;
|
15
|
+
vFlatColour = aVertexColour;
|
16
|
+
}
|
17
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
in vec4 vColour;
|
2
|
+
in vec2 vTexCoord;
|
3
|
+
|
4
|
+
uniform bool uTextured;
|
5
|
+
uniform sampler2D uTexture;
|
6
|
+
|
7
|
+
out vec4 outColour;
|
8
|
+
|
9
|
+
void main(void)
|
10
|
+
{
|
11
|
+
vec4 fColour = vColour;
|
12
|
+
if (uTextured)
|
13
|
+
{
|
14
|
+
vec4 tColour = texture(uTexture, vTexCoord);
|
15
|
+
//Just use the alpha component (red in single component texture)
|
16
|
+
fColour.a = tColour.r;
|
17
|
+
}
|
18
|
+
outColour = fColour;
|
19
|
+
}
|
20
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
in vec4 aVertexPosition;
|
2
|
+
in vec2 aVertexTexCoord;
|
3
|
+
|
4
|
+
uniform mat4 uMVMatrix;
|
5
|
+
uniform mat4 uPMatrix;
|
6
|
+
|
7
|
+
uniform vec4 uColour;
|
8
|
+
|
9
|
+
out vec4 vColour;
|
10
|
+
out vec2 vTexCoord;
|
11
|
+
|
12
|
+
void main(void)
|
13
|
+
{
|
14
|
+
gl_Position = uPMatrix * uMVMatrix * aVertexPosition;
|
15
|
+
vColour = uColour;
|
16
|
+
vTexCoord = aVertexTexCoord;
|
17
|
+
}
|
18
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
in vec4 vColour;
|
2
|
+
in vec3 vVertex;
|
3
|
+
|
4
|
+
uniform float uOpacity;
|
5
|
+
uniform float uBrightness;
|
6
|
+
uniform float uContrast;
|
7
|
+
uniform float uSaturation;
|
8
|
+
uniform vec3 uClipMin;
|
9
|
+
uniform vec3 uClipMax;
|
10
|
+
uniform bool uOpaque;
|
11
|
+
|
12
|
+
out vec4 outColour;
|
13
|
+
|
14
|
+
void main(void)
|
15
|
+
{
|
16
|
+
//Clip planes in X/Y/Z
|
17
|
+
if (any(lessThan(vVertex, uClipMin)) || any(greaterThan(vVertex, uClipMax))) discard;
|
18
|
+
|
19
|
+
vec4 colour = vColour;
|
20
|
+
float alpha = colour.a;
|
21
|
+
if (uOpacity > 0.0) alpha *= uOpacity;
|
22
|
+
|
23
|
+
//Brightness adjust
|
24
|
+
colour += uBrightness;
|
25
|
+
//Saturation & Contrast adjust
|
26
|
+
const vec4 LumCoeff = vec4(0.2125, 0.7154, 0.0721, 0.0);
|
27
|
+
vec4 AvgLumin = vec4(0.5, 0.5, 0.5, 0.0);
|
28
|
+
vec4 intensity = vec4(dot(colour, LumCoeff));
|
29
|
+
colour = mix(intensity, colour, uSaturation);
|
30
|
+
colour = mix(AvgLumin, colour, uContrast);
|
31
|
+
colour.a = alpha;
|
32
|
+
|
33
|
+
if (alpha < 0.01) discard;
|
34
|
+
|
35
|
+
if (uOpaque)
|
36
|
+
colour.a = 1.0;
|
37
|
+
|
38
|
+
outColour = colour;
|
39
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
in vec3 aVertexPosition;
|
2
|
+
in vec4 aVertexColour;
|
3
|
+
uniform mat4 uMVMatrix;
|
4
|
+
uniform mat4 uPMatrix;
|
5
|
+
|
6
|
+
uniform int uPointDist; // Scale by distance
|
7
|
+
|
8
|
+
uniform vec4 uColour;
|
9
|
+
uniform float uOpacity;
|
10
|
+
|
11
|
+
out vec4 vColour;
|
12
|
+
out vec3 vVertex;
|
13
|
+
|
14
|
+
void main(void)
|
15
|
+
{
|
16
|
+
vec4 mvPosition = uMVMatrix * vec4(aVertexPosition, 1.0);
|
17
|
+
gl_Position = uPMatrix * mvPosition;
|
18
|
+
|
19
|
+
if (uColour.a > 0.0)
|
20
|
+
vColour = uColour;
|
21
|
+
else
|
22
|
+
vColour = vec4(aVertexColour.rgb, aVertexColour.a*uOpacity);
|
23
|
+
|
24
|
+
vVertex = aVertexPosition.xyz;
|
25
|
+
}
|
26
|
+
|