Skip to content

Commit e94465d

Browse files
Merge pull request #98 from vpython/Implement_bounding_box_method
Implement bounding-box method
2 parents 2aeb022 + 7d1f9a4 commit e94465d

File tree

1 file changed

+70
-40
lines changed

1 file changed

+70
-40
lines changed

vpython/vpython.py

Lines changed: 70 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import sys
1414
from . import __version__, __gs_version__
1515
from ._notebook_helpers import _isnotebook
16-
from ._vector_import_helper import (vector, mag, norm, dot, adjust_up,
16+
from ._vector_import_helper import (vector, mag, norm, cross, dot, adjust_up,
1717
adjust_axis, object_rotate)
1818

1919
# List of names that will be imported from this file with import *
@@ -28,7 +28,13 @@
2828
'standardAttributes', 'text', 'textures', 'triangle', 'vertex',
2929
'wtext', 'winput', 'keysdown']
3030

31-
from inspect import signature # needed to allow zero arguments in a bound function
31+
__p = platform.python_version()
32+
_ispython3 = (__p[0] == '3')
33+
34+
if _ispython3:
35+
from inspect import signature # Python 3; needed to allow zero arguments in a bound function
36+
else:
37+
from inspect import getargspec # Python 2; needed to allow zero arguments in a bound function
3238

3339
# __version__ is the version number of the Jupyter VPython installer, generated in building the installer.
3440
version = [__version__, 'jupyter']
@@ -91,7 +97,7 @@
9197
'right':'q', 'top':'r', 'bottom':'s', '_cloneid':'t',
9298
'logx':'u', 'logy':'v', 'dot':'w', 'dot_radius':'x',
9399
'markers':'y', 'legend':'z', 'label':'A', 'delta':'B', 'marker_color':'C',
94-
'size_units':'D', 'userpan':'E', 'scroll':'F', 'choices':'G'}
100+
'size_units':'D', 'userpan':'E', 'scroll':'F'}
95101

96102
# methods are X in {'m': '23X....'}
97103
# pos is normally updated as an attribute, but for interval-based trails, it is updated (multiply) as a method
@@ -107,22 +113,16 @@
107113
'marker_color']
108114

109115
__textattrs = ['text', 'align', 'caption', 'title', 'xtitle', 'ytitle', 'selected', 'label', 'capture',
110-
'append_to_caption', 'append_to_title', 'bind', 'unbind', 'pause', 'GSprint', 'choices']
116+
'append_to_caption', 'append_to_title', 'bind', 'unbind', 'pause', 'GSprint']
111117

112118
def _encode_attr2(sendval, val, ismethods):
113119
s = ''
114120
if sendval in __vecattrs: # it would be good to do some kind of compression of doubles
115121
s += "{:.16G},{:.16G},{:.16G}".format(val[0], val[1], val[2])
116122
elif sendval in __textattrs:
117-
if sendval == 'choices':
118-
s2 = ''
119-
for v in val:
120-
s2 += v+' '
121-
val = s2[0:-1]
122-
else:
123-
# '\n' doesn't survive JSON transmission, so we replace '\n' with '<br>' (and convert back in glowcomm)
124-
if not isinstance(val, str): val = print_to_string(val)
125-
val = val.replace('\n', '<br>')
123+
# '\n' doesn't survive JSON transmission, so we replace '\n' with '<br>' (and convert back in glowcomm)
124+
if not isinstance(val, str): val = print_to_string(val)
125+
val = val.replace('\n', '<br>')
126126
s += val
127127
elif sendval == 'rotate':
128128
for p in val:
@@ -169,6 +169,10 @@ def _encode_attr(D, ismethods): # ismethods is True if a list of method operatio
169169
out.append(s)
170170
return out
171171

172+
if sys.version > '3':
173+
long = int
174+
175+
172176
def list_to_vec(L):
173177
return vector(L[0], L[1], L[2])
174178

@@ -375,9 +379,14 @@ def handle_msg(self, msg):
375379
obj._text = evt['text']
376380
obj._number = evt['value']
377381
# inspect the bound function and see what it's expecting
378-
a = signature(obj._bind)
379-
if str(a) != '()': obj._bind( obj )
380-
else: obj._bind()
382+
if _ispython3: # Python 3
383+
a = signature(obj._bind)
384+
if str(a) != '()': obj._bind( obj )
385+
else: obj._bind()
386+
else: # Python 2
387+
a = getargspec(obj._bind)
388+
if len(a.args) > 0: obj._bind( obj )
389+
else: obj._bind()
381390
else: ## a canvas event
382391
if 'trigger' not in evt:
383392
cvs = baseObj.object_registry[evt['canvas']]
@@ -392,7 +401,7 @@ def _wait(cvs): # wait for an event
392401
cvs._waitfor = None
393402
if _isnotebook: baseObj.trigger() # in notebook environment must send methods immediately
394403
while cvs._waitfor is None:
395-
rate(100)
404+
rate(30)
396405
return cvs._waitfor
397406

398407
class color(object):
@@ -547,7 +556,7 @@ class standardAttributes(baseObj):
547556
'extrusion':[ ['pos', 'color', 'start_face_color', 'end_face_color'],
548557
[ 'axis', 'size', 'up' ],
549558
['path', 'shape', 'visible', 'opacity','shininess', 'emissive',
550-
'show_start_face', 'show_end_face', 'smooth',
559+
'show_start_face', 'show_end_face',
551560
'make_trail', 'trail_type', 'interval', 'show_start_face', 'show_end_face',
552561
'retain', 'trail_color', 'trail_radius', 'texture', 'pickable' ],
553562
['red', 'green', 'blue','length', 'width', 'height'] ],
@@ -1006,6 +1015,24 @@ def rotate(self, angle=None, axis=None, origin=None):
10061015
self._pos.value = newpos
10071016
self.addattr('pos')
10081017

1018+
def bounding_box(self):
1019+
centered = ['box', 'compound', 'ellipsoid', 'sphere', 'simple_sphere', 'ring']
1020+
x = norm(self._axis)
1021+
y = norm(self._up)
1022+
z = norm(cross(x,y))
1023+
L = self._size.x
1024+
H = self._size.y
1025+
W = self._size.z
1026+
p = vector(self._pos) # make a copy of pos, so changes to p won't affect the object
1027+
if self._objName not in centered:
1028+
p = p + 0.5*L*x # move to center
1029+
pts = []
1030+
for dx in [-L/2, L/2]:
1031+
for dy in [-H/2, H/2]:
1032+
for dz in [-W/2, W/2]:
1033+
pts.append(p + dx*x + dy*y + dz*z)
1034+
return pts
1035+
10091036
def _on_size_change(self): # the vector class calls this when there's a change in x, y, or z
10101037
self._axis.value = self._axis.norm() * self._size.x # update axis length when box.size.x is changed
10111038
self.addattr('size')
@@ -1431,6 +1458,11 @@ def __init__(self, objList, **args):
14311458
savesize = args['size']
14321459
del args['size']
14331460

1461+
baseObj.sent = False
1462+
while not baseObj.sent: # wait for compounding objects to exist
1463+
if _isnotebook: rate(1000)
1464+
else: time.sleep(0.001)
1465+
14341466
self.compound_idx += 1
14351467
args['_objName'] = 'compound'+str(self.compound_idx)
14361468
super(compound, self).setup(args)
@@ -2688,7 +2720,7 @@ def rotate(self, angle=0, axis=None, origin=None):
26882720
c = self._canvas
26892721
if axis is None: axis = c.up
26902722
if origin is not None and origin != self.pos:
2691-
origin = origin + (self.pos-origin).rotate(angle=angle, axis=axis)
2723+
origin = self.pos + (self.pos-origin).rotate(angle=angle, axis=axis)
26922724
else:
26932725
origin = self.pos
26942726
if c._axis.diff_angle(axis) > 1e-6:
@@ -3084,6 +3116,7 @@ def handle_event(self, evt): ## events and scene info updates
30843116
# Set attribute_vector.value, which avoids nullifying the
30853117
# on_change functions that detect changes in e.g. obj.pos.y
30863118
obj._pos.value = list_to_vec(p)
3119+
obj._origin = obj._pos
30873120
s = evt['size']
30883121
obj._size.value = obj._size0 = list_to_vec(s)
30893122
obj._axis.value = obj._size._x*norm(obj._axis)
@@ -3097,9 +3130,14 @@ def handle_event(self, evt): ## events and scene info updates
30973130
del evt['height']
30983131
for fct in self._binds['resize']:
30993132
# inspect the bound function and see what it's expecting
3100-
a = signature(fct)
3101-
if str(a) != '()': fct( evt )
3102-
else: fct()
3133+
if _ispython3: # Python 3
3134+
a = signature(fct)
3135+
if str(a) != '()': fct( evt )
3136+
else: fct()
3137+
else: # Python 2
3138+
a = getargspec(fct)
3139+
if len(a.args) > 0: fct( evt )
3140+
else: fct()
31033141
else: # pause/waitfor, update_canvas
31043142
if 'pos' in evt:
31053143
pos = evt['pos']
@@ -3119,9 +3157,14 @@ def handle_event(self, evt): ## events and scene info updates
31193157
evt1 = event_return(evt) ## turn it into an object
31203158
for fct in self._binds[ev]:
31213159
# inspect the bound function and see what it's expecting
3122-
a = signature(fct)
3123-
if str(a) != '()': fct( evt1 )
3124-
else: fct()
3160+
if _ispython3: # Python 3
3161+
a = signature(fct)
3162+
if str(a) != '()': fct( evt1 )
3163+
else: fct()
3164+
else: # Python 2
3165+
a = getargspec(fct)
3166+
if len(a.args) > 0: fct( evt1 )
3167+
else: fct()
31253168
self._waitfor = evt1 # what pause and waitfor are looking for
31263169
else: ## user can change forward (spin), range/autoscale (zoom), up (touch), center (pan)
31273170
if 'forward' in evt and self.userspin and not self._set_forward:
@@ -3518,10 +3561,7 @@ def choices(self):
35183561
return self._choices
35193562
@choices.setter
35203563
def choices(self, value):
3521-
self._choices = value
3522-
if not self._constructing:
3523-
self.addattr('choices')
3524-
#raise AttributeError('choices cannot be modified after a menu is created')
3564+
raise AttributeError('choices cannot be modified after a menu is created')
35253565

35263566
@property
35273567
def index(self):
@@ -3762,16 +3802,6 @@ def shape(self):
37623802
def shape(self, value):
37633803
raise AttributeError('shape cannot be changed after extrusion is created')
37643804

3765-
@property
3766-
def smooth(self):
3767-
if self._constructing:
3768-
return self._smooth
3769-
else:
3770-
return None
3771-
@smooth.setter
3772-
def smooth(self, value):
3773-
raise AttributeError('smooth cannot be changed after extrusion is created')
3774-
37753805
@property
37763806
def show_start_face(self):
37773807
if self._constructing:
@@ -4081,4 +4111,4 @@ def set_browser(type='default'):
40814111
if type=='pyqt':
40824112
_browsertype='pyqt'
40834113
else:
4084-
_browsertype='default'
4114+
_browsertype='default'

0 commit comments

Comments
 (0)