class Breadboard(object): """represents a Breadboard. It consists of a matrix (list of lists) of Location objects. Each Location object is then pointed at a particular node object. The coordinate system is (x,-y) or column, row in the matrix. By default, it is powered like a DAQ powers a breadboard, going (bottom to top) ground,2.5V,2.5V,5V. """ def __init__(self): self.numRows = 18 self.numColumns = 63 self.locMatrix = Matrix(self.numColumns,self.numRows) self.componentList = [] self.rails= [0 , 2.5 , 2.5 , 5] #voltage rails. bottom to top. y = 17 16 1 0 self.initializeLocations() #assigns Location objects to each coordinate self.nodeCreation() #assigns functional Nodes to proper Locations self.detailLocations() #sets power rails and display flags def initializeLocations(self): """creates Location objects at every spot in the local matrix. doesnt deal with nodes, voltages, or flags""" for x in range(self.numColumns): for y in range(self.numRows): self.locMatrix.setItem(x,y,Location(x,y)) def getLocation(self,x,y): """returns the Location object at a given x,y coordinate""" if x>=self.numColumns or y>=self.numRows: return None if (x<0 and x!=-1) or (y<0): return None return self.locMatrix.getItem(x,y) def assignNodeHoriz(self,x,y,number): """assigns a Node object to a Location. If the previous x Location already has a Node, the current Location simply points to that previous one. Represents the fact that power at the rails is all the same node electrically""" if self.getLocation(x-1,y).Node.number !=-1: self.locMatrix.getItem(x,y).Node = self.locMatrix.getItem(x-1,y).Node else: self.locMatrix.getItem(x,y).Node = Node(number) def assignNodeVert(self,x,y,number): """assigns a Node object to a Location. If the previous y Location already has a Node, the current Location simply points to that previous one. Represents the fact that power at each 'five family' vertical group is at the same electrical node""" if self.getLocation(x,y-1).Node.number !=-1: self.locMatrix.getItem(x,y).Node = self.locMatrix.getItem(x,y-1).Node else: self.locMatrix.getItem(x,y).Node=Node(number) def nodeCreation(self): """goes through each x,y coordinate on the bb and assigns Node objects to all Locations that exist (not including the ones between pins) nodes are 0,1,2,3 for GND, power 1, power 2, and power 3.""" for i in range(self.width): self.assignNodeHoriz(i,0,1) #5 Volts self.assignNodeHoriz(i,1,2) # 2.5V self.assignNodeHoriz(i,16,3) # 2.5V self.assignNodeHoriz(i,17,0) # 0 is ground for y in range(3,8): for x in range(self.width): self.assignNodeVert(x,y,4*x) for y in range(10,15): for x in range(self.width): self.assignNodeVert(x,y,4*x+1) def detailLocations(self): """assigns display flags, which are for coloring the GUI. Sets the Location objects at coordinates at which there are no pins to Filled. adds voltage at the rails""" for x in range(self.numColumns): for y in range(self.numRows): if y==2: #fills pins between rows self.setFilled(x,y) self.setDisplayFlag(x,y,Location.BLUE_LINE) if y==15: self.setFilled(x,y) self.setDisplayFlag(x,y,Location.RED_LINE) if y==8: self.setFilled(x,y) self.setDisplayFlag(x,y,Location.CENTER_BOTTOM) if y==9: self.setFilled(x,y) self.setDisplayFlag(x,y,Location.CENTER_TOP) if (x+1)%6==0 and (y==0 or y==1 or y==16 or y==17): #fills pins between power fivesomes self.setFilled(x,y) self.setDisplayFlag(x,y,Location.BLANK) if y==0: self.setNodeVoltage(x,y,self.rails[3]) #sets power at top rail if y==1: self.setNodeVoltage(x,y,self.rails[2]) #sets power at second from top rail if y==16: self.setNodeVoltage(x,y,self.rails[1]) #sets power at third from top rail if y==17: self.setNodeVoltage(x,y,self.rails[0]) #sets power at bottom rail def __repr__(self): """returns all Locations""" return self.locMatrix.__repr__() def setNodeVoltage(self,x,y,voltage,voltageType='DC',frequency=0): """changes the Voltage object a particular Location's Node""" self.getLocation(x,y).Node.voltage = Voltage(voltage,voltageType,frequency) return True def clearNodeVoltage(self,x,y): """sets the Voltage at a Location's Node to zero.""" self.setNodeVoltage(x,y,0) return True def getNodeVoltage(self,x,y): """returns Voltage at a Location""" return self.getLocation(x,y).Node.voltage def isFilled(self,x,y): """tells if a pin at a certain Location is filled""" return self.getLocation(x,y).isFilled def setFilled(self,x,y): """fills a pin at a Location""" self.getLocation(x,y).isFilled = True def setDisplayFlag(self,x,y,displayFlag): """a helper function for setting the color of certain pins in the BB""" self.getLocation(x,y).setDisplayFlag(displayFlag) def setUnfilled(self,x,y): """unfills a pin at a given Location""" self.getLocation(x,y).isFilled = False def setAllFilled(self,pinList): """sets all given Locations to be filled. at that point, the reference pin is already defined""" for pin in pinList: self.setFilled(pin.xLoc,pin.yLoc) def setAllUnfilled(self,pinList): """sets all given Locations to be unfilled""" for pin in pinList: self.setUnfilled(pin.xLoc,pin.yLoc) def translateLocation(self,referenceLocation,relativeLocation): """A method to return the absolute Location produced by converting the referenceLocation and relativeLocation. This method returns a reference to an (absolute) Location. """ xCoord = referenceLocation.xLoc + relativeLocation.xLoc yCoord = referenceLocation.yLoc + relativeLocation.yLoc return self.getLocation(xCoord,yCoord) def translateAllLocations(self,refLoc,relLocs): """goes through a list of reference pin Locations and relative Locations and returns a list of corresponding converted (absolute) Locations""" transLocs = [] for relLoc in relLocs: transLocs.append(self.translateLocation(refLoc,relLoc)) return transLocs def canPutComponent(self,aComponent,pinPositions): """Tests whether or not a component can be placed at the reference x,y coordinate by checking if each translated Location specified by the pinList of aComponent if filled""" for i in range(0,len(pinPositions),2): x = pinPositions[i] y = pinPositions[i+1] refLocTest = self.getLocation(x,y) #the loc to test at #first test if the reference location is available if refLocTest == None or refLocTest.isFilled: return False else: #then check if every pin the component specifies is also #available, if not, then we cannot place the component here for relLoc in aComponent.pinList[1:]:#all but the zero'th pin in the pinlist if self.translateLocation(refLocTest,relLoc)==None or self.translateLocation(refLocTest,relLoc).isFilled: return False return True def putComponent(self,aComponent,*args): """This function puts the a component down. First it checks if it can put the component down. Then it translates all the relative Locations to absolute ones and fills all the pins. Adjusts the component's pin list accordingly. Also adds the component to the bb's component list. It takes x and y positions as arguments, depending on what type of component, you give it more or less """ if self.canPutComponent(aComponent,args): self.componentList.append(aComponent) aComponent.referencePin = self.getLocation(args[0],args[1]) if isinstance(aComponent,FixedBreadboardComponent): #opamps aComponent.pinList = self.translateAllLocations(aComponent.referencePin,aComponent.pinList) self.setAllFilled(aComponent.pinList) aComponent.deadPins = self.translateAllLocations(aComponent.referencePin,aComponent.deadPins) self.setAllFilled(aComponent.deadPins) if aComponent.displayName=='Input Device': #Sets voltage from input device self.setNodeVoltage(args[0],args[1],aComponent.voltage.volts,aComponent.voltageType,aComponent.frequency) return True elif isinstance(aComponent,VariableBreadboardComponent): #resistors count=0 for i in range(0,len(args),2): aComponent.pinList[count] = self.locMatrix.getItem(args[i],args[i+1]) self.setFilled(args[i],args[i+1]) count+=1 return True return False def removeComponent(self,aComponent): """removes a component from the breadboard. unfills all the Locations it was anchored to and pops it from the breadboard component list. then deletes the component from memory. if its an op amp this function also unfills the pins in between. If an input device it sets the voltage back to zero""" self.setAllUnfilled(aComponent.pinList) self.componentList.remove(aComponent) if isinstance(aComponent,InputDevice): self.clearNodeVoltage(aComponent.pinList[0].xLoc,aComponent.pinList[0].yLoc) elif isinstance(aComponent,FixedBreadboardComponent): self.setAllUnfilled(aComponent.deadPins) for val in globals(): #actually kills the global variable aComponent refers to if globals()[val]==aComponent: del globals()[val] return True return False def clearBreadboard(self): """ Removes all components from component list unfills all pins deletes all components from memory. Sets all nodes back to zero. Puts rails back to standard.""" while len(self.componentList) > 0: for pin in self.componentList[0].pinList: self.clearNodeVoltage(pin.xLoc,pin.yLoc) self.removeComponent(self.componentList[0]) self.setVoltageAtRail0(0) #standard self.setVoltageAtRail1(2.5) self.setVoltageAtRail2(2.5) self.setVoltageAtRail3(5) def unplugComponent(self,aComponent): """removes a breadboard component, but doesn't delete. unfills the Locations the breadboard was previously on, and reverts the component's pin list to its original list of Relative Locations. this allows you to put the component back down somewhere else""" self.setAllUnfilled(aComponent.pinList) self.setAllUnfilled(aComponent.deadPins) if isinstance(aComponent,InputDevice): #set voltage back to normal self.clearNodeVoltage(aComponent.pinList[0].xLoc,aComponent.pinList[0].yLoc) aComponent.pinList = aComponent.standardPinList return True def checkDistance(self,x,y,aComponent): """Makes sure we aren't stretching a variable component beyond its maximum length""" if isinstance(aComponent,VariableBreadboardComponent): return (x**2 + y**2)**.5 > aComponent.maxLength return True def saveBreadboard(self,saveFileName): """checks if the filetype is a .txt first. pickles the object to a string and saves it to the working directory""" if saveFileName[-4:] != '.txt': saveFileName = saveFileName + '.txt' os.system('touch ' + saveFileName) s = pickle.dumps(self) try: fin = open(saveFileName,'w') fin.write(s) fin.close() return True except: return False @staticmethod def openBreadboard(openFileName): """Opens a pickled breadboard text file. This is a static method because we want to be able to open a breadboard before we have one initialized""" f1 = open(openFileName) s = '' for line in f1: s = s + line try: bb = pickle.loads(s) return bb except: return None def flipComponent(self,aComponent): """Rotates a fixed bbc pi radians. Useful for op amps, which might be oriented left-right or right-left depending on the user""" if not isinstance(aComponent,FixedBreadboardComponent): return False pinListCopy = copy.copy(aComponent.pinList) lenth = len(aComponent.pinList) for i in range(lenth): aComponent.pinList[i] = pinListCopy[(i+lenth/2)%(lenth)] #adds half the length return True def getComponentAtLocation(self,x,y): """returns the component at a Location""" for component in self.componentList: for pin in component.pinList: if pin.xLoc==x and pin.yLoc==y: return component if isinstance(component,FixedBreadboardComponent): for pin in component.deadPins: if pin.xLoc==x and pin.yLoc==y: return component return None def setVoltageAtRail1(self,voltage): """sets Node Voltage at the second rail, second from bottom, y=16""" self.rails[1]=voltage self.setNodeVoltage(0,16,voltage) return True def setVoltageAtRail2(self,voltage): """sets Node Voltage at third from bottom rail, second from top,y=1""" self.rails[2]=voltage self.setNodeVoltage(0,1,voltage) return True def setVoltageAtRail3(self,voltage): """sets Node Voltage at topmost rail,y=0""" self.rails[3]=voltage self.setNodeVoltage(0,0,voltage) return True