# This is an enhanced version of the chain (catenary curve) example # discussed in Chapter 5 of the book Object-Oriented Programming in Python # Authors: Jason Fritts, Michael Goldwasser from cs1graphics import * from math import sqrt #************************* # Define constants #************************* # example chain length, number of links, link length, etc. displaySpeed = 1.0 # larger for faster display, and vice versa numLinks = 50 # number of chain links restingLength = 21.0 # initial length of each link (in pixels) totalSeparation = 630.0 # distance between chain ends (in pixels) gravityConstant = ( 1.0 / 32.2 ) * (totalSeparation / 600) elasticityConstant = 1.0 / totalSeparation speed = displaySpeed * (numLinks / 20) epsilon = sqrt(speed) / max(totalSeparation,400) #************************* # Define functions #************************* #### convenient function for adding up to three (x,y) tuples def combine(A, B, C=(0,0) ): return (A[0] + B[0] + C[0], A[1] + B[1] + C[1]) #### function returns a tuple representing the force being #### exerted upon point A due to the link joining A to B. def calcForce(A, B): dX = (B[0] - A[0]) dY = (B[1] - A[1]) distance = sqrt(dX * dX + dY * dY) forceFactor = 0 if distance > restingLength: # link being stretched stretch = distance - restingLength forceFactor = stretch * elasticityConstant return (forceFactor * dX, forceFactor * dY) # returning a tuple #### function to alter the graphical path and refresh canvas def drawChain(chainData, chainPath, theCanvas): for k in range(len(chainData)): chainPath.setPoint(Point(chainData[k][0], chainData[k][1]), k) theCanvas.refresh() #************************* # Main program #************************* # initialize the chain; one end at (0,0) other at (totalSeparation,0) chain = [] for k in range(numLinks + 1): X = totalSeparation * k / numLinks chain.append( (X, 0.0) ) # add new position # initialize the graphics paper = Canvas(totalSeparation, totalSeparation) paper.setAutoRefresh(False) curve = Path() for p in chain: curve.addPoint(Point(p[0], p[1])) paper.add(curve) graphicsCounter = int(25 * speed) # we will only draw some iterations # as long as forces are not (sufficiently) in equilibrium, adjust # chain link positions and re-draw somethingMoved = True # force loop to start while somethingMoved: somethingMoved = False # default for new iteration oldChain = list(chain) # record a copy of the data # examine forces being applied to a chain link, and # adjust link position if not at equilibrium for k in range(1, numLinks): gravForce = (0, gravityConstant) # downward force leftForce = calcForce(oldChain[k], oldChain[k-1]) rightForce = calcForce(oldChain[k], oldChain[k+1]) adjust = combine(gravForce, leftForce, rightForce) chain[k] = combine(oldChain[k], adjust) if abs(adjust[0]) > epsilon or abs(adjust[1]) > epsilon: somethingMoved = True # only draw intermittent results, otherwise display takes too long graphicsCounter -= 1 if graphicsCounter == 0: drawChain(chain, curve, paper) graphicsCounter = int(25 * speed) # display final result with line emphasized (bold) curve.setBorderWidth(2) drawChain(chain, curve, paper)