Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 4665
1
# physutil.py (v1.27)
2
# An open-source module for highly vpython animations
3
4
from __future__ import division
5
import unittest
6
import csv;
7
import sys;
8
from numpy import ndarray as numpy_ndarray;
9
10
"""
11
#
12
#
13
# UNIT TESTING / IMPORT SETUP CODE ------------------------------------------------------------
14
#
15
#
16
"""
17
18
# Determine whether we are being used as a module or just running unittests (for mock purposes this is important)
19
if __name__ == "__main__":
20
# If we are unit testing, set up mock objects (must be done before classes are defined below!)
21
from vpython import vector
22
class Mock:
23
def __init__(self, name, *args, **kwargs):
24
self.name = name
25
self.called = 0
26
27
def __call__(self, *args, **kwargs):
28
self.args = args
29
self.kwargs = kwargs
30
for name in kwargs:
31
setattr(self, name, kwargs[name])
32
self.called += 1
33
return self
34
35
def reset(self):
36
self.called = 0
37
38
color = Mock("color")
39
color.red = "red"
40
color.green = "green"
41
color.blue = "blue"
42
color.yellow = "yellow"
43
color.orange = "orange"
44
color.cyan = "cyan"
45
color.magenta = "magenta"
46
color.white = "white"
47
48
arrow = Mock("arrow")
49
label = Mock("label")
50
points = Mock("points")
51
curve = Mock("curve")
52
gdisplay = Mock("gdisplay")
53
gcurve = Mock("gcurve")
54
gcurve.plots = []
55
def mockPlot(pos):
56
gcurve.plots.append(pos)
57
gcurve.plot = mockPlot
58
59
"""
60
vector = Mock("vector")
61
def call(x, y, z):
62
vector.x = x
63
vector.y = y
64
vector.z = z
65
vector.__call__ = call
66
"""
67
68
else:
69
# These are the actual imports for the utility
70
from vpython import *
71
#from visual.graph import *
72
73
74
"""
75
#
76
#
77
# ACTUAL PHYSUTIL CODE FOLLOWS --------------------------------------------------
78
#
79
#
80
"""
81
82
# Initialize window positions for students (if we aren't unit testing)
83
if __name__ != "__main__":
84
scene.x = 50
85
scene.y = 50
86
87
# Helper function for returning proper size of something
88
def obj_size(obj):
89
if type(obj) == box or type(obj) == pyramid:
90
return obj.size
91
elif type(obj) == sphere:
92
return vector(obj.radius, obj.radius, obj.radius)
93
94
class MotionMap:
95
"""
96
This class assists students in constructing motion maps
97
using either arrows (measuring a quantity) or "breadcrumbs"
98
(with timestamps).
99
"""
100
101
def __init__(self, obj, tf, numMarkers, markerType="arrow",
102
markerScale=1, markerColor=color.red,
103
labelMarkerOrder=True, labelMarkerOffset=vector(0,0,0),
104
dropTime=False, timeOffset=vector(0,0,0), arrowOffset=vector(0,0,0), labelColor=color.white):
105
# MotionMap
106
# obj - object to track in mapping / placing markers
107
# tf - expected tFinal, used to space marker placement over time
108
# numMarkers - number of markers to place
109
# markerType - determines type of motionmap; options are "arrow" or "breadcrumbs"
110
# markerScale - replaces pSize / quantscale from motionmodule.py depending on type
111
# markerColor - color of markers
112
# labelMarkerOrder - drop numbers of markers?
113
# labelMarkerOffset - amount to offset numbering by
114
# dropTime - boolean determining whether a timestamp should be placed along with the marker
115
# timeOffset - if dropTime is True, determines the offset, if any, of the label from the marker
116
# arrowOffset - shift an arrow by an amount (x,y,z), useful for two arrows views
117
118
self.obj = obj
119
self.tf = tf
120
self.numMarkers = numMarkers
121
self.markerType = markerType
122
self.markerScale = markerScale
123
self.markerColor = markerColor
124
self.labelMarkerOrder = labelMarkerOrder
125
self.labelMarkerOffset = labelMarkerOffset
126
self.timeOffset = timeOffset
127
self.dropTime = dropTime
128
self.arrowOffset = arrowOffset
129
self.labelColor = labelColor
130
131
# Calculate size of interval for each step, set initial step index
132
try:
133
self.interval = self.tf / self.numMarkers
134
except TypeError as err:
135
print("**********TYPE ERROR**********")
136
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)!")
137
print("******************************")
138
print(err)
139
raise err
140
self.curMarker = 0
141
142
143
def update(self, t, quantity=vector(0,0,0)):
144
try:
145
# Display new arrow if t has broken next threshold
146
if t > (self.interval * self.curMarker):
147
# Increment threshold
148
self.curMarker += 1
149
150
# Display marker!
151
if self.markerType == "arrow":
152
arrow(pos=self.obj.pos+self.arrowOffset,
153
axis=self.markerScale*quantity, color=self.markerColor)
154
elif self.markerType == "breadcrumbs":
155
points(pos=self.obj.pos,
156
size=10*self.markerScale*quantity, color=self.markerColor)
157
158
#Also display timestamp if requested
159
if self.dropTime is not False:
160
epsilon = vector(0,self.markerScale*.5,0)+self.timeOffset
161
droptimeText = label(pos=self.obj.pos+epsilon, text='t='+str(t)+'s', height=10, box=False, color=self.labelColor)
162
163
# Same with order label
164
if self.labelMarkerOrder is not False:
165
label(pos=self.obj.pos-vector(0,self.markerScale*.5,0)+self.labelMarkerOffset, text=str(self.curMarker), height=10, box=False, color=self.labelColor)
166
except TypeError as err:
167
print("**********TYPE ERROR**********")
168
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)!")
169
print("******************************")
170
print(err)
171
raise err
172
173
class MotionMapN:
174
"""
175
This class assists students in constructing motion maps
176
using either arrows (measuring a quantity) or "breadcrumbs"
177
(with timestamps).
178
"""
179
180
def __init__(self, obj, dt, numSteps, markerType="arrow",
181
markerScale=1, markerColor=color.red,
182
labelMarkerOrder=True, labelMarkerOffset=vector(0,0,0),
183
dropTime=False, timeOffset=vector(0,0,0), arrowOffset=vector(0,0,0), labelColor=color.white):
184
# MotionMapN
185
# obj - object to track in mapping / placing markers
186
# dt - time between steps
187
# numSteps - number of steps between markers
188
# markerType - determines type of motionmap; options are "arrow" or "breadcrumbs"
189
# markerScale - replaces pSize / quantscale from motionmodule.py depending on type
190
# markerColor - color of markers
191
# labelMarkerOrder - drop numbers of markers?
192
# labelMarkerOffset - amount to offset numbering by
193
# dropTime - boolean determining whether a timestamp should be placed along with the marker
194
# timeOffset - if dropTime is True, determines the offset, if any, of the label from the markers
195
# arrowOffset - shift an arrow by an amount (x,y,z), useful for two arrows views
196
197
self.obj = obj
198
self.dt = dt
199
self.numSteps = numSteps
200
self.markerType = markerType
201
self.markerScale = markerScale
202
self.markerColor = markerColor
203
self.labelMarkerOrder = labelMarkerOrder
204
self.labelMarkerOffset = labelMarkerOffset
205
self.timeOffset = timeOffset
206
self.dropTime = dropTime
207
self.arrowOffset = arrowOffset
208
self.labelColor = labelColor
209
210
# Calculate size of interval for each step, set initial step index
211
try:
212
self.interval = self.dt * self.numSteps
213
except TypeError as err:
214
print("**********TYPE ERROR**********")
215
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)!")
216
print("******************************")
217
print(err)
218
raise err
219
self.curMarker = 0
220
221
222
def update(self, t, quantity=vector(0,0,0)):
223
try:
224
225
threshold = self.interval * self.curMarker
226
# Display new arrow if t has broken new threshold
227
if t >= threshold:
228
229
# Increment marker count
230
self.curMarker += 1
231
232
# Display marker!
233
if self.markerType == "arrow":
234
arrow(pos=self.obj.pos+self.arrowOffset,
235
axis=self.markerScale*quantity, color=self.markerColor)
236
elif self.markerType == "breadcrumbs":
237
points(pos=self.obj.pos,
238
size=10*self.markerScale*quantity, color=self.markerColor)
239
240
#Also display timestamp if requested
241
if self.dropTime is not False:
242
epsilon = vector(0,self.markerScale*.5,0)+self.timeOffset
243
droptimeText = label(pos=self.obj.pos+epsilon, text='t='+str(t)+'s', height=10, box=False, color=self.labelColor)
244
245
# Same with order label
246
if self.labelMarkerOrder is not False:
247
label(pos=self.obj.pos-vector(0,self.markerScale*.5,0)+self.labelMarkerOffset, text=str(self.curMarker), height=10, box=False, color=self.labelColor)
248
249
except TypeError as err:
250
print("**********TYPE ERROR**********")
251
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)!")
252
print("******************************")
253
print(err)
254
raise err
255
256
class PhysAxis:
257
"""
258
This class assists students in creating dynamic axes for their models.
259
"""
260
261
def __init__(self, obj, numLabels, axisType="x", axis=vector(1,0,0), startPos=None,
262
length=None, labels = None, labelOrientation="down", axisColor=color.yellow, labelColor=color.white):
263
# PhysAxis
264
# obj - Object which axis is oriented based on by default
265
# numLabels - number of labels on axis
266
# axisType - sets whether this is a default axis of x or y, or an arbitrary axis
267
# axis - unit vector defining the orientation of the axis to be created IF axisType = "arbitrary"
268
# startPos - start position for the axis - defaults to (-obj_size(obj).x/2,-4*obj_size(obj).y,0)
269
# length - length of the axis - defaults to obj_size(obj).x
270
# labelOrientation - how labels are placed relative to axis markers - "up", "down", "left", or "right"
271
272
try:
273
self.intervalMarkers = []
274
self.intervalLabels = []
275
self.labelText = labels
276
self.obj = obj
277
self.lastPos = vector(self.obj.pos.x, self.obj.pos.y, self.obj.pos.z)
278
self.numLabels = numLabels
279
self.axisType = axisType
280
self.axis = axis if axisType != "y" else vector(0,1,0)
281
self.length = length if (length is not None) else obj_size(obj).x
282
self.startPos = startPos if (startPos is not None) else vector(-obj_size(obj).x/2,-4*obj_size(obj).y,0) + self.obj.pos
283
self.axisColor = axisColor
284
self.labelColor = labelColor
285
286
if labelOrientation == "down":
287
self.labelShift = vector(0,-0.05*self.length,0)
288
elif labelOrientation == "up":
289
self.labelShift = vector(0,0.05*self.length,0)
290
elif labelOrientation == "left":
291
self.labelShift = vector(-0.1*self.length,0,0)
292
elif labelOrientation == "right":
293
self.labelShift = vector(0.1*self.length,0,0)
294
295
self.__reorient()
296
except TypeError as err:
297
print("**********TYPE ERROR**********")
298
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)!")
299
print("******************************")
300
print(err)
301
raise err
302
303
def update(self):
304
try:
305
# Determine if reference obj. has shifted since last update, if so shift us too
306
if self.obj.pos != self.lastPos:
307
diff = self.obj.pos - self.lastPos
308
309
for i in range(len(self.intervalMarkers)):
310
self.intervalMarkers[i].pos += diff
311
self.intervalLabels[i].pos += diff
312
self.axisCurve.pos = [x + diff for x in self.axisCurve.pos]
313
314
self.lastPos = vector(self.obj.pos.x, self.obj.pos.y, self.obj.pos.z)
315
except TypeError as err:
316
print("**********TYPE ERROR**********")
317
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)!")
318
print("******************************")
319
print(err)
320
raise err
321
322
def reorient(self, axis=None, startPos=None, length=None, labels=None, labelOrientation=None):
323
try:
324
# Determine which, if any, parameters are being modified
325
self.axis = axis if axis is not None else self.axis
326
self.startPos = startPos if startPos is not None else self.startPos
327
self.length = length if length is not None else self.length
328
self.labelText = labels if labels is not None else self.labels
329
330
# Re-do label orientation as well, if it has been set
331
if labelOrientation == "down":
332
self.labelShift = vector(0,-0.05*self.length,0)
333
elif labelOrientation == "up":
334
self.labelShift = vector(0,0.05*self.length,0)
335
elif labelOrientation == "left":
336
self.labelShift = vector(-0.1*self.length,0,0)
337
elif labelOrientation == "right":
338
self.labelShift = vector(0.1*self.length,0,0)
339
340
self.__reorient()
341
except TypeError as err:
342
print("**********TYPE ERROR**********")
343
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)!")
344
print("******************************")
345
print(err)
346
raise err
347
348
def __reorient(self):
349
# Actual internal axis setup code... determines first whether we are creating or updating
350
updating = True if len(self.intervalMarkers) > 0 else False
351
352
# Then determines the endpoint of the axis and the interval
353
final = self.startPos + (self.length * self.axis)
354
interval = (self.length / (self.numLabels-1)) * self.axis
355
356
# Loop for each interval marker, setting up or updating the markers and labels
357
i=0
358
while i<self.numLabels:
359
intervalPos = self.startPos+(i*interval)
360
361
# Determine text for this label
362
if self.labelText is not None:
363
labelText = self.labelText[i]
364
elif self.axisType == "y":
365
labelText = "%.2f" % intervalPos.y
366
else:
367
labelText = "%.2f" % intervalPos.x
368
369
if updating:
370
self.intervalMarkers[i].pos = intervalPos
371
self.intervalLabels[i].pos = intervalPos+self.labelShift
372
self.intervalLabels[i].text = str(labelText)
373
else:
374
self.intervalMarkers.append(
375
points(pos=intervalPos,color=self.axisColor,size = 6) )
376
self.intervalLabels.append(
377
label(pos=intervalPos+self.labelShift, text=str(labelText),box=False,height = 8, color=self.labelColor) )
378
i=i+1
379
380
# Finally, create / update the line itself!
381
if updating:
382
self.axisCurve.pos = [self.startPos,final]
383
else:
384
self.axisCurve = curve(pos=[self.startPos,final],color = self.axisColor)
385
386
class PhysTimer:
387
"""
388
This class assists students in creating an onscreen timer display.
389
"""
390
391
def __init__(self, x, y, fontsize = 13, useScientific=False, timerColor=color.white):
392
393
# PhysTimer
394
# x,y - world coordinates for the timer location
395
# fontsize - size of font for timer text
396
# useScientific - bool to turn off/on scientific notation for time
397
# timerColor - attribute controlling the color of the text
398
399
try:
400
self.useScientific = useScientific
401
self.timerColor = timerColor
402
if useScientific is False:
403
self.timerLabel = label(pos=vector(x,y,0), text='00:00:00.00', box=False, height = fontsize)
404
else:
405
self.timerLabel = label(pos=vector(x,y,0), text='00E01', box=False, height = fontsize)
406
except TypeError as err:
407
print("**********TYPE ERROR**********")
408
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)!")
409
print("******************************")
410
print(err)
411
raise err
412
413
def update(self, t):
414
try:
415
# Basically just use sprintf formatting according to either stopwatch or scientific notation
416
if self.useScientific:
417
self.timerLabel.text = "%.4E" % t
418
else:
419
hours = int(t / 3600)
420
mins = int((t / 60) % 60)
421
secs = int(t % 60)
422
frac = int(round(100 * (t % 1)))
423
if frac == 100:
424
frac = 0
425
secs = secs + 1;
426
self.timerLabel.text = "%02d:%02d:%02d.%02d" % (hours, mins, secs, frac)
427
except TypeError as err:
428
print("**********TYPE ERROR**********")
429
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)!")
430
print("******************************")
431
print(err)
432
raise err
433
434
class PhysGraph:
435
"""
436
This class assists students in creating graphs with advanced functionality.
437
"""
438
439
# Static, pre-determined list of colors from which each line will be generated
440
graphColors = [color.red, color.green, color.blue, color.yellow,
441
color.orange, color.cyan, color.magenta, color.white]
442
443
def __init__(self, numPlots=1, title = None, xlabel = None, ylabel = None, backgroundColor = color.white):
444
445
# title - sets window title
446
# xlabel - sets label on the horizontal axis
447
# ylabel - sets label on the vertical axis
448
# backgroundColor - sets background color of graph
449
450
try:
451
# Create our specific graph window
452
self.graphDisplay = graph(x = 475, y = 350, title = title, xtitle = xlabel, ytitle = ylabel, background = backgroundColor)
453
454
self.numPlots = numPlots
455
456
# Initialize each plot curve
457
self.graphs = []
458
for i in range(numPlots):
459
self.graphs.append(gcurve(color=PhysGraph.graphColors[i%len(PhysGraph.graphColors)]))
460
except TypeError as err:
461
print("**********TYPE ERROR**********")
462
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)!")
463
print("******************************")
464
print(err)
465
raise err
466
467
def plot(self, independent, *dependents):
468
try:
469
if len(dependents) != self.numPlots:
470
raise Exception("ERROR: Number of dependent parameters given does not match numPlots given at initialization!")
471
472
# Plot each line based on its parameter!
473
for i in range(len(dependents)):
474
self.graphs[i].plot(pos=(independent,dependents[i]))
475
except TypeError as err:
476
print("**********TYPE ERROR**********")
477
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)!")
478
print("******************************")
479
print(err)
480
raise err
481
482
#########################################################################################
483
## CSV Functions readcsv(), writecsv()
484
def readcsv(filename,cols=1, IgnoreHeader=False, startrow = 0, NumericData=True):
485
data = [0]*(cols);
486
for i in range(cols):
487
data[i]=[];
488
if sys.version_info.major == 2:
489
with open(filename,'rb') as csvfile: #open the file, and iterate over its data
490
csvdata = csv.reader(csvfile); #tell python that the file is a csv
491
for i in range(0,startrow): #skip to the startrow
492
csvdata.next();
493
if IgnoreHeader and startrow!=0:
494
csvdata.next(); #if ignoring header, advance one row
495
for row in csvdata: #iterate over the rows in the csv
496
#Assign the cols of each row to a variable
497
for c in range(cols): #read in the text values as floats in the array
498
if NumericData:
499
data[c].append(float(row[c]));
500
else:
501
data[c].append(row[c]);
502
elif sys.version_info.major == 3:
503
with open(filename,newline='') as csvfile: #open the file, and iterate over its data
504
csvdata = csv.reader(csvfile); #tell python that the file is a csv
505
for i in range(0,startrow): #skip to the startrow
506
csvdata.next();
507
if ignoreHeader and startrow!=0:
508
csvdata.next(); #if ignoring header, advance one row
509
for row in csvdata: #iterate over the rows in the csv
510
#Assign the cols of each row to a variable
511
for c in range(cols): #read in the text values as floats in the array
512
if NumericData:
513
data[c].append(float(row[c]));
514
else:
515
data[c].append(row[c]);
516
else:
517
sys.stderr.write('You need to use python 2* or 3* \n');
518
exit(1);
519
return data;
520
521
def writecsv(filename,datalist, header=[]):
522
csvfile = [];
523
useheader = False;
524
#make sure we have the correct versions of python
525
if sys.version_info.major == 2:
526
csvfile = open(filename,'wb');
527
elif sys.version_info.major == 3:
528
csvfile = open('pythonTest.csv', 'w',newline='');
529
else:
530
sys.stderr.write('You need to use python 2* or 3* \n');
531
exit(1);
532
533
#if user passed a numpy array, convert it
534
if isinstance(datalist,numpy_ndarray):
535
datalist = datalist.T;
536
datalist = datalist.tolist();
537
#if there is no data, close the file
538
if len(datalist)<1:
539
csvfile.close();
540
return;
541
#check to see if datalist is a single list or list of lists
542
isLofL = False;
543
ListLength = 0;
544
numLists = 0;
545
if isinstance(datalist[0],(list,tuple)): #check the first element in datalist
546
isLofL = True;
547
ListLength = len(datalist[0]);
548
numLists = len(datalist);
549
else:
550
isLofL = False;
551
ListLength = len(datalist);
552
numLists = 1;
553
#if a list then make sure everything is the same length
554
if isLofL:
555
for Lidx in range(1,len(datalist)):
556
if len(datalist[Lidx])!=ListLength:
557
sys.stderr.write('All lists in datalist must be the same length \n');
558
csvfile.close();
559
return;
560
#if header is present, make sure it is the same length as the number of cols
561
if len(header)!=0:
562
if len(header)!=numLists:
563
sys.stderr.write('Header length did not match the number of columns, ignoring header.\n');
564
else:
565
useheader = True;
566
567
#now that we've checked the inputs, loop and write outputs
568
DataWriter = csv.writer(csvfile, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL) # Create writer object
569
if useheader:
570
DataWriter.writerow(header);
571
for row in range(0,ListLength):
572
thisrow = [];
573
if numLists > 1:
574
for col in range(0,numLists):
575
thisrow.append(datalist[col][row]);
576
else:
577
thisrow.append(datalist[row]);
578
579
DataWriter.writerow(thisrow);
580
581
#close the csv file to save
582
csvfile.close();
583
## END CSV Functions
584
#########################################################################################
585
586
"""
587
#
588
#
589
# UNIT TESTING BELOW ----------------------------------------------------------------------
590
#
591
#
592
"""
593
594
class TestMotionMap(unittest.TestCase):
595
def setUp(self):
596
self.obj = Mock("obj")
597
self.obj.pos = vector(0,0,0)
598
self.tf = 10
599
self.numMarkers = 5
600
self.timeOffset = vector(1,1,1)
601
self.markerScale = 2
602
self.arrowOffset = vector(1,1,1)
603
604
arrow.reset()
605
points.reset()
606
label.reset()
607
608
self.map = MotionMap(self.obj, self.tf, self.numMarkers, markerType="arrow",
609
markerScale=2, markerColor=color.green,
610
dropTime=True, timeOffset=self.timeOffset, arrowOffset=self.arrowOffset)
611
612
def test_init(self):
613
self.assertEqual(self.obj, self.map.obj)
614
self.assertEqual(self.tf, self.map.tf)
615
self.assertEqual(self.numMarkers, self.map.numMarkers)
616
self.assertEqual("arrow", self.map.markerType)
617
self.assertEqual(self.markerScale, self.map.markerScale)
618
self.assertEqual(color.green, self.map.markerColor)
619
self.assertEqual(vector(1,1,1), self.map.timeOffset)
620
self.assertEqual(True, self.map.dropTime)
621
self.assertEqual(self.map.interval, self.tf / self.numMarkers)
622
self.assertEqual(self.map.curMarker, 0)
623
self.assertEqual(vector(1,1,1), self.map.arrowOffset)
624
625
626
def test_update(self):
627
self.map.curMarker = 1
628
629
self.map.update(0)
630
self.assertEqual(arrow.called, 0)
631
self.assertEqual(points.called, 0)
632
self.assertEqual(label.called, 0)
633
634
self.map.update(3, quantity=2)
635
self.assertEqual(arrow.called, 1)
636
self.assertEqual(points.called, 0)
637
self.assertEqual(label.called, 2)
638
self.assertEqual(self.map.curMarker, 2)
639
self.assertEqual(arrow.pos, self.obj.pos+self.arrowOffset)
640
self.assertEqual(arrow.axis, 4)
641
self.assertEqual(arrow.color, color.green)
642
self.assertEqual(label.text, "2")
643
644
class TestMotionMapN(unittest.TestCase):
645
def setUp(self):
646
self.obj = Mock("obj")
647
self.obj.pos = vector(0,0,0)
648
self.dt = 1
649
self.numSteps = 5
650
self.timeOffset = vector(1,1,1)
651
self.markerScale = 2
652
self.arrowOffset = vector(1,1,1)
653
654
arrow.reset()
655
points.reset()
656
label.reset()
657
658
self.map = MotionMapN(self.obj, self.dt, self.numSteps, markerType="arrow",
659
markerScale=2, markerColor=color.green,
660
dropTime=True, timeOffset=self.timeOffset, arrowOffset=self.arrowOffset)
661
662
def test_init(self):
663
self.assertEqual(self.obj, self.map.obj)
664
self.assertEqual(self.dt, self.map.dt)
665
self.assertEqual(self.numSteps, self.map.numSteps)
666
self.assertEqual("arrow", self.map.markerType)
667
self.assertEqual(self.markerScale, self.map.markerScale)
668
self.assertEqual(color.green, self.map.markerColor)
669
self.assertEqual(vector(1,1,1), self.map.timeOffset)
670
self.assertEqual(True, self.map.dropTime)
671
self.assertEqual(self.map.curMarker, 0)
672
self.assertEqual(vector(1,1,1), self.map.arrowOffset)
673
674
def test_update(self):
675
self.map.curMarker = 1
676
677
self.map.update(0)
678
self.assertEqual(arrow.called, 0)
679
self.assertEqual(points.called, 0)
680
self.assertEqual(label.called, 0)
681
682
self.map.update(3, quantity=2)
683
self.assertEqual(arrow.called, 0)
684
self.assertEqual(points.called, 0)
685
self.assertEqual(label.called, 0)
686
self.assertEqual(self.map.curMarker, 1)
687
self.assertEqual(arrow.pos, self.obj.pos+self.arrowOffset)
688
self.assertEqual(arrow.axis, 4)
689
self.assertEqual(arrow.color, color.green)
690
self.assertEqual(label.text, "2")
691
692
class TestPhysAxis(unittest.TestCase):
693
def setUp(self):
694
self.obj = Mock("obj")
695
self.obj.pos = vector(0,0,0)
696
self.numLabels = 5
697
self.axis = vector(1,1,1)
698
self.startPos = vector(0,1,0)
699
self.length = 10
700
self.labels = ["a", "b", "c", "d", "e"]
701
self.wrongLabels = ["a"]
702
self.axisType = "arbitrary"
703
self.labelOrientation="left"
704
705
curve.reset()
706
707
self.physAxis = PhysAxis(self.obj, self.numLabels, axisType=self.axisType, axis=self.axis,
708
startPos=self.startPos, length=self.length, labels = self.labels,
709
labelOrientation=self.labelOrientation)
710
711
712
def test_init(self):
713
self.assertEqual(self.physAxis.labelText, self.labels)
714
self.assertEqual(self.physAxis.obj, self.obj)
715
self.assertEqual(self.physAxis.lastPos, self.obj.pos)
716
self.assertEqual(self.physAxis.numLabels, self.numLabels)
717
self.assertEqual(self.physAxis.axis, self.axis)
718
self.assertEqual(self.physAxis.length, self.length)
719
self.assertEqual(self.physAxis.startPos, self.startPos)
720
self.assertEqual(self.physAxis.axisType, self.axisType)
721
self.assertEqual(self.physAxis.labelShift, vector(-0.1*self.length, 0, 0))
722
self.assertEqual(len(self.physAxis.intervalMarkers), self.numLabels)
723
self.assertEqual(len(self.physAxis.intervalLabels), self.numLabels)
724
725
intervalPos = self.startPos+(self.length * self.axis)
726
self.assertEqual(self.physAxis.intervalMarkers[-1].pos, intervalPos)
727
self.assertEqual(self.physAxis.intervalLabels[-1].pos, intervalPos+self.physAxis.labelShift)
728
self.assertEqual(self.physAxis.intervalLabels[-1].text, "e")
729
730
self.assertEqual(curve.called, 1)
731
732
def test_reorient(self):
733
newAxis = vector(0,0,1)
734
startPos = vector(1,0,0)
735
otherLabels = ["f", "g", "h", "i", "j"]
736
self.physAxis.reorient(axis=newAxis, startPos=startPos, length=1,
737
labels=otherLabels, labelOrientation="right")
738
self.assertEqual(self.physAxis.axis, newAxis)
739
self.assertEqual(self.physAxis.startPos, startPos)
740
self.assertEqual(self.physAxis.length, 1)
741
self.assertEqual(self.physAxis.labelShift, vector(0.1, 0, 0))
742
743
intervalPos = startPos+newAxis
744
self.assertEqual(self.physAxis.intervalMarkers[-1].pos, intervalPos)
745
self.assertEqual(self.physAxis.intervalLabels[-1].pos, intervalPos+self.physAxis.labelShift)
746
self.assertEqual(self.physAxis.intervalLabels[-1].text, "j")
747
748
self.assertEqual(curve.called, 1)
749
750
751
def test_update(self):
752
startMarkerPos = vector(self.physAxis.intervalMarkers[-1].pos.x,
753
self.physAxis.intervalMarkers[-1].pos.y,
754
self.physAxis.intervalMarkers[-1].pos.z)
755
startLabelPos = vector(self.physAxis.intervalLabels[-1].pos.x,
756
self.physAxis.intervalLabels[-1].pos.y,
757
self.physAxis.intervalLabels[-1].pos.z)
758
startCurvePos = vector(self.physAxis.axisCurve.pos[0].x,
759
self.physAxis.axisCurve.pos[0].y,
760
self.physAxis.axisCurve.pos[0].z)
761
762
self.physAxis.update()
763
764
self.assertEqual(startMarkerPos, self.physAxis.intervalMarkers[-1].pos)
765
self.assertEqual(startLabelPos, self.physAxis.intervalLabels[-1].pos)
766
self.assertEqual(startCurvePos, self.physAxis.axisCurve.pos[0])
767
768
self.physAxis.obj.pos = self.physAxis.obj.pos + vector(1,1,1)
769
self.physAxis.update()
770
771
self.assertNotEqual(startMarkerPos, self.physAxis.intervalMarkers[-1].pos)
772
self.assertNotEqual(startLabelPos, self.physAxis.intervalLabels[-1].pos)
773
self.assertNotEqual(startCurvePos, self.physAxis.axisCurve.pos[0])
774
775
class TestPhysGraph(unittest.TestCase):
776
def setUp(self):
777
self.physGraph = PhysGraph(numPlots = 5)
778
779
def test_init(self):
780
self.assertEqual(self.physGraph.graphDisplay.x, 475)
781
self.assertEqual(self.physGraph.graphDisplay.y, 350)
782
self.assertEqual(self.physGraph.numPlots, 5)
783
self.assertEqual(len(self.physGraph.graphs), 5)
784
785
def test_plot(self):
786
self.assertRaises(Exception, self.physGraph.plot, vector(0,0,0), vector(1,1,1))
787
788
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))
789
self.assertEqual(len(self.physGraph.graphs[-1].plots), 5)
790
self.assertEqual(self.physGraph.graphs[-1].plots[0], (vector(0,0,0), vector(1,1,1)))
791
792
class TestPhysTimer(unittest.TestCase):
793
def setUp(self):
794
self.timer = PhysTimer(1,1)
795
796
def test_init(self):
797
self.assertEquals(self.timer.timerLabel.text, "00:00:00.00")
798
self.assertEquals(self.timer.timerLabel.pos, vector(1,1,0))
799
800
def test_update(self):
801
self.timer.update(3923.65)
802
self.assertEquals(self.timer.timerLabel.text, "01:05:23.65")
803
804
self.timer.useScientific=True
805
self.timer.update(3923.65)
806
self.assertEquals(self.timer.timerLabel.text, "3.9237E+03")
807
808
809
# Now set up unittests to be executed when module is run individually
810
if __name__ == "__main__":
811
print("Beginning unit tests!")
812
unittest.main()
813
814