Ver código fonte

- QRCode Tool: added ability to save the generated QRCode as SVG file or PNG file
- QRCode Tool: added the feature to save the PNG file with transparent background
- QRCode Tool: added GUI category in Preferences window

Marius Stanciu 6 anos atrás
pai
commit
97809b6de1
5 arquivos alterados com 542 adições e 17 exclusões
  1. 96 0
      FlatCAMApp.py
  2. 3 0
      README.md
  3. 176 0
      flatcamGUI/PreferencesUI.py
  4. 265 17
      flatcamTools/ToolQRCode.py
  5. 2 0
      tclCommands/TclCommandAlignDrill.py

+ 96 - 0
FlatCAMApp.py

@@ -849,6 +849,17 @@ class App(QtCore.QObject):
             "tools_cr_dh": self.ui.tools2_defaults_form.tools2_checkrules_group.drill_size_cb,
             "tools_cr_dh_val": self.ui.tools2_defaults_form.tools2_checkrules_group.drill_size_entry,
 
+            # QRCode Tool
+            "tools_qrcode_version": self.ui.tools2_defaults_form.tools2_qrcode_group.version_entry,
+            "tools_qrcode_error": self.ui.tools2_defaults_form.tools2_qrcode_group.error_radio,
+            "tools_qrcode_box_size": self.ui.tools2_defaults_form.tools2_qrcode_group.bsize_entry,
+            "tools_qrcode_border_size": self.ui.tools2_defaults_form.tools2_qrcode_group.border_size_entry,
+            "tools_qrcode_qrdata": self.ui.tools2_defaults_form.tools2_qrcode_group.text_data,
+            "tools_qrcode_polarity": self.ui.tools2_defaults_form.tools2_qrcode_group.pol_radio,
+            "tools_qrcode_rounded": self.ui.tools2_defaults_form.tools2_qrcode_group.bb_radio,
+            "tools_qrcode_fill_color": self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_entry,
+            "tools_qrcode_back_color": self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_entry,
+
             # Utilities
             # File associations
             "fa_excellon": self.ui.util_defaults_form.fa_excellon_group.exc_list_text,
@@ -1308,6 +1319,17 @@ class App(QtCore.QObject):
             "tools_cr_dh": True,
             "tools_cr_dh_val": 0.011811,
 
+            # QRCode Tool
+            "tools_qrcode_version": 1,
+            "tools_qrcode_error": 'L',
+            "tools_qrcode_box_size": 3,
+            "tools_qrcode_border_size": 4,
+            "tools_qrcode_qrdata": '',
+            "tools_qrcode_polarity": 'pos',
+            "tools_qrcode_rounded": 's',
+            "tools_qrcode_fill_color": '#000000',
+            "tools_qrcode_back_color": '#FFFFFF',
+
             # Utilities
             # file associations
             "fa_excellon": 'drd, drl, exc, ncd, tap, xln',
@@ -1731,6 +1753,17 @@ class App(QtCore.QObject):
         self.ui.tools_defaults_form.tools_film_group.film_color_button.setStyleSheet(
             "background-color:%s" % str(self.defaults['tools_film_color'])[:7])
 
+        # Init the Tool QRCode colors
+        self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_entry.set_value(
+            self.defaults['tools_qrcode_fill_color'])
+        self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_button.setStyleSheet(
+            "background-color:%s" % str(self.defaults['tools_qrcode_fill_color'])[:7])
+
+        self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_entry.set_value(
+            self.defaults['tools_qrcode_back_color'])
+        self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_button.setStyleSheet(
+            "background-color:%s" % str(self.defaults['tools_qrcode_back_color'])[:7])
+
         # ### End of Data ####
 
         # ##############################################
@@ -2113,11 +2146,22 @@ class App(QtCore.QObject):
             self.on_annotation_fontcolor_button)
 
         # ########## Tools related signals #############
+        # Film Tool
         self.ui.tools_defaults_form.tools_film_group.film_color_entry.editingFinished.connect(
             self.on_film_color_entry)
         self.ui.tools_defaults_form.tools_film_group.film_color_button.clicked.connect(
             self.on_film_color_button)
 
+        # QRCode Tool
+        self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_entry.editingFinished.connect(
+            self.on_qrcode_fill_color_entry)
+        self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_button.clicked.connect(
+            self.on_qrcode_fill_color_button)
+        self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_entry.editingFinished.connect(
+            self.on_qrcode_back_color_entry)
+        self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_button.clicked.connect(
+            self.on_qrcode_back_color_button)
+
         # portability changed signal
         self.ui.general_defaults_form.general_app_group.portability_cb.stateChanged.connect(self.on_portable_checked)
 
@@ -6701,6 +6745,58 @@ class App(QtCore.QObject):
         self.ui.tools_defaults_form.tools_film_group.film_color_entry.set_value(new_val_sel)
         self.defaults['tools_film_color'] = new_val_sel
 
+    def on_qrcode_fill_color_entry(self):
+        self.defaults['tools_qrcode_fill_color'] = \
+            self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_entry.get_value()
+        self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_button.setStyleSheet(
+            "background-color:%s" % str(self.defaults['tools_qrcode_fill_color']))
+
+    def on_qrcode_fill_color_button(self):
+        current_color = QtGui.QColor(self.defaults['tools_qrcode_fill_color'])
+
+        c_dialog = QtWidgets.QColorDialog()
+        fill_color = c_dialog.getColor(initial=current_color)
+
+        if fill_color.isValid() is False:
+            return
+
+        # if new color is different then mark that the Preferences are changed
+        if fill_color != current_color:
+            self.on_preferences_edited()
+
+        self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_button.setStyleSheet(
+            "background-color:%s" % str(fill_color.name()))
+
+        new_val_sel = str(fill_color.name())
+        self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_entry.set_value(new_val_sel)
+        self.defaults['tools_qrcode_fill_color'] = new_val_sel
+
+    def on_qrcode_back_color_entry(self):
+        self.defaults['tools_qrcode_back_color'] = \
+            self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_entry.get_value()
+        self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_button.setStyleSheet(
+            "background-color:%s" % str(self.defaults['tools_qrcode_back_color']))
+
+    def on_qrcode_back_color_button(self):
+        current_color = QtGui.QColor(self.defaults['tools_qrcode_back_color'])
+
+        c_dialog = QtWidgets.QColorDialog()
+        back_color = c_dialog.getColor(initial=current_color)
+
+        if back_color.isValid() is False:
+            return
+
+        # if new color is different then mark that the Preferences are changed
+        if back_color != current_color:
+            self.on_preferences_edited()
+
+        self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_button.setStyleSheet(
+            "background-color:%s" % str(back_color.name()))
+
+        new_val_sel = str(back_color.name())
+        self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_entry.set_value(new_val_sel)
+        self.defaults['tools_qrcode_back_color'] = new_val_sel
+
     def on_splash_changed(self, state):
         settings = QSettings("Open Source", "FlatCAM")
         settings.setValue('splash_screen', 1) if state else settings.setValue('splash_screen', 0)

+ 3 - 0
README.md

@@ -17,6 +17,9 @@ CAD program, and create G-Code for Isolation routing.
 - fixed imports in all TclCommands
 - fixed the requirements.txt and setup_ubuntu.sh files
 - QRCode Tool: change the plot method parameter
+- QRCode Tool: added ability to save the generated QRCode as SVG file or PNG file
+- QRCode Tool: added the feature to save the PNG file with transparent background
+- QRCode Tool: added GUI category in Preferences window
 
 24.10.2019
 

+ 176 - 0
flatcamGUI/PreferencesUI.py

@@ -216,11 +216,15 @@ class Tools2PreferencesUI(QtWidgets.QWidget):
         self.tools2_optimal_group = Tools2OptimalPrefGroupUI()
         self.tools2_optimal_group.setMinimumWidth(220)
 
+        self.tools2_qrcode_group = Tools2QRCodePrefGroupUI()
+        self.tools2_qrcode_group.setMinimumWidth(220)
+
         self.vlay = QtWidgets.QVBoxLayout()
         self.vlay.addWidget(self.tools2_checkrules_group)
         self.vlay.addWidget(self.tools2_optimal_group)
 
         self.vlay1 = QtWidgets.QVBoxLayout()
+        self.vlay1.addWidget(self.tools2_qrcode_group)
 
 
         self.vlay2 = QtWidgets.QVBoxLayout()
@@ -5388,6 +5392,178 @@ class Tools2OptimalPrefGroupUI(OptionsGroupUI):
         self.layout.addStretch()
 
 
+class Tools2QRCodePrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+
+        super(Tools2QRCodePrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("QRCode Tool Options")))
+        self.decimals = 4
+
+        # ## Parameters
+        self.qrlabel = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
+        self.qrlabel.setToolTip(
+            _("A tool to create a QRCode that can be inserted\n"
+              "into a selected Gerber file, or it can be exported as a file.")
+        )
+        self.layout.addWidget(self.qrlabel)
+
+        # ## Grid Layout
+        grid_lay = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid_lay)
+        grid_lay.setColumnStretch(0, 0)
+        grid_lay.setColumnStretch(1, 1)
+
+        # VERSION #
+        self.version_label = QtWidgets.QLabel('%s:' % _("Version"))
+        self.version_label.setToolTip(
+            _("QRCode version can have values from 1 (21x21 boxes)\n"
+              "to 40 (177x177 boxes).")
+        )
+        self.version_entry = FCSpinner()
+        self.version_entry.set_range(1, 40)
+        self.version_entry.setWrapping(True)
+
+        grid_lay.addWidget(self.version_label, 1, 0)
+        grid_lay.addWidget(self.version_entry, 1, 1)
+
+        # ERROR CORRECTION #
+        self.error_label = QtWidgets.QLabel('%s:' % _("Error correction"))
+        self.error_label.setToolTip(
+            _("Parameter that controls the error correction used for the QR Code.\n"
+              "L = maximum 7% errors can be corrected\n"
+              "M = maximum 15% errors can be corrected\n"
+              "Q = maximum 25% errors can be corrected\n"
+              "H = maximum 30% errors can be corrected.")
+        )
+        self.error_radio = RadioSet([{'label': 'L', 'value': 'L'},
+                                     {'label': 'M', 'value': 'M'},
+                                     {'label': 'Q', 'value': 'Q'},
+                                     {'label': 'H', 'value': 'H'}])
+        self.error_radio.setToolTip(
+            _("Parameter that controls the error correction used for the QR Code.\n"
+              "L = maximum 7% errors can be corrected\n"
+              "M = maximum 15% errors can be corrected\n"
+              "Q = maximum 25% errors can be corrected\n"
+              "H = maximum 30% errors can be corrected.")
+        )
+        grid_lay.addWidget(self.error_label, 2, 0)
+        grid_lay.addWidget(self.error_radio, 2, 1)
+
+        # BOX SIZE #
+        self.bsize_label = QtWidgets.QLabel('%s:' % _("Box Size"))
+        self.bsize_label.setToolTip(
+            _("Box size control the overall size of the QRcode\n"
+              "by adjusting the size of each box in the code.")
+        )
+        self.bsize_entry = FCSpinner()
+        self.bsize_entry.set_range(1, 9999)
+        self.bsize_entry.setWrapping(True)
+
+        grid_lay.addWidget(self.bsize_label, 3, 0)
+        grid_lay.addWidget(self.bsize_entry, 3, 1)
+
+        # BORDER SIZE #
+        self.border_size_label = QtWidgets.QLabel('%s:' % _("Border Size"))
+        self.border_size_label.setToolTip(
+            _("Size of the QRCode border. How many boxes thick is the border.\n"
+              "Default value is 4. The width of the clearance around the QRCode.")
+        )
+        self.border_size_entry = FCSpinner()
+        self.border_size_entry.set_range(1, 9999)
+        self.border_size_entry.setWrapping(True)
+
+        grid_lay.addWidget(self.border_size_label, 4, 0)
+        grid_lay.addWidget(self.border_size_entry, 4, 1)
+
+        # Text box
+        self.text_label = QtWidgets.QLabel('%s:' % _("QRCode Data"))
+        self.text_label.setToolTip(
+            _("QRCode Data. Alphanumeric text to be encoded in the QRCode.")
+        )
+        self.text_data = FCTextArea()
+        self.text_data.setPlaceholderText(
+            _("Add here the text to be included in the QRCode...")
+        )
+        grid_lay.addWidget(self.text_label, 5, 0)
+        grid_lay.addWidget(self.text_data, 6, 0, 1, 2)
+
+        # POLARITY CHOICE #
+        self.pol_label = QtWidgets.QLabel('%s:' % _("Polarity"))
+        self.pol_label.setToolTip(
+            _("Choose the polarity of the QRCode.\n"
+              "It can be drawn in a negative way (squares are clear)\n"
+              "or in a positive way (squares are opaque).")
+        )
+        self.pol_radio = RadioSet([{'label': _('Negative'), 'value': 'neg'},
+                                   {'label': _('Positive'), 'value': 'pos'}])
+        self.pol_radio.setToolTip(
+            _("Choose the type of QRCode to be created.\n"
+              "If added on a Silkscreen Gerber file the QRCode may\n"
+              "be added as positive. If it is added to a Copper Gerber\n"
+              "file then perhaps the QRCode can be added as negative.")
+        )
+        grid_lay.addWidget(self.pol_label, 7, 0)
+        grid_lay.addWidget(self.pol_radio, 7, 1)
+
+        # BOUNDING BOX TYPE #
+        self.bb_label = QtWidgets.QLabel('%s:' % _("Bounding Box"))
+        self.bb_label.setToolTip(
+            _("The bounding box, meaning the empty space that surrounds\n"
+              "the QRCode geometry, can have a rounded or a square shape.")
+        )
+        self.bb_radio = RadioSet([{'label': _('Rounded'), 'value': 'r'},
+                                  {'label': _('Square'), 'value': 's'}])
+        self.bb_radio.setToolTip(
+            _("The bounding box, meaning the empty space that surrounds\n"
+              "the QRCode geometry, can have a rounded or a square shape.")
+        )
+        grid_lay.addWidget(self.bb_label, 8, 0)
+        grid_lay.addWidget(self.bb_radio, 8, 1)
+
+        # FILL COLOR #
+        self.fill_color_label = QtWidgets.QLabel('%s:' % _('Fill Color'))
+        self.fill_color_label.setToolTip(
+            _("Set the QRCode fill color (squares color).")
+        )
+        self.fill_color_entry = FCEntry()
+        self.fill_color_button = QtWidgets.QPushButton()
+        self.fill_color_button.setFixedSize(15, 15)
+
+        fill_lay_child = QtWidgets.QHBoxLayout()
+        fill_lay_child.setContentsMargins(0, 0, 0, 0)
+        fill_lay_child.addWidget(self.fill_color_entry)
+        fill_lay_child.addWidget(self.fill_color_button, alignment=Qt.AlignRight)
+        fill_lay_child.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        fill_color_widget = QtWidgets.QWidget()
+        fill_color_widget.setLayout(fill_lay_child)
+
+        grid_lay.addWidget(self.fill_color_label, 9, 0)
+        grid_lay.addWidget(fill_color_widget, 9, 1)
+
+        # BACK COLOR #
+        self.back_color_label = QtWidgets.QLabel('%s:' % _('Back Color'))
+        self.back_color_label.setToolTip(
+            _("Set the QRCode background color.")
+        )
+        self.back_color_entry = FCEntry()
+        self.back_color_button = QtWidgets.QPushButton()
+        self.back_color_button.setFixedSize(15, 15)
+
+        back_lay_child = QtWidgets.QHBoxLayout()
+        back_lay_child.setContentsMargins(0, 0, 0, 0)
+        back_lay_child.addWidget(self.back_color_entry)
+        back_lay_child.addWidget(self.back_color_button, alignment=Qt.AlignRight)
+        back_lay_child.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        back_color_widget = QtWidgets.QWidget()
+        back_color_widget.setLayout(back_lay_child)
+
+        grid_lay.addWidget(self.back_color_label, 10, 0)
+        grid_lay.addWidget(back_color_widget, 10, 1)
+
+
 class FAExcPrefGroupUI(OptionsGroupUI):
     def __init__(self, parent=None):
         # OptionsGroupUI.__init__(self, "Excellon File associations Preferences", parent=None)

+ 265 - 17
flatcamTools/ToolQRCode.py

@@ -5,10 +5,11 @@
 # MIT Licence                                              #
 # ##########################################################
 
-from PyQt5 import QtWidgets, QtCore
+from PyQt5 import QtWidgets, QtCore, QtGui
+from PyQt5.QtCore import Qt
 
 from FlatCAMTool import FlatCAMTool
-from flatcamGUI.GUIElements import RadioSet, FCTextArea, FCSpinner, FCDoubleSpinner
+from flatcamGUI.GUIElements import RadioSet, FCTextArea, FCSpinner, FCEntry, FCCheckBox, OptionalInputSection
 from flatcamParsers.ParseSVG import *
 
 from shapely.geometry.base import *
@@ -23,6 +24,7 @@ from copy import deepcopy
 
 import qrcode
 import qrcode.image.svg
+import qrcode.image.pil
 from lxml import etree as ET
 
 import gettext
@@ -164,7 +166,7 @@ class QRCode(FlatCAMTool):
         )
         self.text_data = FCTextArea()
         self.text_data.setPlaceholderText(
-            _("Add here the text to be included in the QRData...")
+            _("Add here the text to be included in the QRCode...")
         )
         grid_lay.addWidget(self.text_label, 5, 0)
         grid_lay.addWidget(self.text_data, 6, 0, 1, 2)
@@ -202,13 +204,92 @@ class QRCode(FlatCAMTool):
         grid_lay.addWidget(self.bb_label, 8, 0)
         grid_lay.addWidget(self.bb_radio, 8, 1)
 
-        # ## Create QRCode
-        self.qrcode_button = QtWidgets.QPushButton(_("Create QRCode"))
+        # Export QRCode
+        self.export_cb = FCCheckBox(_("Export QRCode"))
+        self.export_cb.setToolTip(
+            _("Show a set of controls allowing to export the QRCode\n"
+              "to a SVG file or an PNG file.")
+        )
+        grid_lay.addWidget(self.export_cb, 9, 0, 1, 2)
+
+        # this way I can hide/show the frame
+        self.export_frame = QtWidgets.QFrame()
+        self.export_frame.setContentsMargins(0, 0, 0, 0)
+        self.layout.addWidget(self.export_frame)
+        self.export_lay = QtWidgets.QGridLayout()
+        self.export_lay.setContentsMargins(0, 0, 0, 0)
+        self.export_frame.setLayout(self.export_lay)
+        self.export_lay.setColumnStretch(0, 0)
+        self.export_lay.setColumnStretch(1, 1)
+
+        # default is hidden
+        self.export_frame.hide()
+
+        # FILL COLOR #
+        self.fill_color_label = QtWidgets.QLabel('%s:' % _('Fill Color'))
+        self.fill_color_label.setToolTip(
+            _("Set the QRCode fill color (squares color).")
+        )
+        self.fill_color_entry = FCEntry()
+        self.fill_color_button = QtWidgets.QPushButton()
+        self.fill_color_button.setFixedSize(15, 15)
+
+        fill_lay_child = QtWidgets.QHBoxLayout()
+        fill_lay_child.setContentsMargins(0, 0, 0, 0)
+        fill_lay_child.addWidget(self.fill_color_entry)
+        fill_lay_child.addWidget(self.fill_color_button, alignment=Qt.AlignRight)
+        fill_lay_child.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        fill_color_widget = QtWidgets.QWidget()
+        fill_color_widget.setLayout(fill_lay_child)
+
+        self.export_lay.addWidget(self.fill_color_label, 0, 0)
+        self.export_lay.addWidget(fill_color_widget, 0, 1)
+
+        self.transparent_cb = FCCheckBox(_("Transparent back color"))
+        self.export_lay.addWidget(self.transparent_cb, 1, 0, 1, 2)
+
+        # BACK COLOR #
+        self.back_color_label = QtWidgets.QLabel('%s:' % _('Back Color'))
+        self.back_color_label.setToolTip(
+            _("Set the QRCode background color.")
+        )
+        self.back_color_entry = FCEntry()
+        self.back_color_button = QtWidgets.QPushButton()
+        self.back_color_button.setFixedSize(15, 15)
+
+        back_lay_child = QtWidgets.QHBoxLayout()
+        back_lay_child.setContentsMargins(0, 0, 0, 0)
+        back_lay_child.addWidget(self.back_color_entry)
+        back_lay_child.addWidget(self.back_color_button, alignment=Qt.AlignRight)
+        back_lay_child.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        back_color_widget = QtWidgets.QWidget()
+        back_color_widget.setLayout(back_lay_child)
+
+        self.export_lay.addWidget(self.back_color_label, 2, 0)
+        self.export_lay.addWidget(back_color_widget, 2, 1)
+
+        # ## Export QRCode as SVG image
+        self.export_svg_button = QtWidgets.QPushButton(_("Export QRCode SVG"))
+        self.export_svg_button.setToolTip(
+            _("Export a SVG file with the QRCode content.")
+        )
+        self.export_lay.addWidget(self.export_svg_button, 3, 0, 1, 2)
+
+        # ## Export QRCode as PNG image
+        self.export_png_button = QtWidgets.QPushButton(_("Export QRCode PNG"))
+        self.export_png_button.setToolTip(
+            _("Export a PNG image file with the QRCode content.")
+        )
+        self.export_lay.addWidget(self.export_png_button, 4, 0, 1, 2)
+
+        # ## Insert QRCode
+        self.qrcode_button = QtWidgets.QPushButton(_("Insert QRCode"))
         self.qrcode_button.setToolTip(
             _("Create the QRCode object.")
         )
-        grid_lay.addWidget(self.qrcode_button, 9, 0, 1, 2)
-        grid_lay.addWidget(QtWidgets.QLabel(''), 10, 0)
+        self.layout.addWidget(self.qrcode_button)
 
         self.layout.addStretch()
 
@@ -226,6 +307,21 @@ class QRCode(FlatCAMTool):
         self.qrcode_geometry = MultiPolygon()
         self.qrcode_utility_geometry = MultiPolygon()
 
+        self.old_back_color = ''
+
+        # Signals #
+        self.qrcode_button.clicked.connect(self.execute)
+        self.export_cb.stateChanged.connect(self.on_export_frame)
+        self.export_png_button.clicked.connect(self.export_png_file)
+        self.export_svg_button.clicked.connect(self.export_svg_file)
+
+        self.fill_color_entry.editingFinished.connect(self.on_qrcode_fill_color_entry)
+        self.fill_color_button.clicked.connect(self.on_qrcode_fill_color_button)
+        self.back_color_entry.editingFinished.connect(self.on_qrcode_back_color_entry)
+        self.back_color_button.clicked.connect(self.on_qrcode_back_color_button)
+
+        self.transparent_cb.stateChanged.connect(self.on_transparent_back_color)
+
     def run(self, toggle=True):
         self.app.report_usage("QRCode()")
 
@@ -259,15 +355,24 @@ class QRCode(FlatCAMTool):
 
     def set_tool_ui(self):
         self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value()
-        self.version_entry.set_value(1)
-        self.error_radio.set_value('M')
-        self.bsize_entry.set_value(3)
-        self.border_size_entry.set_value(4)
-        self.pol_radio.set_value('pos')
-        self.bb_radio.set_value('r')
+        self.version_entry.set_value(int(self.app.defaults["tools_qrcode_version"]))
+        self.error_radio.set_value(self.app.defaults["tools_qrcode_error"])
+        self.bsize_entry.set_value(int(self.app.defaults["tools_qrcode_box_size"]))
+        self.border_size_entry.set_value(int(self.app.defaults["tools_qrcode_border_size"]))
+        self.pol_radio.set_value(self.app.defaults["tools_qrcode_polarity"])
+        self.bb_radio.set_value(self.app.defaults["tools_qrcode_rounded"])
 
-        # Signals #
-        self.qrcode_button.clicked.connect(self.execute)
+        self.fill_color_entry.set_value(self.app.defaults['tools_qrcode_fill_color'])
+        self.fill_color_button.setStyleSheet("background-color:%s" %
+                                             str(self.app.defaults['tools_qrcode_fill_color'])[:7])
+
+        self.back_color_entry.set_value(self.app.defaults['tools_qrcode_back_color'])
+        self.back_color_button.setStyleSheet("background-color:%s" %
+                                             str(self.app.defaults['tools_qrcode_back_color'])[:7])
+
+    def on_export_frame(self, state):
+        self.export_frame.setVisible(state)
+        self.qrcode_button.setVisible(not state)
 
     def execute(self):
         text_data = self.text_data.get_value()
@@ -327,8 +432,8 @@ class QRCode(FlatCAMTool):
             try:
                 a, b, c, d = self.qrcode_utility_geometry.bounds
                 self.box_poly = box(minx=a, miny=b, maxx=c, maxy=d)
-            except Exception as e:
-                log.debug("QRCode.make() bounds error --> %s" % str(e))
+            except Exception as ee:
+                log.debug("QRCode.make() bounds error --> %s" % str(ee))
 
             app_obj.call_source = 'qrcode_tool'
             app_obj.inform.emit(_("Click on the Destination point ..."))
@@ -602,3 +707,146 @@ class QRCode(FlatCAMTool):
         # delete the utility geometry
         self.delete_utility_geo()
         self.app.call_source = 'app'
+
+    def export_png_file(self):
+        text_data = self.text_data.get_value()
+        if text_data == '':
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Cancelled. There is no QRCode Data in the text box."))
+            return 'fail'
+
+        def job_thread_qr_png(app_obj, fname):
+            error_code = {
+                'L': qrcode.constants.ERROR_CORRECT_L,
+                'M': qrcode.constants.ERROR_CORRECT_M,
+                'Q': qrcode.constants.ERROR_CORRECT_Q,
+                'H': qrcode.constants.ERROR_CORRECT_H
+            }[self.error_radio.get_value()]
+
+            qr = qrcode.QRCode(
+                version=self.version_entry.get_value(),
+                error_correction=error_code,
+                box_size=self.bsize_entry.get_value(),
+                border=self.border_size_entry.get_value(),
+                image_factory=qrcode.image.pil.PilImage
+            )
+            qr.add_data(text_data)
+            qr.make(fit=True)
+
+            img = qr.make_image(fill_color=self.fill_color_entry.get_value(),
+                                back_color=self.back_color_entry.get_value())
+            img.save(fname)
+
+            app_obj.call_source = 'qrcode_tool'
+
+        name = 'qr_code'
+
+        _filter = "PNG File (*.png);;All Files (*.*)"
+        try:
+            filename, _f = QtWidgets.QFileDialog.getSaveFileName(
+                caption=_("Export PNG"),
+                directory=self.app.get_last_save_folder() + '/' + str(name) + '_png',
+                filter=_filter)
+        except TypeError:
+            filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export PNG"), filter=_filter)
+
+        filename = str(filename)
+
+        if filename == "":
+            self.app.inform.emit('[WARNING_NOTCL]%s' % _(" Export PNG cancelled."))
+            return
+        else:
+            self.app.worker_task.emit({'fcn': job_thread_qr_png, 'params': [self.app, filename]})
+
+    def export_svg_file(self):
+        text_data = self.text_data.get_value()
+        if text_data == '':
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Cancelled. There is no QRCode Data in the text box."))
+            return 'fail'
+
+        def job_thread_qr_svg(app_obj, fname):
+            error_code = {
+                'L': qrcode.constants.ERROR_CORRECT_L,
+                'M': qrcode.constants.ERROR_CORRECT_M,
+                'Q': qrcode.constants.ERROR_CORRECT_Q,
+                'H': qrcode.constants.ERROR_CORRECT_H
+            }[self.error_radio.get_value()]
+
+            qr = qrcode.QRCode(
+                version=self.version_entry.get_value(),
+                error_correction=error_code,
+                box_size=self.bsize_entry.get_value(),
+                border=self.border_size_entry.get_value(),
+                image_factory=qrcode.image.svg.SvgPathImage
+            )
+            qr.add_data(text_data)
+            img = qr.make_image(fill_color=self.fill_color_entry.get_value(),
+                                back_color=self.back_color_entry.get_value())
+            img.save(fname)
+
+            app_obj.call_source = 'qrcode_tool'
+
+        name = 'qr_code'
+
+        _filter = "SVG File (*.svg);;All Files (*.*)"
+        try:
+            filename, _f = QtWidgets.QFileDialog.getSaveFileName(
+                caption=_("Export SVG"),
+                directory=self.app.get_last_save_folder() + '/' + str(name) + '_svg',
+                filter=_filter)
+        except TypeError:
+            filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG"), filter=_filter)
+
+        filename = str(filename)
+
+        if filename == "":
+            self.app.inform.emit('[WARNING_NOTCL]%s' % _(" Export SVG cancelled."))
+            return
+        else:
+            self.app.worker_task.emit({'fcn': job_thread_qr_svg, 'params': [self.app, filename]})
+
+    def on_qrcode_fill_color_entry(self):
+        color = self.fill_color_entry.get_value()
+        self.fill_color_button.setStyleSheet("background-color:%s" % str(color))
+
+    def on_qrcode_fill_color_button(self):
+        current_color = QtGui.QColor(self.fill_color_entry.get_value())
+
+        c_dialog = QtWidgets.QColorDialog()
+        fill_color = c_dialog.getColor(initial=current_color)
+
+        if fill_color.isValid() is False:
+            return
+
+        self.fill_color_button.setStyleSheet("background-color:%s" % str(fill_color.name()))
+
+        new_val_sel = str(fill_color.name())
+        self.fill_color_entry.set_value(new_val_sel)
+
+    def on_qrcode_back_color_entry(self):
+        color = self.back_color_entry.get_value()
+        self.back_color_button.setStyleSheet("background-color:%s" % str(color))
+
+    def on_qrcode_back_color_button(self):
+        current_color = QtGui.QColor(self.back_color_entry.get_value())
+
+        c_dialog = QtWidgets.QColorDialog()
+        back_color = c_dialog.getColor(initial=current_color)
+
+        if back_color.isValid() is False:
+            return
+
+        self.back_color_button.setStyleSheet("background-color:%s" % str(back_color.name()))
+
+        new_val_sel = str(back_color.name())
+        self.back_color_entry.set_value(new_val_sel)
+
+    def on_transparent_back_color(self, state):
+        if state:
+            self.back_color_entry.setDisabled(True)
+            self.back_color_button.setDisabled(True)
+            self.old_back_color = self.back_color_entry.get_value()
+            self.back_color_entry.set_value('transparent')
+        else:
+            self.back_color_entry.setDisabled(False)
+            self.back_color_button.setDisabled(False)
+            self.back_color_entry.set_value(self.old_back_color)

+ 2 - 0
tclCommands/TclCommandAlignDrill.py

@@ -46,6 +46,8 @@ class TclCommandAlignDrill(TclCommandSignaled):
             ('name', 'Name of the object (Gerber or Excellon) to mirror.'),
             ('dia', 'Tool diameter'),
             ('box', 'Name of object which act as box (cutout for example.)'),
+            ('holes', 'Tuple of tuples where each tuple it is a set of x, y coordinates. '
+                      'E.g: (x0, y0), (x1, y1), ... '),
             ('grid', 'Aligning to grid, for those, who have aligning pins'
                      'inside table in grid (-5,0),(5,0),(15,0)...'),
             ('gridoffset', 'offset of grid from 0 position.'),