class main: def __init__(self,master): self.frame = Frame(master) self.frame.pack(fill="both", expand=True) self.canvas = Canvas(self.frame, width=300, height=300) self.canvas.pack(fill="both", expand=True) self.label=Label(self.frame, text="Master Yunrui's Gomoku Game", height=6, bg='black', fg='pink') self.label.pack(fill="both", expand=True) self.frameb=Frame(self.frame) self.frameb.pack(fill="both", expand=True) self.Start1=Button(self.frameb, text='Click here to start\n may the plump be with you...', height=4, command=self.start1,bg='white', fg='purple') self.Start1.pack(fill="both", expand=True, side=RIGHT) self._board() def start1(self): self.canvas.delete(ALL) self.canvas.bind("<ButtonPress-1>", self.multiplayer) self._board() self.board=Board() self.player1 = Player(1) self.player2 = Player(2) self.turn = 1 self.j=False self.Start1['text']=("Click To Restart") def end(self): self.canvas.unbind("<ButtonPress-1>") self.j=True def _board(self): for i in xrange(0,300,20): self.canvas.create_line(i,0,i,300) for j in xrange(0,300,20): self.canvas.create_line(0,j,300,j) def distance(self,x1,y1,x2,y2): return math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)) def multiplayer(self,event): x = event.x y = event.y for j in range(0,300,20): for i in range(0,300,20): if (self.distance(x,y,i,j)<5): if self.turn==1: print("click",i,j) self.canvas.create_oval( i+5, j+5, i-5, j-5, width=2, outline="black") self.player1.move(self.board,i/20,j/20) self.turn=2 if self.player1.win(self.board,i/20,j/20): self.label['text']=('Play 1 Wins') print("Player 1 wins") self.end() else: print("click",i,j) self.canvas.create_oval( i+5, j+5, i-5, j-5, width=2, outline="pink") self.player2.move(self.board,i/20,j/20) self.turn=1 if self.player2.win(self.board,i/20,j/20): self.label['text']=('Play 2 Wins') print("Player 2 wins") self.end() print(x,y,self.canvas.find_closest(x,y)[0]) def check(self): #horizontal check for i in range(0,3): if sum(self.TTT[i])==27: self.label['text']=('2nd player wins!') self.end() if sum(self.TTT[i])==3: self.label['text']=('1st player wins!') self.end() #vertical check #the matrix below transposes self.TTT so that it could use the sum fucntion again self.ttt=[[row[i] for row in self.TTT] for i in range(3)] for i in range(0,3): if sum(self.ttt[i])==27: self.label['text']=('2nd player wins!') self.end() if sum(self.ttt[i])==3: self.label['text']=('1st player wins!') self.end() #check for diagonal wins if self.TTT[1][1]==9: if self.TTT[0][0]==self.TTT[1][1] and self.TTT[2][2]==self.TTT[1][1] : self.label['text']=('2nd player wins!') self.end() if self.TTT[0][2]==self.TTT[1][1] and self.TTT[2][0]==self.TTT[1][1] : self.label['text']=('2nd player wins!') self.end() if self.TTT[1][1]==1: if self.TTT[0][0]==self.TTT[1][1] and self.TTT[2][2]==self.TTT[1][1] : self.label['text']=('1st player wins!') self.end() if self.TTT[0][2]==self.TTT[1][1] and self.TTT[2][0]==self.TTT[1][1] : self.label['text']=('1st player wins!') self.end() #check for draws if self.j==False: a=0 for i in range(0,3): a+= sum(self.TTT[i]) if a==41: self.label['text']=("It's a pass!") self.end() def AIcheck(self): #This is built on the self.check function self.ttt=[[row[i] for row in self.TTT] for i in range(3)] #DEFENSE #this is the horizontal checklist for h in range(0,3): k=0 j=0 if sum(self.TTT[h])==2: while k <3: if k==h: while j <3: if self.trigger==False: if self.TTT[k][j]==0: self.cross(j,k) break j+=1 k+=1 #this is the vertical checklist for h in range(0,3): k=0 j=0 if sum(self.ttt[h])==2: while k <3: if k==h: while j <3: if self.trigger==False: if self.ttt[k][j]==0: self.cross(k,j) break j+=1 k+=1 #this is the diagonal checklist if self.TTT[1][1]==1: if self.TTT[0][0]==1: if self.trigger==False: if self.TTT[2][2]==0: self.cross(2,2) if self.TTT[0][2]==1: if self.trigger==False: if self.TTT[2][0]==0: self.cross(0,2) if self.TTT[2][0]==1: if self.trigger==False: if self.TTT[0][2]==0: self.cross(2,0) if self.TTT[2][2]==1: if self.trigger==False: if self.TTT[0][0]==0: self.cross(0,0) if self.TTT[1][1]==0: if self.trigger==False: self.cross(1,1) self.trigger=True else: if self.trigger==False: self.randmove() def cross(self, k, j): # k is the x coords # j is the y coords X=(200*k+100)/2 Y=(200*j+100)/2 X1=int(k) Y1=int(j) self.canvas. create_line( X+20, Y+20, X-20, Y-20, width=4, fill="black") self.canvas. create_line( X-20, Y+20, X+20, Y-20, width=4, fill="black") self.TTT[Y1][X1]+=9 self.check() self.i+=1 self.trigger=True def randmove(self): while True: k=(randint(0,2)) j=(randint(0,2)) if self.TTT[j][k]==0: X=(200*k+100)/2 Y=(200*j+100)/2 self.canvas. create_line( X+20, Y+20, X-20, Y-20, width=4, fill="black") self.canvas. create_line( X-20, Y+20, X+20, Y-20, width=4, fill="black") self.TTT[j][k]+=9 self.check() self.i+=1 self.trigger=True break else: k=(randint(0,2))*100 j=(randint(0,2))*100
class PinceauCanvas: def __init__(self, master, width, height): self.__master = master self.__width = width self.__height = height self.__tmp = {} self.__tmp_rendered = None self.__shapes = [] # Default constants self.__draw_mode = 'add' self.__shape_mode = 'normal' self.__shape_type = 'rectangle' self.__tmp_color = '#E57373' self.__final_color = '#F44336' # Main canvas self.__canvas = Canvas(self.__master, width=self.__width, height=self.__height) self.bind_events() def set_send(self, send_func): # Method sending the drawn shape to the server. self.__send = send_func def switch_shape_mode(self): # Method enabling to switch from a straight shape to a nomrmal shape # and vice-versa. self.__shape_mode = ('straight' if self.__shape_mode == 'normal' else 'normal') def switch_draw_mode(self): # Method enabling to switch from the drawing mode to the erasing mode # and vice-versa. self.__draw_mode = ('erase' if self.__draw_mode == 'add' else 'add') def change_color(self, tmp_color, color): # Method enabling to switch from one color to another. self.__tmp_color = tmp_color self.__final_color = color def change_shape(self, shape_type): # Method enabling to switch from one shape to another. self.__shape_type = shape_type def pack(self): self.__canvas.pack() def bind_events(self): self.__canvas.bind('<KeyPress>', self.key_press) self.__canvas.bind('<KeyRelease>', self.key_press) self.__canvas.bind('<Button-1>', self.mouse_click) self.__canvas.bind('<B1-Motion>', self.mouse_drag) self.__canvas.bind('<ButtonRelease-1>', self.mouse_release) def key_press(self, event): # Method to detect if the 'CTRL' key or 'MAJ' key is pressed using the # keycodes from iOS and Windows. # Indeed, if 'CTRL' is pressed, we turn to erasing mode. # And if 'MAJ" is pressed, we turn to straight shapes mode. keycode = event.keycode if keycode == 262145 or keycode == 262401 or keycode == 17: # Ctrl key is pressed self.switch_draw_mode() if keycode == 131330 or keycode == 131074 or keycode == 131076 or keycode == 16: # Maj key is pressed self.switch_shape_mode() def mouse_click(self, event): self.__canvas.focus_set() x, y = event.x, event.y if self.__draw_mode == 'erase': action = {'action': 'erase', 'x': x, 'y': y} self.erase(action) self.__send(action) elif self.__draw_mode == 'add': self.__tmp = { 'action': 'add', 'x1': x, 'y1': y, 'x2': x, 'y2': y, 'mode': self.__shape_mode, 'type': self.__shape_type, 'fill': self.__tmp_color } def erase(self, action): # Method erasing a shape on the canvas. x, y = action['x'], action['y'] shape = self.__canvas.find_closest(x, y) if shape: self.__canvas.delete(shape) def mouse_drag(self, event): if self.__draw_mode == 'add': self.__canvas.focus_set() self.__tmp['x2'] = event.x self.__tmp['y2'] = event.y self.__tmp['mode'] = self.__shape_mode self.__tmp_rendered = self.draw(self.__tmp) def mouse_release(self, event): if self.__draw_mode == 'add': self.__tmp['fill'] = self.__final_color if self.__tmp['type'] is not None: final_shape = self.draw(self.__tmp) self.__shapes.append(final_shape) self.__send(self.__tmp) # Reset temp shape self.__tmp = {} self.__tmp_rendered = None def draw(self, shape): # Method drawing a shape on the canvas. self.__canvas.delete(self.__tmp_rendered) rendered_shape = None if shape['type'] == 'rectangle' and shape['mode'] == 'normal': rendered_shape = Rectangle(shape) elif shape['type'] == 'rectangle' and shape['mode'] == 'straight': rendered_shape = Square(shape) elif shape['type'] == 'oval' and shape['mode'] == 'normal': rendered_shape = Oval(shape) elif shape['type'] == 'oval' and shape['mode'] == 'straight': rendered_shape = Circle(shape) elif shape['type'] == 'line' and shape['mode'] == 'normal': rendered_shape = Line(shape) elif shape['type'] == 'line' and shape['mode'] == 'straight': rendered_shape = Diagonal(shape) shape_id = rendered_shape.draw_on(self.__canvas) return shape_id
class Proto_Board_Visualizer(Frame): """ Tk Frame to visualize Proto boards. """ def __init__(self, parent, proto_board, show_pwr_gnd_pins): """ |proto_board|: the proto board to visualize. |show_pwr_gnd_pins|: flag whether to show pwr and gnd pins as a reminder to connect to a power supply. """ Frame.__init__(self, parent, background=BACKGROUND_COLOR) self._parent = parent self._parent.title(WINDOW_TITLE) self._parent.resizable(0, 0) self._proto_board = proto_board self._show_pwr_gnd_pins = show_pwr_gnd_pins self._canvas = Canvas(self, width=WIDTH, height=HEIGHT, background=BACKGROUND_COLOR) self._tooltip_helper = Tooltip_Helper(self._canvas) self._wire_parts = {} # state for outline highlighing self._piece_outline_id = None self._wire_outline_ids = [] self._piece_highlight = lambda labels: None self._wire_highlight = lambda label: None self._setup_bindings() self._setup_menu() self._draw_proto_board() def _point_inside_piece(self, piece, x, y): """ Returns True if the point (|x|, |y|) is on the gien |piece|. """ r1, c1, r2, c2 = piece.bbox() x1, y1 = self._rc_to_xy((r1, c1)) x2, y2 = self._rc_to_xy((r2, c2)) return x1 <= x <= x2 + CONNECTOR_SIZE and y1 <= y <= y2 + CONNECTOR_SIZE def _maybe_show_tooltip(self, event): """ Shows a tooltip of the respective node if the cursor is on a wire or a valid location on the proto board, or the respective piece label if the cursor is on a piece. """ # check if cursor is on a wire item = self._canvas.find_closest(event.x, event.y)[0] if item in self._wire_parts: self._wire_highlight(self._wire_parts[item]) return # check if cursor is on a piece for piece in self._proto_board.get_pieces(): if self._point_inside_piece(piece, event.x, event.y): self._tooltip_helper.show_tooltip(event.x, event.y, 'ID: %s' % piece.label, background=TOOLTIP_DRAWABLE_LABEL_BACKGROUND) self._piece_highlight(piece.labels_at((event.x, event.y), self._rc_to_xy(piece.top_left_loc))) return self._piece_highlight([]) # check if cursor is on a valid proto board location loc = self._xy_to_rc(event.x, event.y) if loc: node = self._proto_board.node_for(loc) if node: self._wire_highlight(node) return self._wire_highlight(None) # if none of the above, remove previous tooltip, if any self._tooltip_helper.hide_tooltip() def _setup_bindings(self): """ Sets up event bindings. """ self._canvas.bind('<Motion>', self._maybe_show_tooltip) def _rc_to_xy(self, loc): """ Returns the top left corner of the connector located at row |r| column |c|. """ r, c = loc x = c * (CONNECTOR_SIZE + CONNECTOR_SPACING) + PADDING y = r * (CONNECTOR_SIZE + CONNECTOR_SPACING) + PADDING + ( num_vertical_separators(r) * (VERTICAL_SEPARATION - CONNECTOR_SPACING)) return x, y def _xy_to_rc(self, x, y): """ Returns the row and column of the valid location on the proto board containing the point (|x|, |y|), or None if no such location exists. """ for r in xrange(PROTO_BOARD_HEIGHT): for c in xrange(PROTO_BOARD_WIDTH): if valid_loc((r, c)): x1, y1 = self._rc_to_xy((r, c)) x2, y2 = x1 + CONNECTOR_SIZE, y1 + CONNECTOR_SIZE if x1 <= x <= x2 and y1 <= y <= y2: return r, c return None def _draw_connector(self, x, y, fill=CONNECTOR_COLOR, outline=CONNECTOR_COLOR): """ Draws a connector at the given coordinate and with the given colors. """ self._canvas.create_rectangle(x, y, x + CONNECTOR_SIZE, y + CONNECTOR_SIZE, fill=fill, outline=outline) def _draw_connectors(self): """ Draws the connectors on the proto board. """ for r in ROWS: for c in COLUMNS: if valid_loc((r, c)): self._draw_connector(*self._rc_to_xy((r, c))) def _draw_bus_line(self, y, color): """ Draws a bus line at the given horizontal position |y| and with the given |color|. """ x_1 = self._rc_to_xy((0, 1))[0] x_2 = self._rc_to_xy((0, PROTO_BOARD_WIDTH - 1))[0] self._canvas.create_line(x_1, y, x_2, y, fill=color) def _draw_bus_lines(self): """ Draws all four bus lines on the proto board. """ offset = 10 self._draw_bus_line(self._rc_to_xy((0, 0))[1] - offset, NEGATIVE_COLOR) self._draw_bus_line(self._rc_to_xy((1, 0))[1] + CONNECTOR_SIZE + offset, POSITIVE_COLOR) self._draw_bus_line(self._rc_to_xy((PROTO_BOARD_HEIGHT - 2, 0))[1] - ( offset), NEGATIVE_COLOR) self._draw_bus_line(self._rc_to_xy((PROTO_BOARD_HEIGHT - 1, 0))[1] + ( CONNECTOR_SIZE + offset), POSITIVE_COLOR) def _draw_labels(self): """ Draws the row and column labels. """ # row labels row_labels = dict(zip(range(11, 1, -1), ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'])) for r in filter(lambda r: r in row_labels, ROWS): self._canvas.create_text(self._rc_to_xy((r, -1)), text=row_labels[r]) x, y = self._rc_to_xy((r, PROTO_BOARD_WIDTH)) self._canvas.create_text(x + CONNECTOR_SIZE, y, text=row_labels[r]) # columns labels h_offset = 2 v_offset = 10 for c in filter(lambda c: c == 0 or (c + 1) % 5 == 0, COLUMNS): x_1, y_1 = self._rc_to_xy((2, c)) self._canvas.create_text(x_1 + h_offset, y_1 - v_offset, text=(c + 1)) x_2, y_2 = self._rc_to_xy((PROTO_BOARD_HEIGHT - 3, c)) self._canvas.create_text(x_2 + h_offset, y_2 + CONNECTOR_SIZE + v_offset, text=(c + 1)) def _draw_gnd_pwr_pins(self): """ Draws pins to show the ground and power rails. """ # pin positions g_x, g_y = self._rc_to_xy((GROUND_RAIL, PROTO_BOARD_WIDTH - 3)) p_x, p_y = self._rc_to_xy((POWER_RAIL, PROTO_BOARD_WIDTH - 3)) # big rectangles large_h_offset = 3 * CONNECTOR_SIZE + 2 * CONNECTOR_SPACING small_h_offset = 2 large_v_offset = 7 small_v_offset = 3 self._canvas.create_rectangle(g_x - small_h_offset, g_y - large_v_offset, g_x + large_h_offset, g_y + CONNECTOR_SIZE + small_v_offset, fill=NEGATIVE_COLOR) self._canvas.create_rectangle(p_x - small_h_offset, p_y - small_v_offset, p_x + large_h_offset, p_y + CONNECTOR_SIZE + large_v_offset, fill=POSITIVE_COLOR) # connectors self._draw_connector(g_x, g_y, outline='black') self._draw_connector(p_x, p_y, outline='black') # text text_v_offset = 2 text_h_offset = 17 self._canvas.create_text(g_x + text_h_offset, g_y - text_v_offset, text='gnd', fill='white') self._canvas.create_text(p_x + text_h_offset, p_y + text_v_offset, text='+10', fill='white') def _draw_piece(self, piece): """ Draws the given circuit |piece| on the canvas. """ piece.draw_on(self._canvas, self._rc_to_xy(piece.top_left_loc)) def _draw_pieces(self): """ Draws all the pieces on the proto board. """ for piece in self._proto_board.get_pieces(): self._draw_piece(piece) def _draw_wire(self, wire): """ Draws the given |wire| on the canvas. """ x_1, y_1 = self._rc_to_xy(wire.loc_1) x_2, y_2 = self._rc_to_xy(wire.loc_2) length = int(ceil(wire.length())) fill = 'white' if 1 < length < 10: fill = WIRE_COLORS[length] elif 10 <= length < 50: fill = WIRE_COLORS[(length + 9) / 10] def draw_wire(parts=None): if parts: for part in parts: self._canvas.delete(part) del self._wire_parts[part] new_parts = [self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill=WIRE_OUTLINE, width=7, capstyle='round'), self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill=fill, width=3, capstyle='round')] for part in new_parts: self._canvas.tag_bind(part, '<Button-1>', lambda event: draw_wire( new_parts)) self._wire_parts[part] = wire.node draw_wire() def _draw_wires(self): """ Draws all the wires on the proto board. """ for wire in sorted(self._proto_board.get_wires(), key=lambda wire: -wire.length()): self._draw_wire(wire) def _redraw_wires(self): """ Erases then redraws the wires on the protoboard. """ for part in self._wire_parts: self._canvas.delete(part) self._wire_parts.clear() self._draw_wires() def _draw_proto_board(self): """ Draws the given |proto_board|. """ self._draw_connectors() self._draw_bus_lines() self._draw_labels() self._draw_pieces() self._draw_wires() if self._show_pwr_gnd_pins: self._draw_gnd_pwr_pins() self._canvas.pack() self.pack() def _wire_to_cmax_str(self, wire): """ Returns a CMax representation (when saved in a file) of the given |wire|. """ c1, r1 = loc_to_cmax_rep(wire.loc_1) c2, r2 = loc_to_cmax_rep(wire.loc_2) return 'wire: (%d,%d)--(%d,%d)' % (c1, r1, c2, r2) def _to_cmax_str(self): """ Returns a string CMax representation of the proto board we are visualizing. """ # header lines = ['#CMax circuit'] # wires for wire in self._proto_board.get_wires(): lines.append(self._wire_to_cmax_str(wire)) # circuit pieces for piece in self._proto_board.get_pieces(): cmax_str = piece.to_cmax_str() if cmax_str: lines.append(cmax_str) # power and ground pins if self._show_pwr_gnd_pins: lines.append('+10: (61,20)') lines.append('gnd: (61,19)') return '\n'.join(lines) def _save_as_cmax_file(self): """ Presents a dialog box that will save the proto board we are visualizing as a CMax file. """ file_name = asksaveasfilename(title='Save as CMax file ...', filetypes=[('CMax files', CMAX_FILE_EXTENSION)]) if file_name and not file_name.endswith(CMAX_FILE_EXTENSION): file_name += CMAX_FILE_EXTENSION if file_name: save_file = open(file_name, 'w') save_file.write(self._to_cmax_str()) save_file.close() def _setup_menu(self): """ Sets up a menu that lets the user save the proto board we are visualizing as a CMax file. """ menu = Menu(self._parent, tearoff=0) save_menu = Menu(menu, tearoff=0) save_menu.add_command(label='Save as CMax file', command=self._save_as_cmax_file) menu.add_cascade(label='File', menu=save_menu) edit_menu = Menu(menu, tearoff=0) edit_menu.add_command(label='Redraw wires', command=self._redraw_wires) menu.add_cascade(label='Edit', menu=edit_menu) self._parent.config(menu=menu) def outline_piece_from_label(self, label): """ Draws the appropriate outline for the circuit piece with the given |label|. """ # try block in case canvas is gone with someone still calling this method try: self._canvas.delete(self._piece_outline_id) for piece in self._proto_board.get_pieces(): if label in piece.label.split(','): self._piece_outline_id = piece.outline_label(self._canvas, self._rc_to_xy(piece.top_left_loc), label) return except: pass def outline_wires_from_label(self, label): """ Draws outlines on the wires that have the given |label|. """ # try block in case canvas is gone with someone still calling this method try: for part in self._wire_outline_ids: self._canvas.delete(part) del self._wire_parts[part] self._wire_outline_ids = [] for wire in self._proto_board.get_wires(): if wire.node == label: x_1, y_1 = self._rc_to_xy(wire.loc_1) x_2, y_2 = self._rc_to_xy(wire.loc_2) self._wire_outline_ids.extend([self._canvas.create_line( x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill='black', width=7, capstyle='round'), self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill='cyan', width=3, capstyle='round')]) for part in self._wire_outline_ids: self._wire_parts[part] = label except: pass def set_piece_highlight(self, f): """ Resets the piece highlighting function to |f|. """ self._piece_highlight = f def set_wire_highlight(self, f): """ Resets the wire highlighing function to |f|. """ def g(label): f(label) self.outline_wires_from_label(label) self._wire_highlight = g
class Proto_Board_Visualizer(Frame): """ Tk Frame to visualize Proto boards. """ def __init__(self, parent, proto_board, show_pwr_gnd_pins): """ |proto_board|: the proto board to visualize. |show_pwr_gnd_pins|: flag whether to show pwr and gnd pins as a reminder to connect to a power supply. """ Frame.__init__(self, parent, background=BACKGROUND_COLOR) self._parent = parent self._parent.title(WINDOW_TITLE) self._parent.resizable(0, 0) self._proto_board = proto_board self._show_pwr_gnd_pins = show_pwr_gnd_pins self._canvas = Canvas(self, width=WIDTH, height=HEIGHT, background=BACKGROUND_COLOR) self._tooltip_helper = Tooltip_Helper(self._canvas) self._wire_parts = {} # state for outline highlighing self._piece_outline_id = None self._wire_outline_ids = [] self._piece_highlight = lambda labels: None self._wire_highlight = lambda label: None self._setup_bindings() self._setup_menu() self._draw_proto_board() def _point_inside_piece(self, piece, x, y): """ Returns True if the point (|x|, |y|) is on the gien |piece|. """ r1, c1, r2, c2 = piece.bbox() x1, y1 = self._rc_to_xy((r1, c1)) x2, y2 = self._rc_to_xy((r2, c2)) return x1 <= x <= x2 + CONNECTOR_SIZE and y1 <= y <= y2 + CONNECTOR_SIZE def _maybe_show_tooltip(self, event): """ Shows a tooltip of the respective node if the cursor is on a wire or a valid location on the proto board, or the respective piece label if the cursor is on a piece. """ # check if cursor is on a wire item = self._canvas.find_closest(event.x, event.y)[0] if item in self._wire_parts: self._wire_highlight(self._wire_parts[item]) return # check if cursor is on a piece for piece in self._proto_board.get_pieces(): if self._point_inside_piece(piece, event.x, event.y): self._tooltip_helper.show_tooltip( event.x, event.y, 'ID: %s' % piece.label, background=TOOLTIP_DRAWABLE_LABEL_BACKGROUND) self._piece_highlight( piece.labels_at((event.x, event.y), self._rc_to_xy(piece.top_left_loc))) return self._piece_highlight([]) # check if cursor is on a valid proto board location loc = self._xy_to_rc(event.x, event.y) if loc: node = self._proto_board.node_for(loc) if node: self._wire_highlight(node) return self._wire_highlight(None) # if none of the above, remove previous tooltip, if any self._tooltip_helper.hide_tooltip() def _setup_bindings(self): """ Sets up event bindings. """ self._canvas.bind('<Motion>', self._maybe_show_tooltip) def _rc_to_xy(self, loc): """ Returns the top left corner of the connector located at row |r| column |c|. """ r, c = loc x = c * (CONNECTOR_SIZE + CONNECTOR_SPACING) + PADDING y = r * (CONNECTOR_SIZE + CONNECTOR_SPACING) + PADDING + ( num_vertical_separators(r) * (VERTICAL_SEPARATION - CONNECTOR_SPACING)) return x, y def _xy_to_rc(self, x, y): """ Returns the row and column of the valid location on the proto board containing the point (|x|, |y|), or None if no such location exists. """ for r in xrange(PROTO_BOARD_HEIGHT): for c in xrange(PROTO_BOARD_WIDTH): if valid_loc((r, c)): x1, y1 = self._rc_to_xy((r, c)) x2, y2 = x1 + CONNECTOR_SIZE, y1 + CONNECTOR_SIZE if x1 <= x <= x2 and y1 <= y <= y2: return r, c return None def _draw_connector(self, x, y, fill=CONNECTOR_COLOR, outline=CONNECTOR_COLOR): """ Draws a connector at the given coordinate and with the given colors. """ self._canvas.create_rectangle(x, y, x + CONNECTOR_SIZE, y + CONNECTOR_SIZE, fill=fill, outline=outline) def _draw_connectors(self): """ Draws the connectors on the proto board. """ for r in ROWS: for c in COLUMNS: if valid_loc((r, c)): self._draw_connector(*self._rc_to_xy((r, c))) def _draw_bus_line(self, y, color): """ Draws a bus line at the given horizontal position |y| and with the given |color|. """ x_1 = self._rc_to_xy((0, 1))[0] x_2 = self._rc_to_xy((0, PROTO_BOARD_WIDTH - 1))[0] self._canvas.create_line(x_1, y, x_2, y, fill=color) def _draw_bus_lines(self): """ Draws all four bus lines on the proto board. """ offset = 10 self._draw_bus_line(self._rc_to_xy((0, 0))[1] - offset, NEGATIVE_COLOR) self._draw_bus_line( self._rc_to_xy((1, 0))[1] + CONNECTOR_SIZE + offset, POSITIVE_COLOR) self._draw_bus_line( self._rc_to_xy((PROTO_BOARD_HEIGHT - 2, 0))[1] - (offset), NEGATIVE_COLOR) self._draw_bus_line( self._rc_to_xy( (PROTO_BOARD_HEIGHT - 1, 0))[1] + (CONNECTOR_SIZE + offset), POSITIVE_COLOR) def _draw_labels(self): """ Draws the row and column labels. """ # row labels row_labels = dict( zip(range(11, 1, -1), ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'])) for r in filter(lambda r: r in row_labels, ROWS): self._canvas.create_text(self._rc_to_xy((r, -1)), text=row_labels[r]) x, y = self._rc_to_xy((r, PROTO_BOARD_WIDTH)) self._canvas.create_text(x + CONNECTOR_SIZE, y, text=row_labels[r]) # columns labels h_offset = 2 v_offset = 10 for c in filter(lambda c: c == 0 or (c + 1) % 5 == 0, COLUMNS): x_1, y_1 = self._rc_to_xy((2, c)) self._canvas.create_text(x_1 + h_offset, y_1 - v_offset, text=(c + 1)) x_2, y_2 = self._rc_to_xy((PROTO_BOARD_HEIGHT - 3, c)) self._canvas.create_text(x_2 + h_offset, y_2 + CONNECTOR_SIZE + v_offset, text=(c + 1)) def _draw_gnd_pwr_pins(self): """ Draws pins to show the ground and power rails. """ # pin positions g_x, g_y = self._rc_to_xy((GROUND_RAIL, PROTO_BOARD_WIDTH - 3)) p_x, p_y = self._rc_to_xy((POWER_RAIL, PROTO_BOARD_WIDTH - 3)) # big rectangles large_h_offset = 3 * CONNECTOR_SIZE + 2 * CONNECTOR_SPACING small_h_offset = 2 large_v_offset = 7 small_v_offset = 3 self._canvas.create_rectangle(g_x - small_h_offset, g_y - large_v_offset, g_x + large_h_offset, g_y + CONNECTOR_SIZE + small_v_offset, fill=NEGATIVE_COLOR) self._canvas.create_rectangle(p_x - small_h_offset, p_y - small_v_offset, p_x + large_h_offset, p_y + CONNECTOR_SIZE + large_v_offset, fill=POSITIVE_COLOR) # connectors self._draw_connector(g_x, g_y, outline='black') self._draw_connector(p_x, p_y, outline='black') # text text_v_offset = 2 text_h_offset = 17 self._canvas.create_text(g_x + text_h_offset, g_y - text_v_offset, text='gnd', fill='white') self._canvas.create_text(p_x + text_h_offset, p_y + text_v_offset, text='+10', fill='white') def _draw_piece(self, piece): """ Draws the given circuit |piece| on the canvas. """ piece.draw_on(self._canvas, self._rc_to_xy(piece.top_left_loc)) def _draw_pieces(self): """ Draws all the pieces on the proto board. """ for piece in self._proto_board.get_pieces(): self._draw_piece(piece) def _draw_wire(self, wire): """ Draws the given |wire| on the canvas. """ x_1, y_1 = self._rc_to_xy(wire.loc_1) x_2, y_2 = self._rc_to_xy(wire.loc_2) length = int(ceil(wire.length())) fill = 'white' if 1 < length < 10: fill = WIRE_COLORS[length] elif 10 <= length < 50: fill = WIRE_COLORS[(length + 9) / 10] def draw_wire(parts=None): if parts: for part in parts: self._canvas.delete(part) del self._wire_parts[part] new_parts = [ self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill=WIRE_OUTLINE, width=7, capstyle='round'), self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill=fill, width=3, capstyle='round') ] for part in new_parts: self._canvas.tag_bind(part, '<Button-1>', lambda event: draw_wire(new_parts)) self._wire_parts[part] = wire.node draw_wire() def _draw_wires(self): """ Draws all the wires on the proto board. """ for wire in sorted(self._proto_board.get_wires(), key=lambda wire: -wire.length()): self._draw_wire(wire) def _redraw_wires(self): """ Erases then redraws the wires on the protoboard. """ for part in self._wire_parts: self._canvas.delete(part) self._wire_parts.clear() self._draw_wires() def _draw_proto_board(self): """ Draws the given |proto_board|. """ self._draw_connectors() self._draw_bus_lines() self._draw_labels() self._draw_pieces() self._draw_wires() if self._show_pwr_gnd_pins: self._draw_gnd_pwr_pins() self._canvas.pack() self.pack() def _wire_to_cmax_str(self, wire): """ Returns a CMax representation (when saved in a file) of the given |wire|. """ c1, r1 = loc_to_cmax_rep(wire.loc_1) c2, r2 = loc_to_cmax_rep(wire.loc_2) return 'wire: (%d,%d)--(%d,%d)' % (c1, r1, c2, r2) def _to_cmax_str(self): """ Returns a string CMax representation of the proto board we are visualizing. """ # header lines = ['#CMax circuit'] # wires for wire in self._proto_board.get_wires(): lines.append(self._wire_to_cmax_str(wire)) # circuit pieces for piece in self._proto_board.get_pieces(): cmax_str = piece.to_cmax_str() if cmax_str: lines.append(cmax_str) # power and ground pins if self._show_pwr_gnd_pins: lines.append('+10: (61,20)') lines.append('gnd: (61,19)') return '\n'.join(lines) def _save_as_cmax_file(self): """ Presents a dialog box that will save the proto board we are visualizing as a CMax file. """ file_name = asksaveasfilename(title='Save as CMax file ...', filetypes=[('CMax files', CMAX_FILE_EXTENSION)]) if file_name and not file_name.endswith(CMAX_FILE_EXTENSION): file_name += CMAX_FILE_EXTENSION if file_name: save_file = open(file_name, 'w') save_file.write(self._to_cmax_str()) save_file.close() def _setup_menu(self): """ Sets up a menu that lets the user save the proto board we are visualizing as a CMax file. """ menu = Menu(self._parent, tearoff=0) save_menu = Menu(menu, tearoff=0) save_menu.add_command(label='Save as CMax file', command=self._save_as_cmax_file) menu.add_cascade(label='File', menu=save_menu) edit_menu = Menu(menu, tearoff=0) edit_menu.add_command(label='Redraw wires', command=self._redraw_wires) menu.add_cascade(label='Edit', menu=edit_menu) self._parent.config(menu=menu) def outline_piece_from_label(self, label): """ Draws the appropriate outline for the circuit piece with the given |label|. """ # try block in case canvas is gone with someone still calling this method try: self._canvas.delete(self._piece_outline_id) for piece in self._proto_board.get_pieces(): if label in piece.label.split(','): self._piece_outline_id = piece.outline_label( self._canvas, self._rc_to_xy(piece.top_left_loc), label) return except: pass def outline_wires_from_label(self, label): """ Draws outlines on the wires that have the given |label|. """ # try block in case canvas is gone with someone still calling this method try: for part in self._wire_outline_ids: self._canvas.delete(part) del self._wire_parts[part] self._wire_outline_ids = [] for wire in self._proto_board.get_wires(): if wire.node == label: x_1, y_1 = self._rc_to_xy(wire.loc_1) x_2, y_2 = self._rc_to_xy(wire.loc_2) self._wire_outline_ids.extend([ self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill='black', width=7, capstyle='round'), self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill='cyan', width=3, capstyle='round') ]) for part in self._wire_outline_ids: self._wire_parts[part] = label except: pass def set_piece_highlight(self, f): """ Resets the piece highlighting function to |f|. """ self._piece_highlight = f def set_wire_highlight(self, f): """ Resets the wire highlighing function to |f|. """ def g(label): f(label) self.outline_wires_from_label(label) self._wire_highlight = g
class StlFrame(Frame): def __init__(self, root, scale=1): Frame.__init__(self, root) self.app = root self.gridOffset = 10 self.arrangeMargin = 2 self.centerOnLoad = True self.offsetx = 0 self.offsety = 0 self.scale = scale self.zoom = 1 self.selection = None self.selectable = [] self.itemMap = {} self.canvas = Canvas(self, width=200*self.scale+13, height=200*self.scale+13, bd=2, relief=RIDGE, bg="white") self.canvas.config(takefocus="1") self.canvas.bind("<Key>", self.keyCanvas) self.canvas.bind("<Button-1>", self.clickCanvas) self.canvas.bind("<B1-Motion>", self.dragCanvas) self.canvas.bind("<MouseWheel>", self.wheelCanvas) self.canvas.bind("<Button-4>", self.wheelCanvas) self.canvas.bind("<Button-5>", self.wheelCanvas) self.root = root self.buildarea = [200, 200] self.canvas.grid(row=1,column=1,columnspan=5) self.drawGrid() def keyCanvas(self, event): self.app.setModified() if event.keysym == "Left": self.moveStl(-1, 0) elif event.keysym == "Right": self.moveStl(1, 0) elif event.keysym == "Up": if (event.state & SHIFT_MASK) != 0: self.rotateStl(1) else: self.moveStl(0, 1) elif event.keysym == "Down": if (event.state & SHIFT_MASK) != 0: self.rotateStl(-1) else: self.moveStl(0, -1) def clickCanvas(self, event): self.app.setModified() self.canvas.focus_set() self.startx = event.x/float(self.scale) self.starty = event.y/float(self.scale) itemIdList = self.canvas.find_closest(event.x, event.y) for itemId in itemIdList: if itemId in self.selectable: self.setSelection(itemId) self.app.setSelection(self.itemMap[itemId]) return def dragCanvas(self, event): self.app.setModified() self.canvas.focus_set() x = event.x/float(self.scale) y = event.y/float(self.scale) self.moveStl((x - self.startx), (self.starty - y)) self.startx = x self.starty = y def wheelCanvas(self, event): self.app.setModified() if event.num == 4 or event.delta == 120: self.rotateStl(1) elif event.num == 5 or event.delta == -120: self.rotateStl(-1) def moveStl(self, dx, dy): if self.selection is None: return self.canvas.move(self.selection, dx*self.scale, -dy*self.scale) stlObj = self.itemMap[self.selection] stlObj.deltaTranslation(dx, dy) def rotateStl(self, angle): if self.selection is None: return #self.canvas.move(self.selection, dx, -dy) stlObj = self.itemMap[self.selection] rads = math.radians(angle+stlObj.rotation) cos = math.cos(rads) sin = math.sin(rads) d = [] xMin = yMin = 99999 xMax = yMax = -99999 for x,y in stlObj.hull: xp = (x-stlObj.hxCenter)*cos - (y-stlObj.hyCenter)*sin + stlObj.hxCenter if xp < xMin: xMin=xp if xp > xMax: xMax=xp xp += stlObj.translatex yp = (x-stlObj.hxCenter)*sin + (y-stlObj.hyCenter)*cos + stlObj.hyCenter if yp < yMin: yMin=yp if yp > yMax: yMax=yp yp += stlObj.translatey d.extend(self.transform(xp,self.buildarea[1]-yp)) self.canvas.delete(stlObj.name) self.selectable.remove(self.selection) del self.itemMap[self.selection] itemId = self.canvas.create_polygon(d, fill=hilite, outline="black", tag=stlObj.name, width=3) self.selectable.append(itemId) self.itemMap[itemId] = stlObj self.setSelection(itemId) stlObj.deltaRotation(angle) stlObj.hxSize = xMax-xMin stlObj.hySize = yMax-yMin stlObj.hArea = stlObj.hxSize * stlObj.hySize def setSelection(self, itemId): if itemId not in self.selectable: return if self.selection is not None: self.canvas.itemconfig(self.selection, fill=gray) self.canvas.itemconfig(itemId, fill=hilite) self.selection = itemId def setSelectionByName(self, name): for i in self.itemMap.keys(): if self.itemMap[i].name == name: self.setSelection(i) return def getSelection(self): return self.selection def getSelectedStl(self): if self.selection is None: return None return self.itemMap[self.selection] def drawGrid(self): self.canvas.delete("GRID") ltGray = "#C0C0C0" dkGray = "#808080" yleft = 0 yright = self.buildarea[1]*self.zoom*self.scale if yright > self.buildarea[1]*self.scale: yright = self.buildarea[1]*self.scale for x in range(0, self.buildarea[0]+1, 10): if x%50 == 0: c = dkGray else: c = ltGray x = x*self.zoom*self.scale if x >= 0 and x <= self.buildarea[0]*self.scale: self.canvas.create_line(x+self.gridOffset, yleft+self.gridOffset, x+self.gridOffset, yright+self.gridOffset, fill=c, tags="GRID") xtop = 0 xbottom = self.buildarea[0]*self.zoom*self.scale if xbottom > self.buildarea[0]*self.scale: xbottom = self.buildarea[0]*self.scale for y in range(0, self.buildarea[1]+1, 10): if y%50 == 0: c = dkGray else: c = ltGray y = y*self.zoom*self.scale if y >= 0 and y <= self.buildarea[1]*self.scale: self.canvas.create_line(xtop+self.gridOffset, y+self.gridOffset, xbottom+self.gridOffset, y+self.gridOffset, fill=c, tags="GRID") def addStl(self, stlObject, highlight=False, process=True): self.canvas.delete(stlObject.name) if highlight: col = hilite else: col = gray if self.centerOnLoad and process: dx = (self.buildarea[0]/2) - stlObject.hxCenter dy = (self.buildarea[1]/2) - stlObject.hyCenter stlObject.deltaTranslation(dx, dy) stlObject.applyDeltas() d = [] for x,y in stlObject.hull: d.extend(self.transform(x,self.buildarea[1]-y)) itemId = self.canvas.create_polygon(d, fill=col, outline="black", tag=stlObject.name, width=3) self.selectable.append(itemId) self.itemMap[itemId] = stlObject if highlight: self.setSelection(itemId) def delStl(self): if self.selection is None: return stlObj = self.itemMap[self.selection] self.canvas.delete(stlObj.name) self.selectable.remove(self.selection) del self.itemMap[self.selection] self.selection = None def delAll(self): objs = self.itemMap.values() for o in objs: self.canvas.delete(o.name) self.selectable = [] self.itemMap = {} self.selection = None def commit(self): if self.selection is not None: o = self.itemMap[self.selection] name = o.name else: name = "" objs = self.itemMap.values() self.selectable = [] self.itemMap = {} for o in objs: self.canvas.delete(o.name) o.applyDeltas() self.addStl(o, highlight=(o.name == name), process=False) def getItemIdFromObject(self, obj): for itemId in self.itemMap.keys(): if obj.name == self.itemMap[itemId].name: return itemId return None def arrange(self): def cmpobj(a, b): return cmp(b.hArea, a.hArea) omap = Map(self.buildarea) maxx = maxy = -99999 minx = miny = 99999 objs = sorted(self.itemMap.values(), cmpobj) for o in objs: itemId = self.getItemIdFromObject(o) if itemId is None: continue x, y = omap.find(o.hxSize+self.arrangeMargin*2, o.hySize+self.arrangeMargin*2) if x is None or y is None: showinfo("Plate full", "Object %s does not fit" % o.name, parent=self) else: dx = x - o.hxCenter - o.translatex + o.hxSize/2 + self.arrangeMargin dy = y - o.hyCenter - o.translatey + o.hySize/2 + self.arrangeMargin self.setSelection(itemId) self.app.setSelection(o) self.moveStl(dx, dy) deltax = o.hxSize+self.arrangeMargin*2 deltay = o.hySize+self.arrangeMargin*2 omap.mark(x, y, deltax, deltay) if x < minx: minx=x if x+deltax > maxx: maxx=x+deltax if y < miny: miny=y if y+deltay > maxy: maxy=y+deltay dx = self.buildarea[0]/2-(maxx+minx)/2 dy = self.buildarea[1]/2-(maxy+miny)/2 for o in objs: itemId = self.getItemIdFromObject(o) if itemId is None: continue self.setSelection(itemId) self.app.setSelection(o) self.moveStl(dx, dy) def getStls(self): return self.itemMap.values() def countObjects(self): return len(self.selectable) def transform(self, ptx, pty): x = (ptx+self.offsetx)*self.zoom*self.scale+self.gridOffset y = (pty-self.offsety)*self.zoom*self.scale+self.gridOffset return (x, y) def triangulate(self, p1, p2): dx = p2[0] - p1[0] dy = p2[1] - p1[1] d = math.sqrt(dx*dx + dy*dy) return d