def generateBuffer(self):
        self.context = GCodeContext(self.getFloatOption('xyFeedrate'),
                                    self.getFloatOption('zFeedrate'),
                                    self.getFloatOption('startDelay'),
                                    self.getFloatOption('stopDelay'),
                                    self.getFloatOption('penUpAngle'),
                                    self.getFloatOption('penDownAngle'),
                                    self.getFloatOption('zHeight'),
                                    self.getFloatOption('finishedHeight'),
                                    self.getFloatOption('xHome'),
                                    self.getFloatOption('yHome'),
                                    self.getBooleanOption('registerPen'),
                                    self.getIntOption('numCopies'),
                                    self.getBooleanOption('continuous'),
                                    self.svg_file)

        makeict_foamcutter.svg_parser.curveFlatness = self.getFloatOption(
            'curveFlatness')

        parser = SvgParser(self.document.getroot(),
                           self.getBooleanOption('pauseOnLayerChange'))
        parser.parse()
        for entity in parser.entities:
            entity.get_gcode(self.context)

        return self.context.generate()
class MyEffect(inkex.Effect):
    def __init__(self, ports):
        inkex.Effect.__init__(self)

        self.configFile = 'makeict_foamcutter/config.ini'
        self.config = ConfigParser.ConfigParser({
            'serialPort': '/dev/ttyUSB0',
            'serialBaudRate': '9600',
            'flowControl': 'None',
            'penUpAngle': '180.0',
            'penDownAngle': '0.0',
            'startDelay': '500.0',
            'stopDelay': '500.0',
            'xyFeedrate': '500.0',
            'zFeedrate': '150.0',
            'zHeight': '0.0',
            'finishedHeight': '0.0',
            'registerPen': 'False',
            'xHome': '0.0',
            'yHome': '0.0',
            'numCopies': '1',
            'continuous': 'False',
            'pauseOnLayerChange': 'False',
            'curveFlatness': '0.2',
        })
        self.config.read(self.configFile)
        self.preset = "Foam Cutter Defaults"
        try:
            self.config.add_section(self.preset)
        except:
            pass

        self.serial = None
        self.pos = [0, 0]

        self.ports = ports
        self.backgroundColors = {}
        self.paused = False
        self.stopped = False

        self.allControls = []

    '''
		Dealing with options
	'''

    def getOption(self, option):
        return self.config.get(self.preset, option)

    def getFloatOption(self, option):
        return float(self.getOption(option))

    def getIntOption(self, option):
        return int(self.getOption(option))
        self.backgroundColors = {}

    def getBooleanOption(self, option):
        return self.getOption(option) in [
            'True', 'true', '1', 'Yes', 'yes', 'T', 't', 'Y', 'y'
        ]

    def setOption(self, option, value):
        return self.config.set(self.preset, option, str(value))

    def saveOptions(self):
        for controlInfo in self.allControls:
            if isinstance(controlInfo['control'], gtk.ComboBox):
                self.setOption(controlInfo['id'],
                               controlInfo['control'].get_active_text())
            else:
                self.setOption(controlInfo['id'],
                               controlInfo['control'].get_value())

        f = open(self.configFile, 'w')
        self.config.write(f)
        f.close()

    def optionChanged(self, widget, data=None):
        # saveOptions will update values from ALL controls, every time. Seems silly, but some updates are being lost
        self.saveOptions()

    '''
		Build and display GUI
	'''

    def effect(self):
        versionString = self.document.getroot().get(
            '{http://www.inkscape.org/namespaces/inkscape}version')
        if versionString is None or versionString == '':
            raise Exception('Please save your file first.')

        gobject.threads_init()
        self.buildMainGUI()

        self.autoConnectDialog = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.connectLabel = gtk.Label("Trying to connect to device...")
        self.connectLabel.set_padding(50, 50)
        self.connectLabel.set_justify(gtk.JUSTIFY_CENTER)
        self.autoConnectDialog.add(self.connectLabel)
        self.autoConnectDialog.set_position(gtk.WIN_POS_CENTER)
        self.autoConnectDialog.show_all()
        self.pauseAndStopButtons.hide_all()
        threading.Thread(target=self.autoConnect).start()

        gtk.main()

    def _updateConnectLabel(self):
        self.connectLabel.set_markup(
            "<b>Attempting to connect...</b>\n\n%s\n%s bps" %
            (self.getOption('serialPort'), self.getOption('serialBaudRate')))

    '''
		Auto-connect
		this runs in a separate thread
	'''

    def autoConnect(self):
        for i, p in enumerate(self.ports):
            self.portSelector.set_active(i)
            gobject.idle_add(self._updateConnectLabel)
            try:
                self.connect()
                self.connectButton.set_label("Disconnect")
                self.controls.show_all()
                self.pauseAndStopButtons.hide_all()
                break
            except Exception as exc:
                pass

        gobject.idle_add(self.autoConnectDialog.hide)
        if self.serial is None or self.serial == None:
            gobject.idle_add(self.controls.hide)
            gobject.idle_add(self.notebook.set_current_page, 2)

            def showErrorThenWindow():
                self.showError(
                    "Auto-connect failed. Is the device connected and enabled?"
                )
                self._showMainWindow()

            gobject.idle_add(showErrorThenWindow)
        else:
            gobject.idle_add(self._showMainWindow)

    def _showMainWindow(self):
        self.window.set_position(gtk.WIN_POS_CENTER)
        self.window.maximize()
        self.window.show_all()
        self.pauseAndStopButtons.hide_all()

    def buildMainGUI(self):
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_title("MakeICT Foam Cutter Sender")
        #self.window.set_keep_above(True)

        self.window.set_default_size(640, 640)
        self.window.connect("destroy", self.destroy)
        self.window.set_border_width(10)

        self.notebook = gtk.Notebook()
        self.notebook.append_page(self._buildControlsPage(),
                                  gtk.Label("Controls"))
        self.notebook.append_page(self._buildPlotterSetupPage(),
                                  gtk.Label("Setup"))
        self.notebook.append_page(self._buildSerialControlsPage(),
                                  gtk.Label("Port options"))
        self.notebook.append_page(self._buildGCodeLogPage(),
                                  gtk.Label("GCode Tools"))
        self.notebook.append_page(self._buildAboutPage(), gtk.Label("About"))
        self.window.add(self.notebook)

    '''
		Basic Controls Page
	'''

    def _buildControlsPage(self):
        basicControlsPage = gtk.VBox(False, 10)

        stepSize = 1
        self.controls = gtk.VBox(False, 10)

        arrows = gtk.Table(5, 5, True)
        b = gtk.Button("⇑")
        b.connect("clicked", self.moveY, 10 * stepSize)
        arrows.attach(b, 2, 3, 0, 1)

        b = gtk.Button("↑")
        b.connect("clicked", self.moveY, stepSize)
        arrows.attach(b, 2, 3, 1, 2)

        b = gtk.Button("⇐")
        b.connect("clicked", self.moveX, 10 * stepSize)
        arrows.attach(b, 0, 1, 2, 3)

        b = gtk.Button("←")
        b.connect("clicked", self.moveX, stepSize)
        arrows.attach(b, 1, 2, 2, 3)

        b = gtk.Button("→")
        b.connect("clicked", self.moveX, -stepSize)
        arrows.attach(b, 3, 4, 2, 3)

        b = gtk.Button("⇒")
        b.connect("clicked", self.moveX, -10 * stepSize)
        arrows.attach(b, 4, 5, 2, 3)

        b = gtk.Button("↓")
        b.connect("clicked", self.moveY, -stepSize)
        arrows.attach(b, 2, 3, 3, 4)

        b = gtk.Button("⇓")
        b.connect("clicked", self.moveY, -10 * stepSize)
        arrows.attach(b, 2, 3, 4, 5)

        self.controls.add(arrows)

        self.homeButtons = gtk.Frame()
        box = gtk.HBox(True)
        self.setHomeButton = gtk.Button("Set Home")
        self.setHomeButton.connect("clicked", self.setHome, None)
        box.add(self.setHomeButton)

        self.goHomeButton = gtk.Button("Go Home")
        self.goHomeButton.connect("clicked", self.goHome, None)
        box.add(self.goHomeButton)

        self.homeButtons.add(box)

        #self.controls.add(self.homeButtons)
        self.controls.pack_start(self.homeButtons, False, False)

        sendButtons = gtk.HBox(True)
        #		b = gtk.Button("▶ Selection")
        #		b.connect("clicked", self.sendSelection, None)
        #		sendButtons.add(b)

        self.sendDrawingButton = gtk.Button("▶ Send drawing")
        self.sendDrawingButton.connect("clicked", self.sendAll, None)
        sendButtons.add(self.sendDrawingButton)

        self.controls.pack_start(sendButtons, False, False)

        self.progressBar = gtk.ProgressBar()
        self.progressBar.set_text(" ")
        #		self.controls.add(self.progressBar)
        self.controls.pack_start(self.progressBar, False, False)

        basicControlsPage.add(self.controls)

        self.pauseAndStopButtons = gtk.HBox(True)
        self.pauseButton = gtk.Button("▌▌ Pause")
        self.pauseButton.connect("clicked", self.togglePause, None)
        self.pauseAndStopButtons.add(self.pauseButton)
        self.stopButton = gtk.Button("■ Stop")
        self.stopButton.connect("clicked", self.stop, None)
        self.pauseAndStopButtons.add(self.stopButton)
        basicControlsPage.add(self.pauseAndStopButtons)

        self.pauseButton.modify_bg(gtk.STATE_NORMAL,
                                   gtk.gdk.color_parse('#440'))
        self.pauseButton.modify_bg(gtk.STATE_PRELIGHT,
                                   gtk.gdk.color_parse('#440'))

        self.stopButton.modify_bg(gtk.STATE_NORMAL,
                                  gtk.gdk.color_parse('#f00'))
        self.stopButton.modify_bg(gtk.STATE_PRELIGHT,
                                  gtk.gdk.color_parse('#f00'))

        return basicControlsPage

    '''
		Serial controls
	'''

    def _buildSerialControlsPage(self):
        serialControlsPage = gtk.VBox(False, 10)

        self.portSelector = gtk.combo_box_new_text()
        for i, p in enumerate(self.ports):
            self.portSelector.append_text(p)
            if p == self.getOption('serialPort'):
                self.portSelector.set_active(i)

        bauds = [
            110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400,
            56000, 57600, 115200
        ]
        baudSelector = gtk.combo_box_new_text()
        for i, rate in enumerate(bauds):
            baudSelector.append_text("%s" % rate)
            if rate == self.getFloatOption('serialBaudRate'):
                baudSelector.set_active(i)

        flowOptions = ["None", "XON/XOFF", "RTS/CTS", "DSR/DTR + RTS/CTS"]
        flowControlSelector = gtk.combo_box_new_text()
        for i, opt in enumerate(flowOptions):
            flowControlSelector.append_text("%s" % opt)
            if opt == self.getOption('flowControl'):
                flowControlSelector.set_active(i)

        controls = [
            {
                "label": "Serial Port",
                "id": "serialPort",
                "control": self.portSelector,
            },
            {
                "label": "Baud Rate",
                "id": "serialBaudRate",
                "control": baudSelector,
            },
            {
                "label": "Flow Control",
                "id": "flowControl",
                "control": flowControlSelector,
            },
        ]

        serialOptions = gtk.Table(3, 2, False)
        for i, c in enumerate(controls):
            serialOptions.attach(gtk.Label(c['label']), 0, 1, i, i + 1)
            serialOptions.attach(c['control'], 1, 2, i, i + 1)
            c['control'].connect('changed', self.optionChanged, c)
            c['control'].connect('focus-out-event', self.optionChanged, c)
            #c['control'].connect('value-changed', self.optionChanged, c)

        self.allControls.extend(controls)

        serialControlsPage.pack_start(serialOptions, False, False)

        self.connectButton = gtk.Button("Connect")
        self.connectButton.connect("clicked", self.toggleConnect)
        serialControlsPage.pack_start(self.connectButton, False, False)

        return serialControlsPage

    '''
		Plotter Setup
	'''

    def _buildPlotterSetupPage(self):
        setupPage = gtk.Table(8, 2, False)

        gtk.Adjustment(value=0,
                       lower=0,
                       upper=0,
                       step_incr=0,
                       page_incr=0,
                       page_size=0)

        controls = [
            {
                "label": "Pen up angle",
                "id": "penUpAngle",
                "control":
                gtk.SpinButton(gtk.Adjustment(180, 0, 180, 1, 10, 0)),
            },
            {
                "label": "Pen down angle",
                "id": "penDownAngle",
                "control": gtk.SpinButton(gtk.Adjustment(0, 0, 180, 1, 10, 0)),
            },
            {
                "label":
                "Start delay",
                "id":
                "startDelay",
                "control":
                gtk.SpinButton(gtk.Adjustment(500, 0, 1000, 10, 100, 0)),
            },
            {
                "label":
                "Stop delay",
                "id":
                "stopDelay",
                "control":
                gtk.SpinButton(gtk.Adjustment(500, 0, 1000, 10, 100, 0)),
            },
            {
                "label":
                "X-Y feedrate",
                "id":
                "xyFeedrate",
                "control":
                gtk.SpinButton(gtk.Adjustment(500, 100, 5000, 10, 100, 0)),
            },
            {
                "label":
                "Curve flatness",
                "id":
                "curveFlatness",
                "control":
                gtk.SpinButton(gtk.Adjustment(0.2, .01, 10.0, 0.01, .1, 0),
                               digits=2),
                #			},{
                #				"label": "Z feedrate",
                #				"control": gtk.SpinButton(gtk.Adjustment(500, 0, 1000, 10, 100, 0)),
                #			},{
                #				"label": "Z print height",
                #				"control": gtk.SpinButton(gtk.Adjustment(0, 0, 110, 1, 10, 0)),
                #			},{
                #				"label": "Z finish height",
                #				"control": gtk.SpinButton(gtk.Adjustment(0, 0, 110, 1, 10, 0)),
            }
        ]

        for i, c in enumerate(controls):
            setupPage.attach(gtk.Label(c['label']), 0, 1, i, i + 1)
            setupPage.attach(c['control'], 1, 2, i, i + 1)
            c['control'].set_value(self.getFloatOption(c['id']))
            c['control'].connect('value-changed', self.optionChanged, c)

        self.allControls.extend(controls)

        return setupPage

    '''
		GCode Log
	'''

    def _buildGCodeLogPage(self):
        gcodeLogPage = gtk.VBox(False, 10)

        buttons = gtk.Table(1, 2, True)

        button = gtk.Button("Clear")
        button.connect("clicked", self.clearGCodeLog, None)
        buttons.attach(button, 0, 1, 0, 1)

        button = gtk.Button("Insert drawing")
        button.connect("clicked", self.addDocumentToGCodeLog, None)
        buttons.attach(button, 1, 2, 0, 1)

        gcodeLogPage.pack_start(buttons, False, False)

        self.gcodeLog = gtk.TextView()
        scroll = gtk.ScrolledWindow()
        scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scroll.add(self.gcodeLog)
        gcodeLogPage.add(scroll)

        b = gtk.Button("▶ Send GCode")
        b.connect("clicked", self.sendGCode, None)
        gcodeLogPage.pack_start(b, False, False)

        return gcodeLogPage

    '''
		About Page
	'''

    def _buildAboutPage(self):
        text = 'Software by:\n\n'
        text = text + '  • Dominic Canare &lt;<a href="mailto:[email protected]">[email protected]</a>&gt;\n'
        text = text + '  • Tom McGuire &lt;<a href="*****@*****.**">[email protected]</a>&gt;\n'
        text = text + '\n<a href="http://github.com/makeict/inkscape-foamcutter">github.com/makeict/inkscape-foamcutter</a>\n'

        label = gtk.Label()
        label.set_markup(text)
        label.set_line_wrap(True)

        return label

    def togglePause(self, widget=None, data=None):
        self.paused = not self.paused
        if self.paused:
            self.pauseButton.set_label("▶ Resume")
        else:
            self.pauseButton.set_label("▌▌ Pause")

    def stop(self, widget=None, data=None):
        self.stopped = True

    def clearGCodeLog(self, widget=None, data=None):
        self.gcodeLog.get_buffer().set_text("")

    def addDocumentToGCodeLog(self, widget=None, data=None):
        code = "\n".join(self.generateBuffer())
        self.gcodeLog.get_buffer().insert_at_cursor(code)

    def highlight(self, widget, color="#a00"):
        map = widget.get_colormap()
        color = map.alloc_color(color)

        #copy the current style and replace the background
        style = widget.get_style().copy()

        if not widget in self.backgroundColors:
            self.backgroundColors[widget] = style.bg[gtk.STATE_NORMAL]

        style.bg[gtk.STATE_NORMAL] = color

        #set the button's style to the one you created
        widget.set_style(style)

    def dehighlight(self, widget, color="#000"):
        if widget in self.backgroundColors:
            self.highlight(widget, self.backgroundColors[widget])
        else:
            self.highlight(widget, color)

    def generateBuffer(self):
        self.context = GCodeContext(self.getFloatOption('xyFeedrate'),
                                    self.getFloatOption('zFeedrate'),
                                    self.getFloatOption('startDelay'),
                                    self.getFloatOption('stopDelay'),
                                    self.getFloatOption('penUpAngle'),
                                    self.getFloatOption('penDownAngle'),
                                    self.getFloatOption('zHeight'),
                                    self.getFloatOption('finishedHeight'),
                                    self.getFloatOption('xHome'),
                                    self.getFloatOption('yHome'),
                                    self.getBooleanOption('registerPen'),
                                    self.getIntOption('numCopies'),
                                    self.getBooleanOption('continuous'),
                                    self.svg_file)

        makeict_foamcutter.svg_parser.curveFlatness = self.getFloatOption(
            'curveFlatness')

        parser = SvgParser(self.document.getroot(),
                           self.getBooleanOption('pauseOnLayerChange'))
        parser.parse()
        for entity in parser.entities:
            entity.get_gcode(self.context)

        return self.context.generate()

    def readFromSerial(self, byteCount=1, timeout=30):
        startTime = time.time()
        serialBuffer = ''
        while len(serialBuffer) < byteCount:
            if (time.time() - startTime) >= timeout:
                raise Exception("Timed out waiting for response :(")

            serialBuffer += self.serial.read()
        return serialBuffer

    def send(self, message):
        if self.serial is None:
            self.disconnect()
            raise Exception("Not connected :(")

        self.serial.write("%s\n" % message)
        ok = self.readFromSerial(2)

        if ok != "ok":
            raise Exception("Invalid response: '%s'" % ok)

    def destroy(self, widget, data=None):
        self.disconnect()
        self.stopped = True
        gtk.main_quit()

    def toggleConnect(self, widget, data=None):
        if self.serial is None:
            try:
                self.connect()
                self.connectButton.set_label("Disconnect")
                self.controls.show_all()
                self.notebook.set_current_page(0)
            except Exception as exc:
                self.showError(exc)
        else:
            self.disconnect()
            self.connectButton.set_label("Connect")
            self.controls.hide()

    def connect(self, timeout=0):
        try:
            if self.serial:
                self.serial.close()

            self.serial = serial.Serial()
            self.serial.port = self.getOption('serialPort')
            self.serial.baudrate = self.getFloatOption('serialBaudRate')
            self.serial.timeout = timeout

            if self.getOption('flowControl') == 'XON/XOFF':
                self.serial.xonxoff = True
            if self.getOption('flowControl') == 'RTS/CTS' or self.getOption(
                    'flowControl') == 'DSR/DTR + RTS/CTS':
                self.serial.rtscts = True
            if self.getOption('flowControl') == 'DSR/DTR + RTS/CTS':
                self.serial.dsrdtr = True

            self.serial.open()
            initString = self.readFromSerial(19, 5)
            if initString != "MakeICT Foam Cutter":
                s = "Invalid init string: '%s'" % initString
                raise Exception(s)

            # don't change the serial.timeout value here. You will die.
            self.send("G21 (metric ftw)")
            self.send("G90 (absolute mode)")
            self.send("G92 X%0.2f Y%0.2f" % (self.pos[0], self.pos[1]))

        except Exception as exc:
            self.serial = None
            raise exc

    def disconnect(self):
        try:
            self.serial.flush()
            self.serial.close()
        except:
            pass
        finally:
            self.serial = None

    def showError(self, msg):
        msg = str(msg)

        message = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
                                    buttons=gtk.BUTTONS_CLOSE,
                                    message_format="Error: %s" % msg)
        message.run()
        message.destroy()

    def setHome(self, widget=None, data=None):
        try:
            self.pos = [0, 0]
            self.send("G92 X0.0 Y0.0")
            self.updatePosition()
        except Exception as exc:
            self.showError(exc)

    def goHome(self, widget=None, data=None):
        try:
            self.pos = [0, 0]
            self.updatePosition()
        except Exception as exc:
            self.showError(exc)

    def updatePosition(self):
        try:
            if self.pos[0] != 0 or self.pos[1] != 0:
                self.highlight(self.homeButtons)
            else:
                self.dehighlight(self.homeButtons)
            self.send(
                "G1 X%0.2F Y%0.2F F%0.2F" %
                (self.pos[0], self.pos[1], self.getFloatOption('xyFeedrate')))
        except Exception as exc:
            self.showError(exc)

    def moveX(self, widget, data=None):
        step = data
        self.pos[0] += step
        self.updatePosition()

    def moveY(self, widget, data=None):
        step = data
        self.pos[1] += step
        self.updatePosition()

    def sendAll(self, widget=None, data=None):
        self.stopped = False
        self.paused = False
        self.disableControls()
        self.progressBar.set_fraction(0.0)
        data = self.generateBuffer()
        t = threading.Thread(target=self._sendLines, args=[data])
        t.start()

    def sendGCode(self, widget=None, data=None):
        self.disableControls()
        self.progressBar.set_fraction(0.0)
        self.notebook.set_current_page(0)

        b = self.gcodeLog.get_buffer()
        data = b.get_text(*b.get_bounds()).split("\n")
        t = threading.Thread(target=self._sendLines, args=[data])
        t.start()

    def _sendLines(self, data):
        try:
            for count, line in enumerate(data):
                gobject.idle_add(self._updateProgressBar,
                                 float(count) / len(data))
                if line != "" and line[0] != "(":
                    while self.paused and not self.stopped:
                        time.sleep(0.25)

                    if self.stopped:
                        self.send("M5")
                        self.highlight(self.homeButtons)
                        break
                    self.send(line)
            if not self.stopped:
                gobject.idle_add(self._updateProgressBar, 1.0)
                self.highlight(self.homeButtons)
                self.dehighlight(self.homeButtons)
        except Exception as exc:
            gobject.idle_add(self.showError, exc)
        finally:
            gobject.idle_add(self.enableControls)

    def _updateProgressBar(self, fraction):
        self.progressBar.set_text("%d%%" % int(fraction * 100))
        self.progressBar.set_fraction(fraction)

    def disableControls(self):
        self.controls.set_sensitive(False)
        self.pauseButton.set_label("▌▌ Pause")
        self.pauseAndStopButtons.show_all()

    def enableControls(self):
        self.controls.set_sensitive(True)
        self.pauseAndStopButtons.hide_all()