printrun-src/printrun/gviz.py

changeset 15
0bbb006204fc
child 46
cce0af6351f0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/printrun-src/printrun/gviz.py	Fri Jun 03 09:16:07 2016 +0200
@@ -0,0 +1,554 @@
+# This file is part of the Printrun suite.
+#
+# Printrun 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.
+#
+# Printrun 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 Printrun.  If not, see <http://www.gnu.org/licenses/>.
+
+from Queue import Queue
+from collections import deque
+import numpy
+import wx
+import time
+from . import gcoder
+from .injectgcode import injector, injector_edit
+
+from .utils import imagefile, install_locale, get_home_pos
+install_locale('pronterface')
+
+class GvizBaseFrame(wx.Frame):
+
+    def create_base_ui(self):
+        self.CreateStatusBar(1)
+        self.SetStatusText(_("Layer number and Z position show here when you scroll"))
+
+        hpanel = wx.Panel(self, -1)
+        hbox = wx.BoxSizer(wx.HORIZONTAL)
+
+        panel = wx.Panel(hpanel, -1)
+        vbox = wx.BoxSizer(wx.VERTICAL)
+
+        vbox = wx.BoxSizer(wx.VERTICAL)
+        self.toolbar = wx.ToolBar(panel, -1, style = wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_HORZ_TEXT)
+        self.toolbar.AddSimpleTool(1, wx.Image(imagefile('zoom_in.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap(), _("Zoom In [+]"), '')
+        self.toolbar.AddSimpleTool(2, wx.Image(imagefile('zoom_out.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap(), _("Zoom Out [-]"), '')
+        self.toolbar.AddSeparator()
+        self.toolbar.AddSimpleTool(3, wx.Image(imagefile('arrow_up.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap(), _("Move Up a Layer [U]"), '')
+        self.toolbar.AddSimpleTool(4, wx.Image(imagefile('arrow_down.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap(), _("Move Down a Layer [D]"), '')
+        self.toolbar.AddLabelTool(5, " " + _("Reset view"), wx.Image(imagefile('reset.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap(), shortHelp = _("Reset view"), longHelp = '')
+        self.toolbar.AddSeparator()
+        self.toolbar.AddSimpleTool(6, wx.Image(imagefile('inject.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap(), shortHelpString = _("Inject G-Code"), longHelpString = _("Insert code at the beginning of this layer"))
+        self.toolbar.AddSimpleTool(7, wx.Image(imagefile('edit.png'), wx.BITMAP_TYPE_PNG).ConvertToBitmap(), shortHelpString = _("Edit layer"), longHelpString = _("Edit the G-Code of this layer"))
+
+        vbox.Add(self.toolbar, 0, border = 5)
+
+        panel.SetSizer(vbox)
+
+        hbox.Add(panel, 1, flag = wx.EXPAND)
+        self.layerslider = wx.Slider(hpanel, style = wx.SL_VERTICAL | wx.SL_AUTOTICKS | wx.SL_LEFT | wx.SL_INVERSE)
+        self.layerslider.Bind(wx.EVT_SCROLL, self.process_slider)
+        hbox.Add(self.layerslider, 0, border = 5, flag = wx.LEFT | wx.EXPAND)
+        hpanel.SetSizer(hbox)
+
+        return panel, vbox
+
+    def setlayercb(self, layer):
+        self.layerslider.SetValue(layer)
+
+    def process_slider(self, event):
+        raise NotImplementedError
+
+ID_ABOUT = 101
+ID_EXIT = 110
+class GvizWindow(GvizBaseFrame):
+    def __init__(self, f = None, size = (600, 600), build_dimensions = [200, 200, 100, 0, 0, 0], grid = (10, 50), extrusion_width = 0.5, bgcolor = "#000000"):
+        super(GvizWindow, self).__init__(None, title = _("Gcode view, shift to move view, mousewheel to set layer"), size = size)
+
+        panel, vbox = self.create_base_ui()
+
+        self.p = Gviz(panel, size = size, build_dimensions = build_dimensions, grid = grid, extrusion_width = extrusion_width, bgcolor = bgcolor, realparent = self)
+
+        self.toolbar.Realize()
+        vbox.Add(self.p, 1, wx.EXPAND)
+
+        self.SetMinSize(self.ClientToWindowSize(vbox.GetMinSize()))
+        self.Bind(wx.EVT_TOOL, lambda x: self.p.zoom(-1, -1, 1.2), id = 1)
+        self.Bind(wx.EVT_TOOL, lambda x: self.p.zoom(-1, -1, 1 / 1.2), id = 2)
+        self.Bind(wx.EVT_TOOL, lambda x: self.p.layerup(), id = 3)
+        self.Bind(wx.EVT_TOOL, lambda x: self.p.layerdown(), id = 4)
+        self.Bind(wx.EVT_TOOL, self.resetview, id = 5)
+        self.Bind(wx.EVT_TOOL, lambda x: self.p.inject(), id = 6)
+        self.Bind(wx.EVT_TOOL, lambda x: self.p.editlayer(), id = 7)
+
+        self.initpos = None
+        self.p.Bind(wx.EVT_KEY_DOWN, self.key)
+        self.Bind(wx.EVT_KEY_DOWN, self.key)
+        self.p.Bind(wx.EVT_MOUSEWHEEL, self.zoom)
+        self.Bind(wx.EVT_MOUSEWHEEL, self.zoom)
+        self.p.Bind(wx.EVT_MOUSE_EVENTS, self.mouse)
+        self.Bind(wx.EVT_MOUSE_EVENTS, self.mouse)
+
+        if f:
+            gcode = gcoder.GCode(f, get_home_pos(self.p.build_dimensions))
+            self.p.addfile(gcode)
+
+    def set_current_gline(self, gline):
+        return
+
+    def process_slider(self, event):
+        self.p.layerindex = self.layerslider.GetValue()
+        z = self.p.get_currentz()
+        wx.CallAfter(self.SetStatusText, _("Layer %d - Z = %.03f mm") % (self.p.layerindex + 1, z), 0)
+        self.p.dirty = True
+        wx.CallAfter(self.p.Refresh)
+
+    def resetview(self, event):
+        self.p.translate = [0.0, 0.0]
+        self.p.scale = self.p.basescale
+        self.p.zoom(0, 0, 1.0)
+
+    def mouse(self, event):
+        if event.ButtonUp(wx.MOUSE_BTN_LEFT) or event.ButtonUp(wx.MOUSE_BTN_RIGHT):
+            if self.initpos is not None:
+                self.initpos = None
+        elif event.Dragging():
+            e = event.GetPositionTuple()
+            if self.initpos is None:
+                self.initpos = e
+                self.basetrans = self.p.translate
+            self.p.translate = [self.basetrans[0] + (e[0] - self.initpos[0]),
+                                self.basetrans[1] + (e[1] - self.initpos[1])]
+            self.p.dirty = True
+            wx.CallAfter(self.p.Refresh)
+        else:
+            event.Skip()
+
+    def key(self, event):
+        #  Keycode definitions
+        kup = [85, 315]               # Up keys
+        kdo = [68, 317]               # Down Keys
+        kzi = [388, 316, 61]        # Zoom In Keys
+        kzo = [390, 314, 45]       # Zoom Out Keys
+        x = event.GetKeyCode()
+        cx, cy = self.p.translate
+        if x in kup:
+            self.p.layerup()
+        if x in kdo:
+            self.p.layerdown()
+        if x in kzi:
+            self.p.zoom(cx, cy, 1.2)
+        if x in kzo:
+            self.p.zoom(cx, cy, 1 / 1.2)
+
+    def zoom(self, event):
+        z = event.GetWheelRotation()
+        if event.ShiftDown():
+            if z > 0: self.p.layerdown()
+            elif z < 0: self.p.layerup()
+        else:
+            if z > 0: self.p.zoom(event.GetX(), event.GetY(), 1.2)
+            elif z < 0: self.p.zoom(event.GetX(), event.GetY(), 1 / 1.2)
+
+class Gviz(wx.Panel):
+
+    # Mark canvas as dirty when setting showall
+    _showall = 0
+
+    def _get_showall(self):
+        return self._showall
+
+    def _set_showall(self, showall):
+        if showall != self._showall:
+            self.dirty = True
+            self._showall = showall
+    showall = property(_get_showall, _set_showall)
+
+    def __init__(self, parent, size = (200, 200), build_dimensions = [200, 200, 100, 0, 0, 0], grid = (10, 50), extrusion_width = 0.5, bgcolor = "#000000", realparent = None):
+        wx.Panel.__init__(self, parent, -1)
+        self.widget = self
+        size = [max(1.0, x) for x in size]
+        ratio = size[0] / size[1]
+        self.SetMinSize((150, 150 / ratio))
+        self.parent = realparent if realparent else parent
+        self.size = size
+        self.build_dimensions = build_dimensions
+        self.grid = grid
+        self.Bind(wx.EVT_PAINT, self.paint)
+        self.Bind(wx.EVT_SIZE, self.resize)
+        self.hilight = deque()
+        self.hilightarcs = deque()
+        self.hilightqueue = Queue(0)
+        self.hilightarcsqueue = Queue(0)
+        self.clear()
+        self.filament_width = extrusion_width  # set it to 0 to disable scaling lines with zoom
+        self.update_basescale()
+        self.scale = self.basescale
+        penwidth = max(1.0, self.filament_width * ((self.scale[0] + self.scale[1]) / 2.0))
+        self.translate = [0.0, 0.0]
+        self.mainpen = wx.Pen(wx.Colour(0, 0, 0), penwidth)
+        self.arcpen = wx.Pen(wx.Colour(255, 0, 0), penwidth)
+        self.travelpen = wx.Pen(wx.Colour(10, 80, 80), penwidth)
+        self.hlpen = wx.Pen(wx.Colour(200, 50, 50), penwidth)
+        self.fades = [wx.Pen(wx.Colour(250 - 0.6 ** i * 100, 250 - 0.6 ** i * 100, 200 - 0.4 ** i * 50), penwidth) for i in xrange(6)]
+        self.penslist = [self.mainpen, self.travelpen, self.hlpen] + self.fades
+        self.bgcolor = wx.Colour()
+        self.bgcolor.SetFromName(bgcolor)
+        self.blitmap = wx.EmptyBitmap(self.GetClientSize()[0], self.GetClientSize()[1], -1)
+        self.paint_overlay = None
+
+    def inject(self):
+        layer = self.layers.index(self.layerindex)
+        injector(self.gcode, self.layerindex, layer)
+
+    def editlayer(self):
+        layer = self.layers.index(self.layerindex)
+        injector_edit(self.gcode, self.layerindex, layer)
+
+    def clearhilights(self):
+        self.hilight.clear()
+        self.hilightarcs.clear()
+        while not self.hilightqueue.empty():
+            self.hilightqueue.get_nowait()
+        while not self.hilightarcsqueue.empty():
+            self.hilightarcsqueue.get_nowait()
+
+    def clear(self):
+        self.gcode = None
+        self.lastpos = [0, 0, 0, 0, 0, 0, 0]
+        self.hilightpos = self.lastpos[:]
+        self.lines = {}
+        self.pens = {}
+        self.arcs = {}
+        self.arcpens = {}
+        self.layers = {}
+        self.layersz = []
+        self.clearhilights()
+        self.layerindex = 0
+        self.showall = 0
+        self.dirty = True
+        self.partial = False
+        self.painted_layers = set()
+        wx.CallAfter(self.Refresh)
+
+    def get_currentz(self):
+        z = self.layersz[self.layerindex]
+        z = 0. if z is None else z
+        return z
+
+    def layerup(self):
+        if self.layerindex + 1 < len(self.layers):
+            self.layerindex += 1
+            z = self.get_currentz()
+            wx.CallAfter(self.parent.SetStatusText, _("Layer %d - Going Up - Z = %.03f mm") % (self.layerindex + 1, z), 0)
+            self.dirty = True
+            self.parent.setlayercb(self.layerindex)
+            wx.CallAfter(self.Refresh)
+
+    def layerdown(self):
+        if self.layerindex > 0:
+            self.layerindex -= 1
+            z = self.get_currentz()
+            wx.CallAfter(self.parent.SetStatusText, _("Layer %d - Going Down - Z = %.03f mm") % (self.layerindex + 1, z), 0)
+            self.dirty = True
+            self.parent.setlayercb(self.layerindex)
+            wx.CallAfter(self.Refresh)
+
+    def setlayer(self, layer):
+        if layer in self.layers:
+            self.clearhilights()
+            self.layerindex = self.layers[layer]
+            self.dirty = True
+            self.showall = 0
+            wx.CallAfter(self.Refresh)
+
+    def update_basescale(self):
+        self.basescale = 2 * [min(float(self.size[0] - 1) / self.build_dimensions[0],
+                                  float(self.size[1] - 1) / self.build_dimensions[1])]
+
+    def resize(self, event):
+        old_basescale = self.basescale
+        width, height = self.GetClientSizeTuple()
+        if width < 1 or height < 1:
+            return
+        self.size = (width, height)
+        self.update_basescale()
+        zoomratio = float(self.basescale[0]) / old_basescale[0]
+        wx.CallLater(200, self.zoom, 0, 0, zoomratio)
+
+    def zoom(self, x, y, factor):
+        if x == -1 and y == -1:
+            side = min(self.size)
+            x = y = side / 2
+        self.scale = [s * factor for s in self.scale]
+
+        self.translate = [x - (x - self.translate[0]) * factor,
+                          y - (y - self.translate[1]) * factor]
+        penwidth = max(1.0, self.filament_width * ((self.scale[0] + self.scale[1]) / 2.0))
+        for pen in self.penslist:
+            pen.SetWidth(penwidth)
+        self.dirty = True
+        wx.CallAfter(self.Refresh)
+
+    def _line_scaler(self, x):
+        return (self.scale[0] * x[0],
+                self.scale[1] * x[1],
+                self.scale[0] * x[2],
+                self.scale[1] * x[3],)
+
+    def _arc_scaler(self, x):
+        return (self.scale[0] * x[0],
+                self.scale[1] * x[1],
+                self.scale[0] * x[2],
+                self.scale[1] * x[3],
+                self.scale[0] * x[4],
+                self.scale[1] * x[5],)
+
+    def _drawlines(self, dc, lines, pens):
+        scaled_lines = map(self._line_scaler, lines)
+        dc.DrawLineList(scaled_lines, pens)
+
+    def _drawarcs(self, dc, arcs, pens):
+        scaled_arcs = map(self._arc_scaler, arcs)
+        dc.SetBrush(wx.TRANSPARENT_BRUSH)
+        for i in range(len(scaled_arcs)):
+            dc.SetPen(pens[i] if type(pens) == list else pens)
+            dc.DrawArc(*scaled_arcs[i])
+
+    def repaint_everything(self):
+        width = self.scale[0] * self.build_dimensions[0]
+        height = self.scale[1] * self.build_dimensions[1]
+        self.blitmap = wx.EmptyBitmap(width + 1, height + 1, -1)
+        dc = wx.MemoryDC()
+        dc.SelectObject(self.blitmap)
+        dc.SetBackground(wx.Brush((250, 250, 200)))
+        dc.Clear()
+        dc.SetPen(wx.Pen(wx.Colour(180, 180, 150)))
+        for grid_unit in self.grid:
+            if grid_unit > 0:
+                for x in xrange(int(self.build_dimensions[0] / grid_unit) + 1):
+                    draw_x = self.scale[0] * x * grid_unit
+                    dc.DrawLine(draw_x, 0, draw_x, height)
+                for y in xrange(int(self.build_dimensions[1] / grid_unit) + 1):
+                    draw_y = self.scale[1] * (self.build_dimensions[1] - y * grid_unit)
+                    dc.DrawLine(0, draw_y, width, draw_y)
+            dc.SetPen(wx.Pen(wx.Colour(0, 0, 0)))
+
+        if not self.showall:
+            # Draw layer gauge
+            dc.SetBrush(wx.Brush((43, 144, 255)))
+            dc.DrawRectangle(width - 15, 0, 15, height)
+            dc.SetBrush(wx.Brush((0, 255, 0)))
+            if self.layers:
+                dc.DrawRectangle(width - 14, (1.0 - (1.0 * (self.layerindex + 1)) / len(self.layers)) * height, 13, height - 1)
+
+        if self.showall:
+            for i in range(len(self.layersz)):
+                self.painted_layers.add(i)
+                self._drawlines(dc, self.lines[i], self.pens[i])
+                self._drawarcs(dc, self.arcs[i], self.arcpens[i])
+            dc.SelectObject(wx.NullBitmap)
+            return
+
+        if self.layerindex < len(self.layers) and self.layerindex in self.lines:
+            for layer_i in range(max(0, self.layerindex - 6), self.layerindex):
+                self._drawlines(dc, self.lines[layer_i], self.fades[self.layerindex - layer_i - 1])
+                self._drawarcs(dc, self.arcs[layer_i], self.fades[self.layerindex - layer_i - 1])
+            self._drawlines(dc, self.lines[self.layerindex], self.pens[self.layerindex])
+            self._drawarcs(dc, self.arcs[self.layerindex], self.arcpens[self.layerindex])
+
+        self._drawlines(dc, self.hilight, self.hlpen)
+        self._drawarcs(dc, self.hilightarcs, self.hlpen)
+
+        self.paint_hilights(dc)
+
+        dc.SelectObject(wx.NullBitmap)
+
+    def repaint_partial(self):
+        if self.showall:
+            dc = wx.MemoryDC()
+            dc.SelectObject(self.blitmap)
+            for i in set(range(len(self.layersz))).difference(self.painted_layers):
+                self.painted_layers.add(i)
+                self._drawlines(dc, self.lines[i], self.pens[i])
+                self._drawarcs(dc, self.arcs[i], self.arcpens[i])
+            dc.SelectObject(wx.NullBitmap)
+
+    def paint_hilights(self, dc = None):
+        if self.hilightqueue.empty() and self.hilightarcsqueue.empty():
+            return
+        hl = []
+        if not dc:
+            dc = wx.MemoryDC()
+            dc.SelectObject(self.blitmap)
+        while not self.hilightqueue.empty():
+            hl.append(self.hilightqueue.get_nowait())
+        self._drawlines(dc, hl, self.hlpen)
+        hlarcs = []
+        while not self.hilightarcsqueue.empty():
+            hlarcs.append(self.hilightarcsqueue.get_nowait())
+        self._drawarcs(dc, hlarcs, self.hlpen)
+        dc.SelectObject(wx.NullBitmap)
+
+    def paint(self, event):
+        if self.dirty:
+            self.dirty = False
+            self.partial = False
+            self.repaint_everything()
+        elif self.partial:
+            self.partial = False
+            self.repaint_partial()
+        self.paint_hilights()
+        dc = wx.PaintDC(self)
+        dc.SetBackground(wx.Brush(self.bgcolor))
+        dc.Clear()
+        dc.DrawBitmap(self.blitmap, self.translate[0], self.translate[1])
+        if self.paint_overlay:
+            self.paint_overlay(dc)
+
+    def addfile_perlayer(self, gcode, showall = False):
+        self.clear()
+        self.gcode = gcode
+        self.showall = showall
+        generator = self.add_parsed_gcodes(gcode)
+        generator_output = generator.next()
+        while generator_output is not None:
+            yield generator_output
+            generator_output = generator.next()
+        max_layers = len(self.layers)
+        if hasattr(self.parent, "layerslider"):
+            self.parent.layerslider.SetRange(0, max_layers - 1)
+            self.parent.layerslider.SetValue(0)
+        yield None
+
+    def addfile(self, gcode = None, showall = False):
+        generator = self.addfile_perlayer(gcode, showall)
+        while generator.next() is not None:
+            continue
+
+    def _get_movement(self, start_pos, gline):
+        """Takes a start position and a gcode, and returns a 3-uple containing
+        (final position, line, arc), with line and arc being None if not
+        used"""
+        target = start_pos[:]
+        target[5] = 0.0
+        target[6] = 0.0
+        if gline.current_x is not None: target[0] = gline.current_x
+        if gline.current_y is not None: target[1] = gline.current_y
+        if gline.current_z is not None: target[2] = gline.current_z
+        if gline.e is not None:
+            if gline.relative_e:
+                target[3] += gline.e
+            else:
+                target[3] = gline.e
+        if gline.f is not None: target[4] = gline.f
+        if gline.i is not None: target[5] = gline.i
+        if gline.j is not None: target[6] = gline.j
+
+        if gline.command in ["G0", "G1"]:
+            line = [self._x(start_pos[0]),
+                    self._y(start_pos[1]),
+                    self._x(target[0]),
+                    self._y(target[1])]
+            return target, line, None
+        elif gline.command in ["G2", "G3"]:
+            # startpos, endpos, arc center
+            arc = [self._x(start_pos[0]), self._y(start_pos[1]),
+                   self._x(target[0]), self._y(target[1]),
+                   self._x(start_pos[0] + target[5]), self._y(start_pos[1] + target[6])]
+            if gline.command == "G2":  # clockwise, reverse endpoints
+                arc[0], arc[1], arc[2], arc[3] = arc[2], arc[3], arc[0], arc[1]
+            return target, None, arc
+
+    def _y(self, y):
+        return self.build_dimensions[1] - (y - self.build_dimensions[4])
+
+    def _x(self, x):
+        return x - self.build_dimensions[3]
+
+    def add_parsed_gcodes(self, gcode):
+        start_time = time.time()
+
+        layer_idx = 0
+        while layer_idx < len(gcode.all_layers):
+            layer = gcode.all_layers[layer_idx]
+            has_move = False
+            for gline in layer:
+                if gline.is_move:
+                    has_move = True
+                    break
+            if not has_move:
+                yield layer_idx
+                layer_idx += 1
+                continue
+            viz_layer = len(self.layers)
+            self.lines[viz_layer] = []
+            self.pens[viz_layer] = []
+            self.arcs[viz_layer] = []
+            self.arcpens[viz_layer] = []
+            for gline in layer:
+                if not gline.is_move:
+                    continue
+
+                target, line, arc = self._get_movement(self.lastpos[:], gline)
+
+                if line is not None:
+                    self.lines[viz_layer].append(line)
+                    self.pens[viz_layer].append(self.mainpen if target[3] != self.lastpos[3] else self.travelpen)
+                elif arc is not None:
+                    self.arcs[viz_layer].append(arc)
+                    self.arcpens[viz_layer].append(self.arcpen)
+
+                self.lastpos = target
+            # Transform into a numpy array for memory efficiency
+            self.lines[viz_layer] = numpy.asarray(self.lines[viz_layer], dtype = numpy.float32)
+            self.pens[viz_layer] = numpy.asarray(self.pens[viz_layer])
+            self.arcs[viz_layer] = numpy.asarray(self.arcs[viz_layer], dtype = numpy.float32)
+            self.arcpens[viz_layer] = numpy.asarray(self.arcpens[viz_layer])
+            # Only add layer to self.layers now to prevent the display of an
+            # unfinished layer
+            self.layers[layer_idx] = viz_layer
+            self.layersz.append(layer.z)
+
+            # Refresh display if more than 0.2s have passed
+            if time.time() - start_time > 0.2:
+                start_time = time.time()
+                self.partial = True
+                wx.CallAfter(self.Refresh)
+
+            yield layer_idx
+            layer_idx += 1
+
+        self.dirty = True
+        wx.CallAfter(self.Refresh)
+        yield None
+
+    def addgcodehighlight(self, gline):
+        if gline.command not in ["G0", "G1", "G2", "G3"]:
+            return
+
+        target, line, arc = self._get_movement(self.hilightpos[:], gline)
+
+        if line is not None:
+            self.hilight.append(line)
+            self.hilightqueue.put_nowait(line)
+        elif arc is not None:
+            self.hilightarcs.append(arc)
+            self.hilightarcsqueue.put_nowait(arc)
+
+        self.hilightpos = target
+        wx.CallAfter(self.Refresh)
+
+if __name__ == '__main__':
+    import sys
+    app = wx.App(False)
+    main = GvizWindow(open(sys.argv[1], "rU"))
+    main.Show()
+    app.MainLoop()

mercurial