/
no_notebook.py
219 lines (186 loc) · 7.87 KB
/
no_notebook.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
from .vpython import *
from http.server import BaseHTTPRequestHandler, HTTPServer
import os
import platform
import threading
import json
import webbrowser as _webbrowser
import asyncio
from autobahn.asyncio.websocket import WebSocketServerProtocol, WebSocketServerFactory
import signal
from urllib.parse import unquote
# Check for Ctrl+C
def signal_handler(signal, frame):
os._exit(0)
signal.signal(signal.SIGINT, signal_handler)
# Requests from client to http server can be the following:
# get glowcomm.html, library .js files, images, or font files
import socket
def find_free_port():
s = socket.socket()
s.bind(('',0)) # find an available port
return s.getsockname()[1]
__HTTP_PORT = find_free_port()
__SOCKET_PORT = find_free_port()
try:
if platform.python_implementation() == 'PyPy':
__SOCKET_PORT = 9000 + __SOCKET_PORT % 1000 # use port number between 9000 and 9999 for PyPy
except:
pass
# try: # machinery for reusing ports
# fd = open('free_ports')
# __HTTP_PORT = int(fd.readline())
# __SOCKET_PORT = int(fd.readline())
# except:
# __HTTP_PORT = find_free_port()
# __SOCKET_PORT = find_free_port()
# fd = open('free_ports', 'w') # this writes to user program's directory
# fd.write(str(__HTTP_PORT))
# fd.write('\n')
# fd.write(str(__SOCKET_PORT))
# Make it possible for glowcomm.html to find out what the websocket port is:
js = __file__.replace('no_notebook.py','vpython_libraries'+os.sep+'glowcomm.html')
fd = open(js)
glowcomm = fd.read()
fd.close()
glowcomm = glowcomm.replace('XXX',str(__SOCKET_PORT)) # provide glowcomm.html with socket number
httpserving = False
websocketserving = False
class serveHTTP(BaseHTTPRequestHandler):
serverlib = __file__.replace('no_notebook.py','vpython_libraries')
serverdata = __file__.replace('no_notebook.py','vpython_data')
mimes = {'html':['text/html', serverlib],
'js' :['application/javascript', serverlib],
'css':['text/css', serverlib],
'jpg':['image/jpg', serverdata],
'png':['image/png', serverlib],
'otf':['application/x-font-otf', serverdata],
'ttf':['application/x-font-ttf', serverdata],
'ico':['image/x-icon', serverdata]}
def do_GET(self):
global httpserving
httpserving = True
html = False
if self.path == "/":
self.path = 'glowcomm.html'
html = True
elif self.path[0] == "/":
self.path = os.sep+self.path[1:]
f = self.path.rfind('.')
fext = None
if f > 0: fext = self.path[f+1:]
if fext in self.mimes:
mime = self.mimes[fext]
# For example, mime[0] is image/jpg,
# mime[1] is C:\Users\Bruce\Anaconda3\lib\site-packages\vpython\vpython_data
self.send_response(200)
self.send_header('Content-type', mime[0])
self.end_headers()
if not html:
path = unquote(self.path) # convert %20 to space, for example
# Now path can be for example \Fig 4.6.jpg
cwd = os.getcwd() # user current working directory, e.g. D:\Documents\0GlowScriptWork\LocalServer
loc = cwd + path
if not os.path.isfile(loc):
loc = mime[1] + path # look in vpython_data
fd = open(loc, 'rb')
self.wfile.write(fd.read())
else:
# string.encode() is not available in Python 2.7, but neither is async
self.wfile.write(glowcomm.encode('utf-8'))
def log_message(self, format, *args): # this overrides server stderr output
return
# Requests from client to websocket server can be the following:
# trigger event; return data (constructors, attributes, methods)
# other event; pause, waitfor, pick, compound
class WSserver(WebSocketServerProtocol):
# Data sent and received must be type "bytes", so use string.encode and string.decode
connection = None
def onConnect(self, request):
self.connection = self
def onOpen(self):
global websocketserving
websocketserving = True
# For Python 3.5 and later, the newer syntax eliminates "@asyncio.coroutine"
# in favor of "async def onMessage...", and "yield from" with "await".
# Attempting to use the older Python 3.4 syntax was not successful, so this
# no-notebook version of VPython requires Python 3.5.3 or later.
#@asyncio.coroutine
#def onMessage(self, data, isBinary): # data includes canvas update, events, pick, compound
async def onMessage(self, data, isBinary): # data includes canvas update, events, pick, compound
baseObj.handle_attach() # attach arrow and attach trail
baseObj.sent = False # tell main thread that we're preparing to send data to browser
while True:
try:
objdata = copy.deepcopy(baseObj.updates)
attrdata = copy.deepcopy(baseObj.attrs)
baseObj.initialize() # reinitialize baseObj.updates
break
except:
pass
for a in attrdata: # a is [idx, attr]
idx, attr = a
val = getattr(baseObj.object_registry[idx], attr)
if type(val) is vec: val = [val.x, val.y, val.z]
if idx in objdata['attrs']:
objdata['attrs'][idx][attr] = val
else:
objdata['attrs'][idx] = {attr:val}
objdata = baseObj.package(objdata)
jdata = json.dumps(objdata, separators=(',', ':')).encode('utf_8')
self.sendMessage(jdata, isBinary=False)
baseObj.sent = True
if data != b'trigger': # b'trigger' just asks for updates
d = json.loads(data.decode("utf_8")) # update_canvas info
for m in d:
# Must send events one at a time to GW.handle_msg because bound events need the loop code:
msg = {'content':{'data':[m]}} # message format used by notebook
loop = asyncio.get_event_loop()
await loop.run_in_executor(None, GW.handle_msg, msg)
def onClose(self, wasClean, code, reason):
#print("Server WebSocket connection closed: {0}".format(reason))
self.connection = None
os._exit(0)
try:
if platform.python_implementation() == 'PyPy':
server_address = ('', 0) # let HTTPServer choose a free port
__server = HTTPServer(server_address, serveHTTP)
port = __server.server_port # get the chosen port
_webbrowser.open('http://localhost:{}'.format(port)) # or webbrowser.open_new_tab()
else:
__server = HTTPServer(('', __HTTP_PORT), serveHTTP)
_webbrowser.open('http://localhost:{}'.format(__HTTP_PORT)) # or webbrowser.open_new_tab()
except:
pass
__w = threading.Thread(target=__server.serve_forever)
__w.start()
__factory = WebSocketServerFactory(u"ws://localhost:{}/".format(__SOCKET_PORT))
__factory.protocol = WSserver
__interact_loop = asyncio.get_event_loop()
__coro = __interact_loop.create_server(__factory, '0.0.0.0', __SOCKET_PORT)
__interact_loop.run_until_complete(__coro)
# server.handle_request() is a single-shot interaction
# one-shot interact:
#interact_loop.stop()
#interact_loop.run_forever()
# Need to put interact loop inside a thread in order to get a display
# in the absence of a loop containing a rate statement.
__t = threading.Thread(target=__interact_loop.run_forever)
__t.start()
while not (httpserving and websocketserving): # try to make sure setup is complete
rate(60)
GW = GlowWidget()
scene = canvas()
# This must come after creating a canvas
class MISC(baseObj):
def __init__(self):
super(MISC, self).__init__()
def prnt(self, s):
self.addmethod('GSprint', s)
__misc = MISC()
def GSprint(*args):
s = ''
for a in args:
s += str(a)+' '
s = s[:-1]
__misc.prnt(s)