101101
102102# methods are X in {'m': '23X....'}
103103# pos is normally updated as an attribute, but for interval-based trails, it is updated (multiply) as a method
104- __methods = {'select' :'a' , 'pos' :'b' , 'start' :'c' , 'stop' :'d' , 'clear' :'f' , # unused eghijklmnopvxyzCDFAB
104+ __methods = {'select' :'a' , 'pos' :'b' , 'start' :'c' , 'stop' :'d' , 'clear' :'f' , # unused eghijklmnopvxyzCDFABu
105105 'plot' :'q' , 'add_to_trail' :'s' ,
106- 'follow' :'t' , '_attach_arrow' : 'u' , ' clear_trail' :'w' ,
106+ 'follow' :'t' , 'clear_trail' :'w' ,
107107 'bind' :'G' , 'unbind' :'H' , 'waitfor' :'I' , 'pause' :'J' , 'pick' :'K' , 'GSprint' :'L' ,
108108 'delete' :'M' , 'capture' :'N' }
109109
110- __vecattrs = ['pos' , 'up' , 'color' , 'trail_color' , 'axis' , 'size' , 'origin' , '_attach_arrow' ,
110+ __vecattrs = ['pos' , 'up' , 'color' , 'trail_color' , 'axis' , 'size' , 'origin' ,
111111 'direction' , 'linecolor' , 'bumpaxis' , 'dot_color' , 'ambient' , 'add_to_trail' ,
112112 'foreground' , 'background' , 'ray' , 'ambient' , 'center' , 'forward' , 'normal' ,
113113 'marker_color' ]
@@ -209,14 +209,18 @@ def empty(cls):
209209 def handle_attach (cls ): # called when about to send data to the browser
210210 ## update every attach_arrow if relevant vector has changed
211211 for aa in cls .attach_arrows :
212- obj = baseObj .object_registry [aa ._obj ]
212+ if not aa ._run : continue
213+ obj = baseObj .object_registry [aa ._object ]
214+ if not hasattr (obj , aa .attr ): # no longer an attribute of the object
215+ continue
213216 vval = getattr (obj , aa .attr ) # could be 'velocity', for example
214217 if not isinstance (vval , vector ):
215- raise AttributeError ("attach_arrow value must be a vector." )
216- if (isinstance (aa ._last_val , vector ) and aa ._last_val .equals (vval )):
217- continue
218- aa .addmethod ('_attach_arrow' , vval .value )
219- aa ._last_val = vector (vval ) # keep copy of last vector
218+ raise AttributeError ('attach_arrow attribute "' + aa .attr + '" value must be a vector.' )
219+ if aa ._last_pos .equals (obj ._pos ) and aa ._last_val .equals (vval ): continue
220+ aa ._last_val = vector (vval ) # keep copies of last vectors
221+ aa ._last_pos = vector (obj ._pos )
222+ aa .pos = obj ._pos
223+ aa .axis = aa ._scale * vval
220224
221225 ## update every attach_trail that depends on a function
222226 for aa in cls .attach_trails :
@@ -545,10 +549,6 @@ class standardAttributes(baseObj):
545549 [],
546550 ['texture' , 'bumpmap' , 'visible' , 'pickable' ],
547551 ['v0' , 'v1' , 'v2' , 'v3' ] ],
548- 'attach_arrow' : [ [ 'color' , 'attrval' ],
549- [],
550- ['shaftwidth' , 'round' , 'scale' , 'obj' , 'attr' ],
551- [] ],
552552 'attach_trail' : [ ['color' ],
553553 [],
554554 ['radius' , 'pps' , 'retain' , 'type' , '_obj' ],
@@ -721,14 +721,14 @@ def setup(self, args):
721721
722722 # attribute vectors have these methods which call self.addattr()
723723 # The vector class calls a change function when there's a change in x, y, or z.
724- noSize = ['points' , 'label' , 'vertex' , 'triangle' , 'quad' , 'attach_arrow' , ' attach_trail' ]
724+ noSize = ['points' , 'label' , 'vertex' , 'triangle' , 'quad' , 'attach_trail' ]
725725 if not (objName == 'extrusion' ): #
726726 self ._color .on_change = self ._on_color_change
727727 if objName not in noSize :
728728 self ._axis .on_change = self ._on_axis_change
729729 self ._size .on_change = self ._on_size_change
730730 self ._up .on_change = self ._on_up_change
731- noPos = ['curve' , 'points' , 'triangle' , 'quad' , 'attach_arrow' ]
731+ noPos = ['curve' , 'points' , 'triangle' , 'quad' ]
732732 if objName not in noPos :
733733 self ._pos .on_change = self ._on_pos_change
734734 elif objName == 'curve' :
@@ -1330,24 +1330,6 @@ def headlength(self,value):
13301330 if not self ._constructing :
13311331 self .addattr ('headlength' )
13321332
1333- class attach_arrow (standardAttributes ):
1334- def __init__ (self , obj , attr , ** args ):
1335- attrs = ['pos' , 'size' , 'axis' , 'up' , 'color' ]
1336- args ['_default_size' ] = None
1337- a = getattr (obj , attr ) # This raises an error if obj does not have attr
1338- if not isinstance (a , vector ): raise AttributeError ('The attach_arrow attribute "' + attr + '" is not a vector.' )
1339- self .obj = args ['obj' ] = obj .idx
1340- self .attr = args ['attr' ] = attr # could be for example "velocity"
1341- self .attrval = args ['attrval' ] = getattr (baseObj .object_registry [self .obj ], attr )
1342- args ['_objName' ] = "attach_arrow"
1343- self ._last_val = None
1344- self ._scale = 1
1345- self ._shaftwidth = 0
1346- self ._round = False
1347- super (attach_arrow , self ).setup (args )
1348- # Only if the attribute is a user attribute do we need to add to attach_arrows:
1349- if attr not in attrs : baseObj .attach_arrows .append (self )
1350-
13511333 @property
13521334 def round (self ):
13531335 return self ._round
@@ -1374,10 +1356,34 @@ def shaftwidth(self, value):
13741356 self .addattr ("shaftwidth" )
13751357
13761358 def stop (self ):
1377- self .addmethod ( 'stop' , 'None' )
1359+ self ._run = self . visible = False
13781360
13791361 def start (self ):
1380- self .addmethod ('start' , 'None' )
1362+ self ._run = self .visible = True
1363+
1364+ def attach_arrow (o , attr , ** args ): # factory function returns arrow with special attributes
1365+ '''
1366+ The object "o" with a vector attribute "p" will have an arrow attached with options such as "color".
1367+ The length of the arrow will be args.scale*o.p", updated with every render of the scene.
1368+ If one creates a new attachment with "arr = attach_arrow(obj, attr, options)" you
1369+ can later change (for example) its color with "arr.color = ..."
1370+ '''
1371+ if not hasattr (o , attr ): raise AttributeError ('Cannot attach an arrow to an object that has no "' + attr + '" attribute.' )
1372+ if not isinstance (getattr (o , attr ), vector ): raise AttributeError ('The attach_arrow attribute "' + attr + '" is not a vector.' )
1373+ if not isinstance (o .pos , vector ): raise AttributeError ("The object's pos attribute is not a vector." )
1374+
1375+ scale = 1
1376+ if 'scale' in args : scale = args ['scale' ]
1377+ shaftwidth = 0.5 * o ._size .y
1378+ if 'shaftwidth' in args : shaftwidth = args ['shaftwidth' ]
1379+ c = o .color
1380+ if 'color' in args : c = args ['color' ]
1381+ # Set _last_val to strange values so that the first update to WebGL won't match:
1382+ a = arrow (canvas = o .canvas , pickable = False , _object = o .idx , attr = attr , color = c ,
1383+ scale = scale , shaftwidth = shaftwidth , _run = True ,
1384+ _last_val = vector (134.472 , 789.472 , 465.472 ), _last_pos = vector (134.472 , 789.472 , 465.472 ))
1385+ baseObj .attach_arrows .append (a )
1386+ return a
13811387
13821388class attach_trail (standardAttributes ):
13831389 def __init__ (self , obj , ** args ):
@@ -2428,15 +2434,15 @@ def foreground(self): return self._foreground
24282434 @foreground .setter
24292435 def foreground (self ,val ):
24302436 if not isinstance (val , vector ): raise TypeError ('foreground must be a vector' )
2431- self ._foreground = vector (value )
2437+ self ._foreground = vector (val )
24322438 self .addattr ('foreground' )
24332439
24342440 @property
24352441 def background (self ): return self ._background
24362442 @background .setter
24372443 def background (self ,val ):
24382444 if not isinstance (val ,vector ): raise TypeError ('background must be a vector' )
2439- self ._background = vector (value )
2445+ self ._background = vector (val )
24402446 self .addattr ('background' )
24412447
24422448 @property
@@ -3404,13 +3410,13 @@ def setup(self, args):
34043410 ## default values of common attributes
34053411 self ._constructing = True
34063412 argsToSend = []
3407- objName = args ['_objName' ]
3413+ self . objName = args ['_objName' ]
34083414 del args ['_objName' ]
34093415 if 'pos' in args :
34103416 self .location = args ['pos' ]
34113417 if self .location == print_anchor :
34123418 #self.location = [-1, print_anchor]
3413- raise AttributeError (objName + ': Cannot specify "print_anchor" in VPython 7.' )
3419+ raise AttributeError (self . objName + ': Cannot specify "print_anchor" in VPython 7.' )
34143420 argsToSend .append ('location' )
34153421 del args ['pos' ]
34163422 if 'canvas' in args : ## specified in constructor
@@ -3438,12 +3444,12 @@ def setup(self, args):
34383444
34393445 ## override default scalar attributes
34403446 for a ,val in args .items ():
3441- if a in controls .attrlists [objName ]:
3447+ if a in controls .attrlists [self . objName ]:
34423448 argsToSend .append (a )
34433449 setattr (self , '_' + a , val )
34443450 else :
34453451 setattr (self , a , val )
3446- cmd = {"cmd" : objName , "idx" : self .idx }
3452+ cmd = {"cmd" : self . objName , "idx" : self .idx }
34473453 cmd ["canvas" ] = self .canvas .idx
34483454
34493455 ## send only args specified in constructor
@@ -3465,10 +3471,10 @@ def bind(self, value):
34653471
34663472 @property
34673473 def pos (self ):
3468- raise AttributeError (objName + ' pos attribute is not available.' )
3474+ raise AttributeError (self . objName + ' pos attribute is not available.' )
34693475 @pos .setter
34703476 def pos (self , value ):
3471- raise AttributeError (objName + ' pos attribute cannot be changed.' )
3477+ raise AttributeError (self . objName + ' pos attribute cannot be changed.' )
34723478
34733479 @property
34743480 def disabled (self ):
0 commit comments