Просмотр исходного кода

- updated the Film Tool and added the ability to generate Punched Positive films (holes in the pads) when a Gerber file is the film's source. The punch holes source can be either an Excellon file or the pads center

Marius Stanciu 6 лет назад
Родитель
Сommit
67b0a81f17
2 измененных файлов с 257 добавлено и 62 удалено
  1. 4 0
      README.md
  2. 253 62
      flatcamTools/ToolFilm.py

+ 4 - 0
README.md

@@ -10,6 +10,10 @@ CAD program, and create G-Code for Isolation routing.
 
 
 =================================================
 =================================================
 
 
+4.10.2019
+
+- updated the Film Tool and added the ability to generate Punched Positive films (holes in the pads) when a Gerber file is the film's source. The punch holes source can be either an Excellon file or the pads center
+
 3.10.2019
 3.10.2019
 
 
 - previously I've added the initial layout for the FlatCAMDocument object
 - previously I've added the initial layout for the FlatCAMDocument object

+ 253 - 62
flatcamTools/ToolFilm.py

@@ -7,10 +7,14 @@
 # ########################################################## ##
 # ########################################################## ##
 
 
 from FlatCAMTool import FlatCAMTool
 from FlatCAMTool import FlatCAMTool
+from FlatCAMObj import *
 
 
-from flatcamGUI.GUIElements import RadioSet, FCEntry
+from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, \
+    OptionalHideInputSection, OptionalInputSection
 from PyQt5 import QtGui, QtCore, QtWidgets
 from PyQt5 import QtGui, QtCore, QtWidgets
 
 
+from copy import deepcopy
+
 import gettext
 import gettext
 import FlatCAMTranslation as fcTranslate
 import FlatCAMTranslation as fcTranslate
 import builtins
 import builtins
@@ -39,8 +43,11 @@ class Film(FlatCAMTool):
         self.layout.addWidget(title_label)
         self.layout.addWidget(title_label)
 
 
         # Form Layout
         # Form Layout
-        tf_form_layout = QtWidgets.QFormLayout()
-        self.layout.addLayout(tf_form_layout)
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        grid0.setColumnStretch(0, 0)
+        grid0.setColumnStretch(1, 1)
 
 
         # Type of object for which to create the film
         # Type of object for which to create the film
         self.tf_type_obj_combo = QtWidgets.QComboBox()
         self.tf_type_obj_combo = QtWidgets.QComboBox()
@@ -60,7 +67,8 @@ class Film(FlatCAMTool):
               "The selection here decide the type of objects that will be\n"
               "The selection here decide the type of objects that will be\n"
               "in the Film Object combobox.")
               "in the Film Object combobox.")
         )
         )
-        tf_form_layout.addRow(self.tf_type_obj_combo_label, self.tf_type_obj_combo)
+        grid0.addWidget(self.tf_type_obj_combo_label, 0, 0)
+        grid0.addWidget(self.tf_type_obj_combo, 0, 1)
 
 
         # List of objects for which we can create the film
         # List of objects for which we can create the film
         self.tf_object_combo = QtWidgets.QComboBox()
         self.tf_object_combo = QtWidgets.QComboBox()
@@ -72,7 +80,8 @@ class Film(FlatCAMTool):
         self.tf_object_label.setToolTip(
         self.tf_object_label.setToolTip(
             _("Object for which to create the film.")
             _("Object for which to create the film.")
         )
         )
-        tf_form_layout.addRow(self.tf_object_label, self.tf_object_combo)
+        grid0.addWidget(self.tf_object_label, 1, 0)
+        grid0.addWidget(self.tf_object_combo, 1, 1)
 
 
         # Type of Box Object to be used as an envelope for film creation
         # Type of Box Object to be used as an envelope for film creation
         # Within this we can create negative
         # Within this we can create negative
@@ -93,7 +102,8 @@ class Film(FlatCAMTool):
               "The selection here decide the type of objects that will be\n"
               "The selection here decide the type of objects that will be\n"
               "in the Box Object combobox.")
               "in the Box Object combobox.")
         )
         )
-        tf_form_layout.addRow(self.tf_type_box_combo_label, self.tf_type_box_combo)
+        grid0.addWidget(self.tf_type_box_combo_label, 2, 0)
+        grid0.addWidget(self.tf_type_box_combo, 2, 1)
 
 
         # Box
         # Box
         self.tf_box_combo = QtWidgets.QComboBox()
         self.tf_box_combo = QtWidgets.QComboBox()
@@ -108,11 +118,28 @@ class Film(FlatCAMTool):
               "Usually it is the PCB outline but it can be also the\n"
               "Usually it is the PCB outline but it can be also the\n"
               "same object for which the film is created.")
               "same object for which the film is created.")
         )
         )
-        tf_form_layout.addRow(self.tf_box_combo_label, self.tf_box_combo)
+        grid0.addWidget(self.tf_box_combo_label, 3, 0)
+        grid0.addWidget(self.tf_box_combo, 3, 1)
+
+        # Scale Stroke size
+        self.film_scale_entry = FCDoubleSpinner()
+        self.film_scale_entry.set_range(-999.9999, 999.9999)
+
+        self.film_scale_label = QtWidgets.QLabel('%s:' % _("Scale Stroke"))
+        self.film_scale_label.setToolTip(
+            _("Scale the line stroke thickness of each feature in the SVG file.\n"
+              "It means that the line that envelope each SVG feature will be thicker or thinner,\n"
+              "therefore the fine features may be more affected by this parameter.")
+        )
+        grid0.addWidget(self.film_scale_label, 4, 0)
+        grid0.addWidget(self.film_scale_entry, 4, 1)
+
+        grid0.addWidget(QtWidgets.QLabel(''), 5, 0)
 
 
         # Film Type
         # Film Type
         self.film_type = RadioSet([{'label': _('Positive'), 'value': 'pos'},
         self.film_type = RadioSet([{'label': _('Positive'), 'value': 'pos'},
-                                   {'label': _('Negative'), 'value': 'neg'}])
+                                   {'label': _('Negative'), 'value': 'neg'}],
+                                  stretch=False)
         self.film_type_label = QtWidgets.QLabel(_("Film Type:"))
         self.film_type_label = QtWidgets.QLabel(_("Film Type:"))
         self.film_type_label.setToolTip(
         self.film_type_label.setToolTip(
             _("Generate a Positive black film or a Negative film.\n"
             _("Generate a Positive black film or a Negative film.\n"
@@ -122,11 +149,12 @@ class Film(FlatCAMTool):
               "with white on a black canvas.\n"
               "with white on a black canvas.\n"
               "The Film format is SVG.")
               "The Film format is SVG.")
         )
         )
-        tf_form_layout.addRow(self.film_type_label, self.film_type)
+        grid0.addWidget(self.film_type_label, 6, 0)
+        grid0.addWidget(self.film_type, 6, 1)
 
 
         # Boundary for negative film generation
         # Boundary for negative film generation
-
-        self.boundary_entry = FCEntry()
+        self.boundary_entry = FCDoubleSpinner()
+        self.boundary_entry.set_range(-999.9999, 999.9999)
         self.boundary_label = QtWidgets.QLabel('%s:' % _("Border"))
         self.boundary_label = QtWidgets.QLabel('%s:' % _("Border"))
         self.boundary_label.setToolTip(
         self.boundary_label.setToolTip(
             _("Specify a border around the object.\n"
             _("Specify a border around the object.\n"
@@ -138,21 +166,72 @@ class Film(FlatCAMTool):
               "white color like the rest and which may confound with the\n"
               "white color like the rest and which may confound with the\n"
               "surroundings if not for this border.")
               "surroundings if not for this border.")
         )
         )
-        tf_form_layout.addRow(self.boundary_label, self.boundary_entry)
-
-        self.film_scale_entry = FCEntry()
-        self.film_scale_label = QtWidgets.QLabel('%s:' % _("Scale Stroke"))
-        self.film_scale_label.setToolTip(
-            _("Scale the line stroke thickness of each feature in the SVG file.\n"
-              "It means that the line that envelope each SVG feature will be thicker or thinner,\n"
-              "therefore the fine features may be more affected by this parameter.")
+        grid0.addWidget(self.boundary_label, 7, 0)
+        grid0.addWidget(self.boundary_entry, 7, 1)
+
+        self.boundary_label.hide()
+        self.boundary_entry.hide()
+
+        # Punch Drill holes
+        self.punch_cb = FCCheckBox(_("Punch drill holes"))
+        self.punch_cb.setToolTip(_("When checked the generated film will have holes in pads when\n"
+                                   "the generated film is positive. This is done to help drilling,\n"
+                                   "when done manually."))
+        grid0.addWidget(self.punch_cb, 8, 0, 1, 2)
+
+        # this way I can hide/show the frame
+        self.punch_frame = QtWidgets.QFrame()
+        self.punch_frame.setContentsMargins(0, 0, 0, 0)
+        self.layout.addWidget(self.punch_frame)
+        punch_grid = QtWidgets.QGridLayout()
+        punch_grid.setContentsMargins(0, 0, 0, 0)
+        self.punch_frame.setLayout(punch_grid)
+
+        punch_grid.setColumnStretch(0, 0)
+        punch_grid.setColumnStretch(1, 1)
+
+        self.ois_p = OptionalHideInputSection(self.punch_cb, [self.punch_frame])
+
+        self.source_label = QtWidgets.QLabel('%s:' % _("Source"))
+        self.source_label.setToolTip(
+            _("The punch hole source can be:\n"
+              "- Excellon -> an Excellon holes center will serve as reference.\n"
+              "- Pad Center -> will try to use the pads center as reference.")
+        )
+        self.source_punch = RadioSet([{'label': _('Excellon'), 'value': 'exc'},
+                                   {'label': _('Pad center'), 'value': 'pad'}],
+                                   stretch=False)
+        punch_grid.addWidget(self.source_label, 0, 0)
+        punch_grid.addWidget(self.source_punch, 0, 1)
+
+        self.exc_label = QtWidgets.QLabel('%s:' % _("Excellon Obj"))
+        self.exc_label.setToolTip(
+            _("Remove the geometry of Excellon from the Film to create tge holes in pads.")
         )
         )
-        tf_form_layout.addRow(self.film_scale_label, self.film_scale_entry)
+        self.exc_combo = QtWidgets.QComboBox()
+        self.exc_combo.setModel(self.app.collection)
+        self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
+        self.exc_combo.setCurrentIndex(1)
+        punch_grid.addWidget(self.exc_label, 1, 0)
+        punch_grid.addWidget(self.exc_combo, 1, 1)
+
+        self.exc_label.hide()
+        self.exc_combo.hide()
+
+        self.punch_size_label = QtWidgets.QLabel('%s:' % _("Punch Size"))
+        self.punch_size_label.setToolTip(_("The value here will control how big is the punch hole in the pads."))
+        self.punch_size_spinner = FCDoubleSpinner()
+        self.punch_size_spinner.set_range(0, 999.9999)
+
+        punch_grid.addWidget(self.punch_size_label, 2, 0)
+        punch_grid.addWidget(self.punch_size_spinner, 2, 1)
+
+        self.punch_size_label.hide()
+        self.punch_size_spinner.hide()
 
 
         # Buttons
         # Buttons
         hlay = QtWidgets.QHBoxLayout()
         hlay = QtWidgets.QHBoxLayout()
         self.layout.addLayout(hlay)
         self.layout.addLayout(hlay)
-        hlay.addStretch()
 
 
         self.film_object_button = QtWidgets.QPushButton(_("Save Film"))
         self.film_object_button = QtWidgets.QPushButton(_("Save Film"))
         self.film_object_button.setToolTip(
         self.film_object_button.setToolTip(
@@ -170,6 +249,9 @@ class Film(FlatCAMTool):
         self.tf_type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
         self.tf_type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
         self.tf_type_box_combo.currentIndexChanged.connect(self.on_type_box_index_changed)
         self.tf_type_box_combo.currentIndexChanged.connect(self.on_type_box_index_changed)
 
 
+        self.film_type.activated_custom.connect(self.on_film_type)
+        self.source_punch.activated_custom.connect(self.on_punch_source)
+
     def on_type_obj_index_changed(self, index):
     def on_type_obj_index_changed(self, index):
         obj_type = self.tf_type_obj_combo.currentIndex()
         obj_type = self.tf_type_obj_combo.currentIndex()
         self.tf_object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
         self.tf_object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
@@ -216,6 +298,7 @@ class Film(FlatCAMTool):
 
 
         f_type = self.app.defaults["tools_film_type"] if self.app.defaults["tools_film_type"] else 'neg'
         f_type = self.app.defaults["tools_film_type"] if self.app.defaults["tools_film_type"] else 'neg'
         self.film_type.set_value(str(f_type))
         self.film_type.set_value(str(f_type))
+        self.on_film_type(val=f_type)
 
 
         b_entry = self.app.defaults["tools_film_boundary"] if self.app.defaults["tools_film_boundary"] else 0.0
         b_entry = self.app.defaults["tools_film_boundary"] if self.app.defaults["tools_film_boundary"] else 0.0
         self.boundary_entry.set_value(float(b_entry))
         self.boundary_entry.set_value(float(b_entry))
@@ -223,7 +306,41 @@ class Film(FlatCAMTool):
         scale_stroke_width = self.app.defaults["tools_film_scale"] if self.app.defaults["tools_film_scale"] else 0.0
         scale_stroke_width = self.app.defaults["tools_film_scale"] if self.app.defaults["tools_film_scale"] else 0.0
         self.film_scale_entry.set_value(int(scale_stroke_width))
         self.film_scale_entry.set_value(int(scale_stroke_width))
 
 
+        self.punch_cb.set_value(False)
+        self.source_punch.set_value('exc')
+
+    def on_film_type(self, val):
+        type_of_film = val
+
+        if type_of_film == 'neg':
+            self.boundary_label.show()
+            self.boundary_entry.show()
+            self.punch_cb.hide()
+        else:
+            self.boundary_label.hide()
+            self.boundary_entry.hide()
+            self.punch_cb.show()
+
+    def on_punch_source(self, val):
+        if val == 'pad' and self.punch_cb.get_value():
+            self.punch_size_label.show()
+            self.punch_size_spinner.show()
+            self.exc_label.hide()
+            self.exc_combo.hide()
+        else:
+            self.punch_size_label.hide()
+            self.punch_size_spinner.hide()
+            self.exc_label.show()
+            self.exc_combo.show()
+
+        if val == 'pad' and self.tf_type_obj_combo.currentText() == 'Geometry':
+            self.source_punch.set_value('exc')
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Using the Pad center does not work on Geometry objects. "
+                                                          "Only a Gerber object has pads."))
+
     def on_film_creation(self):
     def on_film_creation(self):
+        log.debug("ToolFilm.Film.on_film_creation() started ...")
+
         try:
         try:
             name = self.tf_object_combo.currentText()
             name = self.tf_object_combo.currentText()
         except Exception as e:
         except Exception as e:
@@ -238,59 +355,133 @@ class Film(FlatCAMTool):
                                  _("No FlatCAM object selected. Load an object for Box and retry."))
                                  _("No FlatCAM object selected. Load an object for Box and retry."))
             return
             return
 
 
-        try:
-            border = float(self.boundary_entry.get_value())
-        except ValueError:
-            # try to convert comma to decimal point. if it's still not working error message and return
-            try:
-                border = float(self.boundary_entry.get_value().replace(',', '.'))
-            except ValueError:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
-                return
+        scale_stroke_width = float(self.film_scale_entry.get_value())
 
 
-        try:
-            scale_stroke_width = int(self.film_scale_entry.get_value())
-        except ValueError:
-            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
-            return
+        source = self.source_punch.get_value()
 
 
-        if border is None:
-            border = 0
+        # #################################################################
+        # ################ STARTING THE JOB ###############################
+        # #################################################################
 
 
         self.app.inform.emit(_("Generating Film ..."))
         self.app.inform.emit(_("Generating Film ..."))
 
 
         if self.film_type.get_value() == "pos":
         if self.film_type.get_value() == "pos":
-            try:
-                filename, _f = QtWidgets.QFileDialog.getSaveFileName(
-                    caption=_("Export SVG positive"),
-                    directory=self.app.get_last_save_folder() + '/' + name,
-                    filter="*.svg")
-            except TypeError:
-                filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG positive"))
-
-            filename = str(filename)
 
 
-            if str(filename) == "":
-                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export SVG positive cancelled."))
-                return
+            if self.punch_cb.get_value() is False:
+                self.generate_positive_normal_film(name, boxname, factor=scale_stroke_width)
             else:
             else:
-                self.app.export_svg_positive(name, boxname, filename, scale_factor=scale_stroke_width)
+                self.generate_positive_punched_film(name, boxname, source, factor=scale_stroke_width)
         else:
         else:
-            try:
-                filename, _f = QtWidgets.QFileDialog.getSaveFileName(
-                    caption=_("Export SVG negative"),
-                    directory=self.app.get_last_save_folder() + '/' + name,
-                    filter="*.svg")
-            except TypeError:
-                filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG negative"))
+            self.generate_negative_film(name, boxname, factor=scale_stroke_width)
+
+    def generate_positive_normal_film(self, name, boxname, factor):
+        log.debug("ToolFilm.Film.generate_positive_normal_film() started ...")
+        try:
+            filename, _f = QtWidgets.QFileDialog.getSaveFileName(
+                caption=_("Export SVG positive"),
+                directory=self.app.get_last_save_folder() + '/' + name,
+                filter="*.svg")
+        except TypeError:
+            filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG positive"))
+
+        filename = str(filename)
+
+        if str(filename) == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export SVG positive cancelled."))
+            return
+        else:
+            self.app.export_svg_positive(name, boxname, filename, scale_factor=factor)
+
+    def generate_positive_punched_film(self, name, boxname, source, factor):
+
+        film_obj = self.app.collection.get_by_name(name)
 
 
-            filename = str(filename)
+        if source == 'exc':
+            log.debug("ToolFilm.Film.generate_positive_punched_film() with Excellon source started ...")
 
 
-            if str(filename) == "":
-                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export SVG negative cancelled."))
+            try:
+                exc_name = self.exc_combo.currentText()
+            except Exception as e:
+                self.app.inform.emit('[ERROR_NOTCL] %s' %
+                                     _("No Excellon object selected. Load an object for punching reference and retry."))
                 return
                 return
-            else:
-                self.app.export_svg_negative(name, boxname, filename, border, scale_factor=scale_stroke_width)
+
+            exc_obj = self.app.collection.get_by_name(exc_name)
+            exc_solid_geometry = MultiPolygon(exc_obj.solid_geometry)
+            punched_solid_geometry = MultiPolygon(film_obj.solid_geometry).difference(exc_solid_geometry)
+
+            def init_func(new_obj, app_obj):
+                new_obj.solid_geometry = deepcopy(punched_solid_geometry)
+
+            outname = name + "_punched"
+            self.app.new_object('gerber', outname, init_func)
+
+            self.generate_positive_normal_film(outname, boxname, factor=factor)
+        else:
+            log.debug("ToolFilm.Film.generate_positive_punched_film() with Pad center source started ...")
+
+            punch_size = float(self.punch_size_spinner.get_value())
+
+            punching_geo = list()
+            for apid in film_obj.apertures:
+                if film_obj.apertures[apid]['type'] == 'C':
+                    if punch_size >= float(film_obj.apertures[apid]['size']):
+                        self.app.inform.emit('[ERROR_NOTCL] %s' %
+                                             _(" Could not generate punched hole film because the punch hole size"
+                                               "is bigger than some of the apertures in the Gerber object."))
+                        return 'fail'
+                    else:
+                        for elem in film_obj.apertures[apid]['geometry']:
+                            if 'follow' in elem:
+                                if isinstance(elem['follow'], Point):
+                                    punching_geo.append(elem['follow'].buffer(punch_size / 2))
+                else:
+                    if punch_size >= float(film_obj.apertures[apid]['width']) or \
+                            punch_size >= float(film_obj.apertures[apid]['height']):
+                        self.app.inform.emit('[ERROR_NOTCL] %s' %
+                                             _(" Could not generate punched hole film because the punch hole size"
+                                               "is bigger than some of the apertures in the Gerber object."))
+                        return 'fail'
+                    else:
+                        for elem in film_obj.apertures[apid]['geometry']:
+                            if 'follow' in elem:
+                                if isinstance(elem['follow'], Point):
+                                    punching_geo.append(elem['follow'].buffer(punch_size / 2))
+
+            punching_geo = MultiPolygon(punching_geo)
+            punched_solid_geometry = MultiPolygon(film_obj.solid_geometry).difference(punching_geo)
+
+            def init_func(new_obj, app_obj):
+                new_obj.solid_geometry = deepcopy(punched_solid_geometry)
+
+            outname = name + "_punched"
+            self.app.new_object('gerber', outname, init_func)
+
+            self.generate_positive_normal_film(outname, boxname, factor=factor)
+
+    def generate_negative_film(self, name, boxname, factor):
+        log.debug("ToolFilm.Film.generate_negative_film() started ...")
+
+        border = float(self.boundary_entry.get_value())
+
+        if border is None:
+            border = 0
+
+        try:
+            filename, _f = QtWidgets.QFileDialog.getSaveFileName(
+                caption=_("Export SVG negative"),
+                directory=self.app.get_last_save_folder() + '/' + name,
+                filter="*.svg")
+        except TypeError:
+            filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG negative"))
+
+        filename = str(filename)
+
+        if str(filename) == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export SVG negative cancelled."))
+            return
+        else:
+            self.app.export_svg_negative(name, boxname, filename, border, scale_factor=factor)
 
 
     def reset_fields(self):
     def reset_fields(self):
         self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))