from __future__ import division
import unittest
import csv;
import sys;
from numpy import ndarray as numpy_ndarray;
"""
#
#
# UNIT TESTING / IMPORT SETUP CODE ------------------------------------------------------------
#
#
"""
if __name__ == "__main__":
from vpython import vector
class Mock:
def __init__(self, name, *args, **kwargs):
self.name = name
self.called = 0
def __call__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
for name in kwargs:
setattr(self, name, kwargs[name])
self.called += 1
return self
def reset(self):
self.called = 0
color = Mock("color")
color.red = "red"
color.green = "green"
color.blue = "blue"
color.yellow = "yellow"
color.orange = "orange"
color.cyan = "cyan"
color.magenta = "magenta"
color.white = "white"
arrow = Mock("arrow")
label = Mock("label")
points = Mock("points")
curve = Mock("curve")
gdisplay = Mock("gdisplay")
gcurve = Mock("gcurve")
gcurve.plots = []
def mockPlot(pos):
gcurve.plots.append(pos)
gcurve.plot = mockPlot
"""
vector = Mock("vector")
def call(x, y, z):
vector.x = x
vector.y = y
vector.z = z
vector.__call__ = call
"""
else:
from vpython import *
"""
#
#
# ACTUAL PHYSUTIL CODE FOLLOWS --------------------------------------------------
#
#
"""
if __name__ != "__main__":
scene.x = 50
scene.y = 50
def obj_size(obj):
if type(obj) == box or type(obj) == pyramid:
return obj.size
elif type(obj) == sphere:
return vector(obj.radius, obj.radius, obj.radius)
class MotionMap:
"""
This class assists students in constructing motion maps
using either arrows (measuring a quantity) or "breadcrumbs"
(with timestamps).
"""
def __init__(self, obj, tf, numMarkers, markerType="arrow",
markerScale=1, markerColor=color.red,
labelMarkerOrder=True, labelMarkerOffset=vector(0,0,0),
dropTime=False, timeOffset=vector(0,0,0), arrowOffset=vector(0,0,0), labelColor=color.white):
self.obj = obj
self.tf = tf
self.numMarkers = numMarkers
self.markerType = markerType
self.markerScale = markerScale
self.markerColor = markerColor
self.labelMarkerOrder = labelMarkerOrder
self.labelMarkerOffset = labelMarkerOffset
self.timeOffset = timeOffset
self.dropTime = dropTime
self.arrowOffset = arrowOffset
self.labelColor = labelColor
try:
self.interval = self.tf / self.numMarkers
except TypeError as err:
print("**********TYPE ERROR**********")
print("Please check that you are not passing in a variable of the wrong type (e.g. a scalar as a vector, or vice-versa)!")
print("******************************")
print(err)
raise err
self.curMarker = 0
def update(self, t, quantity=vector(0,0,0)):
try:
if t > (self.interval * self.curMarker):
self.curMarker += 1
if self.markerType == "arrow":
arrow(pos=self.obj.pos+self.arrowOffset,
axis=self.markerScale*quantity, color=self.markerColor)
elif self.markerType == "breadcrumbs":
points(pos=self.obj.pos,
size=10*self.markerScale*quantity, color=self.markerColor)
if self.dropTime is not False:
epsilon = vector(0,self.markerScale*.5,0)+self.timeOffset
droptimeText = label(pos=self.obj.pos+epsilon, text='t='+str(t)+'s', height=10, box=False, color=self.labelColor)
if self.labelMarkerOrder is not False:
label(pos=self.obj.pos-vector(0,self.markerScale*.5,0)+self.labelMarkerOffset, text=str(self.curMarker), height=10, box=False, color=self.labelColor)
except TypeError as err:
print("**********TYPE ERROR**********")
print("Please check that you are not passing in a variable of the wrong type (e.g. a scalar as a vector, or vice-versa)!")
print("******************************")
print(err)
raise err
class MotionMapN:
"""
This class assists students in constructing motion maps
using either arrows (measuring a quantity) or "breadcrumbs"
(with timestamps).
"""
def __init__(self, obj, dt, numSteps, markerType="arrow",
markerScale=1, markerColor=color.red,
labelMarkerOrder=True, labelMarkerOffset=vector(0,0,0),
dropTime=False, timeOffset=vector(0,0,0), arrowOffset=vector(0,0,0), labelColor=color.white):
self.obj = obj
self.dt = dt
self.numSteps = numSteps
self.markerType = markerType
self.markerScale = markerScale
self.markerColor = markerColor
self.labelMarkerOrder = labelMarkerOrder
self.labelMarkerOffset = labelMarkerOffset
self.timeOffset = timeOffset
self.dropTime = dropTime
self.arrowOffset = arrowOffset
self.labelColor = labelColor
try:
self.interval = self.dt * self.numSteps
except TypeError as err:
print("**********TYPE ERROR**********")
print("Please check that you are not passing in a variable of the wrong type (e.g. a scalar as a vector, or vice-versa)!")
print("******************************")
print(err)
raise err
self.curMarker = 0
def update(self, t, quantity=vector(0,0,0)):
try:
threshold = self.interval * self.curMarker
if t >= threshold:
self.curMarker += 1
if self.markerType == "arrow":
arrow(pos=self.obj.pos+self.arrowOffset,
axis=self.markerScale*quantity, color=self.markerColor)
elif self.markerType == "breadcrumbs":
points(pos=self.obj.pos,
size=10*self.markerScale*quantity, color=self.markerColor)
if self.dropTime is not False:
epsilon = vector(0,self.markerScale*.5,0)+self.timeOffset
droptimeText = label(pos=self.obj.pos+epsilon, text='t='+str(t)+'s', height=10, box=False, color=self.labelColor)
if self.labelMarkerOrder is not False:
label(pos=self.obj.pos-vector(0,self.markerScale*.5,0)+self.labelMarkerOffset, text=str(self.curMarker), height=10, box=False, color=self.labelColor)
except TypeError as err:
print("**********TYPE ERROR**********")
print("Please check that you are not passing in a variable of the wrong type (e.g. a scalar as a vector, or vice-versa)!")
print("******************************")
print(err)
raise err
class PhysAxis:
"""
This class assists students in creating dynamic axes for their models.
"""
def __init__(self, obj, numLabels, axisType="x", axis=vector(1,0,0), startPos=None,
length=None, labels = None, labelOrientation="down", axisColor=color.yellow, labelColor=color.white):
try:
self.intervalMarkers = []
self.intervalLabels = []
self.labelText = labels
self.obj = obj
self.lastPos = vector(self.obj.pos.x, self.obj.pos.y, self.obj.pos.z)
self.numLabels = numLabels
self.axisType = axisType
self.axis = axis if axisType != "y" else vector(0,1,0)
self.length = length if (length is not None) else obj_size(obj).x
self.startPos = startPos if (startPos is not None) else vector(-obj_size(obj).x/2,-4*obj_size(obj).y,0) + self.obj.pos
self.axisColor = axisColor
self.labelColor = labelColor
if labelOrientation == "down":
self.labelShift = vector(0,-0.05*self.length,0)
elif labelOrientation == "up":
self.labelShift = vector(0,0.05*self.length,0)
elif labelOrientation == "left":
self.labelShift = vector(-0.1*self.length,0,0)
elif labelOrientation == "right":
self.labelShift = vector(0.1*self.length,0,0)
self.__reorient()
except TypeError as err:
print("**********TYPE ERROR**********")
print("Please check that you are not passing in a variable of the wrong type (e.g. a scalar as a vector, or vice-versa)!")
print("******************************")
print(err)
raise err
def update(self):
try:
if self.obj.pos != self.lastPos:
diff = self.obj.pos - self.lastPos
for i in range(len(self.intervalMarkers)):
self.intervalMarkers[i].pos += diff
self.intervalLabels[i].pos += diff
self.axisCurve.pos = [x + diff for x in self.axisCurve.pos]
self.lastPos = vector(self.obj.pos.x, self.obj.pos.y, self.obj.pos.z)
except TypeError as err:
print("**********TYPE ERROR**********")
print("Please check that you are not passing in a variable of the wrong type (e.g. a scalar as a vector, or vice-versa)!")
print("******************************")
print(err)
raise err
def reorient(self, axis=None, startPos=None, length=None, labels=None, labelOrientation=None):
try:
self.axis = axis if axis is not None else self.axis
self.startPos = startPos if startPos is not None else self.startPos
self.length = length if length is not None else self.length
self.labelText = labels if labels is not None else self.labels
if labelOrientation == "down":
self.labelShift = vector(0,-0.05*self.length,0)
elif labelOrientation == "up":
self.labelShift = vector(0,0.05*self.length,0)
elif labelOrientation == "left":
self.labelShift = vector(-0.1*self.length,0,0)
elif labelOrientation == "right":
self.labelShift = vector(0.1*self.length,0,0)
self.__reorient()
except TypeError as err:
print("**********TYPE ERROR**********")
print("Please check that you are not passing in a variable of the wrong type (e.g. a scalar as a vector, or vice-versa)!")
print("******************************")
print(err)
raise err
def __reorient(self):
updating = True if len(self.intervalMarkers) > 0 else False
final = self.startPos + (self.length * self.axis)
interval = (self.length / (self.numLabels-1)) * self.axis
i=0
while i<self.numLabels:
intervalPos = self.startPos+(i*interval)
if self.labelText is not None:
labelText = self.labelText[i]
elif self.axisType == "y":
labelText = "%.2f" % intervalPos.y
else:
labelText = "%.2f" % intervalPos.x
if updating:
self.intervalMarkers[i].pos = intervalPos
self.intervalLabels[i].pos = intervalPos+self.labelShift
self.intervalLabels[i].text = str(labelText)
else:
self.intervalMarkers.append(
points(pos=intervalPos,color=self.axisColor,size = 6) )
self.intervalLabels.append(
label(pos=intervalPos+self.labelShift, text=str(labelText),box=False,height = 8, color=self.labelColor) )
i=i+1
if updating:
self.axisCurve.pos = [self.startPos,final]
else:
self.axisCurve = curve(pos=[self.startPos,final],color = self.axisColor)
class PhysTimer:
"""
This class assists students in creating an onscreen timer display.
"""
def __init__(self, x, y, fontsize = 13, useScientific=False, timerColor=color.white):
try:
self.useScientific = useScientific
self.timerColor = timerColor
if useScientific is False:
self.timerLabel = label(pos=vector(x,y,0), text='00:00:00.00', box=False, height = fontsize)
else:
self.timerLabel = label(pos=vector(x,y,0), text='00E01', box=False, height = fontsize)
except TypeError as err:
print("**********TYPE ERROR**********")
print("Please check that you are not passing in a variable of the wrong type (e.g. a scalar as a vector, or vice-versa)!")
print("******************************")
print(err)
raise err
def update(self, t):
try:
if self.useScientific:
self.timerLabel.text = "%.4E" % t
else:
hours = int(t / 3600)
mins = int((t / 60) % 60)
secs = int(t % 60)
frac = int(round(100 * (t % 1)))
if frac == 100:
frac = 0
secs = secs + 1;
self.timerLabel.text = "%02d:%02d:%02d.%02d" % (hours, mins, secs, frac)
except TypeError as err:
print("**********TYPE ERROR**********")
print("Please check that you are not passing in a variable of the wrong type (e.g. a scalar as a vector, or vice-versa)!")
print("******************************")
print(err)
raise err
class PhysGraph:
"""
This class assists students in creating graphs with advanced functionality.
"""
graphColors = [color.red, color.green, color.blue, color.yellow,
color.orange, color.cyan, color.magenta, color.white]
def __init__(self, numPlots=1, title = None, xlabel = None, ylabel = None, backgroundColor = color.white):
try:
self.graphDisplay = graph(x = 475, y = 350, title = title, xtitle = xlabel, ytitle = ylabel, background = backgroundColor)
self.numPlots = numPlots
self.graphs = []
for i in range(numPlots):
self.graphs.append(gcurve(color=PhysGraph.graphColors[i%len(PhysGraph.graphColors)]))
except TypeError as err:
print("**********TYPE ERROR**********")
print("Please check that you are not passing in a variable of the wrong type (e.g. a scalar as a vector, or vice-versa)!")
print("******************************")
print(err)
raise err
def plot(self, independent, *dependents):
try:
if len(dependents) != self.numPlots:
raise Exception("ERROR: Number of dependent parameters given does not match numPlots given at initialization!")
for i in range(len(dependents)):
self.graphs[i].plot(pos=(independent,dependents[i]))
except TypeError as err:
print("**********TYPE ERROR**********")
print("Please check that you are not passing in a variable of the wrong type (e.g. a scalar as a vector, or vice-versa)!")
print("******************************")
print(err)
raise err
def readcsv(filename,cols=1, IgnoreHeader=False, startrow = 0, NumericData=True):
data = [0]*(cols);
for i in range(cols):
data[i]=[];
if sys.version_info.major == 2:
with open(filename,'rb') as csvfile:
csvdata = csv.reader(csvfile);
for i in range(0,startrow):
csvdata.next();
if IgnoreHeader and startrow!=0:
csvdata.next();
for row in csvdata:
for c in range(cols):
if NumericData:
data[c].append(float(row[c]));
else:
data[c].append(row[c]);
elif sys.version_info.major == 3:
with open(filename,newline='') as csvfile:
csvdata = csv.reader(csvfile);
for i in range(0,startrow):
csvdata.next();
if ignoreHeader and startrow!=0:
csvdata.next();
for row in csvdata:
for c in range(cols):
if NumericData:
data[c].append(float(row[c]));
else:
data[c].append(row[c]);
else:
sys.stderr.write('You need to use python 2* or 3* \n');
exit(1);
return data;
def writecsv(filename,datalist, header=[]):
csvfile = [];
useheader = False;
if sys.version_info.major == 2:
csvfile = open(filename,'wb');
elif sys.version_info.major == 3:
csvfile = open('pythonTest.csv', 'w',newline='');
else:
sys.stderr.write('You need to use python 2* or 3* \n');
exit(1);
if isinstance(datalist,numpy_ndarray):
datalist = datalist.T;
datalist = datalist.tolist();
if len(datalist)<1:
csvfile.close();
return;
isLofL = False;
ListLength = 0;
numLists = 0;
if isinstance(datalist[0],(list,tuple)):
isLofL = True;
ListLength = len(datalist[0]);
numLists = len(datalist);
else:
isLofL = False;
ListLength = len(datalist);
numLists = 1;
if isLofL:
for Lidx in range(1,len(datalist)):
if len(datalist[Lidx])!=ListLength:
sys.stderr.write('All lists in datalist must be the same length \n');
csvfile.close();
return;
if len(header)!=0:
if len(header)!=numLists:
sys.stderr.write('Header length did not match the number of columns, ignoring header.\n');
else:
useheader = True;
DataWriter = csv.writer(csvfile, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
if useheader:
DataWriter.writerow(header);
for row in range(0,ListLength):
thisrow = [];
if numLists > 1:
for col in range(0,numLists):
thisrow.append(datalist[col][row]);
else:
thisrow.append(datalist[row]);
DataWriter.writerow(thisrow);
csvfile.close();
"""
#
#
# UNIT TESTING BELOW ----------------------------------------------------------------------
#
#
"""
class TestMotionMap(unittest.TestCase):
def setUp(self):
self.obj = Mock("obj")
self.obj.pos = vector(0,0,0)
self.tf = 10
self.numMarkers = 5
self.timeOffset = vector(1,1,1)
self.markerScale = 2
self.arrowOffset = vector(1,1,1)
arrow.reset()
points.reset()
label.reset()
self.map = MotionMap(self.obj, self.tf, self.numMarkers, markerType="arrow",
markerScale=2, markerColor=color.green,
dropTime=True, timeOffset=self.timeOffset, arrowOffset=self.arrowOffset)
def test_init(self):
self.assertEqual(self.obj, self.map.obj)
self.assertEqual(self.tf, self.map.tf)
self.assertEqual(self.numMarkers, self.map.numMarkers)
self.assertEqual("arrow", self.map.markerType)
self.assertEqual(self.markerScale, self.map.markerScale)
self.assertEqual(color.green, self.map.markerColor)
self.assertEqual(vector(1,1,1), self.map.timeOffset)
self.assertEqual(True, self.map.dropTime)
self.assertEqual(self.map.interval, self.tf / self.numMarkers)
self.assertEqual(self.map.curMarker, 0)
self.assertEqual(vector(1,1,1), self.map.arrowOffset)
def test_update(self):
self.map.curMarker = 1
self.map.update(0)
self.assertEqual(arrow.called, 0)
self.assertEqual(points.called, 0)
self.assertEqual(label.called, 0)
self.map.update(3, quantity=2)
self.assertEqual(arrow.called, 1)
self.assertEqual(points.called, 0)
self.assertEqual(label.called, 2)
self.assertEqual(self.map.curMarker, 2)
self.assertEqual(arrow.pos, self.obj.pos+self.arrowOffset)
self.assertEqual(arrow.axis, 4)
self.assertEqual(arrow.color, color.green)
self.assertEqual(label.text, "2")
class TestMotionMapN(unittest.TestCase):
def setUp(self):
self.obj = Mock("obj")
self.obj.pos = vector(0,0,0)
self.dt = 1
self.numSteps = 5
self.timeOffset = vector(1,1,1)
self.markerScale = 2
self.arrowOffset = vector(1,1,1)
arrow.reset()
points.reset()
label.reset()
self.map = MotionMapN(self.obj, self.dt, self.numSteps, markerType="arrow",
markerScale=2, markerColor=color.green,
dropTime=True, timeOffset=self.timeOffset, arrowOffset=self.arrowOffset)
def test_init(self):
self.assertEqual(self.obj, self.map.obj)
self.assertEqual(self.dt, self.map.dt)
self.assertEqual(self.numSteps, self.map.numSteps)
self.assertEqual("arrow", self.map.markerType)
self.assertEqual(self.markerScale, self.map.markerScale)
self.assertEqual(color.green, self.map.markerColor)
self.assertEqual(vector(1,1,1), self.map.timeOffset)
self.assertEqual(True, self.map.dropTime)
self.assertEqual(self.map.curMarker, 0)
self.assertEqual(vector(1,1,1), self.map.arrowOffset)
def test_update(self):
self.map.curMarker = 1
self.map.update(0)
self.assertEqual(arrow.called, 0)
self.assertEqual(points.called, 0)
self.assertEqual(label.called, 0)
self.map.update(3, quantity=2)
self.assertEqual(arrow.called, 0)
self.assertEqual(points.called, 0)
self.assertEqual(label.called, 0)
self.assertEqual(self.map.curMarker, 1)
self.assertEqual(arrow.pos, self.obj.pos+self.arrowOffset)
self.assertEqual(arrow.axis, 4)
self.assertEqual(arrow.color, color.green)
self.assertEqual(label.text, "2")
class TestPhysAxis(unittest.TestCase):
def setUp(self):
self.obj = Mock("obj")
self.obj.pos = vector(0,0,0)
self.numLabels = 5
self.axis = vector(1,1,1)
self.startPos = vector(0,1,0)
self.length = 10
self.labels = ["a", "b", "c", "d", "e"]
self.wrongLabels = ["a"]
self.axisType = "arbitrary"
self.labelOrientation="left"
curve.reset()
self.physAxis = PhysAxis(self.obj, self.numLabels, axisType=self.axisType, axis=self.axis,
startPos=self.startPos, length=self.length, labels = self.labels,
labelOrientation=self.labelOrientation)
def test_init(self):
self.assertEqual(self.physAxis.labelText, self.labels)
self.assertEqual(self.physAxis.obj, self.obj)
self.assertEqual(self.physAxis.lastPos, self.obj.pos)
self.assertEqual(self.physAxis.numLabels, self.numLabels)
self.assertEqual(self.physAxis.axis, self.axis)
self.assertEqual(self.physAxis.length, self.length)
self.assertEqual(self.physAxis.startPos, self.startPos)
self.assertEqual(self.physAxis.axisType, self.axisType)
self.assertEqual(self.physAxis.labelShift, vector(-0.1*self.length, 0, 0))
self.assertEqual(len(self.physAxis.intervalMarkers), self.numLabels)
self.assertEqual(len(self.physAxis.intervalLabels), self.numLabels)
intervalPos = self.startPos+(self.length * self.axis)
self.assertEqual(self.physAxis.intervalMarkers[-1].pos, intervalPos)
self.assertEqual(self.physAxis.intervalLabels[-1].pos, intervalPos+self.physAxis.labelShift)
self.assertEqual(self.physAxis.intervalLabels[-1].text, "e")
self.assertEqual(curve.called, 1)
def test_reorient(self):
newAxis = vector(0,0,1)
startPos = vector(1,0,0)
otherLabels = ["f", "g", "h", "i", "j"]
self.physAxis.reorient(axis=newAxis, startPos=startPos, length=1,
labels=otherLabels, labelOrientation="right")
self.assertEqual(self.physAxis.axis, newAxis)
self.assertEqual(self.physAxis.startPos, startPos)
self.assertEqual(self.physAxis.length, 1)
self.assertEqual(self.physAxis.labelShift, vector(0.1, 0, 0))
intervalPos = startPos+newAxis
self.assertEqual(self.physAxis.intervalMarkers[-1].pos, intervalPos)
self.assertEqual(self.physAxis.intervalLabels[-1].pos, intervalPos+self.physAxis.labelShift)
self.assertEqual(self.physAxis.intervalLabels[-1].text, "j")
self.assertEqual(curve.called, 1)
def test_update(self):
startMarkerPos = vector(self.physAxis.intervalMarkers[-1].pos.x,
self.physAxis.intervalMarkers[-1].pos.y,
self.physAxis.intervalMarkers[-1].pos.z)
startLabelPos = vector(self.physAxis.intervalLabels[-1].pos.x,
self.physAxis.intervalLabels[-1].pos.y,
self.physAxis.intervalLabels[-1].pos.z)
startCurvePos = vector(self.physAxis.axisCurve.pos[0].x,
self.physAxis.axisCurve.pos[0].y,
self.physAxis.axisCurve.pos[0].z)
self.physAxis.update()
self.assertEqual(startMarkerPos, self.physAxis.intervalMarkers[-1].pos)
self.assertEqual(startLabelPos, self.physAxis.intervalLabels[-1].pos)
self.assertEqual(startCurvePos, self.physAxis.axisCurve.pos[0])
self.physAxis.obj.pos = self.physAxis.obj.pos + vector(1,1,1)
self.physAxis.update()
self.assertNotEqual(startMarkerPos, self.physAxis.intervalMarkers[-1].pos)
self.assertNotEqual(startLabelPos, self.physAxis.intervalLabels[-1].pos)
self.assertNotEqual(startCurvePos, self.physAxis.axisCurve.pos[0])
class TestPhysGraph(unittest.TestCase):
def setUp(self):
self.physGraph = PhysGraph(numPlots = 5)
def test_init(self):
self.assertEqual(self.physGraph.graphDisplay.x, 475)
self.assertEqual(self.physGraph.graphDisplay.y, 350)
self.assertEqual(self.physGraph.numPlots, 5)
self.assertEqual(len(self.physGraph.graphs), 5)
def test_plot(self):
self.assertRaises(Exception, self.physGraph.plot, vector(0,0,0), vector(1,1,1))
self.physGraph.plot(vector(0,0,0), vector(1,1,1), vector(2,2,2), vector(3,3,3), vector(4,4,4), vector(5,5,5))
self.assertEqual(len(self.physGraph.graphs[-1].plots), 5)
self.assertEqual(self.physGraph.graphs[-1].plots[0], (vector(0,0,0), vector(1,1,1)))
class TestPhysTimer(unittest.TestCase):
def setUp(self):
self.timer = PhysTimer(1,1)
def test_init(self):
self.assertEquals(self.timer.timerLabel.text, "00:00:00.00")
self.assertEquals(self.timer.timerLabel.pos, vector(1,1,0))
def test_update(self):
self.timer.update(3923.65)
self.assertEquals(self.timer.timerLabel.text, "01:05:23.65")
self.timer.useScientific=True
self.timer.update(3923.65)
self.assertEquals(self.timer.timerLabel.text, "3.9237E+03")
if __name__ == "__main__":
print("Beginning unit tests!")
unittest.main()