فهرست منبع

- 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 سال پیش
والد
کامیت
97809b6de1
5فایلهای تغییر یافته به همراه542 افزوده شده و 17 حذف شده
  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": 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,
             "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
             # Utilities
             # File associations
             # File associations
             "fa_excellon": self.ui.util_defaults_form.fa_excellon_group.exc_list_text,
             "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": True,
             "tools_cr_dh_val": 0.011811,
             "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
             # Utilities
             # file associations
             # file associations
             "fa_excellon": 'drd, drl, exc, ncd, tap, xln',
             "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(
         self.ui.tools_defaults_form.tools_film_group.film_color_button.setStyleSheet(
             "background-color:%s" % str(self.defaults['tools_film_color'])[:7])
             "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 ####
         # ### End of Data ####
 
 
         # ##############################################
         # ##############################################
@@ -2113,11 +2146,22 @@ class App(QtCore.QObject):
             self.on_annotation_fontcolor_button)
             self.on_annotation_fontcolor_button)
 
 
         # ########## Tools related signals #############
         # ########## Tools related signals #############
+        # Film Tool
         self.ui.tools_defaults_form.tools_film_group.film_color_entry.editingFinished.connect(
         self.ui.tools_defaults_form.tools_film_group.film_color_entry.editingFinished.connect(
             self.on_film_color_entry)
             self.on_film_color_entry)
         self.ui.tools_defaults_form.tools_film_group.film_color_button.clicked.connect(
         self.ui.tools_defaults_form.tools_film_group.film_color_button.clicked.connect(
             self.on_film_color_button)
             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
         # portability changed signal
         self.ui.general_defaults_form.general_app_group.portability_cb.stateChanged.connect(self.on_portable_checked)
         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.ui.tools_defaults_form.tools_film_group.film_color_entry.set_value(new_val_sel)
         self.defaults['tools_film_color'] = 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):
     def on_splash_changed(self, state):
         settings = QSettings("Open Source", "FlatCAM")
         settings = QSettings("Open Source", "FlatCAM")
         settings.setValue('splash_screen', 1) if state else settings.setValue('splash_screen', 0)
         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 imports in all TclCommands
 - fixed the requirements.txt and setup_ubuntu.sh files
 - fixed the requirements.txt and setup_ubuntu.sh files
 - QRCode Tool: change the plot method parameter
 - 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
 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 = Tools2OptimalPrefGroupUI()
         self.tools2_optimal_group.setMinimumWidth(220)
         self.tools2_optimal_group.setMinimumWidth(220)
 
 
+        self.tools2_qrcode_group = Tools2QRCodePrefGroupUI()
+        self.tools2_qrcode_group.setMinimumWidth(220)
+
         self.vlay = QtWidgets.QVBoxLayout()
         self.vlay = QtWidgets.QVBoxLayout()
         self.vlay.addWidget(self.tools2_checkrules_group)
         self.vlay.addWidget(self.tools2_checkrules_group)
         self.vlay.addWidget(self.tools2_optimal_group)
         self.vlay.addWidget(self.tools2_optimal_group)
 
 
         self.vlay1 = QtWidgets.QVBoxLayout()
         self.vlay1 = QtWidgets.QVBoxLayout()
+        self.vlay1.addWidget(self.tools2_qrcode_group)
 
 
 
 
         self.vlay2 = QtWidgets.QVBoxLayout()
         self.vlay2 = QtWidgets.QVBoxLayout()
@@ -5388,6 +5392,178 @@ class Tools2OptimalPrefGroupUI(OptionsGroupUI):
         self.layout.addStretch()
         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):
 class FAExcPrefGroupUI(OptionsGroupUI):
     def __init__(self, parent=None):
     def __init__(self, parent=None):
         # OptionsGroupUI.__init__(self, "Excellon File associations Preferences", parent=None)
         # OptionsGroupUI.__init__(self, "Excellon File associations Preferences", parent=None)

+ 265 - 17
flatcamTools/ToolQRCode.py

@@ -5,10 +5,11 @@
 # MIT Licence                                              #
 # MIT Licence                                              #
 # ##########################################################
 # ##########################################################
 
 
-from PyQt5 import QtWidgets, QtCore
+from PyQt5 import QtWidgets, QtCore, QtGui
+from PyQt5.QtCore import Qt
 
 
 from FlatCAMTool import FlatCAMTool
 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 flatcamParsers.ParseSVG import *
 
 
 from shapely.geometry.base import *
 from shapely.geometry.base import *
@@ -23,6 +24,7 @@ from copy import deepcopy
 
 
 import qrcode
 import qrcode
 import qrcode.image.svg
 import qrcode.image.svg
+import qrcode.image.pil
 from lxml import etree as ET
 from lxml import etree as ET
 
 
 import gettext
 import gettext
@@ -164,7 +166,7 @@ class QRCode(FlatCAMTool):
         )
         )
         self.text_data = FCTextArea()
         self.text_data = FCTextArea()
         self.text_data.setPlaceholderText(
         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_label, 5, 0)
         grid_lay.addWidget(self.text_data, 6, 0, 1, 2)
         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_label, 8, 0)
         grid_lay.addWidget(self.bb_radio, 8, 1)
         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(
         self.qrcode_button.setToolTip(
             _("Create the QRCode object.")
             _("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()
         self.layout.addStretch()
 
 
@@ -226,6 +307,21 @@ class QRCode(FlatCAMTool):
         self.qrcode_geometry = MultiPolygon()
         self.qrcode_geometry = MultiPolygon()
         self.qrcode_utility_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):
     def run(self, toggle=True):
         self.app.report_usage("QRCode()")
         self.app.report_usage("QRCode()")
 
 
@@ -259,15 +355,24 @@ class QRCode(FlatCAMTool):
 
 
     def set_tool_ui(self):
     def set_tool_ui(self):
         self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value()
         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):
     def execute(self):
         text_data = self.text_data.get_value()
         text_data = self.text_data.get_value()
@@ -327,8 +432,8 @@ class QRCode(FlatCAMTool):
             try:
             try:
                 a, b, c, d = self.qrcode_utility_geometry.bounds
                 a, b, c, d = self.qrcode_utility_geometry.bounds
                 self.box_poly = box(minx=a, miny=b, maxx=c, maxy=d)
                 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.call_source = 'qrcode_tool'
             app_obj.inform.emit(_("Click on the Destination point ..."))
             app_obj.inform.emit(_("Click on the Destination point ..."))
@@ -602,3 +707,146 @@ class QRCode(FlatCAMTool):
         # delete the utility geometry
         # delete the utility geometry
         self.delete_utility_geo()
         self.delete_utility_geo()
         self.app.call_source = 'app'
         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.'),
             ('name', 'Name of the object (Gerber or Excellon) to mirror.'),
             ('dia', 'Tool diameter'),
             ('dia', 'Tool diameter'),
             ('box', 'Name of object which act as box (cutout for example.)'),
             ('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'
             ('grid', 'Aligning to grid, for those, who have aligning pins'
                      'inside table in grid (-5,0),(5,0),(15,0)...'),
                      'inside table in grid (-5,0),(5,0),(15,0)...'),
             ('gridoffset', 'offset of grid from 0 position.'),
             ('gridoffset', 'offset of grid from 0 position.'),