├── .gitignore ├── README.md ├── buoyancy ├── buoyancy.py └── physutil.py ├── circular motion ├── circularMotion.py └── physutil.py ├── energy of mass on spring ├── massOnSpring.py └── physutil.py ├── inclined plane ├── inclinedPlane.py ├── inclinedPlane52.py ├── inclinedPlane53.py ├── inclinedPlane54.py └── physutil.py ├── inelastic collision ├── inelastic.py └── physutil.py ├── kinematics ├── 1-dMotionSimulation.py ├── PMPM lab iterative.py ├── PMPM lab.py ├── physutil.py ├── program outline.pdf └── projectile motion.py ├── satellite motion ├── binary.py ├── physutil.py ├── physutil.pyc └── satellite.py ├── simple harmonic motion ├── physutil.py └── shm.py └── special relativity ├── lengthContraction.py ├── momentum.py └── physutil.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vpython-physics 2 | =============== 3 | 4 | collection of computational physics models using physutil and VPython 5 | 6 | I created this repository to share various models that I created for my AP Physics B class. These examples are functional, but students will extend them to explore other aspects of the topic. 7 | 8 | Feel free to use these in your classes and add your own models to the repository. 9 | 10 | 11 | References 12 | ---------- 13 | 14 | VPython: http://www.vpython.org/ 15 | 16 | Physics Utility for VPython (physutil): 17 | https://per.gatech.edu/wiki/doku.php?id=projects:hscomp:physutil 18 | 19 | John Burk's collection of posts on computational modeling: 20 | https://quantumprogress.wordpress.com/computational-modeling/ 21 | 22 | 23 | 24 | Geoff Schmit 25 | @gcschmit 26 | gcschmit@gmail.com -------------------------------------------------------------------------------- /buoyancy/buoyancy.py: -------------------------------------------------------------------------------- 1 | ### INITIALIZE VPYTHON 2 | # ----------------------------------------------------------------------- 3 | 4 | from __future__ import division 5 | from visual import * 6 | from physutil import * 7 | from visual.graph import * 8 | 9 | ### FUNCTIONS 10 | # ------------------------------------------------------------------------ 11 | 12 | # Calculates the volume of the specified objecdt 13 | def volumeSubmerged(object, fluid): 14 | topOfFluid = fluid.pos.y + fluid.size.y/2 15 | topOfObject = object.pos.y + object.size.y/2 16 | bottomOfObject = object.pos.y - object.size.y/2 17 | 18 | if topOfObject <= topOfFluid: 19 | heightSubmerged = object.size.y 20 | elif bottomOfObject >= topOfFluid: 21 | heightSubmerged = 0 22 | else: 23 | heightSubmerged = (topOfFluid - bottomOfObject) 24 | 25 | return (object.size.x * heightSubmerged * object.size.z) 26 | 27 | 28 | ### SETUP ELEMENTS FOR GRAPHING, SIMULATION, VISUALIZATION, TIMING 29 | # ------------------------------------------------------------------------ 30 | 31 | # Set window title 32 | scene.title = "Buoyancy" 33 | 34 | # Make scene background black 35 | scene.background = color.black 36 | 37 | # Define scene objects (units are in meters) 38 | fluid = box(size = (2, 2, .2), color = color.blue, opacity = 0.3) 39 | object = box(color = color.red) 40 | 41 | # Set up graph with three plots 42 | graphs = PhysGraph(2) 43 | 44 | 45 | ### SETUP PARAMETERS AND INITIAL CONDITIONS 46 | # ---------------------------------------------------------------------------------------- 47 | 48 | # Define parameters 49 | 50 | dragCoefficient = -5.0 51 | 52 | fluid.density = 1000 # density of the fluid in units of kg/m^3 53 | 54 | object.density = 500 # density of the object in units of kg/m^3 55 | object.pos = vector(0, 0, 0) # initial position of the mass in (x, y, z) form, units are in meters 56 | object.size = size = (0.4, 0.4, 0.1) # size of the object in (x, y, z) form, units are in meters 57 | object.v = vector(0, 0, 0) # initial velocity of mass in (vx, vy, vz) form, units are m/s 58 | 59 | g = -9.8 # acceleration due to gravity; units are m/s/s 60 | 61 | # Define time parameters 62 | t = 0 # starting time 63 | deltat = 0.001 # time step units are s 64 | 65 | 66 | ### CALCULATION LOOP; perform physics updates and drawing 67 | # ------------------------------------------------------------------------------------ 68 | 69 | while t < 20 and object.pos.y > (fluid.pos.y - fluid.size.y/2) : # run for one second 70 | 71 | # Required to make animation visible / refresh smoothly (keeps program from running faster 72 | # than 1000 frames/s) 73 | rate(1000) 74 | 75 | # compute the force on the object by the fluid (buoyant force) 76 | Fbuoyant = fluid.density * (-g) * volumeSubmerged(object, fluid) 77 | 78 | # compute the force on the object by the gravitational field 79 | mass = object.density * object.size.x * object.size.y * object.size.z 80 | Fgravity = mass * g 81 | 82 | # compute the drag force on the object 83 | Fdrag = dragCoefficient * object.v.y 84 | 85 | # Compute Net Force 86 | Fnet = vector(0, Fbuoyant + Fgravity + Fdrag, 0) 87 | 88 | # Newton's 2nd Law 89 | object.v = object.v + (Fnet/mass * deltat) 90 | 91 | # Position update 92 | object.pos = object.pos + object.v * deltat 93 | 94 | # Update graphs 95 | graphs.plot(t, Fbuoyant, Fgravity) # plot energies 96 | 97 | # Time update 98 | t = t + deltat 99 | 100 | 101 | ### OUTPUT 102 | # -------------------------------------------------------------------------------------- 103 | 104 | # Print the final time and the cart's final position 105 | print t 106 | -------------------------------------------------------------------------------- /buoyancy/physutil.py: -------------------------------------------------------------------------------- 1 | # physutil.py v1.22 2 | # Copyright (c) 2011-2012 GT Physics Education Research Group 3 | # License: GPL-3.0 (http://opensource.org/licenses/GPL-3.0) 4 | 5 | # This module is built to simplify and assist high school physics students 6 | # in the construction of models for lab exercises using VPython. 7 | 8 | 9 | # Revisions by date 10 | 11 | # v1.22 24 January 2011 -- Danny Caballero 12 | # Added labelColor attribute to PhysAxis, MotionMap, and MotionMapN 13 | # controls color of text 14 | 15 | # v1.21 5 December 2011 -- Danny Caballero 16 | # Added timerColor attribute to PhysTimer 17 | # controls color of text 18 | 19 | # v1.2 19 October 2011 -- Danny Caballero 20 | # Added MotionMapN, a class that allows the placing of breadcrumbs or arrows 21 | # every "n" steps. 22 | 23 | # v1.13 26 September 2011 -- Daniel Borrero 24 | # Fixed unit test bug for PhysTimer 25 | 26 | # v1.12 30 August 2011 -- Daniel Borrero 27 | # Fixed bug in PhysTimer output 28 | # E.g., 2.00 s is now displayed as 00:00:02.00 instead of 00:00:01:100 29 | 30 | # v1.11 29 August 2011 -- Danny Caballero 31 | # Changed License to GNU 32 | 33 | # v1.1 15 August 2011 -- Daniel Borrero 34 | # Print statements made compatible with Python 3.1 35 | 36 | # v1.01 16 July 2011 -- Danny Caballero 37 | # Added ability to change PhysAxis color using axisColor 38 | 39 | # v1.0 29 April 2011 -- CS Build Team 40 | # Heavy Modification 41 | 42 | # v0.1 05 January 2011 -- Danny Caballero 43 | # Initial Build 44 | 45 | from __future__ import division 46 | import unittest 47 | 48 | """ 49 | # 50 | # 51 | # UNIT TESTING / IMPORT SETUP CODE ------------------------------------------------------------ 52 | # 53 | # 54 | """ 55 | 56 | # Determine whether we are being used as a module or just running unittests (for mock purposes this is important) 57 | if __name__ == "__main__": 58 | # If we are unit testing, set up mock objects (must be done before classes are defined below!) 59 | from visual import vector 60 | class Mock: 61 | def __init__(self, name, *args, **kwargs): 62 | self.name = name 63 | self.called = 0 64 | 65 | def __call__(self, *args, **kwargs): 66 | self.args = args 67 | self.kwargs = kwargs 68 | for name in kwargs: 69 | setattr(self, name, kwargs[name]) 70 | self.called += 1 71 | return self 72 | 73 | def reset(self): 74 | self.called = 0 75 | 76 | color = Mock("color") 77 | color.red = "red" 78 | color.green = "green" 79 | color.blue = "blue" 80 | color.yellow = "yellow" 81 | color.orange = "orange" 82 | color.cyan = "cyan" 83 | color.magenta = "magenta" 84 | color.white = "white" 85 | 86 | arrow = Mock("arrow") 87 | label = Mock("label") 88 | points = Mock("points") 89 | curve = Mock("curve") 90 | gdisplay = Mock("gdisplay") 91 | gcurve = Mock("gcurve") 92 | gcurve.plots = [] 93 | def mockPlot(pos): 94 | gcurve.plots.append(pos) 95 | gcurve.plot = mockPlot 96 | 97 | """ 98 | vector = Mock("vector") 99 | def call(x, y, z): 100 | vector.x = x 101 | vector.y = y 102 | vector.z = z 103 | vector.__call__ = call 104 | """ 105 | 106 | else: 107 | # These are the actual imports for the utility 108 | from visual import * 109 | from visual.graph import * 110 | 111 | 112 | """ 113 | # 114 | # 115 | # ACTUAL PHYSUTIL CODE FOLLOWS -------------------------------------------------- 116 | # 117 | # 118 | """ 119 | 120 | # Initialize window positions for students (if we aren't unit testing) 121 | if __name__ != "__main__": 122 | scene.x = 50 123 | scene.y = 50 124 | 125 | # Helper function for returning proper size of something 126 | def obj_size(obj): 127 | if type(obj) == box or type(obj) == pyramid: 128 | return obj.size 129 | elif type(obj) == sphere: 130 | return vector(obj.radius, obj.radius, obj.radius) 131 | 132 | class MotionMap: 133 | """ 134 | This class assists students in constructing motion maps 135 | using either arrows (measuring a quantity) or "breadcrumbs" 136 | (with timestamps). 137 | """ 138 | 139 | def __init__(self, obj, tf, numMarkers, markerType="arrow", 140 | markerScale=1, markerColor=color.red, 141 | labelMarkerOrder=True, labelMarkerOffset=vector(0,0,0), 142 | dropTime=False, timeOffset=vector(0,0,0), arrowOffset=vector(0,0,0), labelColor=color.white): 143 | # MotionMap 144 | # obj - object to track in mapping / placing markers 145 | # tf - expected tFinal, used to space marker placement over time 146 | # numMarkers - number of markers to place 147 | # markerType - determines type of motionmap; options are "arrow" or "breadcrumbs" 148 | # markerScale - replaces pSize / quantscale from motionmodule.py depending on type 149 | # markerColor - color of markers 150 | # labelMarkerOrder - drop numbers of markers? 151 | # labelMarkerOffset - amount to offset numbering by 152 | # dropTime - boolean determining whether a timestamp should be placed along with the marker 153 | # timeOffset - if dropTime is True, determines the offset, if any, of the label from the marker 154 | # arrowOffset - shift an arrow by an amount (x,y,z), useful for two arrows views 155 | 156 | self.obj = obj 157 | self.tf = tf 158 | self.numMarkers = numMarkers 159 | self.markerType = markerType 160 | self.markerScale = markerScale 161 | self.markerColor = markerColor 162 | self.labelMarkerOrder = labelMarkerOrder 163 | self.labelMarkerOffset = labelMarkerOffset 164 | self.timeOffset = timeOffset 165 | self.dropTime = dropTime 166 | self.arrowOffset = arrowOffset 167 | self.labelColor = labelColor 168 | 169 | # Calculate size of interval for each step, set initial step index 170 | try: 171 | self.interval = self.tf / self.numMarkers 172 | except TypeError as err: 173 | print("**********TYPE ERROR**********") 174 | 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)!") 175 | print("******************************") 176 | print(err) 177 | raise err 178 | self.curMarker = 0 179 | 180 | 181 | def update(self, t, quantity=1): 182 | try: 183 | # Display new arrow if t has broken next threshold 184 | if t > (self.interval * self.curMarker): 185 | # Increment threshold 186 | self.curMarker += 1 187 | 188 | # Display marker! 189 | if self.markerType == "arrow": 190 | arrow(pos=self.obj.pos+self.arrowOffset, 191 | axis=self.markerScale*quantity, color=self.markerColor) 192 | elif self.markerType == "breadcrumbs": 193 | points(pos=self.obj.pos, 194 | size=10*self.markerScale*quantity, color=self.markerColor) 195 | 196 | #Also display timestamp if requested 197 | if self.dropTime is not False: 198 | epsilon = vector(0,self.markerScale*.5,0)+self.timeOffset 199 | droptimeText = label(pos=self.obj.pos+epsilon, text='t='+str(t)+'s', height=10, box=False, color=self.labelColor) 200 | 201 | # Same with order label 202 | if self.labelMarkerOrder is not False: 203 | label(pos=self.obj.pos-vector(0,self.markerScale*.5,0)+self.labelMarkerOffset, text=str(self.curMarker), height=10, box=False, color=self.labelColor) 204 | except TypeError as err: 205 | print("**********TYPE ERROR**********") 206 | 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)!") 207 | print("******************************") 208 | print(err) 209 | raise err 210 | 211 | class MotionMapN: 212 | """ 213 | This class assists students in constructing motion maps 214 | using either arrows (measuring a quantity) or "breadcrumbs" 215 | (with timestamps). 216 | """ 217 | 218 | def __init__(self, obj, dt, numSteps, markerType="arrow", 219 | markerScale=1, markerColor=color.red, 220 | labelMarkerOrder=True, labelMarkerOffset=vector(0,0,0), 221 | dropTime=False, timeOffset=vector(0,0,0), arrowOffset=vector(0,0,0), labelColor=color.white): 222 | # MotionMapN 223 | # obj - object to track in mapping / placing markers 224 | # dt - time between steps 225 | # numSteps - number of steps between markers 226 | # markerType - determines type of motionmap; options are "arrow" or "breadcrumbs" 227 | # markerScale - replaces pSize / quantscale from motionmodule.py depending on type 228 | # markerColor - color of markers 229 | # labelMarkerOrder - drop numbers of markers? 230 | # labelMarkerOffset - amount to offset numbering by 231 | # dropTime - boolean determining whether a timestamp should be placed along with the marker 232 | # timeOffset - if dropTime is True, determines the offset, if any, of the label from the markers 233 | # arrowOffset - shift an arrow by an amount (x,y,z), useful for two arrows views 234 | 235 | self.obj = obj 236 | self.dt = dt 237 | self.numSteps = numSteps 238 | self.markerType = markerType 239 | self.markerScale = markerScale 240 | self.markerColor = markerColor 241 | self.labelMarkerOrder = labelMarkerOrder 242 | self.labelMarkerOffset = labelMarkerOffset 243 | self.timeOffset = timeOffset 244 | self.dropTime = dropTime 245 | self.arrowOffset = arrowOffset 246 | self.labelColor = labelColor 247 | 248 | # Calculate size of interval for each step, set initial step index 249 | try: 250 | self.interval = self.dt * self.numSteps 251 | except TypeError as err: 252 | print("**********TYPE ERROR**********") 253 | 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)!") 254 | print("******************************") 255 | print(err) 256 | raise err 257 | self.curMarker = 0 258 | 259 | 260 | def update(self, t, quantity=1): 261 | try: 262 | 263 | threshold = self.interval * self.curMarker 264 | # Display new arrow if t has broken new threshold 265 | if t >= threshold: 266 | 267 | # Increment marker count 268 | self.curMarker += 1 269 | 270 | # Display marker! 271 | if self.markerType == "arrow": 272 | arrow(pos=self.obj.pos+self.arrowOffset, 273 | axis=self.markerScale*quantity, color=self.markerColor) 274 | elif self.markerType == "breadcrumbs": 275 | points(pos=self.obj.pos, 276 | size=10*self.markerScale*quantity, color=self.markerColor) 277 | 278 | #Also display timestamp if requested 279 | if self.dropTime is not False: 280 | epsilon = vector(0,self.markerScale*.5,0)+self.timeOffset 281 | droptimeText = label(pos=self.obj.pos+epsilon, text='t='+str(t)+'s', height=10, box=False, color=self.labelColor) 282 | 283 | # Same with order label 284 | if self.labelMarkerOrder is not False: 285 | label(pos=self.obj.pos-vector(0,self.markerScale*.5,0)+self.labelMarkerOffset, text=str(self.curMarker), height=10, box=False, color=self.labelColor) 286 | 287 | except TypeError as err: 288 | print("**********TYPE ERROR**********") 289 | 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)!") 290 | print("******************************") 291 | print(err) 292 | raise err 293 | 294 | class PhysAxis: 295 | """ 296 | This class assists students in creating dynamic axes for their models. 297 | """ 298 | 299 | def __init__(self, obj, numLabels, axisType="x", axis=vector(1,0,0), startPos=None, 300 | length=None, labels = None, labelOrientation="down", axisColor=color.yellow, labelColor=color.white): 301 | # PhysAxis 302 | # obj - Object which axis is oriented based on by default 303 | # numLabels - number of labels on axis 304 | # axisType - sets whether this is a default axis of x or y, or an arbitrary axis 305 | # axis - unit vector defining the orientation of the axis to be created IF axisType = "arbitrary" 306 | # startPos - start position for the axis - defaults to (-obj_size(obj).x/2,-4*obj_size(obj).y,0) 307 | # length - length of the axis - defaults to obj_size(obj).x 308 | # labelOrientation - how labels are placed relative to axis markers - "up", "down", "left", or "right" 309 | 310 | try: 311 | self.intervalMarkers = [] 312 | self.intervalLabels = [] 313 | self.labelText = labels 314 | self.obj = obj 315 | self.lastPos = vector(self.obj.pos.x, self.obj.pos.y, self.obj.pos.z) 316 | self.numLabels = numLabels 317 | self.axisType = axisType 318 | self.axis = axis if axisType != "y" else vector(0,1,0) 319 | self.length = length if (length is not None) else obj_size(obj).x 320 | self.startPos = startPos if (startPos is not None) else vector(-obj_size(obj).x/2,-4*obj_size(obj).y,0) 321 | self.axisColor = axisColor 322 | self.labelColor = labelColor 323 | 324 | if labelOrientation == "down": 325 | self.labelShift = vector(0,-0.05*self.length,0) 326 | elif labelOrientation == "up": 327 | self.labelShift = vector(0,0.05*self.length,0) 328 | elif labelOrientation == "left": 329 | self.labelShift = vector(-0.1*self.length,0,0) 330 | elif labelOrientation == "right": 331 | self.labelShift = vector(0.1*self.length,0,0) 332 | 333 | self.__reorient() 334 | except TypeError as err: 335 | print("**********TYPE ERROR**********") 336 | 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)!") 337 | print("******************************") 338 | print(err) 339 | raise err 340 | 341 | def update(self): 342 | try: 343 | # Determine if reference obj. has shifted since last update, if so shift us too 344 | if self.obj.pos != self.lastPos: 345 | diff = self.obj.pos - self.lastPos 346 | 347 | for i in range(len(self.intervalMarkers)): 348 | self.intervalMarkers[i].pos += diff 349 | self.intervalLabels[i].pos += diff 350 | self.axisCurve.pos = [x + diff for x in self.axisCurve.pos] 351 | 352 | self.lastPos = vector(self.obj.pos.x, self.obj.pos.y, self.obj.pos.z) 353 | except TypeError as err: 354 | print("**********TYPE ERROR**********") 355 | 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)!") 356 | print("******************************") 357 | print(err) 358 | raise err 359 | 360 | def reorient(self, axis=None, startPos=None, length=None, labels=None, labelOrientation=None): 361 | try: 362 | # Determine which, if any, parameters are being modified 363 | self.axis = axis if axis is not None else self.axis 364 | self.startPos = startPos if startPos is not None else self.startPos 365 | self.length = length if length is not None else self.length 366 | self.labelText = labels if labels is not None else self.labels 367 | 368 | # Re-do label orientation as well, if it has been set 369 | if labelOrientation == "down": 370 | self.labelShift = vector(0,-0.05*self.length,0) 371 | elif labelOrientation == "up": 372 | self.labelShift = vector(0,0.05*self.length,0) 373 | elif labelOrientation == "left": 374 | self.labelShift = vector(-0.1*self.length,0,0) 375 | elif labelOrientation == "right": 376 | self.labelShift = vector(0.1*self.length,0,0) 377 | 378 | self.__reorient() 379 | except TypeError as err: 380 | print("**********TYPE ERROR**********") 381 | 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)!") 382 | print("******************************") 383 | print(err) 384 | raise err 385 | 386 | def __reorient(self): 387 | # Actual internal axis setup code... determines first whether we are creating or updating 388 | updating = True if len(self.intervalMarkers) > 0 else False 389 | 390 | # Then determines the endpoint of the axis and the interval 391 | final = self.startPos + (self.length * self.axis) 392 | interval = (self.length / (self.numLabels-1)) * self.axis 393 | 394 | # Loop for each interval marker, setting up or updating the markers and labels 395 | i=0 396 | while i (self.interval * self.curMarker): 185 | # Increment threshold 186 | self.curMarker += 1 187 | 188 | # Display marker! 189 | if self.markerType == "arrow": 190 | arrow(pos=self.obj.pos+self.arrowOffset, 191 | axis=self.markerScale*quantity, color=self.markerColor) 192 | elif self.markerType == "breadcrumbs": 193 | points(pos=self.obj.pos, 194 | size=10*self.markerScale*quantity, color=self.markerColor) 195 | 196 | #Also display timestamp if requested 197 | if self.dropTime is not False: 198 | epsilon = vector(0,self.markerScale*.5,0)+self.timeOffset 199 | droptimeText = label(pos=self.obj.pos+epsilon, text='t='+str(t)+'s', height=10, box=False, color=self.labelColor) 200 | 201 | # Same with order label 202 | if self.labelMarkerOrder is not False: 203 | label(pos=self.obj.pos-vector(0,self.markerScale*.5,0)+self.labelMarkerOffset, text=str(self.curMarker), height=10, box=False, color=self.labelColor) 204 | except TypeError as err: 205 | print("**********TYPE ERROR**********") 206 | 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)!") 207 | print("******************************") 208 | print(err) 209 | raise err 210 | 211 | class MotionMapN: 212 | """ 213 | This class assists students in constructing motion maps 214 | using either arrows (measuring a quantity) or "breadcrumbs" 215 | (with timestamps). 216 | """ 217 | 218 | def __init__(self, obj, dt, numSteps, markerType="arrow", 219 | markerScale=1, markerColor=color.red, 220 | labelMarkerOrder=True, labelMarkerOffset=vector(0,0,0), 221 | dropTime=False, timeOffset=vector(0,0,0), arrowOffset=vector(0,0,0), labelColor=color.white): 222 | # MotionMapN 223 | # obj - object to track in mapping / placing markers 224 | # dt - time between steps 225 | # numSteps - number of steps between markers 226 | # markerType - determines type of motionmap; options are "arrow" or "breadcrumbs" 227 | # markerScale - replaces pSize / quantscale from motionmodule.py depending on type 228 | # markerColor - color of markers 229 | # labelMarkerOrder - drop numbers of markers? 230 | # labelMarkerOffset - amount to offset numbering by 231 | # dropTime - boolean determining whether a timestamp should be placed along with the marker 232 | # timeOffset - if dropTime is True, determines the offset, if any, of the label from the markers 233 | # arrowOffset - shift an arrow by an amount (x,y,z), useful for two arrows views 234 | 235 | self.obj = obj 236 | self.dt = dt 237 | self.numSteps = numSteps 238 | self.markerType = markerType 239 | self.markerScale = markerScale 240 | self.markerColor = markerColor 241 | self.labelMarkerOrder = labelMarkerOrder 242 | self.labelMarkerOffset = labelMarkerOffset 243 | self.timeOffset = timeOffset 244 | self.dropTime = dropTime 245 | self.arrowOffset = arrowOffset 246 | self.labelColor = labelColor 247 | 248 | # Calculate size of interval for each step, set initial step index 249 | try: 250 | self.interval = self.dt * self.numSteps 251 | except TypeError as err: 252 | print("**********TYPE ERROR**********") 253 | 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)!") 254 | print("******************************") 255 | print(err) 256 | raise err 257 | self.curMarker = 0 258 | 259 | 260 | def update(self, t, quantity=1): 261 | try: 262 | 263 | threshold = self.interval * self.curMarker 264 | # Display new arrow if t has broken new threshold 265 | if t >= threshold: 266 | 267 | # Increment marker count 268 | self.curMarker += 1 269 | 270 | # Display marker! 271 | if self.markerType == "arrow": 272 | arrow(pos=self.obj.pos+self.arrowOffset, 273 | axis=self.markerScale*quantity, color=self.markerColor) 274 | elif self.markerType == "breadcrumbs": 275 | points(pos=self.obj.pos, 276 | size=10*self.markerScale*quantity, color=self.markerColor) 277 | 278 | #Also display timestamp if requested 279 | if self.dropTime is not False: 280 | epsilon = vector(0,self.markerScale*.5,0)+self.timeOffset 281 | droptimeText = label(pos=self.obj.pos+epsilon, text='t='+str(t)+'s', height=10, box=False, color=self.labelColor) 282 | 283 | # Same with order label 284 | if self.labelMarkerOrder is not False: 285 | label(pos=self.obj.pos-vector(0,self.markerScale*.5,0)+self.labelMarkerOffset, text=str(self.curMarker), height=10, box=False, color=self.labelColor) 286 | 287 | except TypeError as err: 288 | print("**********TYPE ERROR**********") 289 | 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)!") 290 | print("******************************") 291 | print(err) 292 | raise err 293 | 294 | class PhysAxis: 295 | """ 296 | This class assists students in creating dynamic axes for their models. 297 | """ 298 | 299 | def __init__(self, obj, numLabels, axisType="x", axis=vector(1,0,0), startPos=None, 300 | length=None, labels = None, labelOrientation="down", axisColor=color.yellow, labelColor=color.white): 301 | # PhysAxis 302 | # obj - Object which axis is oriented based on by default 303 | # numLabels - number of labels on axis 304 | # axisType - sets whether this is a default axis of x or y, or an arbitrary axis 305 | # axis - unit vector defining the orientation of the axis to be created IF axisType = "arbitrary" 306 | # startPos - start position for the axis - defaults to (-obj_size(obj).x/2,-4*obj_size(obj).y,0) 307 | # length - length of the axis - defaults to obj_size(obj).x 308 | # labelOrientation - how labels are placed relative to axis markers - "up", "down", "left", or "right" 309 | 310 | try: 311 | self.intervalMarkers = [] 312 | self.intervalLabels = [] 313 | self.labelText = labels 314 | self.obj = obj 315 | self.lastPos = vector(self.obj.pos.x, self.obj.pos.y, self.obj.pos.z) 316 | self.numLabels = numLabels 317 | self.axisType = axisType 318 | self.axis = axis if axisType != "y" else vector(0,1,0) 319 | self.length = length if (length is not None) else obj_size(obj).x 320 | self.startPos = startPos if (startPos is not None) else vector(-obj_size(obj).x/2,-4*obj_size(obj).y,0) 321 | self.axisColor = axisColor 322 | self.labelColor = labelColor 323 | 324 | if labelOrientation == "down": 325 | self.labelShift = vector(0,-0.05*self.length,0) 326 | elif labelOrientation == "up": 327 | self.labelShift = vector(0,0.05*self.length,0) 328 | elif labelOrientation == "left": 329 | self.labelShift = vector(-0.1*self.length,0,0) 330 | elif labelOrientation == "right": 331 | self.labelShift = vector(0.1*self.length,0,0) 332 | 333 | self.__reorient() 334 | except TypeError as err: 335 | print("**********TYPE ERROR**********") 336 | 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)!") 337 | print("******************************") 338 | print(err) 339 | raise err 340 | 341 | def update(self): 342 | try: 343 | # Determine if reference obj. has shifted since last update, if so shift us too 344 | if self.obj.pos != self.lastPos: 345 | diff = self.obj.pos - self.lastPos 346 | 347 | for i in range(len(self.intervalMarkers)): 348 | self.intervalMarkers[i].pos += diff 349 | self.intervalLabels[i].pos += diff 350 | self.axisCurve.pos = [x + diff for x in self.axisCurve.pos] 351 | 352 | self.lastPos = vector(self.obj.pos.x, self.obj.pos.y, self.obj.pos.z) 353 | except TypeError as err: 354 | print("**********TYPE ERROR**********") 355 | 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)!") 356 | print("******************************") 357 | print(err) 358 | raise err 359 | 360 | def reorient(self, axis=None, startPos=None, length=None, labels=None, labelOrientation=None): 361 | try: 362 | # Determine which, if any, parameters are being modified 363 | self.axis = axis if axis is not None else self.axis 364 | self.startPos = startPos if startPos is not None else self.startPos 365 | self.length = length if length is not None else self.length 366 | self.labelText = labels if labels is not None else self.labels 367 | 368 | # Re-do label orientation as well, if it has been set 369 | if labelOrientation == "down": 370 | self.labelShift = vector(0,-0.05*self.length,0) 371 | elif labelOrientation == "up": 372 | self.labelShift = vector(0,0.05*self.length,0) 373 | elif labelOrientation == "left": 374 | self.labelShift = vector(-0.1*self.length,0,0) 375 | elif labelOrientation == "right": 376 | self.labelShift = vector(0.1*self.length,0,0) 377 | 378 | self.__reorient() 379 | except TypeError as err: 380 | print("**********TYPE ERROR**********") 381 | 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)!") 382 | print("******************************") 383 | print(err) 384 | raise err 385 | 386 | def __reorient(self): 387 | # Actual internal axis setup code... determines first whether we are creating or updating 388 | updating = True if len(self.intervalMarkers) > 0 else False 389 | 390 | # Then determines the endpoint of the axis and the interval 391 | final = self.startPos + (self.length * self.axis) 392 | interval = (self.length / (self.numLabels-1)) * self.axis 393 | 394 | # Loop for each interval marker, setting up or updating the markers and labels 395 | i=0 396 | while i 0 : # while the cart's y-position is greater than 0 (above the ground) 67 | 68 | # Required to make animation visible / refresh smoothly (keeps program from running faster 69 | # than 1000 frames/s) 70 | rate(1000) 71 | 72 | # Compute Net Force 73 | # set the direction of the net force along the inclined plane 74 | Fnet = norm(inclinedPlane.axis) 75 | # set the magnitude to the component of the gravitational force parallel to the inclined plane 76 | Fnet.mag = cart.m * g * sin(theta) 77 | 78 | # Newton's 2nd Law 79 | cart.v = cart.v + (Fnet/cart.m * deltat) 80 | 81 | # Position update 82 | cart.pos = cart.pos + cart.v * deltat 83 | 84 | # Update motion map, graph, timer, and trail 85 | motionMap.update(t) 86 | posgraph.plot(t, mag(cart.pos)) # plot position (along inclined plane) vs. time 87 | velgraph.plot(t, mag(cart.v)) # plot velocity (along inclined plane) vs. time 88 | accelgraph.plot(t, mag(Fnet) / cart.m) # plot acceleration (along inclined plane) vs. time 89 | trail.append(pos = cart.pos) 90 | timerDisplay.update(t) 91 | 92 | # Time update 93 | t = t + deltat 94 | 95 | ### OUTPUT 96 | # -------------------------------------------------------------------------------------- 97 | 98 | # Print the final time and the cart's final position 99 | print t 100 | print cart.pos 101 | -------------------------------------------------------------------------------- /inclined plane/inclinedPlane52.py: -------------------------------------------------------------------------------- 1 | ### INITIALIZE VPYTHON 2 | # ----------------------------------------------------------------------- 3 | 4 | from __future__ import division 5 | from visual import * 6 | from physutil import * 7 | from visual.graph import * 8 | 9 | ### SETUP ELEMENTS FOR GRAPHING, SIMULATION, VISUALIZATION, TIMING 10 | # ------------------------------------------------------------------------ 11 | 12 | # Set window title 13 | scene.title = "Incline Plane" 14 | 15 | # Make scene background black 16 | scene.background = color.black 17 | scene.center = (5, 1, 0) # location at which the camera looks 18 | 19 | # Define scene objects (units are in meters) 20 | 21 | # 10-m long inclined plane whose center is at 5 m 22 | inclinedPlane = box(pos = vector(5, 0, 0), size = (10, 0.02, 0.2), 23 | color = color.green, opacity = 0.3) 24 | 25 | # 20-cm long cart on the inclined plane 26 | cart = box(size = (0.2, 0.06, 0.06), color = color.blue) 27 | 28 | # Set up graph with two plots 29 | posgraph = PhysGraph() 30 | velgraph = PhysGraph() 31 | accelgraph = PhysGraph() 32 | 33 | # Set up trail to mark the cart's trajectory 34 | trail = curve(color = color.yellow, radius = 0.01) # units are in meters 35 | 36 | # Set up motion map for cart 37 | motionMap = MotionMap(cart, 3, # expected end time in seconds 38 | 10, # number of markers to draw 39 | markerType = "breadcrumbs", 40 | dropTime = False) 41 | 42 | # Set timer in top right of screen 43 | timerDisplay = PhysTimer(9, 5) # timer position (units are in meters) 44 | 45 | 46 | ### SETUP PARAMETERS AND INITIAL CONDITIONS 47 | # ---------------------------------------------------------------------------------------- 48 | 49 | # Define parameters 50 | cart.m = 0.5 # mass of cart in kg 51 | 52 | # initial position of the cart in(x, y, z) form, units are in meters 53 | # cart is positioned on the inclined plane at the far right end 54 | cart.pos = vector(9.1, 0.04, 0.08) 55 | 56 | cart.v = vector(0, 0, 0) # initial velocity of car in (vx, vy, vz) form, units are m/s 57 | 58 | # angle of inclined plane relative to the horizontal 59 | theta = 22.0 * (pi / 180.0) 60 | 61 | # rotate the cart and the inclined plane based on the specified angle (counterclockwise) 62 | inclinedPlane.rotate(angle = theta, origin = (0, 0, 0), axis = (0,0,1)) 63 | cart.rotate(angle = theta, origin = (0, 0, 0), axis = (0,0,1)) 64 | 65 | g = 9.8 # acceleration due to gravity; units are m/s/s 66 | 67 | # Define time parameters 68 | t = 0 # starting time 69 | deltat = 0.0005 # time step units are s 70 | 71 | print "initial cart position (m): ", cart.pos 72 | 73 | 74 | ### CALCULATION LOOP; perform physics updates and drawing 75 | # ------------------------------------------------------------------------------------ 76 | 77 | while cart.pos.y > 0.04 : # while the cart's y-position is greater than 0 (above the ground) 78 | 79 | # Required to make animation visible / refresh smoothly (keeps program from running faster 80 | # than 1000 frames/s) 81 | rate(1000) 82 | 83 | # Compute Net Force 84 | # set the direction of the net force along the inclined plane 85 | Fnet = norm(inclinedPlane.axis) 86 | # set the magnitude to the component of the gravitational force parallel to the inclined plane 87 | Fnet.mag = -(cart.m * g * sin(theta)) 88 | 89 | # Newton's 2nd Law 90 | cart.v = cart.v + (Fnet/cart.m * deltat) 91 | 92 | # Position update 93 | cart.pos = cart.pos + cart.v * deltat 94 | 95 | # Update motion map, graph, timer, and trail 96 | motionMap.update(t) 97 | posgraph.plot(t, mag(cart.pos)) # plot position (along inclined plane) vs. time 98 | velgraph.plot(t, mag(cart.v)) # plot velocity (along inclined plane) vs. time 99 | accelgraph.plot(t, mag(Fnet) / cart.m) # plot acceleration (along inclined plane) vs. time 100 | trail.append(pos = cart.pos) 101 | timerDisplay.update(t) 102 | 103 | # Time update 104 | t = t + deltat 105 | 106 | ### OUTPUT 107 | # -------------------------------------------------------------------------------------- 108 | 109 | # Print the final time and the cart's final position 110 | print "final time (s): ", t 111 | print "final cart position (m): ", cart.pos 112 | print "final cart velocity (m/s): ", cart.v 113 | print "final cart speed (m/s): ", mag(cart.v) 114 | print "final cart acceleration (m/s/s): ", (mag(Fnet) / cart.m) 115 | 116 | -------------------------------------------------------------------------------- /inclined plane/inclinedPlane53.py: -------------------------------------------------------------------------------- 1 | ### INITIALIZE VPYTHON 2 | # ----------------------------------------------------------------------- 3 | 4 | from __future__ import division 5 | from visual import * 6 | from physutil import * 7 | from visual.graph import * 8 | 9 | ### SETUP ELEMENTS FOR GRAPHING, SIMULATION, VISUALIZATION, TIMING 10 | # ------------------------------------------------------------------------ 11 | 12 | # Set window title 13 | scene.title = "Incline Plane" 14 | 15 | # Make scene background black 16 | scene.background = color.black 17 | scene.center = (1, 1, 0) # location at which the camera looks 18 | 19 | # Define scene objects (units are in meters) 20 | 21 | # 2-m long inclined plane whose center is at 1 m 22 | inclinedPlane = box(pos = vector(1, 0, 0), size = (2, 0.02, 0.2), 23 | color = color.green, opacity = 0.3) 24 | 25 | # 20-cm long cart on the inclined plane 26 | cart = box(size = (0.2, 0.06, 0.06), color = color.blue) 27 | 28 | # Set up graph with two plots 29 | posgraph = PhysGraph() 30 | velgraph = PhysGraph() 31 | accelgraph = PhysGraph() 32 | 33 | # Set up trail to mark the cart's trajectory 34 | trail = curve(color = color.yellow, radius = 0.01) # units are in meters 35 | 36 | # Set up motion map for cart 37 | motionMap = MotionMap(cart, 2, # expected end time in seconds 38 | 10, # number of markers to draw 39 | markerType = "breadcrumbs", 40 | dropTime = False) 41 | 42 | # Set timer in top right of screen 43 | timerDisplay = PhysTimer(2, 1.5) # timer position (units are in meters) 44 | 45 | 46 | ### SETUP PARAMETERS AND INITIAL CONDITIONS 47 | # ---------------------------------------------------------------------------------------- 48 | 49 | # Define parameters 50 | cart.m = 0.5 # mass of cart in kg 51 | 52 | # initial position of the cart in(x, y, z) form, units are in meters 53 | # cart is positioned on the inclined plane at the far left end 54 | cart.pos = vector(0, 0.04, 0.08) 55 | 56 | cart.v = vector(0, 0, 0) # initial velocity of car in (vx, vy, vz) form, units are m/s 57 | 58 | # angle of inclined plane relative to the horizontal 59 | theta = 22.0 * (pi / 180.0) 60 | 61 | # rotate the cart and the inclined plane based on the specified angle (counterclockwise) 62 | inclinedPlane.rotate(angle = theta, origin = (0, 0, 0), axis = (0,0,1)) 63 | cart.rotate(angle = theta, origin = (0, 0, 0), axis = (0,0,1)) 64 | 65 | # set the initial velocity up the ramp; units are m/s 66 | cart.v = norm(inclinedPlane.axis) 67 | cart.v.mag = 3 68 | 69 | g = 9.8 # acceleration due to gravity; units are m/s/s 70 | 71 | # Define time parameters 72 | t = 0 # starting time 73 | deltat = 0.0005 # time step units are s 74 | 75 | print "initial cart position (m): ", cart.pos 76 | 77 | 78 | ### CALCULATION LOOP; perform physics updates and drawing 79 | # ------------------------------------------------------------------------------------ 80 | 81 | while cart.pos.y > 0.03 : # while the cart's y-position is greater than 0 (above the ground) 82 | 83 | # Required to make animation visible / refresh smoothly (keeps program from running faster 84 | # than 1000 frames/s) 85 | rate(1000) 86 | 87 | # Compute Net Force 88 | # set the direction of the net force along the inclined plane 89 | Fnet = norm(inclinedPlane.axis) 90 | # set the magnitude to the component of the gravitational force parallel to the inclined plane 91 | Fnet.mag = -(cart.m * g * sin(theta)) 92 | 93 | # Newton's 2nd Law 94 | cart.v = cart.v + (Fnet/cart.m * deltat) 95 | 96 | # Position update 97 | cart.pos = cart.pos + cart.v * deltat 98 | 99 | # Update motion map, graph, timer, and trail 100 | motionMap.update(t) 101 | posgraph.plot(t, mag(cart.pos)) # plot position (along inclined plane) vs. time 102 | velgraph.plot(t, mag(cart.v)) # plot velocity (along inclined plane) vs. time 103 | accelgraph.plot(t, mag(Fnet) / cart.m) # plot acceleration (along inclined plane) vs. time 104 | trail.append(pos = cart.pos) 105 | timerDisplay.update(t) 106 | 107 | # Time update 108 | t = t + deltat 109 | 110 | ### OUTPUT 111 | # -------------------------------------------------------------------------------------- 112 | 113 | # Print the final time and the cart's final position 114 | print "final time (s): ", t 115 | print "final cart position (m): ", cart.pos 116 | print "final cart velocity (m/s): ", cart.v 117 | print "final cart speed (m/s): ", mag(cart.v) 118 | print "final cart acceleration (m/s/s): ", (mag(Fnet) / cart.m) 119 | 120 | -------------------------------------------------------------------------------- /inclined plane/inclinedPlane54.py: -------------------------------------------------------------------------------- 1 | ### INITIALIZE VPYTHON 2 | # ----------------------------------------------------------------------- 3 | 4 | from __future__ import division 5 | from visual import * 6 | from physutil import * 7 | from visual.graph import * 8 | 9 | ### SETUP ELEMENTS FOR GRAPHING, SIMULATION, VISUALIZATION, TIMING 10 | # ------------------------------------------------------------------------ 11 | 12 | # Set window title 13 | scene.title = "Incline Plane" 14 | 15 | # Make scene background black 16 | scene.background = color.black 17 | scene.center = (0.7, 1, 0) # location at which the camera looks 18 | 19 | # Define scene objects (units are in meters) 20 | 21 | # 1.4-m long inclined plane whose center is at 0.7 m 22 | inclinedPlane = box(pos = vector(0.7, 0, 0), size = (1.4, 0.02, 0.2), 23 | color = color.green, opacity = 0.3) 24 | 25 | # 20-cm long cart on the inclined plane 26 | cart = box(size = (0.2, 0.06, 0.06), color = color.blue) 27 | 28 | # Set up graph with two plots 29 | posgraph = PhysGraph() 30 | velgraph = PhysGraph() 31 | accelgraph = PhysGraph() 32 | 33 | # Set up trail to mark the cart's trajectory 34 | trail = curve(color = color.yellow, radius = 0.01) # units are in meters 35 | 36 | # Set up motion map for cart 37 | motionMap = MotionMap(cart, 2, # expected end time in seconds 38 | 10, # number of markers to draw 39 | markerType = "breadcrumbs", 40 | dropTime = False) 41 | 42 | # Set timer in top right of screen 43 | timerDisplay = PhysTimer(1.5, 1) # timer position (units are in meters) 44 | 45 | 46 | ### SETUP PARAMETERS AND INITIAL CONDITIONS 47 | # ---------------------------------------------------------------------------------------- 48 | 49 | # Define parameters 50 | cart.m = 0.5 # mass of cart in kg 51 | 52 | # initial position of the cart in(x, y, z) form, units are in meters 53 | # cart is positioned on the inclined plane at the far left end 54 | cart.pos = vector(0, 0.04, 0.08) 55 | 56 | cart.v = vector(0, 0, 0) # initial velocity of car in (vx, vy, vz) form, units are m/s 57 | 58 | # angle of inclined plane relative to the horizontal 59 | theta = 22.0 * (pi / 180.0) 60 | 61 | # rotate the cart and the inclined plane based on the specified angle (counterclockwise) 62 | inclinedPlane.rotate(angle = theta, origin = (0, 0, 0), axis = (0,0,1)) 63 | cart.rotate(angle = theta, origin = (0, 0, 0), axis = (0,0,1)) 64 | 65 | # set the initial velocity up the ramp; units are m/s 66 | cart.v = norm(inclinedPlane.axis) 67 | cart.v.mag = 3 68 | 69 | g = 9.8 # acceleration due to gravity; units are m/s/s 70 | 71 | mu = 0.20 # coefficient of friction between cart and plane 72 | 73 | # Define time parameters 74 | t = 0 # starting time 75 | deltat = 0.0005 # time step units are s 76 | 77 | print "initial cart position (m): ", cart.pos 78 | 79 | 80 | ### CALCULATION LOOP; perform physics updates and drawing 81 | # ------------------------------------------------------------------------------------ 82 | 83 | while cart.pos.y > 0.03 : # while the cart's y-position is greater than 0 (above the ground) 84 | 85 | # Required to make animation visible / refresh smoothly (keeps program from running faster 86 | # than 1000 frames/s) 87 | rate(1000) 88 | 89 | # Compute Net Force 90 | # set the direction of the net force along the inclined plane 91 | Fnet = norm(inclinedPlane.axis) 92 | # set the magnitude to the component of the gravitational force parallel to the inclined plane 93 | Fnet.mag = -(cart.m * g * sin(theta)) 94 | if cart.v.y > 0: 95 | Fnet.mag += (mu * cart.m * g * cos(theta)) 96 | else: 97 | Fnet.mag -= (mu * cart.m * g * cos(theta)) 98 | 99 | # Newton's 2nd Law 100 | cart.v = cart.v + (Fnet/cart.m * deltat) 101 | 102 | # Position update 103 | cart.pos = cart.pos + cart.v * deltat 104 | 105 | # Update motion map, graph, timer, and trail 106 | motionMap.update(t) 107 | posgraph.plot(t, mag(cart.pos)) # plot position (along inclined plane) vs. time 108 | velgraph.plot(t, mag(cart.v)) # plot velocity (along inclined plane) vs. time 109 | accelgraph.plot(t, mag(Fnet) / cart.m) # plot acceleration (along inclined plane) vs. time 110 | trail.append(pos = cart.pos) 111 | timerDisplay.update(t) 112 | 113 | # Time update 114 | t = t + deltat 115 | 116 | ### OUTPUT 117 | # -------------------------------------------------------------------------------------- 118 | 119 | # Print the final time and the cart's final position 120 | print "final time (s): ", t 121 | print "final cart position (m): ", cart.pos 122 | print "final cart velocity (m/s): ", cart.v 123 | print "final cart speed (m/s): ", mag(cart.v) 124 | print "final cart acceleration (m/s/s): ", (mag(Fnet) / cart.m) 125 | 126 | -------------------------------------------------------------------------------- /inclined plane/physutil.py: -------------------------------------------------------------------------------- 1 | # physutil.py v1.22 2 | # Copyright (c) 2011-2012 GT Physics Education Research Group 3 | # License: GPL-3.0 (http://opensource.org/licenses/GPL-3.0) 4 | 5 | # This module is built to simplify and assist high school physics students 6 | # in the construction of models for lab exercises using VPython. 7 | 8 | 9 | # Revisions by date 10 | 11 | # v1.22 24 January 2011 -- Danny Caballero 12 | # Added labelColor attribute to PhysAxis, MotionMap, and MotionMapN 13 | # controls color of text 14 | 15 | # v1.21 5 December 2011 -- Danny Caballero 16 | # Added timerColor attribute to PhysTimer 17 | # controls color of text 18 | 19 | # v1.2 19 October 2011 -- Danny Caballero 20 | # Added MotionMapN, a class that allows the placing of breadcrumbs or arrows 21 | # every "n" steps. 22 | 23 | # v1.13 26 September 2011 -- Daniel Borrero 24 | # Fixed unit test bug for PhysTimer 25 | 26 | # v1.12 30 August 2011 -- Daniel Borrero 27 | # Fixed bug in PhysTimer output 28 | # E.g., 2.00 s is now displayed as 00:00:02.00 instead of 00:00:01:100 29 | 30 | # v1.11 29 August 2011 -- Danny Caballero 31 | # Changed License to GNU 32 | 33 | # v1.1 15 August 2011 -- Daniel Borrero 34 | # Print statements made compatible with Python 3.1 35 | 36 | # v1.01 16 July 2011 -- Danny Caballero 37 | # Added ability to change PhysAxis color using axisColor 38 | 39 | # v1.0 29 April 2011 -- CS Build Team 40 | # Heavy Modification 41 | 42 | # v0.1 05 January 2011 -- Danny Caballero 43 | # Initial Build 44 | 45 | from __future__ import division 46 | import unittest 47 | 48 | """ 49 | # 50 | # 51 | # UNIT TESTING / IMPORT SETUP CODE ------------------------------------------------------------ 52 | # 53 | # 54 | """ 55 | 56 | # Determine whether we are being used as a module or just running unittests (for mock purposes this is important) 57 | if __name__ == "__main__": 58 | # If we are unit testing, set up mock objects (must be done before classes are defined below!) 59 | from visual import vector 60 | class Mock: 61 | def __init__(self, name, *args, **kwargs): 62 | self.name = name 63 | self.called = 0 64 | 65 | def __call__(self, *args, **kwargs): 66 | self.args = args 67 | self.kwargs = kwargs 68 | for name in kwargs: 69 | setattr(self, name, kwargs[name]) 70 | self.called += 1 71 | return self 72 | 73 | def reset(self): 74 | self.called = 0 75 | 76 | color = Mock("color") 77 | color.red = "red" 78 | color.green = "green" 79 | color.blue = "blue" 80 | color.yellow = "yellow" 81 | color.orange = "orange" 82 | color.cyan = "cyan" 83 | color.magenta = "magenta" 84 | color.white = "white" 85 | 86 | arrow = Mock("arrow") 87 | label = Mock("label") 88 | points = Mock("points") 89 | curve = Mock("curve") 90 | gdisplay = Mock("gdisplay") 91 | gcurve = Mock("gcurve") 92 | gcurve.plots = [] 93 | def mockPlot(pos): 94 | gcurve.plots.append(pos) 95 | gcurve.plot = mockPlot 96 | 97 | """ 98 | vector = Mock("vector") 99 | def call(x, y, z): 100 | vector.x = x 101 | vector.y = y 102 | vector.z = z 103 | vector.__call__ = call 104 | """ 105 | 106 | else: 107 | # These are the actual imports for the utility 108 | from visual import * 109 | from visual.graph import * 110 | 111 | 112 | """ 113 | # 114 | # 115 | # ACTUAL PHYSUTIL CODE FOLLOWS -------------------------------------------------- 116 | # 117 | # 118 | """ 119 | 120 | # Initialize window positions for students (if we aren't unit testing) 121 | if __name__ != "__main__": 122 | scene.x = 50 123 | scene.y = 50 124 | 125 | # Helper function for returning proper size of something 126 | def obj_size(obj): 127 | if type(obj) == box or type(obj) == pyramid: 128 | return obj.size 129 | elif type(obj) == sphere: 130 | return vector(obj.radius, obj.radius, obj.radius) 131 | 132 | class MotionMap: 133 | """ 134 | This class assists students in constructing motion maps 135 | using either arrows (measuring a quantity) or "breadcrumbs" 136 | (with timestamps). 137 | """ 138 | 139 | def __init__(self, obj, tf, numMarkers, markerType="arrow", 140 | markerScale=1, markerColor=color.red, 141 | labelMarkerOrder=True, labelMarkerOffset=vector(0,0,0), 142 | dropTime=False, timeOffset=vector(0,0,0), arrowOffset=vector(0,0,0), labelColor=color.white): 143 | # MotionMap 144 | # obj - object to track in mapping / placing markers 145 | # tf - expected tFinal, used to space marker placement over time 146 | # numMarkers - number of markers to place 147 | # markerType - determines type of motionmap; options are "arrow" or "breadcrumbs" 148 | # markerScale - replaces pSize / quantscale from motionmodule.py depending on type 149 | # markerColor - color of markers 150 | # labelMarkerOrder - drop numbers of markers? 151 | # labelMarkerOffset - amount to offset numbering by 152 | # dropTime - boolean determining whether a timestamp should be placed along with the marker 153 | # timeOffset - if dropTime is True, determines the offset, if any, of the label from the marker 154 | # arrowOffset - shift an arrow by an amount (x,y,z), useful for two arrows views 155 | 156 | self.obj = obj 157 | self.tf = tf 158 | self.numMarkers = numMarkers 159 | self.markerType = markerType 160 | self.markerScale = markerScale 161 | self.markerColor = markerColor 162 | self.labelMarkerOrder = labelMarkerOrder 163 | self.labelMarkerOffset = labelMarkerOffset 164 | self.timeOffset = timeOffset 165 | self.dropTime = dropTime 166 | self.arrowOffset = arrowOffset 167 | self.labelColor = labelColor 168 | 169 | # Calculate size of interval for each step, set initial step index 170 | try: 171 | self.interval = self.tf / self.numMarkers 172 | except TypeError as err: 173 | print("**********TYPE ERROR**********") 174 | 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)!") 175 | print("******************************") 176 | print(err) 177 | raise err 178 | self.curMarker = 0 179 | 180 | 181 | def update(self, t, quantity=1): 182 | try: 183 | # Display new arrow if t has broken next threshold 184 | if t > (self.interval * self.curMarker): 185 | # Increment threshold 186 | self.curMarker += 1 187 | 188 | # Display marker! 189 | if self.markerType == "arrow": 190 | arrow(pos=self.obj.pos+self.arrowOffset, 191 | axis=self.markerScale*quantity, color=self.markerColor) 192 | elif self.markerType == "breadcrumbs": 193 | points(pos=self.obj.pos, 194 | size=10*self.markerScale*quantity, color=self.markerColor) 195 | 196 | #Also display timestamp if requested 197 | if self.dropTime is not False: 198 | epsilon = vector(0,self.markerScale*.5,0)+self.timeOffset 199 | droptimeText = label(pos=self.obj.pos+epsilon, text='t='+str(t)+'s', height=10, box=False, color=self.labelColor) 200 | 201 | # Same with order label 202 | if self.labelMarkerOrder is not False: 203 | label(pos=self.obj.pos-vector(0,self.markerScale*.5,0)+self.labelMarkerOffset, text=str(self.curMarker), height=10, box=False, color=self.labelColor) 204 | except TypeError as err: 205 | print("**********TYPE ERROR**********") 206 | 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)!") 207 | print("******************************") 208 | print(err) 209 | raise err 210 | 211 | class MotionMapN: 212 | """ 213 | This class assists students in constructing motion maps 214 | using either arrows (measuring a quantity) or "breadcrumbs" 215 | (with timestamps). 216 | """ 217 | 218 | def __init__(self, obj, dt, numSteps, markerType="arrow", 219 | markerScale=1, markerColor=color.red, 220 | labelMarkerOrder=True, labelMarkerOffset=vector(0,0,0), 221 | dropTime=False, timeOffset=vector(0,0,0), arrowOffset=vector(0,0,0), labelColor=color.white): 222 | # MotionMapN 223 | # obj - object to track in mapping / placing markers 224 | # dt - time between steps 225 | # numSteps - number of steps between markers 226 | # markerType - determines type of motionmap; options are "arrow" or "breadcrumbs" 227 | # markerScale - replaces pSize / quantscale from motionmodule.py depending on type 228 | # markerColor - color of markers 229 | # labelMarkerOrder - drop numbers of markers? 230 | # labelMarkerOffset - amount to offset numbering by 231 | # dropTime - boolean determining whether a timestamp should be placed along with the marker 232 | # timeOffset - if dropTime is True, determines the offset, if any, of the label from the markers 233 | # arrowOffset - shift an arrow by an amount (x,y,z), useful for two arrows views 234 | 235 | self.obj = obj 236 | self.dt = dt 237 | self.numSteps = numSteps 238 | self.markerType = markerType 239 | self.markerScale = markerScale 240 | self.markerColor = markerColor 241 | self.labelMarkerOrder = labelMarkerOrder 242 | self.labelMarkerOffset = labelMarkerOffset 243 | self.timeOffset = timeOffset 244 | self.dropTime = dropTime 245 | self.arrowOffset = arrowOffset 246 | self.labelColor = labelColor 247 | 248 | # Calculate size of interval for each step, set initial step index 249 | try: 250 | self.interval = self.dt * self.numSteps 251 | except TypeError as err: 252 | print("**********TYPE ERROR**********") 253 | 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)!") 254 | print("******************************") 255 | print(err) 256 | raise err 257 | self.curMarker = 0 258 | 259 | 260 | def update(self, t, quantity=1): 261 | try: 262 | 263 | threshold = self.interval * self.curMarker 264 | # Display new arrow if t has broken new threshold 265 | if t >= threshold: 266 | 267 | # Increment marker count 268 | self.curMarker += 1 269 | 270 | # Display marker! 271 | if self.markerType == "arrow": 272 | arrow(pos=self.obj.pos+self.arrowOffset, 273 | axis=self.markerScale*quantity, color=self.markerColor) 274 | elif self.markerType == "breadcrumbs": 275 | points(pos=self.obj.pos, 276 | size=10*self.markerScale*quantity, color=self.markerColor) 277 | 278 | #Also display timestamp if requested 279 | if self.dropTime is not False: 280 | epsilon = vector(0,self.markerScale*.5,0)+self.timeOffset 281 | droptimeText = label(pos=self.obj.pos+epsilon, text='t='+str(t)+'s', height=10, box=False, color=self.labelColor) 282 | 283 | # Same with order label 284 | if self.labelMarkerOrder is not False: 285 | label(pos=self.obj.pos-vector(0,self.markerScale*.5,0)+self.labelMarkerOffset, text=str(self.curMarker), height=10, box=False, color=self.labelColor) 286 | 287 | except TypeError as err: 288 | print("**********TYPE ERROR**********") 289 | 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)!") 290 | print("******************************") 291 | print(err) 292 | raise err 293 | 294 | class PhysAxis: 295 | """ 296 | This class assists students in creating dynamic axes for their models. 297 | """ 298 | 299 | def __init__(self, obj, numLabels, axisType="x", axis=vector(1,0,0), startPos=None, 300 | length=None, labels = None, labelOrientation="down", axisColor=color.yellow, labelColor=color.white): 301 | # PhysAxis 302 | # obj - Object which axis is oriented based on by default 303 | # numLabels - number of labels on axis 304 | # axisType - sets whether this is a default axis of x or y, or an arbitrary axis 305 | # axis - unit vector defining the orientation of the axis to be created IF axisType = "arbitrary" 306 | # startPos - start position for the axis - defaults to (-obj_size(obj).x/2,-4*obj_size(obj).y,0) 307 | # length - length of the axis - defaults to obj_size(obj).x 308 | # labelOrientation - how labels are placed relative to axis markers - "up", "down", "left", or "right" 309 | 310 | try: 311 | self.intervalMarkers = [] 312 | self.intervalLabels = [] 313 | self.labelText = labels 314 | self.obj = obj 315 | self.lastPos = vector(self.obj.pos.x, self.obj.pos.y, self.obj.pos.z) 316 | self.numLabels = numLabels 317 | self.axisType = axisType 318 | self.axis = axis if axisType != "y" else vector(0,1,0) 319 | self.length = length if (length is not None) else obj_size(obj).x 320 | self.startPos = startPos if (startPos is not None) else vector(-obj_size(obj).x/2,-4*obj_size(obj).y,0) 321 | self.axisColor = axisColor 322 | self.labelColor = labelColor 323 | 324 | if labelOrientation == "down": 325 | self.labelShift = vector(0,-0.05*self.length,0) 326 | elif labelOrientation == "up": 327 | self.labelShift = vector(0,0.05*self.length,0) 328 | elif labelOrientation == "left": 329 | self.labelShift = vector(-0.1*self.length,0,0) 330 | elif labelOrientation == "right": 331 | self.labelShift = vector(0.1*self.length,0,0) 332 | 333 | self.__reorient() 334 | except TypeError as err: 335 | print("**********TYPE ERROR**********") 336 | 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)!") 337 | print("******************************") 338 | print(err) 339 | raise err 340 | 341 | def update(self): 342 | try: 343 | # Determine if reference obj. has shifted since last update, if so shift us too 344 | if self.obj.pos != self.lastPos: 345 | diff = self.obj.pos - self.lastPos 346 | 347 | for i in range(len(self.intervalMarkers)): 348 | self.intervalMarkers[i].pos += diff 349 | self.intervalLabels[i].pos += diff 350 | self.axisCurve.pos = [x + diff for x in self.axisCurve.pos] 351 | 352 | self.lastPos = vector(self.obj.pos.x, self.obj.pos.y, self.obj.pos.z) 353 | except TypeError as err: 354 | print("**********TYPE ERROR**********") 355 | 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)!") 356 | print("******************************") 357 | print(err) 358 | raise err 359 | 360 | def reorient(self, axis=None, startPos=None, length=None, labels=None, labelOrientation=None): 361 | try: 362 | # Determine which, if any, parameters are being modified 363 | self.axis = axis if axis is not None else self.axis 364 | self.startPos = startPos if startPos is not None else self.startPos 365 | self.length = length if length is not None else self.length 366 | self.labelText = labels if labels is not None else self.labels 367 | 368 | # Re-do label orientation as well, if it has been set 369 | if labelOrientation == "down": 370 | self.labelShift = vector(0,-0.05*self.length,0) 371 | elif labelOrientation == "up": 372 | self.labelShift = vector(0,0.05*self.length,0) 373 | elif labelOrientation == "left": 374 | self.labelShift = vector(-0.1*self.length,0,0) 375 | elif labelOrientation == "right": 376 | self.labelShift = vector(0.1*self.length,0,0) 377 | 378 | self.__reorient() 379 | except TypeError as err: 380 | print("**********TYPE ERROR**********") 381 | 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)!") 382 | print("******************************") 383 | print(err) 384 | raise err 385 | 386 | def __reorient(self): 387 | # Actual internal axis setup code... determines first whether we are creating or updating 388 | updating = True if len(self.intervalMarkers) > 0 else False 389 | 390 | # Then determines the endpoint of the axis and the interval 391 | final = self.startPos + (self.length * self.axis) 392 | interval = (self.length / (self.numLabels-1)) * self.axis 393 | 394 | # Loop for each interval marker, setting up or updating the markers and labels 395 | i=0 396 | while i -1.50 and car.pos.x < 1.50 : #while the ball's x-position is between -1.5 and 1.5 55 | 56 | 57 | # Required to make animation visible / refresh smoothly (keeps program from running faster than 1000 frames/s) 58 | rate(1000) 59 | 60 | # Compute Net Force 61 | Fnet = vector(0,0,0) 62 | 63 | # Newton's 2nd Law 64 | car.v = car.v + Fnet/car.m * deltat 65 | 66 | # Position update 67 | car.pos = car.pos + car.v*deltat 68 | 69 | # Update timer, graph, and trail 70 | timerDisplay.update(t) 71 | positiongraph.plot(t,car.pos.x) #this plots one point in the graph in (x,y) form 72 | trail.append(pos = car.pos) 73 | 74 | # Time update 75 | t=t+deltat 76 | 77 | ### OUTPUT 78 | # -------------------------------------------------------------------------------------- 79 | 80 | # Print the final time and the car's final position 81 | print t 82 | print car.pos 83 | -------------------------------------------------------------------------------- /kinematics/PMPM lab iterative.py: -------------------------------------------------------------------------------- 1 | ### INITIALIZE VPYTHON 2 | # ----------------------------------------------------------------------- 3 | 4 | from __future__ import division 5 | from visual import * 6 | from physutil import * 7 | from visual.graph import * 8 | 9 | ### SETUP ELEMENTS FOR GRAPHING, SIMULATION, VISUALIZATION, TIMING 10 | # ------------------------------------------------------------------------ 11 | 12 | # Set window title 13 | scene.title = "PMPM Lab Model" 14 | 15 | # Make scene background black 16 | scene.background = color.black 17 | 18 | # Define scene objects (units are in meters) 19 | field = box(pos = vector(1.5, 0, 0), size = (3, .10, 1), color = color.green, opacity = 0.3) 20 | ball = sphere(radius = .05, color = color.blue) 21 | 22 | # Define axis marks the field with a specified number of tick marks 23 | xaxis = PhysAxis(field, 10, length = 4.5) # 10 tick marks 24 | yaxis = PhysAxis(field, 5, # 5 tick marks 25 | axisType = "y", 26 | labelOrientation = "left", 27 | startPos = vector(0, 0, 0), # start the y axis at the left edge of the scene 28 | length = 1) # units are in meters 29 | 30 | # Set up graph with two plots 31 | posgraph = PhysGraph(1) 32 | 33 | # Set up trail to mark the ball's trajectory 34 | trail = curve(color = color.yellow, radius = .01) # units are in meters 35 | 36 | # Set timer in top right of screen 37 | timerDisplay = PhysTimer(2.0, 1.50) # timer position (units are in meters) 38 | 39 | 40 | ### SETUP PARAMETERS AND INITIAL CONDITIONS 41 | # ---------------------------------------------------------------------------------------- 42 | 43 | # Define parameters 44 | theta = 20; # angle in degrees 45 | v = 4.1; # initial launcher velocity in m/s 46 | targetRange = 1.75; 47 | ball.m = 0.6 # mass of ball in kg 48 | 49 | while (ball.pos.x < targetRange - 0.04) or (ball.pos.x > targetRange + 0.04) : 50 | 51 | # Increment the launch angle 52 | theta += 2; 53 | 54 | # set (reset) the initial position and velocity 55 | ball.pos = vector(0, 1.17, 0) # initial position of the ball in(x, y, z) form, units are in meters 56 | ball.v = vector(v*cos(radians(theta)), v*sin(radians(theta)), 0) # initial velocity of car in (vx, vy, vz) form, units are m/s 57 | 58 | g = vector(0, -9.8, 0) # acceleration due to gravity; units are m/s/s 59 | 60 | # Define time parameters 61 | t = 0 # starting time 62 | deltat = 0.001 # time step units are s 63 | 64 | 65 | ### CALCULATION LOOP; perform physics updates and drawing 66 | # ------------------------------------------------------------------------------------ 67 | 68 | while ball.pos.y >= 0 : #while the ball's y-position is greater than 0 (above the ground) 69 | 70 | # Required to make animation visible / refresh smoothly (keeps program from running faster 71 | # than 1000 frames/s) 72 | rate(1000) 73 | 74 | # Compute Net Force 75 | Fnet = ball.m * g 76 | 77 | # Newton's 2nd Law 78 | ball.v = ball.v + (Fnet/ball.m * deltat) 79 | 80 | # Position update 81 | ball.pos = ball.pos + ball.v * deltat 82 | 83 | # Update motion map, graph, timer, and trail 84 | posgraph.plot(ball.pos.x, ball.pos.y) # plot x and y position vs. time 85 | trail.append(pos = ball.pos) 86 | timerDisplay.update(t) 87 | 88 | # Time update 89 | t = t + deltat 90 | 91 | 92 | ### OUTPUT 93 | # -------------------------------------------------------------------------------------- 94 | 95 | # Print the final time and the ball's final position 96 | print t 97 | print ball.pos 98 | 99 | # Print the final time and the ball's final position 100 | print t 101 | print ball.pos 102 | print theta 103 | -------------------------------------------------------------------------------- /kinematics/PMPM lab.py: -------------------------------------------------------------------------------- 1 | ### INITIALIZE VPYTHON 2 | # ----------------------------------------------------------------------- 3 | 4 | from __future__ import division 5 | from visual import * 6 | from physutil import * 7 | from visual.graph import * 8 | 9 | ### SETUP ELEMENTS FOR GRAPHING, SIMULATION, VISUALIZATION, TIMING 10 | # ------------------------------------------------------------------------ 11 | 12 | # Set window title 13 | scene.title = "PMPM Lab Model" 14 | 15 | # Make scene background black 16 | scene.background = color.black 17 | 18 | # Define scene objects (units are in meters) 19 | field = box(pos = vector(1.5, 0, 0), size = (3, .10, 1), color = color.green, opacity = 0.3) 20 | ball = sphere(radius = .05, color = color.blue) 21 | 22 | # Define axis marks the field with a specified number of tick marks 23 | xaxis = PhysAxis(field, 10, length = 4.5) # 10 tick marks 24 | yaxis = PhysAxis(field, 5, # 5 tick marks 25 | axisType = "y", 26 | labelOrientation = "left", 27 | startPos = vector(0, 0, 0), # start the y axis at the left edge of the scene 28 | length = 1) # units are in meters 29 | 30 | # Set up graph with two plots 31 | posgraph = PhysGraph(1) 32 | 33 | # Set up trail to mark the ball's trajectory 34 | trail = curve(color = color.yellow, radius = .01) # units are in meters 35 | 36 | # Set timer in top right of screen 37 | timerDisplay = PhysTimer(2.0, 1.50) # timer position (units are in meters) 38 | 39 | 40 | ### SETUP PARAMETERS AND INITIAL CONDITIONS 41 | # ---------------------------------------------------------------------------------------- 42 | 43 | # Define parameters 44 | theta = 30; # angle in degrees 45 | v = 4.1; # initial launcher velocity in m/s 46 | ball.m = 0.6 # mass of ball in kg 47 | ball.pos = vector(0, 1.17, 0) # initial position of the ball in(x, y, z) form, units are in meters 48 | ball.v = vector(v*cos(radians(theta)), v*sin(radians(theta)), 0) # initial velocity of car in (vx, vy, vz) form, units are m/s 49 | 50 | g = vector(0, -9.8, 0) # acceleration due to gravity; units are m/s/s 51 | 52 | # Define time parameters 53 | t = 0 # starting time 54 | deltat = 0.001 # time step units are s 55 | 56 | 57 | ### CALCULATION LOOP; perform physics updates and drawing 58 | # ------------------------------------------------------------------------------------ 59 | 60 | while ball.pos.y >= 0 : #while the ball's y-position is greater than 0 (above the ground) 61 | 62 | # Required to make animation visible / refresh smoothly (keeps program from running faster 63 | # than 1000 frames/s) 64 | rate(1000) 65 | 66 | # Compute Net Force 67 | Fnet = ball.m * g 68 | 69 | # Newton's 2nd Law 70 | ball.v = ball.v + (Fnet/ball.m * deltat) 71 | 72 | # Position update 73 | ball.pos = ball.pos + ball.v * deltat 74 | 75 | # Update motion map, graph, timer, and trail 76 | posgraph.plot(ball.pos.x, ball.pos.y) # plot x and y position vs. time 77 | trail.append(pos = ball.pos) 78 | timerDisplay.update(t) 79 | 80 | # Time update 81 | t = t + deltat 82 | 83 | 84 | ### OUTPUT 85 | # -------------------------------------------------------------------------------------- 86 | 87 | # Print the final time and the ball's final position 88 | print t 89 | print ball.pos 90 | -------------------------------------------------------------------------------- /kinematics/physutil.py: -------------------------------------------------------------------------------- 1 | # physutil.py v1.22 2 | # Copyright (c) 2011-2012 GT Physics Education Research Group 3 | # License: GPL-3.0 (http://opensource.org/licenses/GPL-3.0) 4 | 5 | # This module is built to simplify and assist high school physics students 6 | # in the construction of models for lab exercises using VPython. 7 | 8 | 9 | # Revisions by date 10 | 11 | # v1.22 24 January 2011 -- Danny Caballero 12 | # Added labelColor attribute to PhysAxis, MotionMap, and MotionMapN 13 | # controls color of text 14 | 15 | # v1.21 5 December 2011 -- Danny Caballero 16 | # Added timerColor attribute to PhysTimer 17 | # controls color of text 18 | 19 | # v1.2 19 October 2011 -- Danny Caballero 20 | # Added MotionMapN, a class that allows the placing of breadcrumbs or arrows 21 | # every "n" steps. 22 | 23 | # v1.13 26 September 2011 -- Daniel Borrero 24 | # Fixed unit test bug for PhysTimer 25 | 26 | # v1.12 30 August 2011 -- Daniel Borrero 27 | # Fixed bug in PhysTimer output 28 | # E.g., 2.00 s is now displayed as 00:00:02.00 instead of 00:00:01:100 29 | 30 | # v1.11 29 August 2011 -- Danny Caballero 31 | # Changed License to GNU 32 | 33 | # v1.1 15 August 2011 -- Daniel Borrero 34 | # Print statements made compatible with Python 3.1 35 | 36 | # v1.01 16 July 2011 -- Danny Caballero 37 | # Added ability to change PhysAxis color using axisColor 38 | 39 | # v1.0 29 April 2011 -- CS Build Team 40 | # Heavy Modification 41 | 42 | # v0.1 05 January 2011 -- Danny Caballero 43 | # Initial Build 44 | 45 | from __future__ import division 46 | import unittest 47 | 48 | """ 49 | # 50 | # 51 | # UNIT TESTING / IMPORT SETUP CODE ------------------------------------------------------------ 52 | # 53 | # 54 | """ 55 | 56 | # Determine whether we are being used as a module or just running unittests (for mock purposes this is important) 57 | if __name__ == "__main__": 58 | # If we are unit testing, set up mock objects (must be done before classes are defined below!) 59 | from visual import vector 60 | class Mock: 61 | def __init__(self, name, *args, **kwargs): 62 | self.name = name 63 | self.called = 0 64 | 65 | def __call__(self, *args, **kwargs): 66 | self.args = args 67 | self.kwargs = kwargs 68 | for name in kwargs: 69 | setattr(self, name, kwargs[name]) 70 | self.called += 1 71 | return self 72 | 73 | def reset(self): 74 | self.called = 0 75 | 76 | color = Mock("color") 77 | color.red = "red" 78 | color.green = "green" 79 | color.blue = "blue" 80 | color.yellow = "yellow" 81 | color.orange = "orange" 82 | color.cyan = "cyan" 83 | color.magenta = "magenta" 84 | color.white = "white" 85 | 86 | arrow = Mock("arrow") 87 | label = Mock("label") 88 | points = Mock("points") 89 | curve = Mock("curve") 90 | gdisplay = Mock("gdisplay") 91 | gcurve = Mock("gcurve") 92 | gcurve.plots = [] 93 | def mockPlot(pos): 94 | gcurve.plots.append(pos) 95 | gcurve.plot = mockPlot 96 | 97 | """ 98 | vector = Mock("vector") 99 | def call(x, y, z): 100 | vector.x = x 101 | vector.y = y 102 | vector.z = z 103 | vector.__call__ = call 104 | """ 105 | 106 | else: 107 | # These are the actual imports for the utility 108 | from visual import * 109 | from visual.graph import * 110 | 111 | 112 | """ 113 | # 114 | # 115 | # ACTUAL PHYSUTIL CODE FOLLOWS -------------------------------------------------- 116 | # 117 | # 118 | """ 119 | 120 | # Initialize window positions for students (if we aren't unit testing) 121 | if __name__ != "__main__": 122 | scene.x = 50 123 | scene.y = 50 124 | 125 | # Helper function for returning proper size of something 126 | def obj_size(obj): 127 | if type(obj) == box or type(obj) == pyramid: 128 | return obj.size 129 | elif type(obj) == sphere: 130 | return vector(obj.radius, obj.radius, obj.radius) 131 | 132 | class MotionMap: 133 | """ 134 | This class assists students in constructing motion maps 135 | using either arrows (measuring a quantity) or "breadcrumbs" 136 | (with timestamps). 137 | """ 138 | 139 | def __init__(self, obj, tf, numMarkers, markerType="arrow", 140 | markerScale=1, markerColor=color.red, 141 | labelMarkerOrder=True, labelMarkerOffset=vector(0,0,0), 142 | dropTime=False, timeOffset=vector(0,0,0), arrowOffset=vector(0,0,0), labelColor=color.white): 143 | # MotionMap 144 | # obj - object to track in mapping / placing markers 145 | # tf - expected tFinal, used to space marker placement over time 146 | # numMarkers - number of markers to place 147 | # markerType - determines type of motionmap; options are "arrow" or "breadcrumbs" 148 | # markerScale - replaces pSize / quantscale from motionmodule.py depending on type 149 | # markerColor - color of markers 150 | # labelMarkerOrder - drop numbers of markers? 151 | # labelMarkerOffset - amount to offset numbering by 152 | # dropTime - boolean determining whether a timestamp should be placed along with the marker 153 | # timeOffset - if dropTime is True, determines the offset, if any, of the label from the marker 154 | # arrowOffset - shift an arrow by an amount (x,y,z), useful for two arrows views 155 | 156 | self.obj = obj 157 | self.tf = tf 158 | self.numMarkers = numMarkers 159 | self.markerType = markerType 160 | self.markerScale = markerScale 161 | self.markerColor = markerColor 162 | self.labelMarkerOrder = labelMarkerOrder 163 | self.labelMarkerOffset = labelMarkerOffset 164 | self.timeOffset = timeOffset 165 | self.dropTime = dropTime 166 | self.arrowOffset = arrowOffset 167 | self.labelColor = labelColor 168 | 169 | # Calculate size of interval for each step, set initial step index 170 | try: 171 | self.interval = self.tf / self.numMarkers 172 | except TypeError as err: 173 | print("**********TYPE ERROR**********") 174 | 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)!") 175 | print("******************************") 176 | print(err) 177 | raise err 178 | self.curMarker = 0 179 | 180 | 181 | def update(self, t, quantity=1): 182 | try: 183 | # Display new arrow if t has broken next threshold 184 | if t > (self.interval * self.curMarker): 185 | # Increment threshold 186 | self.curMarker += 1 187 | 188 | # Display marker! 189 | if self.markerType == "arrow": 190 | arrow(pos=self.obj.pos+self.arrowOffset, 191 | axis=self.markerScale*quantity, color=self.markerColor) 192 | elif self.markerType == "breadcrumbs": 193 | points(pos=self.obj.pos, 194 | size=10*self.markerScale*quantity, color=self.markerColor) 195 | 196 | #Also display timestamp if requested 197 | if self.dropTime is not False: 198 | epsilon = vector(0,self.markerScale*.5,0)+self.timeOffset 199 | droptimeText = label(pos=self.obj.pos+epsilon, text='t='+str(t)+'s', height=10, box=False, color=self.labelColor) 200 | 201 | # Same with order label 202 | if self.labelMarkerOrder is not False: 203 | label(pos=self.obj.pos-vector(0,self.markerScale*.5,0)+self.labelMarkerOffset, text=str(self.curMarker), height=10, box=False, color=self.labelColor) 204 | except TypeError as err: 205 | print("**********TYPE ERROR**********") 206 | 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)!") 207 | print("******************************") 208 | print(err) 209 | raise err 210 | 211 | class MotionMapN: 212 | """ 213 | This class assists students in constructing motion maps 214 | using either arrows (measuring a quantity) or "breadcrumbs" 215 | (with timestamps). 216 | """ 217 | 218 | def __init__(self, obj, dt, numSteps, markerType="arrow", 219 | markerScale=1, markerColor=color.red, 220 | labelMarkerOrder=True, labelMarkerOffset=vector(0,0,0), 221 | dropTime=False, timeOffset=vector(0,0,0), arrowOffset=vector(0,0,0), labelColor=color.white): 222 | # MotionMapN 223 | # obj - object to track in mapping / placing markers 224 | # dt - time between steps 225 | # numSteps - number of steps between markers 226 | # markerType - determines type of motionmap; options are "arrow" or "breadcrumbs" 227 | # markerScale - replaces pSize / quantscale from motionmodule.py depending on type 228 | # markerColor - color of markers 229 | # labelMarkerOrder - drop numbers of markers? 230 | # labelMarkerOffset - amount to offset numbering by 231 | # dropTime - boolean determining whether a timestamp should be placed along with the marker 232 | # timeOffset - if dropTime is True, determines the offset, if any, of the label from the markers 233 | # arrowOffset - shift an arrow by an amount (x,y,z), useful for two arrows views 234 | 235 | self.obj = obj 236 | self.dt = dt 237 | self.numSteps = numSteps 238 | self.markerType = markerType 239 | self.markerScale = markerScale 240 | self.markerColor = markerColor 241 | self.labelMarkerOrder = labelMarkerOrder 242 | self.labelMarkerOffset = labelMarkerOffset 243 | self.timeOffset = timeOffset 244 | self.dropTime = dropTime 245 | self.arrowOffset = arrowOffset 246 | self.labelColor = labelColor 247 | 248 | # Calculate size of interval for each step, set initial step index 249 | try: 250 | self.interval = self.dt * self.numSteps 251 | except TypeError as err: 252 | print("**********TYPE ERROR**********") 253 | 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)!") 254 | print("******************************") 255 | print(err) 256 | raise err 257 | self.curMarker = 0 258 | 259 | 260 | def update(self, t, quantity=1): 261 | try: 262 | 263 | threshold = self.interval * self.curMarker 264 | # Display new arrow if t has broken new threshold 265 | if t >= threshold: 266 | 267 | # Increment marker count 268 | self.curMarker += 1 269 | 270 | # Display marker! 271 | if self.markerType == "arrow": 272 | arrow(pos=self.obj.pos+self.arrowOffset, 273 | axis=self.markerScale*quantity, color=self.markerColor) 274 | elif self.markerType == "breadcrumbs": 275 | points(pos=self.obj.pos, 276 | size=10*self.markerScale*quantity, color=self.markerColor) 277 | 278 | #Also display timestamp if requested 279 | if self.dropTime is not False: 280 | epsilon = vector(0,self.markerScale*.5,0)+self.timeOffset 281 | droptimeText = label(pos=self.obj.pos+epsilon, text='t='+str(t)+'s', height=10, box=False, color=self.labelColor) 282 | 283 | # Same with order label 284 | if self.labelMarkerOrder is not False: 285 | label(pos=self.obj.pos-vector(0,self.markerScale*.5,0)+self.labelMarkerOffset, text=str(self.curMarker), height=10, box=False, color=self.labelColor) 286 | 287 | except TypeError as err: 288 | print("**********TYPE ERROR**********") 289 | 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)!") 290 | print("******************************") 291 | print(err) 292 | raise err 293 | 294 | class PhysAxis: 295 | """ 296 | This class assists students in creating dynamic axes for their models. 297 | """ 298 | 299 | def __init__(self, obj, numLabels, axisType="x", axis=vector(1,0,0), startPos=None, 300 | length=None, labels = None, labelOrientation="down", axisColor=color.yellow, labelColor=color.white): 301 | # PhysAxis 302 | # obj - Object which axis is oriented based on by default 303 | # numLabels - number of labels on axis 304 | # axisType - sets whether this is a default axis of x or y, or an arbitrary axis 305 | # axis - unit vector defining the orientation of the axis to be created IF axisType = "arbitrary" 306 | # startPos - start position for the axis - defaults to (-obj_size(obj).x/2,-4*obj_size(obj).y,0) 307 | # length - length of the axis - defaults to obj_size(obj).x 308 | # labelOrientation - how labels are placed relative to axis markers - "up", "down", "left", or "right" 309 | 310 | try: 311 | self.intervalMarkers = [] 312 | self.intervalLabels = [] 313 | self.labelText = labels 314 | self.obj = obj 315 | self.lastPos = vector(self.obj.pos.x, self.obj.pos.y, self.obj.pos.z) 316 | self.numLabels = numLabels 317 | self.axisType = axisType 318 | self.axis = axis if axisType != "y" else vector(0,1,0) 319 | self.length = length if (length is not None) else obj_size(obj).x 320 | self.startPos = startPos if (startPos is not None) else vector(-obj_size(obj).x/2,-4*obj_size(obj).y,0) 321 | self.axisColor = axisColor 322 | self.labelColor = labelColor 323 | 324 | if labelOrientation == "down": 325 | self.labelShift = vector(0,-0.05*self.length,0) 326 | elif labelOrientation == "up": 327 | self.labelShift = vector(0,0.05*self.length,0) 328 | elif labelOrientation == "left": 329 | self.labelShift = vector(-0.1*self.length,0,0) 330 | elif labelOrientation == "right": 331 | self.labelShift = vector(0.1*self.length,0,0) 332 | 333 | self.__reorient() 334 | except TypeError as err: 335 | print("**********TYPE ERROR**********") 336 | 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)!") 337 | print("******************************") 338 | print(err) 339 | raise err 340 | 341 | def update(self): 342 | try: 343 | # Determine if reference obj. has shifted since last update, if so shift us too 344 | if self.obj.pos != self.lastPos: 345 | diff = self.obj.pos - self.lastPos 346 | 347 | for i in range(len(self.intervalMarkers)): 348 | self.intervalMarkers[i].pos += diff 349 | self.intervalLabels[i].pos += diff 350 | self.axisCurve.pos = [x + diff for x in self.axisCurve.pos] 351 | 352 | self.lastPos = vector(self.obj.pos.x, self.obj.pos.y, self.obj.pos.z) 353 | except TypeError as err: 354 | print("**********TYPE ERROR**********") 355 | 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)!") 356 | print("******************************") 357 | print(err) 358 | raise err 359 | 360 | def reorient(self, axis=None, startPos=None, length=None, labels=None, labelOrientation=None): 361 | try: 362 | # Determine which, if any, parameters are being modified 363 | self.axis = axis if axis is not None else self.axis 364 | self.startPos = startPos if startPos is not None else self.startPos 365 | self.length = length if length is not None else self.length 366 | self.labelText = labels if labels is not None else self.labels 367 | 368 | # Re-do label orientation as well, if it has been set 369 | if labelOrientation == "down": 370 | self.labelShift = vector(0,-0.05*self.length,0) 371 | elif labelOrientation == "up": 372 | self.labelShift = vector(0,0.05*self.length,0) 373 | elif labelOrientation == "left": 374 | self.labelShift = vector(-0.1*self.length,0,0) 375 | elif labelOrientation == "right": 376 | self.labelShift = vector(0.1*self.length,0,0) 377 | 378 | self.__reorient() 379 | except TypeError as err: 380 | print("**********TYPE ERROR**********") 381 | 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)!") 382 | print("******************************") 383 | print(err) 384 | raise err 385 | 386 | def __reorient(self): 387 | # Actual internal axis setup code... determines first whether we are creating or updating 388 | updating = True if len(self.intervalMarkers) > 0 else False 389 | 390 | # Then determines the endpoint of the axis and the interval 391 | final = self.startPos + (self.length * self.axis) 392 | interval = (self.length / (self.numLabels-1)) * self.axis 393 | 394 | # Loop for each interval marker, setting up or updating the markers and labels 395 | i=0 396 | while i= 0 : #while the ball's y-position is greater than 0 (above the ground) 65 | 66 | # Required to make animation visible / refresh smoothly (keeps program from running faster 67 | # than 1000 frames/s) 68 | rate(1000) 69 | 70 | # Compute Net Force 71 | Fnet = ball.m * g 72 | 73 | # Newton's 2nd Law 74 | ball.v = ball.v + (Fnet/ball.m * deltat) 75 | 76 | # Position update 77 | ball.pos = ball.pos + ball.v * deltat 78 | 79 | # Update motion map, graph, timer, and trail 80 | motionMap.update(t, ball.v) 81 | posgraph.plot(t, ball.pos.x, ball.pos.y) # plot x and y position vs. time 82 | trail.append(pos = ball.pos) 83 | timerDisplay.update(t) 84 | 85 | # Time update 86 | t = t + deltat 87 | 88 | 89 | ### OUTPUT 90 | # -------------------------------------------------------------------------------------- 91 | 92 | # Print the final time and the ball's final position 93 | print t 94 | print ball.pos 95 | -------------------------------------------------------------------------------- /satellite motion/binary.py: -------------------------------------------------------------------------------- 1 | ### INITIALIZE VPYTHON 2 | # ----------------------------------------------------------------------- 3 | 4 | from __future__ import division 5 | from visual import * 6 | from physutil import * 7 | from visual.graph import * 8 | 9 | 10 | ### SETUP ELEMENTS FOR GRAPHING, SIMULATION, VISUALIZATION, TIMING 11 | # ------------------------------------------------------------------------ 12 | 13 | # Set window title 14 | scene.title = "Satellite Motion" 15 | 16 | # Make scene background black 17 | scene.background = color.black 18 | 19 | # Define scene objects (units are in meters) 20 | earth1 = sphere(radius = 6.378e6, color = color.blue) 21 | earth2 = sphere(radius = 6.378e6, color = color.blue) 22 | 23 | satellite = sphere(radius = 6.378e6, color = color.green) # r = 10 24 | 25 | # Set up trail to mark the satellite's trajectory 26 | trail = curve(color = color.yellow, radius = 5e5) # units are in meters 27 | 28 | # Set up motion map for satellite's velocity 29 | vMotionMap = MotionMap(satellite, 100000, # expected end time in seconds 30 | 10, # number of markers to draw 31 | markerScale = 5e3, # scale the vector so it fits on the screen 32 | labelMarkerOrder = False) 33 | 34 | # Set up motion map for satellite's acceleration 35 | aMotionMap = MotionMap(satellite, 100000, # expected end time in seconds 36 | 10, # number of markers to draw 37 | markerScale = 5e7, # scale the vector so it fits on the screen 38 | markerColor = color.orange, 39 | labelMarkerOrder = False) 40 | 41 | 42 | ### SETUP PARAMETERS AND INITIAL CONDITIONS 43 | # ---------------------------------------------------------------------------------------- 44 | 45 | # Define parameters 46 | 47 | earth1.m = 1*5.972e24 # mass of the earth in kg 48 | earth2.m = 1 * 5.972e24 49 | earth2.pos = vector(2 * 4.23e7, 0, 0) 50 | satellite.m = 1000 # mass of satellite in kg 51 | satellite.pos = vector(5*4.23e7, 3*4.23e7, 0) # initial position of the satellite, units are in meters 52 | satellite.v = vector(-.0*3.07e3, -.4*3.07e3, 0) # initial velocity of the satellite 53 | 54 | # Define time parameters 55 | t = 0 # starting time 56 | deltat = 36 # time step units are s 57 | 58 | 59 | ### CALCULATION LOOP; perform physics updates and drawing 60 | # ------------------------------------------------------------------------------------ 61 | 62 | while t < 5000000 : 63 | 64 | # Required to make animation visible / refresh smoothly (keeps program from running faster 65 | # than 1000 frames/s) 66 | rate(1000) # 1000 67 | 68 | # Note: This model makes the false assumption that the stars are stationary 69 | #in space 70 | 71 | # Force on satellite by both stars 72 | Fg1 = (6.673e-11) * satellite.m * earth1.m / ((earth1.pos.x - satellite.pos.x)**2 + (earth1.pos.y - satellite.pos.y)**2) 73 | Fg2 = (6.673e-11) * satellite.m * earth2.m / ((earth2.pos.x - satellite.pos.x)**2 + (earth2.pos.y - satellite.pos.y)**2) 74 | 75 | 76 | # Fnet1 = vector(Fg1*(earth1.pos.x - satellite.pos.x), 77 | # Fg1*(earth1.pos.y - satellite.pos.y),0) 78 | # Fnet2 = vector(Fg2*(earth2.pos.x - satellite.pos.x), 79 | # Fg2*(earth2.pos.y - satellite.pos.y),0) 80 | 81 | Fnet1 = vector((earth1.pos.x - satellite.pos.x), 82 | (earth1.pos.y - satellite.pos.y),0) 83 | Fnet1.mag = Fg1 84 | 85 | Fnet2 = vector((earth2.pos.x - satellite.pos.x), 86 | (earth2.pos.y - satellite.pos.y),0) 87 | Fnet2.mag = Fg2 88 | 89 | Fnet = Fnet1 + Fnet2 90 | 91 | # Arctan can be undefined if x = 0,while it's unlickily, it's still possible 92 | # if Fnet.x == 0: 93 | # Fnet.mag = Fg1 +Fg2 94 | # else: 95 | # Fnet.mag = (Fg1 +Fg2) * math.sin(math.atan(math.fabs(Fnet.y/Fnet.x))) 96 | 97 | 98 | # Newton's 2nd Law 99 | satellite.v = satellite.v + (Fnet/satellite.m * deltat) 100 | 101 | # Position update 102 | satellite.pos = satellite.pos + satellite.v * deltat 103 | 104 | # Update motion maps and trail 105 | trail.append(pos = satellite.pos) 106 | #vMotionMap.update(t, satellite.v) 107 | #aMotionMap.update(t, Fnet/satellite.m) 108 | 109 | # Time update 110 | t = t + deltat 111 | 112 | 113 | ### OUTPUT 114 | # -------------------------------------------------------------------------------------- 115 | 116 | # Print the final time and the satellite's final position 117 | print t 118 | print satellite.v 119 | print Fnet/satellite.m 120 | -------------------------------------------------------------------------------- /satellite motion/physutil.py: -------------------------------------------------------------------------------- 1 | # physutil.py v1.22 2 | # Copyright (c) 2011-2012 GT Physics Education Research Group 3 | # License: GPL-3.0 (http://opensource.org/licenses/GPL-3.0) 4 | 5 | # This module is built to simplify and assist high school physics students 6 | # in the construction of models for lab exercises using VPython. 7 | 8 | 9 | # Revisions by date 10 | 11 | # v1.22 24 January 2011 -- Danny Caballero 12 | # Added labelColor attribute to PhysAxis, MotionMap, and MotionMapN 13 | # controls color of text 14 | 15 | # v1.21 5 December 2011 -- Danny Caballero 16 | # Added timerColor attribute to PhysTimer 17 | # controls color of text 18 | 19 | # v1.2 19 October 2011 -- Danny Caballero 20 | # Added MotionMapN, a class that allows the placing of breadcrumbs or arrows 21 | # every "n" steps. 22 | 23 | # v1.13 26 September 2011 -- Daniel Borrero 24 | # Fixed unit test bug for PhysTimer 25 | 26 | # v1.12 30 August 2011 -- Daniel Borrero 27 | # Fixed bug in PhysTimer output 28 | # E.g., 2.00 s is now displayed as 00:00:02.00 instead of 00:00:01:100 29 | 30 | # v1.11 29 August 2011 -- Danny Caballero 31 | # Changed License to GNU 32 | 33 | # v1.1 15 August 2011 -- Daniel Borrero 34 | # Print statements made compatible with Python 3.1 35 | 36 | # v1.01 16 July 2011 -- Danny Caballero 37 | # Added ability to change PhysAxis color using axisColor 38 | 39 | # v1.0 29 April 2011 -- CS Build Team 40 | # Heavy Modification 41 | 42 | # v0.1 05 January 2011 -- Danny Caballero 43 | # Initial Build 44 | 45 | from __future__ import division 46 | import unittest 47 | 48 | """ 49 | # 50 | # 51 | # UNIT TESTING / IMPORT SETUP CODE ------------------------------------------------------------ 52 | # 53 | # 54 | """ 55 | 56 | # Determine whether we are being used as a module or just running unittests (for mock purposes this is important) 57 | if __name__ == "__main__": 58 | # If we are unit testing, set up mock objects (must be done before classes are defined below!) 59 | from visual import vector 60 | class Mock: 61 | def __init__(self, name, *args, **kwargs): 62 | self.name = name 63 | self.called = 0 64 | 65 | def __call__(self, *args, **kwargs): 66 | self.args = args 67 | self.kwargs = kwargs 68 | for name in kwargs: 69 | setattr(self, name, kwargs[name]) 70 | self.called += 1 71 | return self 72 | 73 | def reset(self): 74 | self.called = 0 75 | 76 | color = Mock("color") 77 | color.red = "red" 78 | color.green = "green" 79 | color.blue = "blue" 80 | color.yellow = "yellow" 81 | color.orange = "orange" 82 | color.cyan = "cyan" 83 | color.magenta = "magenta" 84 | color.white = "white" 85 | 86 | arrow = Mock("arrow") 87 | label = Mock("label") 88 | points = Mock("points") 89 | curve = Mock("curve") 90 | gdisplay = Mock("gdisplay") 91 | gcurve = Mock("gcurve") 92 | gcurve.plots = [] 93 | def mockPlot(pos): 94 | gcurve.plots.append(pos) 95 | gcurve.plot = mockPlot 96 | 97 | """ 98 | vector = Mock("vector") 99 | def call(x, y, z): 100 | vector.x = x 101 | vector.y = y 102 | vector.z = z 103 | vector.__call__ = call 104 | """ 105 | 106 | else: 107 | # These are the actual imports for the utility 108 | from visual import * 109 | from visual.graph import * 110 | 111 | 112 | """ 113 | # 114 | # 115 | # ACTUAL PHYSUTIL CODE FOLLOWS -------------------------------------------------- 116 | # 117 | # 118 | """ 119 | 120 | # Initialize window positions for students (if we aren't unit testing) 121 | if __name__ != "__main__": 122 | scene.x = 50 123 | scene.y = 50 124 | 125 | # Helper function for returning proper size of something 126 | def obj_size(obj): 127 | if type(obj) == box or type(obj) == pyramid: 128 | return obj.size 129 | elif type(obj) == sphere: 130 | return vector(obj.radius, obj.radius, obj.radius) 131 | 132 | class MotionMap: 133 | """ 134 | This class assists students in constructing motion maps 135 | using either arrows (measuring a quantity) or "breadcrumbs" 136 | (with timestamps). 137 | """ 138 | 139 | def __init__(self, obj, tf, numMarkers, markerType="arrow", 140 | markerScale=1, markerColor=color.red, 141 | labelMarkerOrder=True, labelMarkerOffset=vector(0,0,0), 142 | dropTime=False, timeOffset=vector(0,0,0), arrowOffset=vector(0,0,0), labelColor=color.white): 143 | # MotionMap 144 | # obj - object to track in mapping / placing markers 145 | # tf - expected tFinal, used to space marker placement over time 146 | # numMarkers - number of markers to place 147 | # markerType - determines type of motionmap; options are "arrow" or "breadcrumbs" 148 | # markerScale - replaces pSize / quantscale from motionmodule.py depending on type 149 | # markerColor - color of markers 150 | # labelMarkerOrder - drop numbers of markers? 151 | # labelMarkerOffset - amount to offset numbering by 152 | # dropTime - boolean determining whether a timestamp should be placed along with the marker 153 | # timeOffset - if dropTime is True, determines the offset, if any, of the label from the marker 154 | # arrowOffset - shift an arrow by an amount (x,y,z), useful for two arrows views 155 | 156 | self.obj = obj 157 | self.tf = tf 158 | self.numMarkers = numMarkers 159 | self.markerType = markerType 160 | self.markerScale = markerScale 161 | self.markerColor = markerColor 162 | self.labelMarkerOrder = labelMarkerOrder 163 | self.labelMarkerOffset = labelMarkerOffset 164 | self.timeOffset = timeOffset 165 | self.dropTime = dropTime 166 | self.arrowOffset = arrowOffset 167 | self.labelColor = labelColor 168 | 169 | # Calculate size of interval for each step, set initial step index 170 | try: 171 | self.interval = self.tf / self.numMarkers 172 | except TypeError as err: 173 | print("**********TYPE ERROR**********") 174 | 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)!") 175 | print("******************************") 176 | print(err) 177 | raise err 178 | self.curMarker = 0 179 | 180 | 181 | def update(self, t, quantity=1): 182 | try: 183 | # Display new arrow if t has broken next threshold 184 | if t > (self.interval * self.curMarker): 185 | # Increment threshold 186 | self.curMarker += 1 187 | 188 | # Display marker! 189 | if self.markerType == "arrow": 190 | arrow(pos=self.obj.pos+self.arrowOffset, 191 | axis=self.markerScale*quantity, color=self.markerColor) 192 | elif self.markerType == "breadcrumbs": 193 | points(pos=self.obj.pos, 194 | size=10*self.markerScale*quantity, color=self.markerColor) 195 | 196 | #Also display timestamp if requested 197 | if self.dropTime is not False: 198 | epsilon = vector(0,self.markerScale*.5,0)+self.timeOffset 199 | droptimeText = label(pos=self.obj.pos+epsilon, text='t='+str(t)+'s', height=10, box=False, color=self.labelColor) 200 | 201 | # Same with order label 202 | if self.labelMarkerOrder is not False: 203 | label(pos=self.obj.pos-vector(0,self.markerScale*.5,0)+self.labelMarkerOffset, text=str(self.curMarker), height=10, box=False, color=self.labelColor) 204 | except TypeError as err: 205 | print("**********TYPE ERROR**********") 206 | 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)!") 207 | print("******************************") 208 | print(err) 209 | raise err 210 | 211 | class MotionMapN: 212 | """ 213 | This class assists students in constructing motion maps 214 | using either arrows (measuring a quantity) or "breadcrumbs" 215 | (with timestamps). 216 | """ 217 | 218 | def __init__(self, obj, dt, numSteps, markerType="arrow", 219 | markerScale=1, markerColor=color.red, 220 | labelMarkerOrder=True, labelMarkerOffset=vector(0,0,0), 221 | dropTime=False, timeOffset=vector(0,0,0), arrowOffset=vector(0,0,0), labelColor=color.white): 222 | # MotionMapN 223 | # obj - object to track in mapping / placing markers 224 | # dt - time between steps 225 | # numSteps - number of steps between markers 226 | # markerType - determines type of motionmap; options are "arrow" or "breadcrumbs" 227 | # markerScale - replaces pSize / quantscale from motionmodule.py depending on type 228 | # markerColor - color of markers 229 | # labelMarkerOrder - drop numbers of markers? 230 | # labelMarkerOffset - amount to offset numbering by 231 | # dropTime - boolean determining whether a timestamp should be placed along with the marker 232 | # timeOffset - if dropTime is True, determines the offset, if any, of the label from the markers 233 | # arrowOffset - shift an arrow by an amount (x,y,z), useful for two arrows views 234 | 235 | self.obj = obj 236 | self.dt = dt 237 | self.numSteps = numSteps 238 | self.markerType = markerType 239 | self.markerScale = markerScale 240 | self.markerColor = markerColor 241 | self.labelMarkerOrder = labelMarkerOrder 242 | self.labelMarkerOffset = labelMarkerOffset 243 | self.timeOffset = timeOffset 244 | self.dropTime = dropTime 245 | self.arrowOffset = arrowOffset 246 | self.labelColor = labelColor 247 | 248 | # Calculate size of interval for each step, set initial step index 249 | try: 250 | self.interval = self.dt * self.numSteps 251 | except TypeError as err: 252 | print("**********TYPE ERROR**********") 253 | 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)!") 254 | print("******************************") 255 | print(err) 256 | raise err 257 | self.curMarker = 0 258 | 259 | 260 | def update(self, t, quantity=1): 261 | try: 262 | 263 | threshold = self.interval * self.curMarker 264 | # Display new arrow if t has broken new threshold 265 | if t >= threshold: 266 | 267 | # Increment marker count 268 | self.curMarker += 1 269 | 270 | # Display marker! 271 | if self.markerType == "arrow": 272 | arrow(pos=self.obj.pos+self.arrowOffset, 273 | axis=self.markerScale*quantity, color=self.markerColor) 274 | elif self.markerType == "breadcrumbs": 275 | points(pos=self.obj.pos, 276 | size=10*self.markerScale*quantity, color=self.markerColor) 277 | 278 | #Also display timestamp if requested 279 | if self.dropTime is not False: 280 | epsilon = vector(0,self.markerScale*.5,0)+self.timeOffset 281 | droptimeText = label(pos=self.obj.pos+epsilon, text='t='+str(t)+'s', height=10, box=False, color=self.labelColor) 282 | 283 | # Same with order label 284 | if self.labelMarkerOrder is not False: 285 | label(pos=self.obj.pos-vector(0,self.markerScale*.5,0)+self.labelMarkerOffset, text=str(self.curMarker), height=10, box=False, color=self.labelColor) 286 | 287 | except TypeError as err: 288 | print("**********TYPE ERROR**********") 289 | 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)!") 290 | print("******************************") 291 | print(err) 292 | raise err 293 | 294 | class PhysAxis: 295 | """ 296 | This class assists students in creating dynamic axes for their models. 297 | """ 298 | 299 | def __init__(self, obj, numLabels, axisType="x", axis=vector(1,0,0), startPos=None, 300 | length=None, labels = None, labelOrientation="down", axisColor=color.yellow, labelColor=color.white): 301 | # PhysAxis 302 | # obj - Object which axis is oriented based on by default 303 | # numLabels - number of labels on axis 304 | # axisType - sets whether this is a default axis of x or y, or an arbitrary axis 305 | # axis - unit vector defining the orientation of the axis to be created IF axisType = "arbitrary" 306 | # startPos - start position for the axis - defaults to (-obj_size(obj).x/2,-4*obj_size(obj).y,0) 307 | # length - length of the axis - defaults to obj_size(obj).x 308 | # labelOrientation - how labels are placed relative to axis markers - "up", "down", "left", or "right" 309 | 310 | try: 311 | self.intervalMarkers = [] 312 | self.intervalLabels = [] 313 | self.labelText = labels 314 | self.obj = obj 315 | self.lastPos = vector(self.obj.pos.x, self.obj.pos.y, self.obj.pos.z) 316 | self.numLabels = numLabels 317 | self.axisType = axisType 318 | self.axis = axis if axisType != "y" else vector(0,1,0) 319 | self.length = length if (length is not None) else obj_size(obj).x 320 | self.startPos = startPos if (startPos is not None) else vector(-obj_size(obj).x/2,-4*obj_size(obj).y,0) 321 | self.axisColor = axisColor 322 | self.labelColor = labelColor 323 | 324 | if labelOrientation == "down": 325 | self.labelShift = vector(0,-0.05*self.length,0) 326 | elif labelOrientation == "up": 327 | self.labelShift = vector(0,0.05*self.length,0) 328 | elif labelOrientation == "left": 329 | self.labelShift = vector(-0.1*self.length,0,0) 330 | elif labelOrientation == "right": 331 | self.labelShift = vector(0.1*self.length,0,0) 332 | 333 | self.__reorient() 334 | except TypeError as err: 335 | print("**********TYPE ERROR**********") 336 | 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)!") 337 | print("******************************") 338 | print(err) 339 | raise err 340 | 341 | def update(self): 342 | try: 343 | # Determine if reference obj. has shifted since last update, if so shift us too 344 | if self.obj.pos != self.lastPos: 345 | diff = self.obj.pos - self.lastPos 346 | 347 | for i in range(len(self.intervalMarkers)): 348 | self.intervalMarkers[i].pos += diff 349 | self.intervalLabels[i].pos += diff 350 | self.axisCurve.pos = [x + diff for x in self.axisCurve.pos] 351 | 352 | self.lastPos = vector(self.obj.pos.x, self.obj.pos.y, self.obj.pos.z) 353 | except TypeError as err: 354 | print("**********TYPE ERROR**********") 355 | 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)!") 356 | print("******************************") 357 | print(err) 358 | raise err 359 | 360 | def reorient(self, axis=None, startPos=None, length=None, labels=None, labelOrientation=None): 361 | try: 362 | # Determine which, if any, parameters are being modified 363 | self.axis = axis if axis is not None else self.axis 364 | self.startPos = startPos if startPos is not None else self.startPos 365 | self.length = length if length is not None else self.length 366 | self.labelText = labels if labels is not None else self.labels 367 | 368 | # Re-do label orientation as well, if it has been set 369 | if labelOrientation == "down": 370 | self.labelShift = vector(0,-0.05*self.length,0) 371 | elif labelOrientation == "up": 372 | self.labelShift = vector(0,0.05*self.length,0) 373 | elif labelOrientation == "left": 374 | self.labelShift = vector(-0.1*self.length,0,0) 375 | elif labelOrientation == "right": 376 | self.labelShift = vector(0.1*self.length,0,0) 377 | 378 | self.__reorient() 379 | except TypeError as err: 380 | print("**********TYPE ERROR**********") 381 | 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)!") 382 | print("******************************") 383 | print(err) 384 | raise err 385 | 386 | def __reorient(self): 387 | # Actual internal axis setup code... determines first whether we are creating or updating 388 | updating = True if len(self.intervalMarkers) > 0 else False 389 | 390 | # Then determines the endpoint of the axis and the interval 391 | final = self.startPos + (self.length * self.axis) 392 | interval = (self.length / (self.numLabels-1)) * self.axis 393 | 394 | # Loop for each interval marker, setting up or updating the markers and labels 395 | i=0 396 | while i -4.5 and car.pos.x < 4.5 : 63 | 64 | # Required to make animation visible / refresh smoothly (keeps program from running faster 65 | # than 1000 frames/s) 66 | rate(1000) 67 | 68 | # Compute Net Force 69 | Fnet = vector(0,0,0) 70 | 71 | # Newton's 2nd Law 72 | car.v = car.v + Fnet/car.m * deltat 73 | 74 | # Position update 75 | car.pos = car.pos + car.v*deltat 76 | 77 | # Update timer, graph, and trail 78 | timerDisplay.update(t) 79 | 80 | # Time update 81 | t = t + deltat 82 | 83 | ### OUTPUT 84 | # -------------------------------------------------------------------------------------- 85 | 86 | # Print the final time and the car's size 87 | print t 88 | print car.size 89 | -------------------------------------------------------------------------------- /special relativity/momentum.py: -------------------------------------------------------------------------------- 1 | from __future__ import division, print_function 2 | from visual import * 3 | from visual.graph import * 4 | scene.background = color.white 5 | scene.height = 50 6 | scene.width = 50 7 | scene.x = scene.y =0 8 | ## Josh Gates 2012, started with kernel of Ruth Chabay program 9 | print (""" 10 | Click anywhere in display window to start. 11 | Click to stop. 12 | Momentum and velocity are shown as .6 kg block is pushed with a constant 1,000,000 N force. 13 | Uncomment gamma line to remove classical p def'n/assumption""") 14 | 15 | ## CONSTANTS 16 | delta_t = 0.01 ## for 100 steps 17 | mblock = 0.06 18 | c=3e8 19 | Fnet = vector(1e6,0,0) 20 | 21 | ## OBJECTS & INITIAL VALUES 22 | pblock = mblock*vector(0,0,0) 23 | gamma = 1 24 | 25 | # start time at 0 26 | t = 0 27 | scene.center = (0,.1,0) # move camera up 28 | scene.range = 0.15 29 | 30 | ## GRAPH STUFF 31 | gp = gdisplay(background=color.white, foreground=color.black, y=0, x=250, height=300, 32 | title='Momentum vs. time: block with constant F', xtitle='Time (s)', ytitle='Position (m)', 33 | ymax=3e7,xmax=30) 34 | blockp = gcurve(color=color.magenta) 35 | blockp.plot(pos=(t,pblock.x)) ## initial pos.x of block 36 | 37 | gv = gdisplay(background=color.white, foreground=color.black, y=300, x=250, height=300, 38 | title='Velocity vs. time: block with constant F', xtitle='Time (s)', ytitle='Velocity (m/s)', 39 | ymax = 3e8, ymin=0, xmax=30) 40 | blockv = gcurve(color=color.blue) 41 | blockv.plot(pos=(t,pblock.x/(gamma*mblock))) #initial velocity 42 | asymptote=gcurve(color=color.red) 43 | 44 | 45 | scene.mouse.getclick() 46 | stopper='go' 47 | 48 | a=.1 49 | while a<1000: 50 | asymptote.plot(pos=(30/a,3e8)) 51 | a=a+1 52 | 53 | ## CALCULATIONS 54 | while stopper=='go': 55 | rate(500) 56 | 57 | pblock = pblock + Fnet*delta_t 58 | 59 | # comment to make classical approximation 60 | #gamma = (1-(pblock.x)**2/(mblock**2 * c**2+(pblock.x)**2))**-.5 61 | 62 | # update time 63 | t = t + delta_t 64 | # plot pos.x, velocity.x of block 65 | blockp.plot(pos=(t,pblock.x)) 66 | blockv.plot(pos=(t,pblock.x/(gamma*mblock))) 67 | ## print t, block.y 68 | if scene.mouse.clicked: 69 | stopper='stop' 70 | --------------------------------------------------------------------------------