# gcompris - electric # # Copyright (C) 2005, 2008 Bruno Coudoin # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # import goocanvas import gcompris import gcompris.utils import gcompris.skin import gcompris.admin import gcompris.bonus import gtk import gtk.gdk import gobject import cairo import os import tempfile # Set to True to debug debug = False from gcompris import gcompris_gettext as _ class Gcompris_electric: """Tux hide a number, you must guess it""" def __init__(self, gcomprisBoard): self.gcomprisBoard = gcomprisBoard self.gcomprisBoard.disable_im_context = True # Part of UI : tools buttons # TOOL SELECTION self.tools = [ ["DEL", "electric/tool-del.png", "electric/tool-del_on.png", gcompris.CURSOR_DEL], ["SELECT", "electric/tool-select.png", "electric/tool-select_on.png", gcompris.CURSOR_SELECT] ] # These are used to let us restart only after the bonus is displayed. # When the bonus is displayed, it call us first with pause(1) and then # with pause(0) self.board_paused = 0 self.gamewon = 0 # The list of placed components self.components = [] self.gnucap_timer = 0 self.gnucap_timer_interval = 500 self.gnucap_binary = None def start(self): self.gcomprisBoard.level=1 self.gcomprisBoard.maxlevel=3 self.gcomprisBoard.sublevel=1 self.gcomprisBoard.number_of_sublevel=1 gcompris.bar_set(gcompris.BAR_LEVEL) gcompris.bar_set_level(self.gcomprisBoard) gcompris.bar_location(gcompris.BOARD_WIDTH - 160, -1, 0.7) gcompris.set_default_background(self.gcomprisBoard.canvas.get_root_item()) self.display_game() # # Check gnucap is installed and save it's path in self.gnucap_binary # # You can provide a gnucap binary in python_plugin_dir. # for binary in (os.environ.get('GNUCAP', ''), "gnucap.exe", os.path.join(gcompris.PYTHON_PLUGIN_DIR, 'bin', 'gnucap'), "/usr/bin/gnucap", "/usr/local/bin/gnucap", os.path.join(gcompris.DATA_DIR, '..' , '..', '..', 'bin', 'gnucap')): if(os.path.exists(binary)): self.gnucap_binary = binary break if not self.gnucap_binary: gcompris.utils.dialog(_("Cannot find the 'gnucap' electric simulator.\nYou can download and install it from:\n\nTo be detected, it must be installed in\n/usr/bin/gnucap or /usr/local/bin/gnucap.\nYou can still use this activity to draw schematics without computer simulation."), None) def end(self): gcompris.set_cursor(gcompris.CURSOR_DEFAULT); # Remove the root item removes all the others inside it self.cleanup_game() def ok(self): pass def repeat(self): if debug: print("Gcompris_electric repeat.") def key_press(self, keyval, commit_str, preedit_str): # Return True if you did process a key # Return False if you did not processed a key # (gtk need to send it to next widget) return False def pause(self, pause): self.board_paused = pause # When the bonus is displayed, it call us first with pause(1) and then # with pause(0) # the game is won if(pause == 0 and self.gamewon): self.increment_level() self.gamewon = 0 self.cleanup_game() self.display_game() return def set_level(self, level): self.gcomprisBoard.level=level; self.gcomprisBoard.sublevel=1; # Set the level in the control bar gcompris.bar_set_level(self.gcomprisBoard); self.cleanup_game() self.display_game() # # End of Initialisation # --------------------- # # ------------------------------------------------------------ # ------------------------------------------------------------ # ------------------------------------------------------------ # ------------------------------------------------------------ # Code that increments the sublevel and level # And bail out if no more levels are available # return 1 if continue, 0 if bail out def increment_level(self): self.gcomprisBoard.sublevel += 1 if(self.gcomprisBoard.sublevel>self.gcomprisBoard.number_of_sublevel): # Try the next level self.gcomprisBoard.sublevel=1 self.gcomprisBoard.level += 1 gcompris.bar_set_level(self.gcomprisBoard) if(self.gcomprisBoard.level>self.gcomprisBoard.maxlevel): self.gcomprisBoard.level = self.gcomprisBoard.maxlevel return 1 # Cleanup the board game def cleanup_game(self): self.gamewon = False if self.gnucap_timer : gobject.source_remove(self.gnucap_timer) self.gnucap_timer = 0 # remove the appended items from our tools for i in range(0,len(self.tools)): self.tools[i].pop() # No more component in the simulation set self.components = [] # Remove the root item removes all the others inside it self.rootitem.remove() # Display the board game def display_game(self): # Create our rootitem. We put each canvas item in it so at the end we # only have to kill it. The canvas deletes all the items it contains # automaticaly. self.rootitem = \ goocanvas.Group(parent = self.gcomprisBoard.canvas.get_root_item()) self.create_components(self.gcomprisBoard.level) # Display the tools x = 12 y = 10 for i in range(0,len(self.tools)): item = \ goocanvas.Image( parent = self.rootitem, pixbuf = gcompris.utils.load_pixmap(self.tools[i][1]), x=x, y=y ) x += 45 item.connect("button_press_event", self.tool_item_event, i) if(self.tools[i][0]=="SELECT"): self.select_tool = item self.select_tool_number = i # Always select the SELECT item by default self.current_tool = i self.old_tool_item = item self.old_tool_item.props.pixbuf = gcompris.utils.load_pixmap(self.tools[i][2]) gcompris.set_cursor(self.tools[i][3]); # Add the item in self.tools for later use self.tools[i].append(item) # Return the textual form of the current selected tool # Return on of self.tools[i][0] def get_current_tools(self): return(self.tools[self.current_tool][0]) # Event when a tool is selected # Perform instant action or swich the tool selection def tool_item_event(self, item, target, event, tool): if event.type == gtk.gdk.BUTTON_PRESS: if event.button == 1: self.assign_tool(tool) return True return False def assign_tool(self, newtool): # Deactivate old button item = self.tools[self.current_tool][4] item.set_properties(pixbuf = gcompris.utils.load_pixmap(self.tools[self.current_tool][1])) # Activate new button self.current_tool = newtool item = self.tools[newtool][4] item.set_properties(pixbuf = gcompris.utils.load_pixmap(self.tools[self.current_tool][2])) gcompris.set_cursor(self.tools[self.current_tool][3]); # # Depending on the given level, initialize the component selector # def create_components(self, level): if(level == 1): # A list of couple (component class, it's value) component_set = ((Battery, 10), (Bulb, 0.11), (Switch, None), ) elif(level == 2): # A list of couple (component class, it's value) component_set = ((Battery, 10), (Bulb, 0.11), (Rheostat, 1000), (Switch, None), (Switch2, None), (Connection, None), ) elif(level == 3): # A list of couple (component class, it's value) component_set = ((Battery, 10), (Bulb, 0.11), (Rheostat, 1000), (Resistor, 1000), (Switch, None), (Connection, None), (Diode, None), ) Selector(self, component_set) # ---------------------------------------------------------------------- # ---------------------------------------------------------------------- # ---------------------------------------------------------------------- # ---------------------------------------------------------------------- def run_simulation(self): if debug: print "run_simulation %s" %(self.gnucap_binary,) if not self.gnucap_binary: return if debug: print "self.gnucap_timer = %d" %(self.gnucap_timer,) if not self.gnucap_timer: if debug: print "run_simulation timeout_add" self.gnucap_timer = gobject.timeout_add(self.gnucap_timer_interval, self.call_gnucap) def call_gnucap(self): if not self.components: if debug: print "call_gnucap: No component" self.gnucap_timer = 0 return connected = False for component in self.components: if component.is_connected(): connected = True if debug: print "call_gnucap create the tempfile" fd, filename = tempfile.mkstemp(".gnucap", "gcompris_electric", None, True) f = os.fdopen(fd, "w+t") gnucap = "Title GCompris\n" # Ugly hack: connect a 0 ohm (1 fempto) resistor between net 0 # and first net found gnucap += "R999999999 0 " found = False for component in self.components: if component.is_connected(): for node in component.get_nodes(): if(node.get_wires()): gnucap += str(node.get_wires()[0].get_wire_id()) found = True break if found: break gnucap += " 1f\n" gnucap_print = "" for component in self.components: if component.is_connected(): thisgnucap = component.to_gnucap("") gnucap += thisgnucap[0] gnucap_print += thisgnucap[1] gnucap += gnucap_print gnucap += ".dc\n" gnucap += ".end\n" if debug: print gnucap f.writelines(gnucap) f.close() # # Run gnucap with the temporary datafile we created. # if debug: print "calling gnucap: %s -b %s" % (self.gnucap_binary, filename) output = os.popen("%s -b %s" % (self.gnucap_binary, filename)) # # Read and analyse gnucap result # if debug: print "---------------- GNUCAP OUTPUT PARSING ---------------------" line = "" for line in output.readlines(): if debug: print "===" if debug: print line if(line.split() == " 0."): break if debug: print "------------------------------------------------------------" if debug: print "===>" if debug: print line if debug: print "===>" # Close it to check errors results = output.close() if results: print('Failed to run gnugap with error ', results) self.gnucap_timer = 0 return values = [] if(line.split()[0] == "0."): if debug: print "FOUND 0." values = line.split() del values[0] if debug: print values i = 0 # Set all component values for component in self.components: if not component.is_connected(): done = component.set_voltage_intensity(False, 0.0, 0.0) else: # Each Component class may have several gnucap component to retrieve # data from done = False while not done: if debug: print "Processing component %d" %(i,) try: if debug: print values[i] if debug: print values[i+1] dumy = values[i] dumy = values[i+1] except: if debug: print "Warning: gnucap parsing mismatch" done = True continue try: volt = self.convert_gnucap_value(values[i]) amp = self.convert_gnucap_value(values[i+1]) done = component.set_voltage_intensity(True, volt, amp) if debug: print "Converted U=%sV I=%sA" %(volt, amp) except: if debug: print "Failed to convert V=%sV I=%sA" %(values[i], values[i+1]) done = component.set_voltage_intensity(False, 0.0, 0.0) i += 2 if not debug: os.remove(filename) self.gnucap_timer = 0 # Convert a gnucap value back in a regular number # Return a float value # Or a ValueError exception def convert_gnucap_value(self, value): unit = 1 if value.endswith("T"): unit = e12 value = value.replace("T", "") if value.endswith("G"): unit = 1e9 value = value.replace("G", "") if value.endswith("Meg"): unit = 1e6 value = value.replace("Meg", "") if value.endswith("K"): unit = 1e3 value = value.replace("K", "") if value.endswith("u"): unit = 1e-6 value = value.replace("u", "") if value.endswith("n"): unit = 1e-9 value = value.replace("n", "") if value.endswith("p"): unit = 1e-12 value = value.replace("p", "") if value.endswith("f"): unit = 1e-15 value = value.replace("f", "") # return absolue value sign = 1 if float(value) < 0: sign = -1 return (float(sign)*float(value)*unit) # ---------------------------------------------------------------------- # ---------------------------------------------------------------------- # ---------------------------------------------------------------------- # ---------------------------------------------------------------------- # ---------------------------------------------------------------------- # A wire between 2 Nodes class Wire: # BUG: Wire ID 0 is a dummy wire, we start at 1 # 2006/02/19 chgans@gna.org: From gnucap manual chapter 3.1: # Node 0 is used as a reference for all calculations and is assumed #to have a voltage of zero. (This is the ground, earth or common #node.) Nodes must be nonnegative integers, but need not be #numbered sequentially. counter = 1 connection = {} colors = [ 0xdfc766FFL, 0xdf9766FFL, 0xdf667dFFL, 0xdf66bcFFL, 0xc466dfFFL, 0x9c66dfFFL, 0xA4FFB3FFL, 0x6666dfFFL, 0x669fdfFFL, 0x66df6cFFL, 0x66dfd8FFL ] def __init__(self, electric, source_node, x1, y1, x2, y2): self.electric = electric self.rootitem = electric.rootitem self.source_node = source_node self.target_node = None self.x1 = x1 self.y1 = y1 self.x2 = x2 self.y2 = y2 self.wire_item = \ goocanvas.Polyline( parent = self.rootitem, points = goocanvas.Points([(self.x1, self.y1), (self.x2, self.y2)]), stroke_color_rgba = 0xFF0000FFL, line_cap = cairo.LINE_CAP_ROUND, line_width=5.0 ) self.wire_item.connect("button_press_event", self.delete_wire, self) self.wire_id = -1 self._add_connection(source_node) def _add_connection(self, node): # Is this node already connected if(node.get_wires()): wire_id = node.get_wires()[0].get_wire_id() if debug: print "Node already connected to %d (self.wire_id=%d)" %(wire_id, self.wire_id) if self.wire_id >= 0: # This node was already connected elsewhere, reset it to use our # wire_id for wire in node.get_wires(): if debug: print " Was connected to %d" %(wire.get_wire_id(),) if wire.get_wire_id() != self.wire_id: wire.set_wire_id(self.wire_id) else: if wire_id == -1: self.wire_id = Wire.counter Wire.counter += 1 else: self.wire_id = wire_id else: if self.wire_id == -1: self.wire_id = Wire.counter Wire.counter += 1 Wire.connection[node] = self if debug: print "WIRE_ID = %d" %self.wire_id def set_wire_id(self, id): self.wire_id = id if(self.target_node): self.target_node.renumber_wire(self, id) if(self.source_node): self.source_node.renumber_wire(self, id) # Colorize the wire self.wire_item.set_properties(fill_color_rgba = Wire.colors[id % len(Wire.colors)]) def get_wire_id(self): return self.wire_id def remove(self): self.wire_item.remove() self.wire_id = -1 self.source_node.remove_wire(self, None) if self.target_node: self.target_node.remove_wire(self, Wire.counter) Wire.counter += 1 self.source_node = None self.target_node = None self.electric.run_simulation() def set_target_node(self, node): self.target_node = node self._add_connection(node) # Colorize the wire self.wire_item.set_properties(fill_color_rgba = Wire.colors[self.wire_id % len(Wire.colors)]) # Move wire. In fact, the attached component are moved and THEY # move the wire def move_all_wire(self, x, y): if(self.source_node and self.target_node): source_component = self.source_node.get_component() target_component = self.target_node.get_component() # TBD Need to move the components but not loosing their distance. # Don't know yet how # Move one node of the wire at a time, depending on the given node def move(self, node, x, y): if(node == self.source_node): self.move_source_node(x, y) elif(node== self.target_node): self.move_target_node(x, y) def move_source_node(self, x1, y1): self.x1 = x1 self.y1 = y1 self.wire_item.set_properties(points = goocanvas.Points([(self.x1, self.y1), (self.x2, self.y2)]) ) def move_target_node(self, x2, y2): self.x2 = x2 self.y2 = y2 self.wire_item.set_properties(points = goocanvas.Points([(self.x1, self.y1), (self.x2, self.y2)]) ) # Callback event to delete a wire def delete_wire(self, widget, target, event, wire): if event.type == gtk.gdk.BUTTON_PRESS: if event.button == 1: wire.remove() elif event.button == 3: if debug: print "WIRE_ID = %d" %self.wire_id return False #------------------------------------------------------------ # Node # # x, y are node coordinate relative to the component image class Node: def __init__(self, image, name, x, y): self.image = image self.name = name self.x = x self.y = y self.wires = [] self.item = None # Return the newly create Node item def create(self, component, x, y): self.component = component self.rootitem = component.get_rootitem() pixmap = gcompris.utils.load_pixmap(self.image) self.center_x = pixmap.get_width()/2 self.center_y = pixmap.get_height()/2 self.item = \ goocanvas.Image( parent = self.rootitem, pixbuf = pixmap, x = x + self.center_x, y = y + self.center_y, ) self.item.set_data('node', self) return self.item def get_component(self): return self.component # Add a wire to this node def add_wire(self, wire): self.wires.append(wire) # Remove a wire from this node and reasign all wires # already connected to this node a new given wire number def remove_wire(self, wire, wire_id): try: self.wires.remove(wire) if(wire_id): self.renumber_wire(wire, wire_id) except: pass # Renumber the wires def renumber_wire(self, wire, wire_id): for wire in self.wires: if(wire.get_wire_id() != wire_id): wire.set_wire_id(wire_id) # Return the list of all wires on this node def get_wires(self): return(self.wires) # Has wire, return True if the given wire is already connected # to this node def has_wire(self, wire): try: if(self.wires.index(wire)): return True else: return False except: pass def move(self, x, y): if(self.item): self.item.set_properties(x = x + self.x, y = y + self.y) for wire in self.wires: wire.move(self, x + self.x + self.center_x, y + self.y + self.center_y) #------------------------------------------------------------ # Component # # nodes is a list of class Node object class Component(object): counter = 0 def __init__(self, electric, gnucap_name, gnucap_value, image, nodes): self.electric = electric self.canvas = electric.gcomprisBoard.canvas self.rootitem = electric.rootitem if gnucap_name: self.gnucap_name = gnucap_name + str(Component.counter) self.electric.components.append(self) else: self.gnucap_name = "" Component.counter += 1 self.gnucap_value = gnucap_value self.image = image self.nodes = nodes # Create a group for this component self.comp_rootitem = \ goocanvas.Group( parent = self.rootitem, ) self.comp_rootitem.props.visibility = goocanvas.ITEM_INVISIBLE # Add the component image pixmap = gcompris.utils.load_pixmap(self.image) self.x = 0 self.y = 0 self.center_x = pixmap.get_width()/2 self.center_y = pixmap.get_height()/2 self.component_item_offset_x = 0 self.component_item_offset_y = 0 self.component_item = \ goocanvas.Image( parent = self.comp_rootitem, pixbuf = pixmap, x = self.x + self.component_item_offset_x, y = self.y + self.component_item_offset_y, ) self.component_item.connect("button_press_event", self.component_move, self) self.component_item.connect("motion_notify_event", self.component_move, self) # Add each connector for node in self.nodes: item = node.create(self, node.x, node.y) item.connect("button_press_event", self.create_wire, node) item.connect("button_release_event", self.create_wire, node) item.connect("motion_notify_event", self.create_wire, node) # A text item to display textual values (volt and amp) self.item_values_x = 0 self.item_values_y = 0 self.item_values = \ goocanvas.Text( parent = self.comp_rootitem, x = self.item_values_x, y = self.item_values_y, font = "Sans 8", text = "", fill_color = "white", anchor = gtk.ANCHOR_CENTER ) self.item_values.connect("button_press_event", self.component_move, self) self.item_values.connect("motion_notify_event", self.component_move, self) # Return False if we need more value to complete our component # This is usefull in case where one Component is made of several gnucap component def set_voltage_intensity(self, valid_value, voltage, intensity): self.voltage = voltage self.intensity = intensity if(valid_value): self.item_values.set_properties(text="V=%.2fV\nI=%.3fA"%(voltage,intensity)) self.item_values.props.visibility = goocanvas.ITEM_VISIBLE else: self.item_values.props.visibility = goocanvas.ITEM_INVISIBLE return True def get_rootitem(self): return self.comp_rootitem def move(self, x, y): self.x = x - self.center_x self.y = y - self.center_y self.item_values.set_properties(x = self.item_values_x + self.x, y = self.item_values_y + self.y) self.component_item.set_properties(x = self.x + self.component_item_offset_x, y = self.y + self.component_item_offset_y ) for node in self.nodes: node.move( self.x, self.y) def show(self): self.comp_rootitem.props.visibility = goocanvas.ITEM_VISIBLE def hide(self): self.comp_rootitem.props.visibility = goocanvas.ITEM_INVISIBLE def remove(self): try: # Remove ourself from the list of gnucap aware components self.electric.components.remove(self) except: pass for node in self.nodes: while node.get_wires(): wire = node.get_wires()[0] node.remove_wire(wire, None) wire.remove() self.comp_rootitem.remove() # Return the nodes def get_nodes(self): return self.nodes # Return True if this component is connected and can provides a gnucap # description # # It assume that if a single node is not connected, the whole # component is not connected def is_connected(self): for node in self.nodes: if not node.get_wires(): return False return True # Return the gnucap definition for this component # model is optional # def to_gnucap(self, model): gnucap = self.gnucap_name # No definition, it happens for connection spot # But in this case, it should not be called at all. it's not in the top level # components list. if gnucap == "": return "* Component ignored\n" # ignore component if there are some unconnected node. for node in self.nodes: if not node.get_wires(): if debug: print "Component ignored" return "* Component ignored\n" for node in self.nodes: gnucap += " " if(node.get_wires()): gnucap += str(node.get_wires()[0].get_wire_id()) gnucap += " " gnucap += str(self.gnucap_value) gnucap += "\n" gnucap += model return [gnucap, ".print dc + v(%s) i(%s)\n" %(self.gnucap_name, self.gnucap_name)] # Callback event to move the component def component_move(self, widget, target, event, component): if event.type == gtk.gdk.MOTION_NOTIFY: if event.state & gtk.gdk.BUTTON1_MASK: if(self.electric.get_current_tools()=="SELECT"): component.move(event.x, event.y) else: if(self.electric.get_current_tools()=="DEL"): self.remove() return True # Callback event to create a wire def create_wire(self, widget, target, event, node): if(self.electric.get_current_tools()=="DEL"): return True if event.type == gtk.gdk.BUTTON_PRESS: if event.button == 1: bounds = widget.get_bounds() self.pos_x = (bounds.x1+bounds.x2)/2 self.pos_y = (bounds.y1+bounds.y2)/2 self.wire = Wire(self.electric, node, self.pos_x, self.pos_y, event.x, event.y) node.add_wire(self.wire) return True if event.type == gtk.gdk.MOTION_NOTIFY: if event.state & gtk.gdk.BUTTON1_MASK: self.wire.move_target_node(event.x, event.y) if event.type == gtk.gdk.BUTTON_RELEASE: if event.button == 1: node_target = None # Our wire line confuses the get_item_at. Look arround. for x in range(-10, 11, 5): target_item = self.canvas.get_item_at(event.x + x, event.y, True) if target_item: node_target = target_item.get_data('node') if(node_target): break # Take care not to wire the same component or 2 times the same node if(not node_target or node.get_component() == node_target.get_component() or node_target.has_wire(self.wire)): node.remove_wire(self.wire, None) self.wire.remove() else: self.wire.set_target_node(node_target) node_target.add_wire(self.wire) self.electric.run_simulation() return True return False # ----------------------------------------------------------------------- # ----------------------------------------------------------------------- # ----------------------------------------------------------------------- # # Pre defined components # ---------------------- # # ---------------------------------------- # RESISTOR # # class Resistor(Component): image = "electric/resistor.png" icon = "electric/resistor_icon.png" def __init__(self, electric, x, y, value): super(Resistor, self).__init__(electric, "R", value, self.image, [Node("electric/connect.png", "A", -30, -5), Node("electric/connect.png", "B", 92, -5)]) # Overide some values self.item_values_x = 50 self.item_values_y = 12 self.move(x, y) self.show() # ---------------------------------------- # DIODE # # class Diode(Component): image = "electric/diode.png" icon = "electric/diode_icon.png" def __init__(self, electric, x, y, dummy): super(Diode, self).__init__(electric, "D", "ddd 1.", self.image, [Node("electric/connect.png", "A", -30, -9), Node("electric/connect.png", "B", 95, -9)]) # Overide some values self.item_values_x = 50 self.item_values_y = 12 self.move(x, y) self.show() # Return the gnucap definition for this component # Here we just add the diode model def to_gnucap(self, model): # Our 'ddd' Diode model # Idealized diode: ~0V treshold voltage. Characteristic graph # passes through the two points (10 mV, 10 mA) and (20 mV, 2000 # mA) => N = 0.072 IS = 5x10-5 A model = ".model ddd d ( is= 50.u rs= 0. n= 0.072 tt= 0. cjo= 1.p vj= 1. m= 0.5\n" model += "+ eg= 1.11 xti= 3. kf= 0. af= 1. fc= 0.5 bv= 0. ibv= 0.001 )\n" gnucap = [] gnucap = super(Diode, self).to_gnucap(model) return gnucap # ---------------------------------------- # SWITCH # # class Switch(Component): image = "electric/switch_off.png" icon = "electric/switch_icon.png" def __init__(self, electric, x, y, dummy): self.click_ofset_x = 32 self.click_ofset_y = -28 self.value_on = "0" self.value_off = "10000k" super(Switch, self).__init__(electric, "R", self.value_off, self.image, [Node("electric/connect.png", "A", -30, -10), Node("electric/connect.png", "B", 100, -10)]) # Overide some values self.item_values.props.visibility = goocanvas.ITEM_INVISIBLE self.move(x, y) pixmap = gcompris.utils.load_pixmap("electric/switch_click.png") self.click_item = goocanvas.Image( parent = self.comp_rootitem, pixbuf = pixmap, x = self.x + self.click_ofset_x, y = self.y + self.click_ofset_y, ) self.click_item.connect("button_press_event", self.component_click) self.show() # Callback event on the switch def component_click(self, widget, target, event): if event.button == 1: if(self.gnucap_value == self.value_off): self.gnucap_value = self.value_on pixmap = gcompris.utils.load_pixmap("electric/switch_on.png") else: self.gnucap_value = self.value_off pixmap = gcompris.utils.load_pixmap("electric/switch_off.png") self.component_item.set_properties(pixbuf = pixmap) self.electric.run_simulation() return False # Callback event to move the component def component_move(self, widget, target, event, component): super(Switch, self).component_move(widget, target, event, component) if(self.electric.get_current_tools()=="DEL"): return True self.click_item.set_properties( x = self.x + self.click_ofset_x, y = self.y + self.click_ofset_y) return True # Return False if we need more value to complete our component # This is usefull in case where one Component is made of several gnucap component def set_voltage_intensity(self, valid_value, voltage, intensity): self.voltage = voltage self.intensity = intensity # Never show values self.item_values.props.visibility = goocanvas.ITEM_INVISIBLE return True # ---------------------------------------- # SWITCH2 # # class Switch2(Component): image = "electric/switch2_on.png" icon = "electric/switch2_icon.png" def __init__(self, electric, x, y, dummy): self.click_ofset_x = 22 self.click_ofset_y = -28 self.gnucap_current_resistor = 1 self.gnucap_nb_resistor = 0 self.value_on = "0" self.value_off = "10000k" self.value_top = self.value_on self.value_bottom = self.value_off super(Switch2, self).__init__(electric, "R", self.value_off, self.image, [Node("electric/connect.png", "C", -15, 0), Node("electric/connect.png", "A", 80, -25), Node("electric/connect.png", "B", 80, 25)]) # Overide some values self.item_values.props.visibility = goocanvas.ITEM_INVISIBLE self.move(x, y) pixmap = gcompris.utils.load_pixmap("electric/switch_click.png") self.click_item = goocanvas.Image( parent = self.comp_rootitem, pixbuf = pixmap, x = self.x + self.click_ofset_x, y = self.y + self.click_ofset_y, ) self.click_item.connect("button_press_event", self.component_click) self.show() # Callback event on the switch def component_click(self, widget, target, event): if event.button == 1: if(self.value_top == self.value_off): self.value_top = self.value_on self.value_bottom = self.value_off self.component_item_offset_y = - 6 pixmap = gcompris.utils.load_pixmap("electric/switch2_on.png") else: self.value_top = self.value_off self.value_bottom = self.value_on self.component_item_offset_y = 10 pixmap = gcompris.utils.load_pixmap("electric/switch2_off.png") self.component_item.set_properties(y = self.y + self.component_item_offset_y) self.component_item.set_properties(pixbuf = pixmap) self.electric.run_simulation() return False # Callback event to move the component def component_move(self, widget, target, event, component): super(Switch2, self).component_move(widget, target, event, component) if(self.electric.get_current_tools()=="DEL"): return True self.click_item.set_properties( x = self.x + self.click_ofset_x, y = self.y + self.click_ofset_y) return True # Return True if this component is connected and can provides a gnucap # description # # The switch2 needs at least 2 connected nodes # def is_connected(self): count = 0 for node in self.nodes: if node.get_wires(): count += 1 if count >= 2: return True return False # Return the gnucap definition for a single resitor of the switch2 # node_id1 and node_id2 are the index in the list of nodes def to_gnucap_res(self, gnucap_name, node_id1, node_id2, gnucap_value): gnucap = gnucap_name gnucap += " " for i in (node_id1, node_id2): node = self.nodes[i] if node.get_wires(): gnucap += str(node.get_wires()[0].get_wire_id()) gnucap += " " gnucap += " " gnucap += str(gnucap_value) gnucap += "\n" return [gnucap, ".print dc + v(%s) i(%s)\n" %(gnucap_name, gnucap_name)] # Return the gnucap definition for this component # depending of the connected nodes, it create one or two resistor def to_gnucap(self, model): gnucap = ["", ""] # reset set_voltage_intensity counter self.gnucap_current_resistor = 0 self.gnucap_nb_resistor = 0 # top resistor if self.nodes[0].get_wires() and \ self.nodes[1].get_wires(): gnucap_resp = self.to_gnucap_res(self.gnucap_name + "_top", 0, 1, self.value_top) gnucap[0] += gnucap_resp[0] gnucap[1] += gnucap_resp[1] self.gnucap_nb_resistor += 1 # bottom resistor if self.nodes[0].get_wires() and \ self.nodes[2].get_wires(): gnucap_resp = self.to_gnucap_res(self.gnucap_name + "_bot", 0, 2, self.value_bottom) gnucap[0] += gnucap_resp[0] gnucap[1] += gnucap_resp[1] self.gnucap_nb_resistor += 1 return [gnucap[0], gnucap[1]] # Return False if we need more value to complete our component # This is usefull in case where one Component is made of several gnucap component def set_voltage_intensity(self, valid_value, voltage, intensity): # Never show values self.item_values.props.visibility = goocanvas.ITEM_INVISIBLE self.gnucap_current_resistor += 1 if(self.gnucap_nb_resistor == self.gnucap_current_resistor): return True else: return False # ---------------------------------------- # RHEOSTAT # # class Rheostat(Component): image = "electric/resistor_track.png" icon = "electric/resistor_track_icon.png" def __init__(self, electric, x, y, resitance): self.gnucap_current_resistor = 1 self.gnucap_nb_resistor = 0 self.wiper_ofset_x = -2 self.wiper_ofset_min_y = 22 self.wiper_ofset_max_y = 103 self.wiper_ofset_y = self.wiper_ofset_min_y self.resitance = resitance super(Rheostat, self).__init__(electric, "R", self.resitance, self.image, [Node("electric/connect.png", "A", 0, -25), Node("electric/connect.png", "B", 50, 50), Node("electric/connect.png", "C", 0, 125)]) # Overide some values self.item_values_x = 20 self.item_values_y = 70 self.item_values.set_properties(fill_color="blue") self.move(x, y) # The wiper wire self.wiper_wire_item = goocanvas.Polyline( parent = self.comp_rootitem, points = goocanvas.Points([(0,0), (0,0)]), fill_color_rgba = 0x5A5A5AFFL, line_width=5.0 ) self.update_wiper_wire() pixmap = gcompris.utils.load_pixmap("electric/resistor_wiper.png") self.wiper_item = goocanvas.Image( parent = self.comp_rootitem, pixbuf = pixmap, x = self.x + self.wiper_ofset_x, y = self.y + self.wiper_ofset_y, ) self.wiper_item.connect("button_press_event", self.component_click) self.wiper_item.connect("motion_notify_event", self.component_click) self.show() def update_wiper_wire(self): self.wiper_wire_item.set_properties( points = goocanvas.Points([(self.x + self.wiper_ofset_x + 35, self.y + self.wiper_ofset_y + 10), (self.x + 55, self.y + 65)]) ) def move_wiper(self, new_y): if(new_y>self.y+self.wiper_ofset_max_y): self.wiper_ofset_y = self.wiper_ofset_max_y elif(new_y= 2: return True return False # Return the gnucap definition for a single resitor of the rheostat # node_id1 and node_id2 are the index in the list of nodes def to_gnucap_res(self, gnucap_name, node_id1, node_id2, gnucap_value): # Ignore the component if there is some unconnected nodes. for i in (node_id1, node_id2): node = self.nodes[i] if not node.get_wires(): gnucap = "* %s: component ignored: not connected\n" %(gnucap_name) return [gnucap, ""] gnucap = gnucap_name gnucap += " " for i in (node_id1, node_id2): node = self.nodes[i] if node.get_wires(): gnucap += str(node.get_wires()[0].get_wire_id()) gnucap += " " gnucap += " " gnucap += str(gnucap_value) gnucap += "\n" return [gnucap, ".print dc + v(%s) i(%s)\n" %(gnucap_name, gnucap_name)] # Return the gnucap definition for this component # depending of the connected nodes, it create one or two resistor def to_gnucap(self, model): gnucap = ["", ""] # reset set_voltage_intensity counter self.gnucap_current_resistor = 0 gnucap_value = self.resitance * \ (self.wiper_ofset_y - self.wiper_ofset_min_y) / \ (self.wiper_ofset_max_y - self.wiper_ofset_min_y) # Main resistor if self.nodes[0].get_wires() and \ not self.nodes[1].get_wires() and \ self.nodes[2].get_wires(): self.gnucap_nb_resistor = 1 gnucap_resp = self.to_gnucap_res(self.gnucap_name + "_all", 0, 2, self.resitance) gnucap[0] += gnucap_resp[0] gnucap[1] += gnucap_resp[1] return [gnucap[0], gnucap[1]] self.gnucap_nb_resistor = 0 # top resistor if self.nodes[0].get_wires() and \ self.nodes[1].get_wires(): self.gnucap_nb_resistor += 1 gnucap_resp = self.to_gnucap_res(self.gnucap_name + "_top", 0, 1, gnucap_value) gnucap[0] += gnucap_resp[0] gnucap[1] += gnucap_resp[1] # bottom resistor if self.nodes[1].get_wires() and \ self.nodes[2].get_wires(): self.gnucap_nb_resistor += 1 gnucap_resp = self.to_gnucap_res(self.gnucap_name + "_bot", 1, 2, self.resitance - gnucap_value) gnucap[0] += gnucap_resp[0] gnucap[1] += gnucap_resp[1] return [gnucap[0], gnucap[1]] # Return False if we need more value to complete our component # This is usefull in case one Component is made of several gnucap component def set_voltage_intensity(self, valid_value, voltage, intensity): self.gnucap_current_resistor += 1 if self.gnucap_current_resistor == 1: self.voltage = voltage self.intensity = intensity if(valid_value ): self.item_values.set_properties(text="U=%.1fV\nI=%.2fA"%(voltage,intensity)) self.item_values.props.visibility = goocanvas.ITEM_VISIBLE else: self.item_values.props.visibility = goocanvas.ITEM_INVISIBLE if self.gnucap_nb_resistor != 1: self.gnucap_current_resistor += 1 if self.gnucap_current_resistor > self.gnucap_nb_resistor: self.gnucap_current_resistor = 0 return True else: self.gnucap_current_resistor = 0 return True return False # ---------------------------------------- # BULB # # class Bulb(Component): image = "electric/bulb1.png" icon = "electric/bulb_icon.png" def __init__(self, electric, x, y, power_max): self.internal_resistor = 1000 super(Bulb, self).__init__(electric, "R", self.internal_resistor, self.image, [Node("electric/connect.png", "A", 7, 170), Node("electric/connect.png", "B", 85, 170)]) # Overide some values self.item_values_x = 55 self.item_values_y = 80 self.item_values.set_properties(fill_color="red") # Specific Bulb values self.is_blown = False self.move(x, y) self.show() self.power_max = power_max self.resistor_blown = 100000000 # Change the pixmap depending on the real power in the Bulb # Return False if we need more value to complete our component # This is usefull in case where one Component is made of several gnucap component def set_voltage_intensity(self, valid_value, voltage, intensity): super(Bulb, self).set_voltage_intensity(valid_value, voltage, intensity) # If the Bulb is blown, do not update it anymore if self.is_blown: return True power = abs(voltage * intensity) image_index = min((power * 10) / self.power_max + 1, 11) pixmap = gcompris.utils.load_pixmap("electric/bulb%d.png" %(image_index,)) if debug: print "Power = %f (Max=%f) Image index = %d" %(power, self.power_max, image_index) self.component_item.set_properties(pixbuf = pixmap) # If the Bulb is blown, we have to change it's internal # Resistor value to infinite and ask for a circuit recalc if image_index == 11: self.gnucap_value = self.resistor_blown self.electric.run_simulation() self.is_blown = True return True # Callback event to move the component # We override it to repair the Bulb def component_move(self, widget, target, event, component): # If the Bulb is blown and we get a click repair it # If the bulb is not blown, you can blown it by right clicking on it if (event.state & gtk.gdk.BUTTON1_MASK) \ and self.electric.get_current_tools() == "SELECT": if self.is_blown: self.is_blown = False self.gnucap_value = self.internal_resistor self.electric.run_simulation() elif (event.state & gtk.gdk.BUTTON3_MASK) and self.electric.get_current_tools()=="SELECT": if not self.is_blown: # Blown us with arbitrate high value self.set_voltage_intensity(True, 100, 10) return super(Bulb, self).component_move(widget, target, event, component) # ---------------------------------------- # BATTERY # # class Battery(Component): image = "electric/battery.png" icon = "electric/battery_icon.png" def __init__(self, electric, x, y, value): super(Battery, self).__init__(electric, "Vsupply", value, self.image, [Node("electric/connect.png", "A", 6, -35), Node("electric/connect.png", "B", 6, 120)]) # Overide some values self.item_values_x = 23 self.item_values_y = 70 self.move(x, y) self.show() # Return False if we need more value to complete our component # This is usefull in case where one Component is made of several gnucap component def set_voltage_intensity(self, valid_value, voltage, intensity): super(Battery, self).set_voltage_intensity(valid_value, voltage, intensity) if(abs(self.intensity) > 1): # Short circuit case, set the dead battery icon self.component_item.set_properties(pixbuf = gcompris.utils.load_pixmap("electric/battery_dead.png")) else: self.component_item.set_properties(pixbuf = gcompris.utils.load_pixmap("electric/battery.png")) return True # ---------------------------------------- # CONNECTION # (a simple spot to connect wire and make a cute scematic) # class Connection(Component): image = "electric/connect_spot.png" icon = "electric/connect_icon.png" def __init__(self, electric, x, y, dummy): super(Connection, self).__init__(electric, "", "", self.image, [Node("electric/connect.png", "A", 18, 10)]) self.move(x, y) self.show() # ----------------------------------------------------------------------- # ----------------------------------------------------------------------- # ----------------------------------------------------------------------- # # Component selector # ------------------ # class Selector: def __init__(self, electric, components_class): self.electric = electric self.rootitem = electric.rootitem goocanvas.Svg(parent = self.rootitem, svg_handle = gcompris.skin.svg_get(), svg_id = "#SELECTOR" ) self.x = 15 self.y = 60 index_y = 10 gap = 20 self.init_coord = {} self.offset_x = self.offset_y = 0 for component_class in components_class: pixmap = gcompris.utils.load_pixmap(component_class[0].icon) item = \ goocanvas.Image( parent = self.rootitem, pixbuf = pixmap, x = self.x, y = self.y + index_y, ) # Save the original coord to set it back once dropped self.init_coord[component_class[0]] = (self.x, self.y + index_y) index_y += pixmap.get_height() + gap item.connect("button_press_event", self.component_click, component_class) item.connect("button_release_event", self.component_click, component_class) item.connect("motion_notify_event", self.component_click, component_class) # Callback event on the component def component_click(self, widget, target, event, component_class): if (event.state & gtk.gdk.BUTTON1_MASK and self.electric.get_current_tools()=="DEL"): # Switch to select mode self.electric.assign_tool(1) if event.type == gtk.gdk.MOTION_NOTIFY: if event.state & gtk.gdk.BUTTON1_MASK: # Save the click to image offset if self.offset_x == 0: bounds = widget.get_bounds() self.offset_x = (event.x - bounds.x1) self.offset_y = (event.y - bounds.y1) widget.set_properties(x = event.x - self.offset_x, y = event.y - self.offset_y) if event.type == gtk.gdk.BUTTON_RELEASE: if event.button == 1: bounds = widget.get_bounds() component_class[0](self.electric, event.x - self.offset_x + (bounds.x2-bounds.x1)/2, event.y - self.offset_y + (bounds.y2-bounds.y1)/2, component_class[1]) widget.set_properties(x = self.init_coord[component_class[0]][0], y = self.init_coord[component_class[0]][1]) self.offset_x = self.offset_y = 0 return True # ------------------------------------------------------------ # ------------------------------------------------------------ # ------------------------------------------------------------ def stop_board(): pass