User controls
From GeoMod
The following code allows the use of buttons and slider bars. The built in VPython controls can be tricky to use and are not easy to label. These, hopefully, are more useful.
Contents |
[edit]
The code
called uControls.py in the examples. Download uControls.py
from visual import *
#see elipse.py for usage of slider
class slider:
def __init__(self, pos=vector(0,0,0),
min_val=0.0, max_val=1.0,
init_val=0.0,
stop_but= 0, stop_val=0.0,
scale=1.0,
title = None,
axis = vector(1,0,0)):
'''
pos = position of the slider bar
min_val = minimum value on the slider bar
max_val = maximum value on the slider bar
init_val = initial value of slider bar (if beyond min_val or max_val is reset to min_val or max_val)
stop_but = set to 0 for no stop button and to 1 for a stop button (not yet in)
Note: The minimum y position of the slider is 0.0 and the maximum is self.scale
'''
self.pos = pos
self.min_val = min_val
self.max_val = max_val
print min_val, max_val
self.stop_val = 0.0
self.scale = scale
self.value = init_val
self.title = title
self.axis = axis
'''confirm that the maximum is greater than the minimum'''
if self.max_val <= self.min_val:
print "Problem with slider. Minimum value greater than (or equal to) maximum value"
self.bits = frame(pos=self.pos, axis=self.axis)
self.cyl = cylinder(frame=self.bits, radius=0.05*self.scale, axis=(0,self.scale,0), color=color.yellow)
self.but = sphere(frame=self.bits, pos=(0,self.get_y(), 0), radius = self.cyl.radius * 2.0, color = color.green)
if self.title:
self.title = label(frame=self.bits, pos=self.cyl.axis*1.2, text=self.title,height=10, box=0, xoffset=0, yoffset=0, space = 2)
self.val_label = label(frame=self.bits, pos=self.cyl.axis*-0.2, text=str(round(self.get_val(),1)),height=10, box=0, xoffset=0, yoffset=0, space = 2)
#adjust for boundary values
self.boundary_set()
def get_y(self):
return self.scale * (self.value - self.min_val) / (self.max_val - self.min_val)
def get_val(self):
return self.min_val + (self.but.pos.y / self.scale)*(self.max_val - self.min_val)
def set_value(self, val=None):
if val:
self.value = val
self.but.pos = vector(0,self.get_y(),0)
print "get_Y", self.value, self.get_y()
def boundary_set(self):
'''to keep values in bounds'''
if self.value < self.min_val:
self.value = self.min_val
self.but.pos.y = 0.0
if self.value > self.max_val:
self.value = self.max_val
self.but.pos.y = self.scale
def but_move(self, cursor_pos):
nx = 0.0 #self.bits.x
ny = cursor_pos.y
nz = 0.0 #self.bits.z
if ny < 0.0: #self.bits.y:
ny = 0.0 #self.bits.y
nv = self.min_val
elif ny > self.cyl.axis.y: #self.bits.y + self.cyl.axis.y:
ny = self.cyl.axis.y #self.bits.y + self.cyl.axis.y
nv = self.max_val
nv = self.min_val + ((ny) / self.scale)*(self.max_val - self.min_val)
print ny, nv
self.value = nv
self.val_label.text = str(round(self.get_val(),1))
return vector(nx, ny, nz)
class uSwitch:
def __init__(self, radius=1.0,init_val=0, pos=vector(0,0,0), title=None):
self.title = title
self.value = init_val
self.bits = frame(pos=pos)
self.button = sphere(frame=self.bits, radius = radius, color = self.get_color())
if title:
self.title = label(frame=self.bits, pos=(0,radius*1.2,0),
text=self.title, height=10, box=0,
xoffset=0, yoffset=0, space = 2)
self.val_label = label(frame=self.bits, pos=(0,-radius*1.2,0),
text=self.get_val(),height=10, box=0, xoffset=0, yoffset=0, space = 2)
def switch(self):
if self.value == 1:
self.value = 0
#self.button.color = color.red
else:
self.value = 1
#self.button.color = color.blue
self.val_label.text = self.get_val()
self.button.color = self.get_color()
#print self.get_val()
def get_val(self):
if self.value == 1:
val = "On"
else:
val = "Off"
return val
def get_color(self):
if self.value == 1:
col = color.red
else:
col = color.blue
return col
[edit]
Useage
Usage is a bit tricky since you need to put code into two or three places
- create control
- get the user's interaction (mouse click for example)
- do operations based on changed values from the control
[edit]
Example use of the slider
This example is of an eliptical orbit of a planet around the sun. The slider changes the elipticiy of the orbit. Note: The bits of code directly related to the control of the slider are bounded by;
####Control################### ####\Control###################
ellipse.py:
from visual import *
####Control###################
from uControls import *
####\Control###################
class orbits:
def __init__(self):
self.orbit_path = []
self.planets = []
def draw(self,e=0.0,rad=1.0, mu=10.0, planet_size=1.0, frate=10000000):
self.orbit_path.append( curve(color=color.green))
self.planets.append( sphere(color=(0.7,0.7,1),radius=planet_size))
if e >= 1.0:
e = 0.99
if e < 0.0:
e = 0.0
fx = 2.0 - 2.0*min(abs((1-e*e)/(1-e)), abs(-(1-e*e)/(1+e)))
self.planets[-1].fx = fx
self.planets[-1].e = e
self.planets[-1].rad = rad
for i in range(361):
rate(frate)
theta = radians(float(i))
#h_sq = a*(1-e*e)*mu
r = (1-e*e)/(1-e*cos(theta))
nx = rad*(r*cos(theta) - fx)
ny = rad*r*sin(theta)
#print i, f, r, cos(theta), sin(theta), nx, ny
self.orbit_path[-1].append(pos=(nx, ny, 0.0))
self.planets[-1].pos=vector(nx,ny,0.0)
def re_draw(self,e=0.0,rad=1.0,mu=10.0, frate=10000000):
#redraw last path
if e >= 1.0:
e = 0.99
if e < 0.0:
e = 0.0
fx = 2.0 - 2.0*min(abs((1-e*e)/(1-e)), abs(-(1-e*e)/(1+e)))
self.planets[-1].fx = fx
self.planets[-1].e = e
self.planets[-1].rad = rad
for i in range(361):
rate(frate)
theta = radians(float(i))
#h_sq = a*(1-e*e)*mu
r = (1-e*e)/(1-e*cos(theta))
nx = rad*(r*cos(theta) - fx)
ny = rad*r*sin(theta)
self.orbit_path[-1].pos[i]=vector(nx, ny, 0.0)
def move_planets(self, theta):
for i in self.planets:
#to show the planet orbiting the Sun
theta = radians(float(theta))
r = (1-i.e*i.e)/(1-i.e*cos(theta))
nx = i.rad*(r*cos(theta) - i.fx)
ny = i.rad*r*sin(theta)
i.pos = vector(nx,ny,0.0)
mu = 10.0 #gravitational acceleration
e = 0.6 #eccentricity
a = 10.0 #distance of sun from center of elipse
# to draw an elipse
sun = sphere(color=color.yellow)
#orbitor = sphere()
####Control###################
e_slide = slider(pos=vector(10,5,0), init_val=e, title="elipticity",scale=5.0)
####Control###################
orbs = orbits()
orbs.draw(e=e, rad=a,mu=mu,frate=100,planet_size=0.25)
scene.center = sun.pos
scene.autoscale = 0
runtime = 0.0
framerate = 60
pick = None
planet_angle = 0
while 1:
rate(framerate)
runtime += 1.0/framerate
####Control###################
#mouse events
if scene.mouse.events:
m1 = scene.mouse.getevent() # obtain drag or drop event
if m1.drag and (m1.pick == e_slide.but):
drag_pos = m1.pickpos
pick = m1.pick
scene.cursor.visible = 0 # make cursor invisible
elif m1.drop:
pick = None # end dragging
scene.cursor.visible = 1 # cursor visible
if pick:
new_pos = scene.mouse.project(normal=(0,0,1))
if new_pos != drag_pos:
pick.pos += new_pos - drag_pos
drag_pos = new_pos
if pick == e_slide.but:
pick.pos = e_slide.but_move(pick.pos)
e = e_slide.value #so e changes when the value of the slider changes
#print e
#make adjustments based on new value of the slider
orbs.re_draw(e=e, rad=a, mu=mu)
####\Control###################
planet_angle += 1
if planet_angle >= 361:
planet_angle = 1
orbs.move_planets(planet_angle)
[edit]
Example use of switch button
The button switches from red to blue (on/off) and changes its value from 1 to 0.
stars_nebula3.py
from visual import *
from time import clock
from random import Random, random
####Control###################
from uControls import *
####\Control###################
# Stars interacting gravitationally
# Program uses Numeric Python arrays for high speed computations
win=600
Nstars = 50 # change this to have more or fewer stars
G = 6.7e-11 # Universal gravitational constant
# Typical values
Msun = 2E30
Rsun = 2E9
Rtrail = 2e8
L = 6e10 #1.2e11
vsun = 0.8*sqrt(G*Msun/Rsun)
print """
Right button drag to rotate view.
Left button drag up or down to move in or out.
"""
origx = 0
origy = 0
w = 704+4+4
h = 576+24+4
scene.width=w
scene.height=h
scene.x = origx
scene.y = origy
#setting up camera moves lurbano
maxRange = 2*L
minRange = 0.75*L
lastRange = maxRange
Rsteps = 200
start_steps = 400
scene = display(title="Stars", width=w, height=h, x = 0, y=0,
range=lastRange, forward=(-1,-1,-1))
scene.autocenter = 0
##xaxis = curve(pos=[(0,0,0), (L,0,0)], color=(0.5,0.5,0.5))
##yaxis = curve(pos=[(0,0,0), (0,L,0)], color=(0.5,0.5,0.5))
##zaxis = curve(pos=[(0,0,0), (0,0,L)], color=(0.5,0.5,0.5))
Stars = []
colors = [color.red, color.green, color.blue,
color.yellow, color.cyan, color.magenta]
poslist = []
plist = []
mlist = []
rlist = []
rv = Random(10)
print rv
for i in range(Nstars):
x = -L+2*L*rv.random()
y = -L+2*L*rv.random()
z = -L+2*L*rv.random()
r = Rsun/2+Rsun*rv.random()
Stars = Stars+[sphere(pos=(x,y,z), radius=r, color=colors[i % 6])]
#Stars[-1].trail = curve(pos=[Stars[-1].pos], color=colors[i % 6], radius=Rtrail)
Stars[-1].trail = curve( color=colors[i % 6], radius=Rtrail)
Stars[-1].alive = 1
Stars[-1].showtrail = 0
Stars[-1].vec = 0.0
mass = Msun*r**3/Rsun**3
px = mass*(-vsun+2*vsun*rv.random())
py = mass*(-vsun+2*vsun*rv.random())
pz = mass*(-vsun+2*vsun*rv.random())
poslist.append((x,y,z))
plist.append((px,py,pz))
mlist.append(mass)
rlist.append(r)
pos = array(poslist)
p = array(plist)
m = array(mlist)
m.shape = (Nstars,1) # Numeric Python: (1 by Nstars) vs. (Nstars by 1)
radius = array(rlist)
vcm = sum(p)/sum(m) # velocity of center of mass
p = p-m*vcm # make total initial momentum equal zero
####Control###################
#create control window - lurbano
cwin = display(title="Controls", width=200, height=100,
x=0, y=h+24)
#button to show trails
trail_but = uSwitch(title="Trails")
#button for viewing from the perspective of the star
ride_but = uSwitch(title="Ride", pos=(2.5,0,0))
ride_star = -1
####\Control###################
t = 0.0
dt = 1000.0
Nsteps = 0
pos = pos+(p/m)*(dt/2.) # initial half-step
time = clock()
Nhits = 0
while 1:
# Compute all forces on all stars
r = pos-pos[:,NewAxis] # all pairs of star-to-star vectors
for n in range(Nstars):
r[n,n] = 1e6 # otherwise the self-forces are infinite
rmag = sqrt(add.reduce(r*r,-1)) # star-to-star scalar distances
hit = less_equal(rmag,radius+radius[:,NewAxis])-identity(Nstars)
hitlist = sort(nonzero(hit.flat)) # 1,2 encoded as 1*Nstars+2
F = G*m*m[:,NewAxis]*r/rmag[:,:,NewAxis]**3 # all force pairs
for n in range(Nstars):
F[n,n] = 0 # no self-forces
p = p+sum(F,1)*dt
# Having updated all momenta, now update all positions
pos = pos+(p/m)*dt
# Update positions of display objects; add trail
for i in range(Nstars):
Stars[i].vec = Stars[i].pos - pos[i]
Stars[i].pos = pos[i]
####Control###################
####the trail_but.value comes from the button class ###################
if (Nsteps % 5 == 0 and
trail_but.value == 1 and Stars[i].alive == 1) or Stars[i].showtrail == 1:
####\Control###################
Stars[i].trail.append(pos=pos[i])
Stars[i].trail.visible = 1
# If any collisions took place, merge those stars
for ij in hitlist:
i, j = divmod(ij,Nstars) # decode star pair
if not Stars[i].visible: continue
if not Stars[j].visible: continue
# m[i] is a one-element list, e.g. [6e30]
# m[i,0] is an ordinary number, e.g. 6e30
newpos = (pos[i]*m[i,0]+pos[j]*m[j,0])/(m[i,0]+m[j,0])
newmass = m[i,0]+m[j,0]
newp = p[i]+p[j]
newradius = Rsun*((newmass/Msun)**(1./3.))
iset, jset = i, j
if radius[j] > radius[i]:
iset, jset = j, i
Stars[iset].radius = newradius
m[iset,0] = newmass
pos[iset] = newpos
p[iset] = newp
Stars[jset].trail.visible = 0
Stars[jset].visible = 0
p[jset] = vector(0,0,0)
m[jset,0] = Msun*1E-30 # give it a tiny mass
Nhits = Nhits+1
pos[jset] = (10.*L*Nhits, 0, 0) # put it far away
Stars[jset].alive = 0
Stars[jset].showtrail = 0
if jset == ride_star:
ride_star = -1
if Nsteps == 100:
print '%3.1f seconds for %d steps with %d stars' % (clock()-time, Nsteps, Nstars)
Nsteps = Nsteps+1
t = t+dt
rate(100)
####Control###################
if cwin.mouse.events:
m1 = cwin.mouse.getevent() # obtain drag or drop event
if m1.pick == trail_but.button and m1.release:
trail_but.switch()
if trail_but.value == 0:
for i in range(Nstars):
Stars[i].trail.visible = 0 #pos = Stars[i].trails.pos * 0.0
Stars[i].trail.pos=[]
if m1.pick == ride_but.button and m1.release:
ride_but.switch()
if ride_but.value == 0:
scene.forward = vector(-1,-1,-1)
scene.range = lastRange
ride_star = -1
####\Control###################
if scene.mouse.events:
m1 = scene.mouse.getevent()
nct = 0
for i in Stars:
if m1.pick == i and m1.release:
if i.showtrail == 1:
i.showtrail = 0
i.trail.visible = 0
i.trail.pos=[]
else:
i.showtrail = 1
####Control###################
if ride_but.value == 1:
####\Control###################
ride_star = nct
nct += 1
####Control###################
if ride_but.value == 1 and ride_star <> -1:
####\Control###################
scene.forward = -Stars[ride_star].pos
lastRange = scene.range
scene.range = 1.1 *mag(Stars[ride_star].pos)

