Skip to content

Commit 96abd65

Browse files
author
Aaron Titus
committed
added support for pyqt browser window for display vpython canvas
1 parent 8b53c41 commit 96abd65

File tree

2 files changed

+68
-70
lines changed

2 files changed

+68
-70
lines changed

vpython/no_notebook.py

100644100755
Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
from .vpython import GlowWidget, baseObj, vector, canvas, _browsertype
1+
from .vpython import GlowWidget, baseObj, vector, canvas
22
from ._notebook_helpers import _in_spyder, _undo_vpython_import_in_spyder
3-
#from .qtbrowser import createQtBrowser
43

54
from http.server import BaseHTTPRequestHandler, HTTPServer
65
import os
@@ -14,18 +13,13 @@
1413
import txaio
1514
import copy
1615
import socket
17-
import PyQt5.QtCore
18-
import PyQt5.QtWebEngineWidgets
19-
from PyQt5.QtWidgets import QApplication
20-
import multiprocessing
2116

2217
import signal
2318
from urllib.parse import unquote
2419

2520
from .rate_control import rate
2621

2722

28-
2923
# Check for Ctrl+C. SIGINT will also be sent by our code if WServer is closed.
3024
def signal_handler(signal, frame):
3125
stop_server()
@@ -237,25 +231,10 @@ def onClose(self, wasClean, code, reason):
237231
else:
238232
__server = HTTPServer(('', __HTTP_PORT), serveHTTP)
239233
# or webbrowser.open_new_tab()
240-
if(_browsertype=='default'): #uses default browser
241-
_webbrowser.open('http://localhost:{}'.format(__HTTP_PORT)) #uses default browser
242-
234+
_webbrowser.open('http://localhost:{}'.format(__HTTP_PORT))
243235
except:
244236
pass
245237

246-
def start_Qapp(port):
247-
# creates a python browser with PyQt5
248-
# runs qtbrowser.py in a separate process
249-
filepath=os.path.dirname(__file__)
250-
filename=filepath+'/qtbrowser.py'
251-
os.system('python '+filename+' http://localhost:{}'.format(port))
252-
253-
254-
#create a browser in its own process
255-
if(_browsertype=='pyqt'): #uses default browser
256-
__m = multiprocessing.Process(target=start_Qapp, args=(__HTTP_PORT,))
257-
__m.start()
258-
259238
__w = threading.Thread(target=__server.serve_forever)
260239
__w.start()
261240

@@ -323,6 +302,7 @@ def stop_server():
323302
# python/threading.py:_shutdown). Since we just stopped those threads,
324303
# we'll now exit.
325304

305+
326306
GW = GlowWidget()
327307

328308
while not (httpserving and websocketserving): # try to make sure setup is complete

vpython/vpython.py

100644100755
Lines changed: 65 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
'event_return', 'extrusion', 'faces', 'frame', 'gcurve', 'gdots',
2525
'ghbars', 'gobj', 'graph', 'gvbars', 'helix', 'label',
2626
'local_light', 'menu', 'meta_canvas', 'points', 'pyramid',
27-
'quad', 'radio', 'ring', 'set_browser', 'simple_sphere', 'sleep', 'slider', 'sphere',
27+
'quad', 'radio', 'ring', 'simple_sphere', 'sleep', 'slider', 'sphere',
2828
'standardAttributes', 'text', 'textures', 'triangle', 'vertex',
29-
'wtext', 'winput']
29+
'wtext', 'winput', 'keysdown']
3030

3131
__p = platform.python_version()
3232
_ispython3 = (__p[0] == '3')
@@ -40,6 +40,8 @@
4040
version = [__version__, 'jupyter']
4141
GSversion = [__gs_version__, 'glowscript']
4242

43+
keysdownlist = [] # list of keys currently pressed
44+
4345
# To print immediately, do this:
4446
# print(.....)
4547
# sys.stdout.flush()
@@ -95,7 +97,7 @@
9597
'right':'q', 'top':'r', 'bottom':'s', '_cloneid':'t',
9698
'logx':'u', 'logy':'v', 'dot':'w', 'dot_radius':'x',
9799
'markers':'y', 'legend':'z', 'label':'A', 'delta':'B', 'marker_color':'C',
98-
'size_units':'D', 'userpan':'E'}
100+
'size_units':'D', 'userpan':'E', 'scroll':'F'}
99101

100102
# methods are X in {'m': '23X....'}
101103
# pos is normally updated as an attribute, but for interval-based trails, it is updated (multiply) as a method
@@ -522,7 +524,7 @@ class standardAttributes(baseObj):
522524
['visible'],
523525
[]],
524526
'compound':[['pos', 'color', 'trail_color'],
525-
['axis', 'size', 'up'],
527+
['axis', 'size', 'up', 'origin'],
526528
['visible', 'opacity','shininess', 'emissive',
527529
'make_trail', 'trail_type', 'interval', 'texture',
528530
'retain', 'trail_color', 'trail_radius', 'obj_idxs', 'pickable'],
@@ -698,7 +700,7 @@ def setup(self, args):
698700

699701

700702
# set canvas
701-
if self.canvas == None: ## not specified in constructor
703+
if self.canvas is None: ## not specified in constructor
702704
self.canvas = canvas.get_selected()
703705
#cmd["attrs"].append({"attr": 'canvas', "value": self.canvas.idx})
704706
cmd['canvas'] = self.canvas.idx
@@ -711,7 +713,7 @@ def setup(self, args):
711713
if _special_clone is not None: cmd["_cloneid"] = _special_clone
712714
self.appendcmd(cmd)
713715

714-
# if ('frame' in args and args['frame'] != None):
716+
# if ('frame' in args and args['frame'] is not None):
715717
# frame.objects.append(self)
716718
# frame.update_obj_list()
717719

@@ -999,9 +1001,9 @@ def rotate(self, angle=None, axis=None, origin=None):
9991001
saveorigin = origin
10001002
if angle == 0:
10011003
return
1002-
if angle == None:
1004+
if angle is None:
10031005
raise TypeError('You must specify an angle through which to rotate')
1004-
if axis == None:
1006+
if axis is None:
10051007
rotaxis = self.axis
10061008
else:
10071009
rotaxis = axis
@@ -1528,32 +1530,34 @@ def size(self,value): # compound axis and size don't interact
15281530
if not self._constructing:
15291531
self.addattr('size')
15301532

1531-
def _world_zaxis(self):
1532-
axis = self._axis
1533-
up = norm(self._up)
1534-
if abs(axis.dot(up)) / sqrt(axis.mag2) > 0.98:
1535-
if abs(norm(axis).dot(vector(-1,0,0))) > 0.98:
1536-
z_axis = axis.cross(vector(0,0,1)).norm()
1537-
else:
1538-
z_axis = axis.cross(vector(-1,0,0)).norm()
1539-
else:
1540-
z_axis = axis.cross(up).norm()
1541-
return z_axis
1533+
@property
1534+
def origin(self):
1535+
return self._origin
1536+
@origin.setter
1537+
def origin(self,value): # compound origin cannot be reset
1538+
if not self._constructing:
1539+
raise AttributeError('The compound "origin" attribute is read-only; change "pos" instead.')
1540+
self._origin = value
15421541

15431542
def world_to_compound(self, v):
1544-
axis = self._axis
1545-
z_axis = self._world_zaxis()
1546-
y_axis = z_axis.cross(axis).norm()
1547-
x_axis = axis.norm()
1548-
v = v - self._pos
1549-
return vector(v.dot(x_axis), v.dot(y_axis), v.dot(z_axis))
1543+
v = v-self._pos
1544+
x_axis = self._axis.hat
1545+
y_axis = self._up.hat
1546+
z_axis = x_axis.cross(y_axis)
1547+
ox = self._size0.x/self._size.x # _size0 is the original size
1548+
oy = self._size0.y/self._size.y
1549+
oz = self._size0.z/self._size.z
1550+
return self._origin + vector(v.dot(x_axis)*ox, v.dot(y_axis)*oy, v.dot(z_axis)*oz)
15501551

15511552
def compound_to_world(self, v):
1552-
axis = self._axis
1553-
z_axis = self._world_zaxis()
1554-
y_axis = z_axis.cross(axis).norm()
1555-
x_axis = axis.norm()
1556-
return self._pos+(v.x*x_axis) + (v.y*y_axis) + (v.z*z_axis)
1553+
v = v-self._origin
1554+
x_axis = self._axis.hat
1555+
y_axis = self._up.hat
1556+
z_axis = x_axis.cross(y_axis)
1557+
ox = self._size.x/self._size0.x # _size0 is the original size
1558+
oy = self._size.y/self._size0.y
1559+
oz = self._size.z/self._size0.z
1560+
return self._pos + v.x*ox*x_axis + v.y*oy*y_axis + v.z*oz*z_axis
15571561

15581562
class vertex(standardAttributes):
15591563
def __init__(self, **args):
@@ -1961,7 +1965,7 @@ def __init__(self,*args1, **args):
19611965

19621966
super(curveMethods, self).setup(args)
19631967

1964-
if tpos != None:
1968+
if tpos is not None:
19651969
if len(args1) > 0: raise AttributeError('Malformed constructor')
19661970
self.append(tpos)
19671971
if len(args1) > 0:
@@ -1985,7 +1989,7 @@ def __init__(self,*args1, **args):
19851989

19861990
super(curveMethods, self).setup(args)
19871991

1988-
if tpos != None:
1992+
if tpos is not None:
19891993
if len(args1) > 0: raise AttributeError('Malformed constructor')
19901994
self.append(tpos)
19911995
if len(args1) > 0:
@@ -2278,6 +2282,7 @@ def __init__(self, **args):
22782282
self._title = ""
22792283
self._xtitle = ""
22802284
self._ytitle = ""
2285+
self._scroll = False
22812286
argsToSend = []
22822287

22832288
## override default vector attributes
@@ -2292,7 +2297,7 @@ def __init__(self, **args):
22922297

22932298
## override default scalar attributes
22942299
scalarAttributes = ['width', 'height', 'title', 'xtitle', 'ytitle','align',
2295-
'xmin', 'xmax', 'ymin', 'ymax', 'logx', 'logy', 'fast']
2300+
'xmin', 'xmax', 'ymin', 'ymax', 'logx', 'logy', 'fast', 'scroll']
22962301
for a in scalarAttributes:
22972302
if a in args:
22982303
argsToSend.append(a)
@@ -2305,6 +2310,12 @@ def __init__(self, **args):
23052310

23062311
cmd = {"cmd": objName, "idx": self.idx}
23072312

2313+
if self._scroll:
2314+
if not ('xmin' in argsToSend and 'xmax' in argsToSend):
2315+
raise AttributeError("For a scrolling graph, both xmin and xmax must be specified.")
2316+
if self._xmax <= self._xmin:
2317+
raise AttributeError("For a scrolling graph, xmax must be greater than xmin.")
2318+
23082319
## send only args specified in constructor
23092320
for a in argsToSend:
23102321
aval = getattr(self,a)
@@ -2318,11 +2329,16 @@ def __init__(self, **args):
23182329
def fast(self): return self._fast
23192330
@fast.setter
23202331
def fast(self,val):
2321-
# if _isnotebook and not val:
2322-
# raise AttributeError('"fast = False" is currently not available in a Jupyter notebook.')
23232332
self._fast = val
23242333
self.addattr('fast')
23252334

2335+
@property
2336+
def scroll(self): return self._scroll
2337+
@scroll.setter
2338+
def scroll(self,val):
2339+
self._scroll = val
2340+
self.addattr('scroll')
2341+
23262342
@property
23272343
def width(self): return self._width
23282344
@width.setter
@@ -2814,7 +2830,7 @@ def __init__(self, **args):
28142830

28152831
for a in canvasNonVecAttrs:
28162832
if a in args:
2817-
if args[a] != None:
2833+
if args[a] is not None:
28182834
setattr(self, '_'+a, args[a])
28192835
cmd[a]= args[a]
28202836
del args[a]
@@ -3099,11 +3115,13 @@ def objz(self, obj, operation):
30993115

31003116
## key events conflict with notebook command mode; not permitted for now
31013117
def handle_event(self, evt): ## events and scene info updates
3118+
global keysdownlist
31023119
ev = evt['event']
31033120
if ev == 'pick':
31043121
self.mouse.setpick( evt )
31053122
self._waitfor = True # what pick is looking for
31063123
elif ev == '_compound': # compound, text, extrusion
3124+
print('compound event return')
31073125
obj = self._compound
31083126
p = evt['pos']
31093127
if obj._objName == 'text':
@@ -3115,7 +3133,7 @@ def handle_event(self, evt): ## events and scene info updates
31153133
# on_change functions that detect changes in e.g. obj.pos.y
31163134
obj._pos.value = list_to_vec(p)
31173135
s = evt['size']
3118-
obj._size.value = list_to_vec(s)
3136+
obj._size.value = obj._size0 = list_to_vec(s)
31193137
obj._axis.value = obj._size._x*norm(obj._axis)
31203138
obj._up.value = list_to_vec(evt['up'])
31213139
self._waitfor = True # what compound and text and extrusion are looking for in _wait()
@@ -3182,6 +3200,8 @@ def handle_event(self, evt): ## events and scene info updates
31823200
if 'autoscale' in evt and self.userzoom and not self._set_autoscale:
31833201
self._autoscale = evt['autoscale']
31843202
self._set_autoscale = False
3203+
if 'keysdown' in evt: keysdownlist = evt['keysdown']
3204+
31853205

31863206
def bind(self, eventtype, whattodo):
31873207
evts = eventtype.split()
@@ -3255,7 +3275,7 @@ def __init__(self, **args):
32553275
args['_objName'] = "local_light"
32563276
super(local_light, self).setup(args)
32573277

3258-
if (canvas.get_selected() != None):
3278+
if (canvas.get_selected() is not None):
32593279
canvas.get_selected()._lights.append(self)
32603280

32613281
class distant_light(standardAttributes):
@@ -3265,7 +3285,7 @@ def __init__(self, **args):
32653285
self._direction = vector(0,0,1)
32663286
super(distant_light, self).setup(args)
32673287

3268-
if (canvas.get_selected() != None):
3288+
if (canvas.get_selected() is not None):
32693289
canvas.get_selected()._lights.append(self)
32703290

32713291
@property
@@ -4092,11 +4112,9 @@ def print_to_string(*args): # treatment of <br> vs. \n not quite right here
40924112
s = s[:-1]
40934113
return(s)
40944114

4095-
# global variable for type of web browser to display vpython
4096-
_browsertype = 'default'
4097-
def set_browser(type='default'):
4098-
global _browsertype
4099-
if type=='pyqt':
4100-
_browsertype='pyqt'
4101-
else:
4102-
_browsertype='default'
4115+
def keysdown():
4116+
global keysdownlist
4117+
keys = []
4118+
for k in keysdownlist: # return a copy of keysdownlist
4119+
keys.append(k)
4120+
return keys

0 commit comments

Comments
 (0)