123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495 |
- from io import StringIO
- from gi.repository import GObject
- from gi.repository import Gdk
- from gi.repository import Gtk
- from gi.repository import GtkFlow
- import pinmagik.nodes
- from pinmagik.nodes.source import Source
- from pinmagik.raspi import RaspiContext, RaspiInNode, RaspiOutNode
- import json
- # Placeholder function for gettext
- def _(string):
- return string
- try:
- import RPi.GPIO as GPIO
- IS_REAL_RASPI = True
- except ImportError:
- IS_REAL_RASPI = False
- PD_FIELD_ID = 0
- PD_FIELD_NAME = 1
- PD_FIELD_HUMAN_NAME = 2
- PROJECT_TYPES = {
- "raspi" : (0x01, "raspi", _("Raspberry Pi Model A/B")),
- "raspi_plus" : (0x02, "raspi_plus", _("Raspberry Pi Model A+/B+")),
- }
- class Compiler(object):
- FRAME = """#!/usr/bin/python3
- #
- # *** Generated by Pinmagic ***
- #
- from sys import exit
- from time import sleep
- import RPi.GPIO as GPIO
- def init():
- GPIO.setmode(GPIO.BCM)
- %(INIT)s
- def loopstep():
- %(LOOP)s
- init()
- if not __name__ == "__main__":
- exit(0)
- try:
- while True:
- sleep(0.01)
- loopstep()
- except KeyboardInterrupt:
- GPIO.cleanup()
- """
- def __init__(self, project):
- self._project = project
- self._visited_nodes_init = []
- self._visited_nodes_loop = []
- self._init_buffer = StringIO()
- self._loop_buffer = StringIO()
- def compile(self):
- self._visited_nodes_init = []
- self._visited_nodes_loop = []
- self._init_buffer = StringIO()
- self._loop_buffer = StringIO()
- start_node = self._project.get_output_node()
- start_node.generate_raspi_init(self)
- start_node.generate_raspi_loop(self)
- code = Compiler.FRAME%{"INIT": self._init_buffer.getvalue(),
- "LOOP": self._loop_buffer.getvalue()}
- return code
- def get_init_buffer(self):
- return self._init_buffer
- def get_loop_buffer(self):
- return self._loop_buffer
- def rendered_as_init(self, node):
- return node in self._visited_nodes_init
- def rendered_as_loop(self, node):
- return node in self._visited_nodes_loop
- def set_rendered_init(self, node):
- self._visited_nodes_init.append(node)
- def set_rendered_loop(self, node):
- self._visited_nodes_loop.append(node)
- class Serializer(object):
- def __init__(self, project):
- self._project = project
- self._visited_nodes = []
- self._remaining_nodes = project.get_nodes()
- self._serialized_nodes = []
- def serialize_node(self,node):
- cons = []
- sinks = node.get_sinks()
- for sink in sinks:
- try:
- source = sink.get_sources()[0]
- except IndexError:
- continue
- targetnode = source.get_node()
- sources = targetnode.get_sources()
- cons.append((sinks.index(sink), id(targetnode), sources.index(source)))
- alloc = PinMagic.INSTANCE.nodeview.get_node_allocation(node)
- return {
- "clsid": node.__class__.ID,
- "x": alloc.x,
- "y": alloc.y,
- "node_info": {},
- "id": id(node),
- "connections": cons
- }
- def serialize(self):
- startnode = self._project.get_output_node()
- startnode.serialize(self)
- while len(self._remaining_nodes) > 0:
- startnode = self._remaining_nodes[0]
- startnode.serialize(self)
- return json.dumps({
- "type":self._project.get_type(),
- "nodes":self._serialized_nodes
- })
- def is_serialized(self, node):
- return node in self._visited_nodes
- def set_serialized(self, node, serialized):
- self._serialized_nodes.append(serialized)
- self._visited_nodes.append(node)
- self._remaining_nodes.remove(node)
- class Deserializer(object):
- def __init__(self, project, json_data):
- self._data = json.loads(json_data)
- self._project = project
- def deserialize(self):
- rc = RaspiContext(RaspiContext.REV_1)
- node_id_map = {}
- in_node = out_node = None
- for nd in self._data["nodes"]:
- if not nd["clsid"] in PinMagic.NODE_INDEX:
- return
- node_cls = PinMagic.NODE_INDEX[nd["clsid"]]
- if node_cls is None:
- return
-
- if nd["clsid"] in (0x8001, 0x8002):
- new_node = node_cls(rc)
- else:
- new_node = node_cls()
- node_id_map[nd["id"]] = new_node
- new_node.deserialize(nd["node_info"])
- if new_node.__class__.ID in (0x8001, 0x8002):
- new_node.add_to_nodeview(PinMagic.S().nodeview)
- elif hasattr(new_node, "childwidget") and new_node.childwidget:
- PinMagic.S().nodeview.add_with_child(new_node, new_node.childwidget)
- else:
- PinMagic.S().nodeview.add_node(new_node)
- if new_node.__class__.ID == 0x8001:
- in_node = new_node
- elif new_node.__class__.ID == 0x8002:
- out_node = new_node
- else:
- self._project.get_nodes().append(new_node)
- PinMagic.S().nodeview.set_node_position(new_node, nd["x"], nd["y"])
- PinMagic.S().nodeview.set_show_types(False)
- self._project.get_nodes().insert(0, in_node)
- self._project.get_nodes().insert(1, out_node)
- for nd in self._data["nodes"]:
- node = node_id_map[nd["id"]]
- for con in nd["connections"]:
- target = node_id_map[con[1]]
- node.get_sinks()[con[0]].link(target.get_sources()[con[2]])
- class Project(object):
- def __init__(self, typ):
- self._type = typ
- self._nodes = []
- self._filename = None
- def get_node_by_id(id_):
- for node in self._nodes:
- if id(node) == id_:
- return node
- return None
- def compile(self):
- return Compiler(self).compile()
- def set_filename(self, fn):
- self._filename = fn
- def get_filename(self):
- return self._filename
- def get_nodes(self):
- return self._nodes
- def set_nodes(self, nodes):
- self._nodes = nodes
- def get_type(self):
- return self._type
- def get_output_node(self):
- return self._nodes[1]
- def serialize(self):
- return Serializer(self).serialize()
- def deserialize(self, json_data):
- Deserializer(self, json_data).deserialize()
- class PinMagic(object):
- NODE_INDEX = {}
- INSTANCE = None
- @staticmethod
- def get_node_classes():
- ret = []
- for x in dir(pinmagik.nodes):
- if not x.startswith("_") and x not in pinmagik.nodes.EXCLUDES:
- exec("ret.append(pinmagik.nodes.%s)"%(x,))
- return ret
- @classmethod
- def build_node_index(cls):
- ret = []
- for x in dir(pinmagik.nodes):
- if not x.startswith("_") and x not in pinmagik.nodes.EXCLUDES:
- exec("cls.NODE_INDEX[pinmagik.nodes.%s.ID] = pinmagik.nodes.%s"%(x,x))
- cls.NODE_INDEX[RaspiOutNode.ID] = RaspiOutNode
- cls.NODE_INDEX[RaspiInNode.ID] = RaspiInNode
- @classmethod
- def S(cls):
- if cls.INSTANCE is None:
- cls.INSTANCE = PinMagic()
- return cls.INSTANCE
- def __init__(self):
- Gtk.init([])
- PinMagic.build_node_index()
- self._current_project = None
- self.headerbar = Gtk.HeaderBar.new()
- self.headerbar.set_title("PinMagic")
- self.headerbar.set_subtitle("untitled")
- self.nodeview = GtkFlow.NodeView.new()
- self.nodeview.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY)
- self.nodeview.connect("drag-data-received", self.on_new_node)
- self.nodeview.drag_dest_add_text_targets()
- self.builder = Gtk.Builder.new()
- self.builder.add_from_file("main.ui")
- self.window = self.builder.get_object("window")
- self.scrollarea = self.builder.get_object("scrolledwindow")
- self.nodestree = self.builder.get_object("nodestreeview")
- box = self.builder.get_object("box")
- revealer = self.builder.get_object("info_revealer")
- revealer.set_reveal_child(False)
- box.pack_start(self.headerbar, False, True, 0)
- box.reorder_child(self.headerbar,0)
- self.scrollarea.add_with_viewport(self.nodeview)
- crt = Gtk.CellRendererText()
- col = Gtk.TreeViewColumn(_("Toolbox"), crt, text=0)
- self.nodestree.append_column(col)
- self.nodestree.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [],
- Gdk.DragAction.COPY)
- self.nodestree.connect("drag-data-get", self.on_drag_toolbox)
- self.nodestree.drag_source_add_text_targets()
- self.export = Gtk.Button.new_from_icon_name("document-send", Gtk.IconSize.BUTTON)
- self.export.connect("clicked", self.on_export)
- self.headerbar.pack_end(self.export)
- self.new = Gtk.MenuButton.new()
- self.new.set_popup(self._build_new_menu())
- self.new.set_image(Gtk.Image.new_from_icon_name("document-new", Gtk.IconSize.BUTTON))
- self.headerbar.pack_start(self.new)
- self.save = Gtk.Button.new_from_icon_name("document-save", Gtk.IconSize.BUTTON)
- self.save.connect("clicked", self.on_save)
- self.headerbar.pack_start(self.save)
- self.open = Gtk.Button.new_from_icon_name("document-open", Gtk.IconSize.BUTTON)
- self.open.connect("clicked", self.on_load)
- self.headerbar.pack_start(self.open)
- self.live = None
- if IS_REAL_RASPI:
- self.live = Gtk.Button.new_from_icon_name("media-playback-start", Gtk.IconSize.BUTTON)
- self.headerbar.pack_end(self.live)
- self.window.show_all()
- self.window.connect("destroy", self.quit)
- self.update_ui()
- PinMagic.get_node_classes()
- def on_drag_toolbox(self, widget, darg_context, data, info, time):
- selected_path = self.nodestree.get_selection().get_selected_rows()[1][0]
- if len(selected_path) < 2:
- return
- m = self.nodestree.get_model()
- treeiter = m.get_iter(selected_path)
- data.set_text("node_"+str(m.get_value(treeiter,1)),-1)
- def on_new_node(self, widget, drag_context, x, y, data, info, time):
- txt = data.get_text()
- if txt is None or not txt.startswith("node_"):
- return
- node_cls_id = int(txt.replace("node_","",1))
- if not node_cls_id in PinMagic.NODE_INDEX:
- return
- node_cls = PinMagic.NODE_INDEX[node_cls_id]
- if node_cls is None:
- return
- new_node = node_cls()
- if hasattr(new_node, "childwidget") and new_node.childwidget:
- self.nodeview.add_with_child(new_node, new_node.childwidget)
- else:
- self.nodeview.add_node(new_node)
- self._current_project.get_nodes().append(new_node)
- self.nodeview.set_node_position(new_node, x, y)
- self.nodeview.set_show_types(False)
- def _build_new_model(self):
- if self._current_project:
- store = Gtk.TreeStore.new([GObject.TYPE_STRING,GObject.TYPE_INT])
- categories = {}
- for node in PinMagic.get_node_classes():
- if not pinmagik.nodes.supports(
- node, self._current_project.get_type()[PD_FIELD_NAME]):
- continue
- if not node.CATEGORY in categories:
- categories[node.CATEGORY] = store.append(None, (node.CATEGORY,-1))
- store.append(categories[node.CATEGORY],(node.HR_NAME,node.ID))
- else:
- store = None
- self.nodestree.set_model(store)
-
- def _build_new_menu(self):
- menu = Gtk.Menu.new()
- for descriptor in PROJECT_TYPES.values():
- i = Gtk.MenuItem.new_with_label(descriptor[PD_FIELD_HUMAN_NAME])
- i.connect("activate", self.new_project, descriptor[PD_FIELD_NAME])
- menu.add(i)
- menu.show_all()
- return menu
-
- def update_ui(self):
- has_project = self._current_project is not None
- self.export.set_sensitive(has_project)
- self.save.set_sensitive(has_project)
- self.scrollarea.set_sensitive(has_project)
- self.nodeview.set_sensitive(has_project)
- if has_project:
- if self._current_project.get_filename() is not None:
- self.headerbar.set_subtitle(self._current_project.get_filename())
- else:
- self.headerbar.set_subtitle(_("untitled"))
- else:
- self.headerbar.set_subtitle("")
- if self.live:
- self.live.set_sensitive(has_project)
- self.live.set_visible(self._current_project.get_type()[PD_FIELDNMAE] == "raspi")
- self._build_new_model()
- def on_export(self, widget=None, data=None):
- if self._current_project:
- dialog = Gtk.FileChooserDialog(_("Choose a filename"), self.window,
- Gtk.FileChooserAction.SAVE,
- (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
- #self.window.add_filters(dialog)
- response = dialog.run()
- if response == Gtk.ResponseType.OK:
- code = self._current_project.compile()
- f = open(dialog.get_filename(),"w")
- f.write(code)
- f.close()
- dialog.destroy()
- def on_save(self, widget=None, data=None):
- if self._current_project:
- dialog = Gtk.FileChooserDialog(_("Choose a filename"), self.window,
- Gtk.FileChooserAction.SAVE,
- (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
- filt = Gtk.FileFilter()
- filt.set_name(_("PinMagic Projects"))
- filt.add_pattern("*.pimp")
- dialog.add_filter(filt)
- response = dialog.run()
- if response == Gtk.ResponseType.OK:
- json_data = self._current_project.serialize()
- f = open(dialog.get_filename(),"w")
- f.write(json_data)
- f.close()
- self._current_project.set_filename(dialog.get_filename())
- self.update_ui()
- dialog.destroy()
- def on_load(self, widget=None, data=None):
- dialog = Gtk.FileChooserDialog(_("Choose a filename"), self.window,
- Gtk.FileChooserAction.SAVE,
- (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
- filt = Gtk.FileFilter()
- filt.set_name(_("PinMagic Projects"))
- filt.add_pattern("*.pimp")
- dialog.add_filter(filt)
- response = dialog.run()
- if response == Gtk.ResponseType.OK:
- f = open(dialog.get_filename(),"r")
- json_data = f.read()
- f.close()
- self.nodeview.set_sensitive(True)
- self._clear_current_project()
- self._current_project = Project(json.loads(json_data)["type"])
- self._current_project.set_filename(dialog.get_filename())
- self.update_ui()
- self._current_project.deserialize(json_data)
- dialog.destroy()
-
- def _clear_current_project(self):
- if self._current_project:
- for node in self._current_project.get_nodes():
- self.nodeview.remove_node(node)
- def new_project(self, widget=None, data=None):
- self._clear_current_project()
- self._current_project = Project(PROJECT_TYPES[data])
- self.update_ui()
- rc = RaspiContext(RaspiContext.REV_1)
- rin = RaspiInNode(rc)
- rin.add_to_nodeview(self.nodeview)
- ron = RaspiOutNode(rc)
- ron.add_to_nodeview(self.nodeview)
- self.nodeview.set_node_position(rin, 1, 1)
- self.nodeview.set_node_position(ron, 600, 1)
- self._current_project.get_nodes().append(rin)
- self._current_project.get_nodes().append(ron)
- def load_project(self, project):
- self._clear_current_project()
- self._current_project = project
- self.update_ui()
- def quit(self, widget=None, data=None):
- Gtk.main_quit()
- @staticmethod
- def run():
- pm = PinMagic.S()
- Gtk.main()
|