From fae00150fbeaad81265e74f990886fea3eac4ad4 Mon Sep 17 00:00:00 2001 From: TJ Date: Sat, 16 Nov 2013 18:05:16 +0000 Subject: [PATCH] Revise circuits module, improve models, use Exceptions, factor out common code --- circuits.py | 191 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 111 insertions(+), 80 deletions(-) diff --git a/circuits.py b/circuits.py index f7bae7c..936982e 100644 --- a/circuits.py +++ b/circuits.py @@ -7,30 +7,31 @@ # TODO: module in development, not ready for any use yet # -import os.path, pygame, engine +import os.path, pygame, engine, library from library import enum + class TraceSegment: """ Models a straight line segment of a complete Trace """ - # C-style pseudo-enum representing the directions a segment might run - directions = enum('N', 'NE', 'E', 'SE', 'S' , 'SW', 'W' , 'NW') - angles = list( 0 , 45 , 90, 135, 180, 225, 270, 315) + # N NE E SE S SW W NW + angles = ( 0 , 45 , 90, 135, 180, 225, 270, 315) # references to an attached Via and its position via = None - via_pos = enum('START', 'MID', 'END') via_position = None - def __init__(self, length=1, direction=TraceSegment.directions.E, via=None, position=TraceSegment.via_pos.START): + def __init__(self, length=1, direction=library.directions.E, via=None, via_position=library.positions.NONE): """ default segment runs for one game tile length left-to-right - length: number of game 'tiles' this segment covers - direction: direction + length: number of game 'tiles' this segment covers + direction: enum directions + via: Via + via_position: enum positions """ # ensure all values are legal before doing any assignment - if not direction in TraceSegment.directions.reverse_mapping: + if not direction in library.directions.reverse_mapping: # 'direction' is an index enum, so ensure it exists in the enum before using it to select the angle raise IndexError('direction invalid') - elif not position in TraceSegment.via_pos: + elif not via_position in library.positions: # 'position' is an enum so ensure it is valid raise IndexError('position invalid') elif via and not isinstance(via, Via): @@ -39,45 +40,70 @@ class TraceSegment: self.length = length self.angle = angles[direction] self.via = via - self.via_position = position + self.via_position = via_position - def via_set(self, via, position): + def set_via(self, via, position): """ link to the related Via via: Via object position: enum via_pos """ - if via and isinstance(via, Via): - if position in via_pos: - # FIXME: ensure the line above works for testing position's membership of via_pos - self.via = via - self.via_position = position + if not isinstance(via, Via): + raise TypeError('expecting a Via') + elif not position in library.positions: + # FIXME: ensure the line above works for testing position's membership + raise IndexError('position invalid') + + self.via = via + self.via_position = position + + def set_direction(self, direction): + """ set the direction the segment is headed + direction: enum directions + """ + if not direction in library.directions.reverse_mapping: + raise IndexError('direction invalid') + self.angle = self.angles(direction) class Trace: """ Models a single circuit trace made up of multiple segments """ segments = list() # ordered list of one or more TraceSegments colours = dict() - colours['metal'] = (192, 192, 192) # colours of the trace - colours['shadow'] = (64, 64, 64) - colours['reflection'] = (255, 255, 255) + # colours['metal'] = (192, 192, 192) # colours of the trace + # colours['shadow'] = (64, 64, 64) + # colours['reflection'] = (255, 255, 255) def check_range(self, colour): """ Ensure a colour value is legal """ - return colour >= 0 and colour <= 255 + if not (type(colour) == tuple or type(colour) == int): + raise TypeError('colour expeted to be tuple or int') + elif not len(colour) == 3: + raise ValueError('colour tuples require exactly 3 items (RGB)') + + if type(colour) == int: + return colour >= 0 and colour <= 255 + else: + return self.check_range(colour[0]) and self.check_range(colour[1]) and self.check_range(colour[2]) - def __init__(self, metal=None, shadow=None, reflection=None, segments=TraceSegment()): + def __init__(self, metal=(192, 192, 192), shadow=(64, 64, 64), reflection=(255, 255, 255), segments=None): # set the colours of the trace to give a 3D raised appearance if metal and check_range(metal): self.colours['metal'] = metal if shadow and check_range(shadow): self.colours['shadow'] = shadow if reflection and check_range(reflection): self.colours['reflection'] = reflection - # initialise the via dictionary + if segments == None: + self.segments = TraceSegment() + else: + self.segments = segments + with self.segments[0]: via = None - via_position = via_pos.START + via_position = library.positions.NONE def draw(self, surface): """ Draw the Trace onto pygame.Surface """ + return + class Via: """ Models a through-hole otherwise known as a Via """ @@ -96,30 +122,20 @@ class Bus: class IntegratedCircuit: """ semiconductor component """ - signals = None # key = signal name, value = pin number - - def __init__(self): - """ populate pseudo-enum with common signal names """ - if not IntegratedCircuit.signals: - signals = ('NC', 'GND', 'Vcc', 'Vdd') - # logic gate signal names (covers multi-gate components up to quad-gate) - for l in ('A', 'B', 'Y'): - for n in range(1, 5): - signals.append("%s%s" % (n, l)) - - IntegratedCircuit.signals = enum(signals) + pins = list() # index = pin number, value = signal name class DualInLine(IntegratedCircuit): """ Dual In-Line Integrated Circuit """ sides = 2 qty = 0 - pins = list() - def __init__(self, pins): - if pins > 0 and pins % 2 == 0: - for p in range(1, pins+1): - pins[p] = IntegratedCircuit.signals.NC + def __init__(self, pin_qty): + if not (pin_qty > 0 and pin_qty % 2 == 0): + raise ValueError('DIL ICs have even number of pins') + + for p in range(1, pin_qty+1): + self.pins.insert(p, library.signals.NC) class Gate: @@ -127,22 +143,21 @@ class Gate: types = None inputs = list() output = False - type = None + gate_type = None - def __init__(self, type, inputs=None): - # populate dictionary with common logic gate types - if not Gate.types: - Gate.types = enum('AND', 'OR', 'XOR', 'NOT', 'NAND', 'NOR') + def __init__(self, gate_type, inputs=None): + if not gate_type in library.gates: + raise IndexError() - if type in Gate.types: - self.type = type + self.gate_type = gate_type - if inputs and type(inputs) is list and isinstance(inputs[0], bool): - self.inputs = inputs - else: + if inputs == None: # default to 2 inputs at logic 0 self.inputs = (False, False) - + elif not (type(inputs) is list and isinstance(inputs[0], bool)): + raise TypeError() + + self.inputs = inputs self.input_max = len(self.inputs) self.update() @@ -151,23 +166,27 @@ class Gate: return def set_input(self, input, value): - if input >= 0 and input < self.input_max: - if isinstance(value, bool): - self.inputs[input] = value - self.update() + if not(input >= 0 and input < self.input_max): + raise ValueError("input expected between 0 and" % self.input_max) + + if not isinstance(value, bool): + raise TypeError('Boolean expected') + + self.inputs[input] = value + self.update() def get_output(self): return self.output def min_inputs(self, inputs, minimum): - """ enforce a minimum number of logic inputs """ + """ enforce a minimum number of logic inputs in the list """ return inputs if len(inputs) >= minimum else (inputs, (False for i in range(0, minimum - len(inputs)))) # Derive each of the common logic gate types class GateAND(Gate): def __init__(self, inputs=None): - Gate.__init__(Gate.types.AND, self.min_inputs(inputs, 2)) + Gate.__init__(library.gates.AND, self.min_inputs(inputs, 2)) def update(self): self.output = self.inputs[0] @@ -177,7 +196,7 @@ class GateAND(Gate): class GateOR(Gate): def __init__(self, inputs=None): - Gate.__init__(Gate.types.OR, self.min_inputs(inputs, 2)) + Gate.__init__(library.gates.OR, self.min_inputs(inputs, 2)) def update(self): self.output = self.inputs[0] @@ -187,7 +206,7 @@ class GateOR(Gate): class GateXOR(Gate): def __init__(self, inputs=None): - Gate.__init__(Gate.types.XOR, self.min_inputs(inputs, 2)) + Gate.__init__(library.gates.XOR, self.min_inputs(inputs, 2)) def update(self): self.output = self.inputs[0] @@ -197,18 +216,17 @@ class GateXOR(Gate): class GateNOT(Gate): def __init__(self, inputs=(False, )): - if len(inputs) > 1: - # inverters can only have 1 input - inputs = (inputs[0], ) - Gate.__init__(Gate.types.NOT, inputs) + if len(inputs) != 1: + raise ValueError('NOT must have only 1 input') + Gate.__init__(library.gates.NOT, inputs) def update(self): self.output = ~self.inputs[0] # invert class GateNAND(Gate): - def __init__(self, inputs=None): - Gate.__init__(Gate.types.NAND, self.min_inputs(inputs, 2)) + def __init__(self, inputs=None): + Gate.__init__(library.gates.NAND, self.min_inputs(inputs, 2)) def update(self): self.output = self.inputs[0] @@ -219,7 +237,7 @@ class GateNAND(Gate): class GateNOR(Gate): def __init__(self, inputs=None): - Gate.__init__(Gate.types.NOR, self.min_inputs(inputs, 2)) + Gate.__init__(library.gates.NOR, self.min_inputs(inputs, 2)) def update(self): self.output = self.inputs[0] @@ -231,17 +249,29 @@ class GateNOR(Gate): class ICLogic: """ Models an IC that contains one or more digital logic gates """ gates = list() # list of gates - package = None - def __init__(self, gates=(), package=DualInLine(14)): - if package: self.package = package - if type(gates) is list and type gates[0] is Gate: self.gates = gates + def __init__(self, gates=()): + if not type(gates) is list: + raise TypeError('expected list') + elif not type(gates[0]) is Gate: + raise TypeError('expected Gate') + + self.gates = gates def add(self, gate): - if gate and type(gate) is Gate: self.gates.append(gate) + if not type(gate) is Gate: + raise TypeError('expected Gate') + + self.gates.append(gate) +class ICLogicDIL(ICLogic, DualInLine): + """ Model of a Logic IC ina DIL package """ + def __init__(self, gates=(), pins_qty=14): + ICLogic.__init(gates) + DualInLine.__init__(pins_qty) -class SN7400(ICLogic, DualInLine): + +class SN7400(ICLogicDIL): """ Model of a Texas Instruments SN7400 quad 2-input NAND gate DIP14 package """ def __init__(self): @@ -249,12 +279,10 @@ class SN7400(ICLogic, DualInLine): gates = list() for i in range(0, 4): gates.append(GateNAND()) - # TODO: may need to move this functionality into an intermediate super class that combines package and Logic chip - ICLogic.__init__(gates) - DualInLine.__init(14) + ICLogicDIL.__init__(gates, 14) -class SN741G00(ICLogic, ...): +class SN741G00(ICLogicDIL): """ Model of a Texas Instruments SN741G00 single 2-input NAND gate """ @@ -264,7 +292,7 @@ class TSSOP(IntegratedCircuit): pins = list() def __init__(self, pins): - + return class PCB: @@ -273,10 +301,13 @@ class PCB: def __init__(self): """ """ + return def trace_add(self, trace_list): - """ Add a """ + """ Add a Trace """ + return def component_insert(self, component): - """ Adds a component to """ + """ Adds a component to the circuit """ + return -- 2.17.1