Explorar el Código

- Film Tool - moved the Tool UI in its own class

Marius Stanciu hace 5 años
padre
commit
b3a22f3d0a
Se han modificado 2 ficheros con 1057 adiciones y 1026 borrados
  1. 1 0
      CHANGELOG.md
  2. 1056 1026
      appTools/ToolFilm.py

+ 1 - 0
CHANGELOG.md

@@ -15,6 +15,7 @@ CHANGELOG for FlatCAM beta
 - in Tool Cutout, when using fix gaps made sure that this feature is not activated if the value is zero
 - in Tool Cutout: modified the UI in preparation for adding the Mouse Bites feature
 - Turkish translation strings were updated by the translator, Mehmet Kaya
+- Film Tool - moved the Tool UI in its own class
 
 26.08.2020
 

+ 1056 - 1026
appTools/ToolFilm.py

@@ -39,1226 +39,1256 @@ log = logging.getLogger('base')
 
 class Film(AppTool):
 
-    toolName = _("Film PCB")
-
     def __init__(self, app):
         AppTool.__init__(self, app)
 
         self.decimals = self.app.decimals
+        self.units = self.app.defaults['units']
 
-        # Title
-        title_label = QtWidgets.QLabel("%s" % self.toolName)
-        title_label.setStyleSheet("""
-                        QLabel
-                        {
-                            font-size: 16px;
-                            font-weight: bold;
-                        }
-                        """)
-        self.layout.addWidget(title_label)
+        # #############################################################################
+        # ######################### Tool GUI ##########################################
+        # #############################################################################
+        self.ui = FilmUI(layout=self.layout, app=self.app)
+        self.toolName = self.ui.toolName
 
-        # Form Layout
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
+        # ## Signals
+        self.ui.film_object_button.clicked.connect(self.on_film_creation)
+        self.ui.tf_type_obj_combo.activated_custom.connect(self.on_type_obj_index_changed)
+        self.ui.tf_type_box_combo.activated_custom.connect(self.on_type_box_index_changed)
 
-        grid0.setColumnStretch(0, 0)
-        grid0.setColumnStretch(1, 1)
+        self.ui.film_type.activated_custom.connect(self.ui.on_film_type)
+        self.ui.source_punch.activated_custom.connect(self.ui.on_punch_source)
+        self.ui.file_type_radio.activated_custom.connect(self.ui.on_file_type)
+        self.ui.reset_button.clicked.connect(self.set_tool_ui)
 
-        # Type of object for which to create the film
-        self.tf_type_obj_combo = RadioSet([{'label': _('Gerber'), 'value': 'grb'},
-                                           {'label': _('Geometry'), 'value': 'geo'}])
+    def on_type_obj_index_changed(self, val):
+        obj_type = 2 if val == 'geo' else 0
+        self.ui.tf_object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        self.ui.tf_object_combo.setCurrentIndex(0)
+        self.ui.tf_object_combo.obj_type = {
+            "grb": "gerber", "geo": "geometry"
+        }[self.ui.tf_type_obj_combo.get_value()]
 
-        self.tf_type_obj_combo_label = QtWidgets.QLabel('<b>%s</b>:' % _("Object"))
-        self.tf_type_obj_combo_label.setToolTip(
-            _("Specify the type of object for which to create the film.\n"
-              "The object can be of type: Gerber or Geometry.\n"
-              "The selection here decide the type of objects that will be\n"
-              "in the Film Object combobox.")
-        )
-        grid0.addWidget(self.tf_type_obj_combo_label, 0, 0)
-        grid0.addWidget(self.tf_type_obj_combo, 0, 1)
+    def on_type_box_index_changed(self, val):
+        obj_type = 2 if val == 'geo' else 0
+        self.ui.tf_box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        self.ui.tf_box_combo.setCurrentIndex(0)
+        self.ui.tf_box_combo.obj_type = {
+            "grb": "gerber", "geo": "geometry"
+        }[self.ui.tf_type_obj_combo.get_value()]
 
-        # List of objects for which we can create the film
-        self.tf_object_combo = FCComboBox()
-        self.tf_object_combo.setModel(self.app.collection)
-        self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.tf_object_combo.is_last = True
+    def run(self, toggle=True):
+        self.app.defaults.report_usage("ToolFilm()")
 
-        grid0.addWidget(self.tf_object_combo, 1, 0, 1, 2)
+        if toggle:
+            # if the splitter is hidden, display it, else hide it but only if the current widget is the same
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
+            else:
+                try:
+                    if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
+                except AttributeError:
+                    pass
+        else:
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
 
-        # Type of Box Object to be used as an envelope for film creation
-        # Within this we can create negative
-        self.tf_type_box_combo = RadioSet([{'label': _('Gerber'), 'value': 'grb'},
-                                           {'label': _('Geometry'), 'value': 'geo'}])
+        AppTool.run(self)
 
-        self.tf_type_box_combo_label = QtWidgets.QLabel(_("Box Type:"))
-        self.tf_type_box_combo_label.setToolTip(
-            _("Specify the type of object to be used as an container for\n"
-              "film creation. It can be: Gerber or Geometry type."
-              "The selection here decide the type of objects that will be\n"
-              "in the Box Object combobox.")
-        )
-        grid0.addWidget(self.tf_type_box_combo_label, 2, 0)
-        grid0.addWidget(self.tf_type_box_combo, 2, 1)
+        self.set_tool_ui()
 
-        # Box
-        self.tf_box_combo = FCComboBox()
-        self.tf_box_combo.setModel(self.app.collection)
-        self.tf_box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.tf_box_combo.is_last = True
+        self.app.ui.notebook.setTabText(2, _("Film Tool"))
 
-        grid0.addWidget(self.tf_box_combo, 3, 0, 1, 2)
+    def install(self, icon=None, separator=None, **kwargs):
+        AppTool.install(self, icon, separator, shortcut='Alt+L', **kwargs)
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 4, 0, 1, 2)
+    def set_tool_ui(self):
+        self.reset_fields()
 
-        self.film_adj_label = QtWidgets.QLabel('<b>%s</b>' % _("Film Adjustments"))
-        self.film_adj_label.setToolTip(
-            _("Sometime the printers will distort the print shape, especially the Laser types.\n"
-              "This section provide the tools to compensate for the print distortions.")
-        )
+        f_type = self.app.defaults["tools_film_type"] if self.app.defaults["tools_film_type"] else 'neg'
+        self.ui.film_type.set_value(str(f_type))
+        self.ui.on_film_type(val=f_type)
 
-        grid0.addWidget(self.film_adj_label, 5, 0, 1, 2)
+        b_entry = self.app.defaults["tools_film_boundary"] if self.app.defaults["tools_film_boundary"] else 0.0
+        self.ui.boundary_entry.set_value(float(b_entry))
 
-        # Scale Geometry
-        self.film_scale_cb = FCCheckBox('%s' % _("Scale Film geometry"))
-        self.film_scale_cb.setToolTip(
-            _("A value greater than 1 will stretch the film\n"
-              "while a value less than 1 will jolt it.")
-        )
-        self.film_scale_cb.setStyleSheet(
-            """
-            QCheckBox {font-weight: bold; color: black}
-            """
-        )
-        grid0.addWidget(self.film_scale_cb, 6, 0, 1, 2)
+        scale_stroke_width = self.app.defaults["tools_film_scale_stroke"] if \
+            self.app.defaults["tools_film_scale_stroke"] else 0.0
+        self.ui.film_scale_stroke_entry.set_value(int(scale_stroke_width))
+
+        self.ui.punch_cb.set_value(False)
+        self.ui.source_punch.set_value('exc')
+
+        self.ui.film_scale_cb.set_value(self.app.defaults["tools_film_scale_cb"])
+        self.ui.film_scalex_entry.set_value(float(self.app.defaults["tools_film_scale_x_entry"]))
+        self.ui.film_scaley_entry.set_value(float(self.app.defaults["tools_film_scale_y_entry"]))
+        self.ui.film_skew_cb.set_value(self.app.defaults["tools_film_skew_cb"])
+        self.ui.film_skewx_entry.set_value(float(self.app.defaults["tools_film_skew_x_entry"]))
+        self.ui.film_skewy_entry.set_value(float(self.app.defaults["tools_film_skew_y_entry"]))
+        self.ui.film_skew_reference.set_value(self.app.defaults["tools_film_skew_ref_radio"])
+        self.ui.film_mirror_cb.set_value(self.app.defaults["tools_film_mirror_cb"])
+        self.ui.film_mirror_axis.set_value(self.app.defaults["tools_film_mirror_axis_radio"])
+        self.ui.file_type_radio.set_value(self.app.defaults["tools_film_file_type_radio"])
+        self.ui.orientation_radio.set_value(self.app.defaults["tools_film_orientation"])
+        self.ui.pagesize_combo.set_value(self.app.defaults["tools_film_pagesize"])
+
+        self.ui.tf_type_obj_combo.set_value('grb')
+        self.ui.tf_type_box_combo.set_value('grb')
+        # run once to update the obj_type attribute in the FCCombobox so the last object is showed in cb
+        self.on_type_obj_index_changed(val='grb')
+        self.on_type_box_index_changed(val='grb')
 
-        self.film_scalex_label = QtWidgets.QLabel('%s:' % _("X factor"))
-        self.film_scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.film_scalex_entry.set_range(-999.9999, 999.9999)
-        self.film_scalex_entry.set_precision(self.decimals)
-        self.film_scalex_entry.setSingleStep(0.01)
+    def on_film_creation(self):
+        log.debug("ToolFilm.Film.on_film_creation() started ...")
 
-        grid0.addWidget(self.film_scalex_label, 7, 0)
-        grid0.addWidget(self.film_scalex_entry, 7, 1)
+        try:
+            name = self.ui.tf_object_combo.currentText()
+        except Exception:
+            self.app.inform.emit('[ERROR_NOTCL] %s' %
+                                 _("No FlatCAM object selected. Load an object for Film and retry."))
+            return
 
-        self.film_scaley_label = QtWidgets.QLabel('%s:' % _("Y factor"))
-        self.film_scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.film_scaley_entry.set_range(-999.9999, 999.9999)
-        self.film_scaley_entry.set_precision(self.decimals)
-        self.film_scaley_entry.setSingleStep(0.01)
+        try:
+            boxname = self.ui.tf_box_combo.currentText()
+        except Exception:
+            self.app.inform.emit('[ERROR_NOTCL] %s' %
+                                 _("No FlatCAM object selected. Load an object for Box and retry."))
+            return
 
-        grid0.addWidget(self.film_scaley_label, 8, 0)
-        grid0.addWidget(self.film_scaley_entry, 8, 1)
+        if name == '' or boxname == '':
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("No FlatCAM object selected."))
+            return
 
-        self.ois_scale = OptionalHideInputSection(self.film_scale_cb,
-                                                  [
-                                                      self.film_scalex_label,
-                                                      self.film_scalex_entry,
-                                                      self.film_scaley_label,
-                                                      self.film_scaley_entry
-                                                  ])
+        scale_stroke_width = float(self.ui.film_scale_stroke_entry.get_value())
+        source = self.ui.source_punch.get_value()
+        file_type = self.ui.file_type_radio.get_value()
 
-        separator_line = QtWidgets.QFrame()
-        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line, 9, 0, 1, 2)
+        # #################################################################
+        # ################ STARTING THE JOB ###############################
+        # #################################################################
 
-        # Skew Geometry
-        self.film_skew_cb = FCCheckBox('%s' % _("Skew Film geometry"))
-        self.film_skew_cb.setToolTip(
-            _("Positive values will skew to the right\n"
-              "while negative values will skew to the left.")
-        )
-        self.film_skew_cb.setStyleSheet(
-            """
-            QCheckBox {font-weight: bold; color: black}
-            """
-        )
-        grid0.addWidget(self.film_skew_cb, 10, 0, 1, 2)
+        self.app.inform.emit(_("Generating Film ..."))
 
-        self.film_skewx_label = QtWidgets.QLabel('%s:' % _("X angle"))
-        self.film_skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.film_skewx_entry.set_range(-999.9999, 999.9999)
-        self.film_skewx_entry.set_precision(self.decimals)
-        self.film_skewx_entry.setSingleStep(0.01)
+        if self.ui.film_type.get_value() == "pos":
 
-        grid0.addWidget(self.film_skewx_label, 11, 0)
-        grid0.addWidget(self.film_skewx_entry, 11, 1)
+            if self.ui.punch_cb.get_value() is False:
+                self.generate_positive_normal_film(name, boxname, factor=scale_stroke_width, ftype=file_type)
+            else:
+                self.generate_positive_punched_film(name, boxname, source, factor=scale_stroke_width, ftype=file_type)
+        else:
+            self.generate_negative_film(name, boxname, factor=scale_stroke_width, ftype=file_type)
 
-        self.film_skewy_label = QtWidgets.QLabel('%s:' % _("Y angle"))
-        self.film_skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.film_skewy_entry.set_range(-999.9999, 999.9999)
-        self.film_skewy_entry.set_precision(self.decimals)
-        self.film_skewy_entry.setSingleStep(0.01)
+    def generate_positive_normal_film(self, name, boxname, factor, ftype='svg'):
+        log.debug("ToolFilm.Film.generate_positive_normal_film() started ...")
 
-        grid0.addWidget(self.film_skewy_label, 12, 0)
-        grid0.addWidget(self.film_skewy_entry, 12, 1)
+        scale_factor_x = None
+        scale_factor_y = None
+        skew_factor_x = None
+        skew_factor_y = None
+        mirror = None
+        skew_reference = 'center'
 
-        self.film_skew_ref_label = QtWidgets.QLabel('%s:' % _("Reference"))
-        self.film_skew_ref_label.setToolTip(
-            _("The reference point to be used as origin for the skew.\n"
-              "It can be one of the four points of the geometry bounding box.")
-        )
-        self.film_skew_reference = RadioSet([{'label': _('Bottom Left'), 'value': 'bottomleft'},
-                                             {'label': _('Top Left'), 'value': 'topleft'},
-                                             {'label': _('Bottom Right'), 'value': 'bottomright'},
-                                             {'label': _('Top right'), 'value': 'topright'}],
-                                            orientation='vertical',
-                                            stretch=False)
+        if self.ui.film_scale_cb.get_value():
+            if self.ui.film_scalex_entry.get_value() != 1.0:
+                scale_factor_x = self.ui.film_scalex_entry.get_value()
+            if self.ui.film_scaley_entry.get_value() != 1.0:
+                scale_factor_y = self.ui.film_scaley_entry.get_value()
+        if self.ui.film_skew_cb.get_value():
+            if self.ui.film_skewx_entry.get_value() != 0.0:
+                skew_factor_x = self.ui.film_skewx_entry.get_value()
+            if self.ui.film_skewy_entry.get_value() != 0.0:
+                skew_factor_y = self.ui.film_skewy_entry.get_value()
+
+            skew_reference = self.ui.film_skew_reference.get_value()
+        if self.ui.film_mirror_cb.get_value():
+            if self.ui.film_mirror_axis.get_value() != 'none':
+                mirror = self.ui.film_mirror_axis.get_value()
 
-        grid0.addWidget(self.film_skew_ref_label, 13, 0)
-        grid0.addWidget(self.film_skew_reference, 13, 1)
+        if ftype == 'svg':
+            filter_ext = "SVG Files (*.SVG);;"\
+                         "All Files (*.*)"
+        elif ftype == 'png':
+            filter_ext = "PNG Files (*.PNG);;" \
+                         "All Files (*.*)"
+        else:
+            filter_ext = "PDF Files (*.PDF);;" \
+                         "All Files (*.*)"
 
-        self.ois_skew = OptionalHideInputSection(self.film_skew_cb,
-                                                 [
-                                                     self.film_skewx_label,
-                                                     self.film_skewx_entry,
-                                                     self.film_skewy_label,
-                                                     self.film_skewy_entry,
-                                                     self.film_skew_ref_label,
-                                                     self.film_skew_reference
-                                                 ])
+        try:
+            filename, _f = FCFileSaveDialog.get_saved_filename(
+                caption=_("Export positive film"),
+                directory=self.app.get_last_save_folder() + '/' + name + '_film',
+                ext_filter=filter_ext)
+        except TypeError:
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export positive film"))
 
-        separator_line1 = QtWidgets.QFrame()
-        separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line1, 14, 0, 1, 2)
+        filename = str(filename)
 
-        # Mirror Geometry
-        self.film_mirror_cb = FCCheckBox('%s' % _("Mirror Film geometry"))
-        self.film_mirror_cb.setToolTip(
-            _("Mirror the film geometry on the selected axis or on both.")
-        )
-        self.film_mirror_cb.setStyleSheet(
-            """
-            QCheckBox {font-weight: bold; color: black}
-            """
-        )
-        grid0.addWidget(self.film_mirror_cb, 15, 0, 1, 2)
+        if str(filename) == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
+            return
+        else:
+            pagesize = self.ui.pagesize_combo.get_value()
+            orientation = self.ui.orientation_radio.get_value()
+            color = self.app.defaults['tools_film_color']
 
-        self.film_mirror_axis = RadioSet([{'label': _('None'), 'value': 'none'},
-                                          {'label': _('X'), 'value': 'x'},
-                                          {'label': _('Y'), 'value': 'y'},
-                                          {'label': _('Both'), 'value': 'both'}],
-                                         stretch=False)
-        self.film_mirror_axis_label = QtWidgets.QLabel('%s:' % _("Mirror axis"))
+            self.export_positive(name, boxname, filename,
+                                 scale_stroke_factor=factor,
+                                 scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
+                                 skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
+                                 skew_reference=skew_reference,
+                                 mirror=mirror,
+                                 pagesize_val=pagesize, orientation_val=orientation, color_val=color, opacity_val=1.0,
+                                 ftype=ftype
+                                 )
 
-        grid0.addWidget(self.film_mirror_axis_label, 16, 0)
-        grid0.addWidget(self.film_mirror_axis, 16, 1)
+    def generate_positive_punched_film(self, name, boxname, source, factor, ftype='svg'):
 
-        self.ois_mirror = OptionalHideInputSection(self.film_mirror_cb,
-                                                   [
-                                                       self.film_mirror_axis_label,
-                                                       self.film_mirror_axis
-                                                   ])
+        film_obj = self.app.collection.get_by_name(name)
 
-        separator_line2 = QtWidgets.QFrame()
-        separator_line2.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid0.addWidget(separator_line2, 17, 0, 1, 2)
+        if source == 'exc':
+            log.debug("ToolFilm.Film.generate_positive_punched_film() with Excellon source started ...")
 
-        self.film_param_label = QtWidgets.QLabel('<b>%s</b>' % _("Film Parameters"))
+            try:
+                exc_name = self.ui.exc_combo.currentText()
+            except Exception:
+                self.app.inform.emit('[ERROR_NOTCL] %s' %
+                                     _("No Excellon object selected. Load an object for punching reference and retry."))
+                return
 
-        grid0.addWidget(self.film_param_label, 18, 0, 1, 2)
+            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)
 
-        # Scale Stroke size
-        self.film_scale_stroke_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.film_scale_stroke_entry.set_range(-999.9999, 999.9999)
-        self.film_scale_stroke_entry.setSingleStep(0.01)
-        self.film_scale_stroke_entry.set_precision(self.decimals)
+            def init_func(new_obj, app_obj):
+                new_obj.solid_geometry = deepcopy(punched_solid_geometry)
 
-        self.film_scale_stroke_label = QtWidgets.QLabel('%s:' % _("Scale Stroke"))
-        self.film_scale_stroke_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_stroke_label, 19, 0)
-        grid0.addWidget(self.film_scale_stroke_entry, 19, 1)
+            outname = name + "_punched"
+            self.app.app_obj.new_object('gerber', outname, init_func)
 
-        # Film Type
-        self.film_type = RadioSet([{'label': _('Positive'), 'value': 'pos'},
-                                   {'label': _('Negative'), 'value': 'neg'}],
-                                  stretch=False)
-        self.film_type_label = QtWidgets.QLabel(_("Film Type:"))
-        self.film_type_label.setToolTip(
-            _("Generate a Positive black film or a Negative film.\n"
-              "Positive means that it will print the features\n"
-              "with black on a white canvas.\n"
-              "Negative means that it will print the features\n"
-              "with white on a black canvas.\n"
-              "The Film format is SVG.")
-        )
-        grid0.addWidget(self.film_type_label, 21, 0)
-        grid0.addWidget(self.film_type, 21, 1)
+            self.generate_positive_normal_film(outname, boxname, factor=factor, ftype=ftype)
+        else:
+            log.debug("ToolFilm.Film.generate_positive_punched_film() with Pad center source started ...")
 
-        # Boundary for negative film generation
-        self.boundary_entry = FCDoubleSpinner(callback=self.confirmation_message)
-        self.boundary_entry.set_range(-999.9999, 999.9999)
-        self.boundary_entry.setSingleStep(0.01)
-        self.boundary_entry.set_precision(self.decimals)
+            punch_size = float(self.ui.punch_size_spinner.get_value())
 
-        self.boundary_label = QtWidgets.QLabel('%s:' % _("Border"))
-        self.boundary_label.setToolTip(
-            _("Specify a border around the object.\n"
-              "Only for negative film.\n"
-              "It helps if we use as a Box Object the same \n"
-              "object as in Film Object. It will create a thick\n"
-              "black bar around the actual print allowing for a\n"
-              "better delimitation of the outline features which are of\n"
-              "white color like the rest and which may confound with the\n"
-              "surroundings if not for this border.")
-        )
-        grid0.addWidget(self.boundary_label, 22, 0)
-        grid0.addWidget(self.boundary_entry, 22, 1)
+            punching_geo = []
+            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))
 
-        self.boundary_label.hide()
-        self.boundary_entry.hide()
+            punching_geo = MultiPolygon(punching_geo)
+            if not isinstance(film_obj.solid_geometry, Polygon):
+                temp_solid_geometry = MultiPolygon(film_obj.solid_geometry)
+            else:
+                temp_solid_geometry = film_obj.solid_geometry
+            punched_solid_geometry = temp_solid_geometry.difference(punching_geo)
 
-        # 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, 23, 0, 1, 2)
+            if punched_solid_geometry == temp_solid_geometry:
+                self.app.inform.emit('[WARNING_NOTCL] %s' %
+                                     _("Could not generate punched hole film because the newly created object geometry "
+                                       "is the same as the one in the source object geometry..."))
+                return 'fail'
 
-        # 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)
+            def init_func(new_obj, app_obj):
+                new_obj.solid_geometry = deepcopy(punched_solid_geometry)
 
-        punch_grid.setColumnStretch(0, 0)
-        punch_grid.setColumnStretch(1, 1)
+            outname = name + "_punched"
+            self.app.app_obj.new_object('gerber', outname, init_func)
 
-        self.ois_p = OptionalHideInputSection(self.punch_cb, [self.punch_frame])
+            self.generate_positive_normal_film(outname, boxname, factor=factor, ftype=ftype)
 
-        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)
+    def generate_negative_film(self, name, boxname, factor, ftype='svg'):
+        log.debug("ToolFilm.Film.generate_negative_film() started ...")
 
-        self.exc_label = QtWidgets.QLabel('%s:' % _("Excellon Obj"))
-        self.exc_label.setToolTip(
-            _("Remove the geometry of Excellon from the Film to create the holes in pads.")
-        )
-        self.exc_combo = FCComboBox()
-        self.exc_combo.setModel(self.app.collection)
-        self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
-        self.exc_combo.is_last = True
-        self.exc_combo.obj_type = "Excellon"
+        scale_factor_x = None
+        scale_factor_y = None
+        skew_factor_x = None
+        skew_factor_y = None
+        mirror = None
+        skew_reference = 'center'
 
-        punch_grid.addWidget(self.exc_label, 1, 0)
-        punch_grid.addWidget(self.exc_combo, 1, 1)
+        if self.ui.film_scale_cb.get_value():
+            if self.ui.film_scalex_entry.get_value() != 1.0:
+                scale_factor_x = self.ui.film_scalex_entry.get_value()
+            if self.ui.film_scaley_entry.get_value() != 1.0:
+                scale_factor_y = self.ui.film_scaley_entry.get_value()
+        if self.ui.film_skew_cb.get_value():
+            if self.ui.film_skewx_entry.get_value() != 0.0:
+                skew_factor_x = self.ui.film_skewx_entry.get_value()
+            if self.ui.film_skewy_entry.get_value() != 0.0:
+                skew_factor_y = self.ui.film_skewy_entry.get_value()
 
-        self.exc_label.hide()
-        self.exc_combo.hide()
+            skew_reference = self.ui.film_skew_reference.get_value()
+        if self.ui.film_mirror_cb.get_value():
+            if self.ui.film_mirror_axis.get_value() != 'none':
+                mirror = self.ui.film_mirror_axis.get_value()
 
-        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(callback=self.confirmation_message)
-        self.punch_size_spinner.set_range(0, 999.9999)
-        self.punch_size_spinner.setSingleStep(0.1)
-        self.punch_size_spinner.set_precision(self.decimals)
+        border = float(self.ui.boundary_entry.get_value())
 
-        punch_grid.addWidget(self.punch_size_label, 2, 0)
-        punch_grid.addWidget(self.punch_size_spinner, 2, 1)
+        if border is None:
+            border = 0
 
-        self.punch_size_label.hide()
-        self.punch_size_spinner.hide()
+        if ftype == 'svg':
+            filter_ext = "SVG Files (*.SVG);;"\
+                         "All Files (*.*)"
+        elif ftype == 'png':
+            filter_ext = "PNG Files (*.PNG);;" \
+                         "All Files (*.*)"
+        else:
+            filter_ext = "PDF Files (*.PDF);;" \
+                         "All Files (*.*)"
 
-        grid1 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid1)
-        grid1.setColumnStretch(0, 0)
-        grid1.setColumnStretch(1, 1)
+        try:
+            filename, _f = FCFileSaveDialog.get_saved_filename(
+                caption=_("Export negative film"),
+                directory=self.app.get_last_save_folder() + '/' + name + '_film',
+                ext_filter=filter_ext)
+        except TypeError:
+            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export negative film"))
 
-        separator_line3 = QtWidgets.QFrame()
-        separator_line3.setFrameShape(QtWidgets.QFrame.HLine)
-        separator_line3.setFrameShadow(QtWidgets.QFrame.Sunken)
-        grid1.addWidget(separator_line3, 0, 0, 1, 2)
+        filename = str(filename)
 
-        # File type
-        self.file_type_radio = RadioSet([{'label': _('SVG'), 'value': 'svg'},
-                                         {'label': _('PNG'), 'value': 'png'},
-                                         {'label': _('PDF'), 'value': 'pdf'}
-                                         ], stretch=False)
+        if str(filename) == "":
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
+            return
+        else:
+            self.export_negative(name, boxname, filename, border,
+                                 scale_stroke_factor=factor,
+                                 scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
+                                 skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
+                                 skew_reference=skew_reference,
+                                 mirror=mirror, ftype=ftype
+                                 )
 
-        self.file_type_label = QtWidgets.QLabel(_("Film Type:"))
-        self.file_type_label.setToolTip(
-            _("The file type of the saved film. Can be:\n"
-              "- 'SVG' -> open-source vectorial format\n"
-              "- 'PNG' -> raster image\n"
-              "- 'PDF' -> portable document format")
-        )
-        grid1.addWidget(self.file_type_label, 1, 0)
-        grid1.addWidget(self.file_type_radio, 1, 1)
+    def export_negative(self, obj_name, box_name, filename, boundary,
+                        scale_stroke_factor=0.00,
+                        scale_factor_x=None, scale_factor_y=None,
+                        skew_factor_x=None, skew_factor_y=None, skew_reference='center',
+                        mirror=None,
+                        use_thread=True, ftype='svg'):
+        """
+        Exports a Geometry Object to an SVG file in negative.
 
-        # Page orientation
-        self.orientation_label = QtWidgets.QLabel('%s:' % _("Page Orientation"))
-        self.orientation_label.setToolTip(_("Can be:\n"
-                                            "- Portrait\n"
-                                            "- Landscape"))
-
-        self.orientation_radio = RadioSet([{'label': _('Portrait'), 'value': 'p'},
-                                           {'label': _('Landscape'), 'value': 'l'},
-                                           ], stretch=False)
-
-        grid1.addWidget(self.orientation_label, 2, 0)
-        grid1.addWidget(self.orientation_radio, 2, 1)
-
-        # Page Size
-        self.pagesize_label = QtWidgets.QLabel('%s:' % _("Page Size"))
-        self.pagesize_label.setToolTip(_("A selection of standard ISO 216 page sizes."))
+        :param obj_name: the name of the FlatCAM object to be saved as SVG
+        :param box_name: the name of the FlatCAM object to be used as delimitation of the content to be saved
+        :param filename: Path to the SVG file to save to.
+        :param boundary: thickness of a black border to surround all the features
+        :param scale_stroke_factor: factor by which to change/scale the thickness of the features
+        :param scale_factor_x: factor to scale the svg geometry on the X axis
+        :param scale_factor_y: factor to scale the svg geometry on the Y axis
+        :param skew_factor_x: factor to skew the svg geometry on the X axis
+        :param skew_factor_y: factor to skew the svg geometry on the Y axis
+        :param skew_reference: reference to use for skew. Can be 'bottomleft', 'bottomright', 'topleft', 'topright' and
+        those are the 4 points of the bounding box of the geometry to be skewed.
+        :param mirror: can be 'x' or 'y' or 'both'. Axis on which to mirror the svg geometry
+        :param use_thread: if to be run in a separate thread; boolean
+        :param ftype: the type of file for saving the film: 'svg', 'png' or 'pdf'
+        :return:
+        """
+        self.app.defaults.report_usage("export_negative()")
 
-        self.pagesize_combo = FCComboBox()
+        if filename is None:
+            filename = self.app.defaults["global_last_save_folder"]
 
-        self.pagesize = {}
-        self.pagesize.update(
-            {
-                'Bounds': None,
-                'A0': (841*mm, 1189*mm),
-                'A1': (594*mm, 841*mm),
-                'A2': (420*mm, 594*mm),
-                'A3': (297*mm, 420*mm),
-                'A4': (210*mm, 297*mm),
-                'A5': (148*mm, 210*mm),
-                'A6': (105*mm, 148*mm),
-                'A7': (74*mm, 105*mm),
-                'A8': (52*mm, 74*mm),
-                'A9': (37*mm, 52*mm),
-                'A10': (26*mm, 37*mm),
-
-                'B0': (1000*mm, 1414*mm),
-                'B1': (707*mm, 1000*mm),
-                'B2': (500*mm, 707*mm),
-                'B3': (353*mm, 500*mm),
-                'B4': (250*mm, 353*mm),
-                'B5': (176*mm, 250*mm),
-                'B6': (125*mm, 176*mm),
-                'B7': (88*mm, 125*mm),
-                'B8': (62*mm, 88*mm),
-                'B9': (44*mm, 62*mm),
-                'B10': (31*mm, 44*mm),
-
-                'C0': (917*mm, 1297*mm),
-                'C1': (648*mm, 917*mm),
-                'C2': (458*mm, 648*mm),
-                'C3': (324*mm, 458*mm),
-                'C4': (229*mm, 324*mm),
-                'C5': (162*mm, 229*mm),
-                'C6': (114*mm, 162*mm),
-                'C7': (81*mm, 114*mm),
-                'C8': (57*mm, 81*mm),
-                'C9': (40*mm, 57*mm),
-                'C10': (28*mm, 40*mm),
+        self.app.log.debug("export_svg() negative")
 
-                # American paper sizes
-                'LETTER': (8.5*inch, 11*inch),
-                'LEGAL': (8.5*inch, 14*inch),
-                'ELEVENSEVENTEEN': (11*inch, 17*inch),
+        try:
+            obj = self.app.collection.get_by_name(str(obj_name))
+        except Exception:
+            return "Could not retrieve object: %s" % obj_name
 
-                # From https://en.wikipedia.org/wiki/Paper_size
-                'JUNIOR_LEGAL': (5*inch, 8*inch),
-                'HALF_LETTER': (5.5*inch, 8*inch),
-                'GOV_LETTER': (8*inch, 10.5*inch),
-                'GOV_LEGAL': (8.5*inch, 13*inch),
-                'LEDGER': (17*inch, 11*inch),
-            }
-        )
+        try:
+            box = self.app.collection.get_by_name(str(box_name))
+        except Exception:
+            return "Could not retrieve object: %s" % box_name
 
-        page_size_list = list(self.pagesize.keys())
-        self.pagesize_combo.addItems(page_size_list)
+        if box is None:
+            self.app.inform.emit('[WARNING_NOTCL] %s: %s' % (_("No object Box. Using instead"), obj))
+            box = obj
 
-        grid1.addWidget(self.pagesize_label, 3, 0)
-        grid1.addWidget(self.pagesize_combo, 3, 1)
+        def make_negative_film():
+            exported_svg = obj.export_svg(scale_stroke_factor=scale_stroke_factor,
+                                          scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
+                                          skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
+                                          mirror=mirror
+                                          )
 
-        self.on_film_type(val='hide')
+            # Determine bounding area for svg export
+            bounds = box.bounds()
+            size = box.size()
 
-        # Buttons
-        self.film_object_button = QtWidgets.QPushButton(_("Save Film"))
-        self.film_object_button.setToolTip(
-            _("Create a Film for the selected object, within\n"
-              "the specified box. Does not create a new \n "
-              "FlatCAM object, but directly save it in the\n"
-              "selected format.")
-        )
-        self.film_object_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        grid1.addWidget(self.film_object_button, 4, 0, 1, 2)
+            uom = obj.units.lower()
 
-        self.layout.addStretch()
+            # Convert everything to strings for use in the xml doc
+            svgwidth = str(size[0] + (2 * boundary))
+            svgheight = str(size[1] + (2 * boundary))
+            minx = str(bounds[0] - boundary)
+            miny = str(bounds[1] + boundary + size[1])
+            miny_rect = str(bounds[1] - boundary)
 
-        # ## Reset Tool
-        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
-        self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
-        self.reset_button.setToolTip(
-            _("Will reset the tool parameters.")
-        )
-        self.reset_button.setStyleSheet("""
-                        QPushButton
-                        {
-                            font-weight: bold;
-                        }
-                        """)
-        self.layout.addWidget(self.reset_button)
+            # Add a SVG Header and footer to the svg output from shapely
+            # The transform flips the Y Axis so that everything renders
+            # properly within svg apps such as inkscape
+            svg_header = '<svg xmlns="http://www.w3.org/2000/svg" ' \
+                         'version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" '
+            svg_header += 'width="' + svgwidth + uom + '" '
+            svg_header += 'height="' + svgheight + uom + '" '
+            svg_header += 'viewBox="' + minx + ' -' + miny + ' ' + svgwidth + ' ' + svgheight + '" '
+            svg_header += '>'
+            svg_header += '<g transform="scale(1,-1)">'
+            svg_footer = '</g> </svg>'
 
-        self.units = self.app.defaults['units']
+            # Change the attributes of the exported SVG
+            # We don't need stroke-width - wrong, we do when we have lines with certain width
+            # We set opacity to maximum
+            # We set the color to WHITE
+            root = ET.fromstring(exported_svg)
+            for child in root:
+                child.set('fill', '#FFFFFF')
+                child.set('opacity', '1.0')
+                child.set('stroke', '#FFFFFF')
 
-        # ## Signals
-        self.film_object_button.clicked.connect(self.on_film_creation)
-        self.tf_type_obj_combo.activated_custom.connect(self.on_type_obj_index_changed)
-        self.tf_type_box_combo.activated_custom.connect(self.on_type_box_index_changed)
+            # first_svg_elem = 'rect x="' + minx + '" ' + 'y="' + miny_rect + '" '
+            # first_svg_elem += 'width="' + svgwidth + '" ' + 'height="' + svgheight + '" '
+            # first_svg_elem += 'fill="#000000" opacity="1.0" stroke-width="0.0"'
 
-        self.film_type.activated_custom.connect(self.on_film_type)
-        self.source_punch.activated_custom.connect(self.on_punch_source)
-        self.file_type_radio.activated_custom.connect(self.on_file_type)
-        self.reset_button.clicked.connect(self.set_tool_ui)
+            first_svg_elem_tag = 'rect'
+            first_svg_elem_attribs = {
+                'x': minx,
+                'y': miny_rect,
+                'width': svgwidth,
+                'height': svgheight,
+                'id': 'neg_rect',
+                'style': 'fill:#000000;opacity:1.0;stroke-width:0.0'
+            }
 
-    def on_type_obj_index_changed(self, val):
-        obj_type = 2 if val == 'geo' else 0
-        self.tf_object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
-        self.tf_object_combo.setCurrentIndex(0)
-        self.tf_object_combo.obj_type = {
-            "grb": "gerber", "geo": "geometry"
-        }[self.tf_type_obj_combo.get_value()]
+            root.insert(0, ET.Element(first_svg_elem_tag, first_svg_elem_attribs))
+            exported_svg = ET.tostring(root)
 
-    def on_type_box_index_changed(self, val):
-        obj_type = 2 if val == 'geo' else 0
-        self.tf_box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
-        self.tf_box_combo.setCurrentIndex(0)
-        self.tf_box_combo.obj_type = {
-            "grb": "gerber", "geo": "geometry"
-        }[self.tf_type_obj_combo.get_value()]
+            svg_elem = svg_header + str(exported_svg) + svg_footer
 
-    def run(self, toggle=True):
-        self.app.defaults.report_usage("ToolFilm()")
+            # Parse the xml through a xml parser just to add line feeds
+            # and to make it look more pretty for the output
+            doc = parse_xml_string(svg_elem)
+            doc_final = doc.toprettyxml()
 
-        if toggle:
-            # if the splitter is hidden, display it, else hide it but only if the current widget is the same
-            if self.app.ui.splitter.sizes()[0] == 0:
-                self.app.ui.splitter.setSizes([1, 1])
+            if ftype == 'svg':
+                try:
+                    with open(filename, 'w') as fp:
+                        fp.write(doc_final)
+                except PermissionError:
+                    self.app.inform.emit('[WARNING] %s' %
+                                         _("Permission denied, saving not possible.\n"
+                                           "Most likely another app is holding the file open and not accessible."))
+                    return 'fail'
+            elif ftype == 'png':
+                try:
+                    doc_final = StringIO(doc_final)
+                    drawing = svg2rlg(doc_final)
+                    renderPM.drawToFile(drawing, filename, 'PNG')
+                except Exception as e:
+                    log.debug("FilmTool.export_negative() --> PNG output --> %s" % str(e))
+                    return 'fail'
             else:
                 try:
-                    if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
-                        # if tab is populated with the tool but it does not have the focus, focus on it
-                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
-                            # focus on Tool Tab
-                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
-                        else:
-                            self.app.ui.splitter.setSizes([0, 1])
-                except AttributeError:
-                    pass
-        else:
-            if self.app.ui.splitter.sizes()[0] == 0:
-                self.app.ui.splitter.setSizes([1, 1])
+                    if self.units == 'INCH':
+                        unit = inch
+                    else:
+                        unit = mm
 
-        AppTool.run(self)
+                    doc_final = StringIO(doc_final)
+                    drawing = svg2rlg(doc_final)
 
-        self.set_tool_ui()
+                    p_size = self.ui.pagesize_combo.get_value()
+                    if p_size == 'Bounds':
+                        renderPDF.drawToFile(drawing, filename)
+                    else:
+                        if self.ui.orientation_radio.get_value() == 'p':
+                            page_size = portrait(self.ui.pagesize[p_size])
+                        else:
+                            page_size = landscape(self.ui.pagesize[p_size])
 
-        self.app.ui.notebook.setTabText(2, _("Film Tool"))
+                        my_canvas = canvas.Canvas(filename, pagesize=page_size)
+                        my_canvas.translate(bounds[0] * unit, bounds[1] * unit)
+                        renderPDF.draw(drawing, my_canvas, 0, 0)
+                        my_canvas.save()
+                except Exception as e:
+                    log.debug("FilmTool.export_negative() --> PDF output --> %s" % str(e))
+                    return 'fail'
 
-    def install(self, icon=None, separator=None, **kwargs):
-        AppTool.install(self, icon, separator, shortcut='Alt+L', **kwargs)
+            if self.app.defaults["global_open_style"] is False:
+                self.app.file_opened.emit("SVG", filename)
+            self.app.file_saved.emit("SVG", filename)
+            self.app.inform.emit('[success] %s: %s' % (_("Film file exported to"), filename))
 
-    def set_tool_ui(self):
-        self.reset_fields()
+        if use_thread is True:
+            proc = self.app.proc_container.new(_("Generating Film ... Please wait."))
 
-        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.on_film_type(val=f_type)
+            def job_thread_film(app_obj):
+                try:
+                    make_negative_film()
+                except Exception:
+                    proc.done()
+                    return
+                proc.done()
 
-        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.app.worker_task.emit({'fcn': job_thread_film, 'params': [self]})
+        else:
+            make_negative_film()
 
-        scale_stroke_width = self.app.defaults["tools_film_scale_stroke"] if \
-            self.app.defaults["tools_film_scale_stroke"] else 0.0
-        self.film_scale_stroke_entry.set_value(int(scale_stroke_width))
-
-        self.punch_cb.set_value(False)
-        self.source_punch.set_value('exc')
-
-        self.film_scale_cb.set_value(self.app.defaults["tools_film_scale_cb"])
-        self.film_scalex_entry.set_value(float(self.app.defaults["tools_film_scale_x_entry"]))
-        self.film_scaley_entry.set_value(float(self.app.defaults["tools_film_scale_y_entry"]))
-        self.film_skew_cb.set_value(self.app.defaults["tools_film_skew_cb"])
-        self.film_skewx_entry.set_value(float(self.app.defaults["tools_film_skew_x_entry"]))
-        self.film_skewy_entry.set_value(float(self.app.defaults["tools_film_skew_y_entry"]))
-        self.film_skew_reference.set_value(self.app.defaults["tools_film_skew_ref_radio"])
-        self.film_mirror_cb.set_value(self.app.defaults["tools_film_mirror_cb"])
-        self.film_mirror_axis.set_value(self.app.defaults["tools_film_mirror_axis_radio"])
-        self.file_type_radio.set_value(self.app.defaults["tools_film_file_type_radio"])
-        self.orientation_radio.set_value(self.app.defaults["tools_film_orientation"])
-        self.pagesize_combo.set_value(self.app.defaults["tools_film_pagesize"])
-
-        self.tf_type_obj_combo.set_value('grb')
-        self.tf_type_box_combo.set_value('grb')
-        # run once to update the obj_type attribute in the FCCombobox so the last object is showed in cb
-        self.on_type_obj_index_changed(val='grb')
-        self.on_type_box_index_changed(val='grb')
+    def export_positive(self, obj_name, box_name, filename,
+                        scale_stroke_factor=0.00,
+                        scale_factor_x=None, scale_factor_y=None,
+                        skew_factor_x=None, skew_factor_y=None, skew_reference='center',
+                        mirror=None,  orientation_val='p', pagesize_val='A4', color_val='black', opacity_val=1.0,
+                        use_thread=True, ftype='svg'):
 
-    def on_film_type(self, val):
-        type_of_film = val
+        """
+        Exports a Geometry Object to an SVG file in positive black.
 
-        if type_of_film == 'neg':
-            self.boundary_label.show()
-            self.boundary_entry.show()
-            self.punch_cb.set_value(False)  # required so the self.punch_frame it's hidden also by the signal emitted
-            self.punch_cb.hide()
-        else:
-            self.boundary_label.hide()
-            self.boundary_entry.hide()
-            self.punch_cb.show()
+        :param obj_name:            the name of the FlatCAM object to be saved
+        :param box_name:            the name of the FlatCAM object to be used as delimitation of the content to be saved
+        :param filename:            Path to the file to save to.
+        :param scale_stroke_factor: factor by which to change/scale the thickness of the features
+        :param scale_factor_x:      factor to scale the geometry on the X axis
+        :param scale_factor_y:      factor to scale the geometry on the Y axis
+        :param skew_factor_x:       factor to skew the geometry on the X axis
+        :param skew_factor_y:       factor to skew the geometry on the Y axis
+        :param skew_reference:      reference to use for skew. Can be 'bottomleft', 'bottomright', 'topleft',
+        'topright' and those are the 4 points of the bounding box of the geometry to be skewed.
+        :param mirror:              can be 'x' or 'y' or 'both'. Axis on which to mirror the svg geometry
+        :param orientation_val:
+        :param pagesize_val:
+        :param color_val:
+        :param opacity_val:
+        :param use_thread:          if to be run in a separate thread; boolean
+        :param ftype:               the type of file for saving the film: 'svg', 'png' or 'pdf'
 
-    def on_file_type(self, val):
-        if val == 'pdf':
-            self.orientation_label.show()
-            self.orientation_radio.show()
-            self.pagesize_label.show()
-            self.pagesize_combo.show()
-        else:
-            self.orientation_label.hide()
-            self.orientation_radio.hide()
-            self.pagesize_label.hide()
-            self.pagesize_combo.hide()
-
-    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()
+        :return:
+        """
+        self.app.defaults.report_usage("export_positive()")
 
-        if val == 'pad' and self.tf_type_obj_combo.get_value() == 'geo':
-            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."))
+        if filename is None:
+            filename = self.app.defaults["global_last_save_folder"]
 
-    def on_film_creation(self):
-        log.debug("ToolFilm.Film.on_film_creation() started ...")
+        self.app.log.debug("export_svg() black")
 
         try:
-            name = self.tf_object_combo.currentText()
+            obj = self.app.collection.get_by_name(str(obj_name))
         except Exception:
-            self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _("No FlatCAM object selected. Load an object for Film and retry."))
-            return
+            return "Could not retrieve object: %s" % obj_name
 
         try:
-            boxname = self.tf_box_combo.currentText()
+            box = self.app.collection.get_by_name(str(box_name))
         except Exception:
-            self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _("No FlatCAM object selected. Load an object for Box and retry."))
-            return
-
-        if name == '' or boxname == '':
-            self.app.inform.emit('[ERROR_NOTCL] %s' % _("No FlatCAM object selected."))
-            return
+            return "Could not retrieve object: %s" % box_name
 
-        scale_stroke_width = float(self.film_scale_stroke_entry.get_value())
-        source = self.source_punch.get_value()
-        file_type = self.file_type_radio.get_value()
+        if box is None:
+            self.inform.emit('[WARNING_NOTCL] %s: %s' % (_("No object Box. Using instead"), obj))
+            box = obj
 
-        # #################################################################
-        # ################ STARTING THE JOB ###############################
-        # #################################################################
+        p_size = pagesize_val
+        orientation = orientation_val
+        color = color_val
+        transparency_level = opacity_val
 
-        self.app.inform.emit(_("Generating Film ..."))
+        def make_positive_film(p_size, orientation, color, transparency_level):
+            log.debug("FilmTool.export_positive().make_positive_film()")
 
-        if self.film_type.get_value() == "pos":
+            exported_svg = obj.export_svg(scale_stroke_factor=scale_stroke_factor,
+                                          scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
+                                          skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
+                                          mirror=mirror
+                                          )
 
-            if self.punch_cb.get_value() is False:
-                self.generate_positive_normal_film(name, boxname, factor=scale_stroke_width, ftype=file_type)
-            else:
-                self.generate_positive_punched_film(name, boxname, source, factor=scale_stroke_width, ftype=file_type)
-        else:
-            self.generate_negative_film(name, boxname, factor=scale_stroke_width, ftype=file_type)
+            # Change the attributes of the exported SVG
+            # We don't need stroke-width
+            # We set opacity to maximum
+            # We set the colour to WHITE
+            root = ET.fromstring(exported_svg)
+            for child in root:
+                child.set('fill', str(color))
+                child.set('opacity', str(transparency_level))
+                child.set('stroke', str(color))
 
-    def generate_positive_normal_film(self, name, boxname, factor, ftype='svg'):
-        log.debug("ToolFilm.Film.generate_positive_normal_film() started ...")
+            exported_svg = ET.tostring(root)
 
-        scale_factor_x = None
-        scale_factor_y = None
-        skew_factor_x = None
-        skew_factor_y = None
-        mirror = None
-        skew_reference = 'center'
+            # Determine bounding area for svg export
+            bounds = box.bounds()
+            size = box.size()
 
-        if self.film_scale_cb.get_value():
-            if self.film_scalex_entry.get_value() != 1.0:
-                scale_factor_x = self.film_scalex_entry.get_value()
-            if self.film_scaley_entry.get_value() != 1.0:
-                scale_factor_y = self.film_scaley_entry.get_value()
-        if self.film_skew_cb.get_value():
-            if self.film_skewx_entry.get_value() != 0.0:
-                skew_factor_x = self.film_skewx_entry.get_value()
-            if self.film_skewy_entry.get_value() != 0.0:
-                skew_factor_y = self.film_skewy_entry.get_value()
-
-            skew_reference = self.film_skew_reference.get_value()
-        if self.film_mirror_cb.get_value():
-            if self.film_mirror_axis.get_value() != 'none':
-                mirror = self.film_mirror_axis.get_value()
+            # This contain the measure units
+            uom = obj.units.lower()
 
-        if ftype == 'svg':
-            filter_ext = "SVG Files (*.SVG);;"\
-                         "All Files (*.*)"
-        elif ftype == 'png':
-            filter_ext = "PNG Files (*.PNG);;" \
-                         "All Files (*.*)"
-        else:
-            filter_ext = "PDF Files (*.PDF);;" \
-                         "All Files (*.*)"
+            # Define a boundary around SVG of about 1.0mm (~39mils)
+            if uom in "mm":
+                boundary = 1.0
+            else:
+                boundary = 0.0393701
 
-        try:
-            filename, _f = FCFileSaveDialog.get_saved_filename(
-                caption=_("Export positive film"),
-                directory=self.app.get_last_save_folder() + '/' + name + '_film',
-                ext_filter=filter_ext)
-        except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export positive film"))
+            # Convert everything to strings for use in the xml doc
+            svgwidth = str(size[0] + (2 * boundary))
+            svgheight = str(size[1] + (2 * boundary))
+            minx = str(bounds[0] - boundary)
+            miny = str(bounds[1] + boundary + size[1])
 
-        filename = str(filename)
+            # Add a SVG Header and footer to the svg output from shapely
+            # The transform flips the Y Axis so that everything renders
+            # properly within svg apps such as inkscape
+            svg_header = '<svg xmlns="http://www.w3.org/2000/svg" ' \
+                         'version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" '
+            svg_header += 'width="' + svgwidth + uom + '" '
+            svg_header += 'height="' + svgheight + uom + '" '
+            svg_header += 'viewBox="' + minx + ' -' + miny + ' ' + svgwidth + ' ' + svgheight + '" '
+            svg_header += '>'
+            svg_header += '<g transform="scale(1,-1)">'
+            svg_footer = '</g> </svg>'
 
-        if str(filename) == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
-            return
-        else:
-            pagesize = self.pagesize_combo.get_value()
-            orientation = self.orientation_radio.get_value()
-            color = self.app.defaults['tools_film_color']
+            svg_elem = str(svg_header) + str(exported_svg) + str(svg_footer)
 
-            self.export_positive(name, boxname, filename,
-                                 scale_stroke_factor=factor,
-                                 scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
-                                 skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
-                                 skew_reference=skew_reference,
-                                 mirror=mirror,
-                                 pagesize_val=pagesize, orientation_val=orientation, color_val=color, opacity_val=1.0,
-                                 ftype=ftype
-                                 )
+            # Parse the xml through a xml parser just to add line feeds
+            # and to make it look more pretty for the output
+            doc = parse_xml_string(svg_elem)
+            doc_final = doc.toprettyxml()
 
-    def generate_positive_punched_film(self, name, boxname, source, factor, ftype='svg'):
+            if ftype == 'svg':
+                try:
+                    with open(filename, 'w') as fp:
+                        fp.write(doc_final)
+                except PermissionError:
+                    self.app.inform.emit('[WARNING] %s' %
+                                         _("Permission denied, saving not possible.\n"
+                                           "Most likely another app is holding the file open and not accessible."))
+                    return 'fail'
+            elif ftype == 'png':
+                try:
+                    doc_final = StringIO(doc_final)
+                    drawing = svg2rlg(doc_final)
+                    renderPM.drawToFile(drawing, filename, 'PNG')
+                except Exception as e:
+                    log.debug("FilmTool.export_positive() --> PNG output --> %s" % str(e))
+                    return 'fail'
+            else:
+                try:
+                    if self.units == 'IN':
+                        unit = inch
+                    else:
+                        unit = mm
 
-        film_obj = self.app.collection.get_by_name(name)
+                    doc_final = StringIO(doc_final)
+                    drawing = svg2rlg(doc_final)
 
-        if source == 'exc':
-            log.debug("ToolFilm.Film.generate_positive_punched_film() with Excellon source started ...")
+                    if p_size == 'Bounds':
+                        renderPDF.drawToFile(drawing, filename)
+                    else:
+                        if orientation == 'p':
+                            page_size = portrait(self.pagesize[p_size])
+                        else:
+                            page_size = landscape(self.pagesize[p_size])
 
-            try:
-                exc_name = self.exc_combo.currentText()
-            except Exception:
-                self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                     _("No Excellon object selected. Load an object for punching reference and retry."))
-                return
+                        my_canvas = canvas.Canvas(filename, pagesize=page_size)
+                        my_canvas.translate(bounds[0] * unit, bounds[1] * unit)
+                        renderPDF.draw(drawing, my_canvas, 0, 0)
+                        my_canvas.save()
+                except Exception as e:
+                    log.debug("FilmTool.export_positive() --> PDF output --> %s" % str(e))
+                    return 'fail'
 
-            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)
+            if self.app.defaults["global_open_style"] is False:
+                self.app.file_opened.emit("SVG", filename)
+            self.app.file_saved.emit("SVG", filename)
+            self.app.inform.emit('[success] %s: %s' % (_("Film file exported to"), filename))
 
-            def init_func(new_obj, app_obj):
-                new_obj.solid_geometry = deepcopy(punched_solid_geometry)
+        if use_thread is True:
+            proc = self.app.proc_container.new(_("Generating Film ... Please wait."))
 
-            outname = name + "_punched"
-            self.app.app_obj.new_object('gerber', outname, init_func)
+            def job_thread_film():
+                try:
+                    make_positive_film(p_size=p_size, orientation=orientation, color=color,
+                                       transparency_level=transparency_level)
+                except Exception:
+                    proc.done()
+                    return
+                proc.done()
 
-            self.generate_positive_normal_film(outname, boxname, factor=factor, ftype=ftype)
+            self.app.worker_task.emit({'fcn': job_thread_film, 'params': []})
         else:
-            log.debug("ToolFilm.Film.generate_positive_punched_film() with Pad center source started ...")
+            make_positive_film(p_size=p_size, orientation=orientation, color=color,
+                               transparency_level=transparency_level)
 
-            punch_size = float(self.punch_size_spinner.get_value())
+    def reset_fields(self):
+        self.ui.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.ui.tf_box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
 
-            punching_geo = []
-            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)
-            if not isinstance(film_obj.solid_geometry, Polygon):
-                temp_solid_geometry = MultiPolygon(film_obj.solid_geometry)
-            else:
-                temp_solid_geometry = film_obj.solid_geometry
-            punched_solid_geometry = temp_solid_geometry.difference(punching_geo)
+class FilmUI:
 
-            if punched_solid_geometry == temp_solid_geometry:
-                self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                     _("Could not generate punched hole film because the newly created object geometry "
-                                       "is the same as the one in the source object geometry..."))
-                return 'fail'
+    toolName = _("Film PCB")
 
-            def init_func(new_obj, app_obj):
-                new_obj.solid_geometry = deepcopy(punched_solid_geometry)
+    def __init__(self, layout, app):
+        self.app = app
+        self.decimals = self.app.decimals
+        self.layout = layout
 
-            outname = name + "_punched"
-            self.app.app_obj.new_object('gerber', outname, init_func)
+        # ## Title
+        title_label = QtWidgets.QLabel("%s" % self.toolName)
+        title_label.setStyleSheet("""
+                                QLabel
+                                {
+                                    font-size: 16px;
+                                    font-weight: bold;
+                                }
+                                """)
+        self.layout.addWidget(title_label)
+        self.layout.addWidget(QtWidgets.QLabel(""))
 
-            self.generate_positive_normal_film(outname, boxname, factor=factor, ftype=ftype)
+        # Form Layout
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
 
-    def generate_negative_film(self, name, boxname, factor, ftype='svg'):
-        log.debug("ToolFilm.Film.generate_negative_film() started ...")
+        grid0.setColumnStretch(0, 0)
+        grid0.setColumnStretch(1, 1)
 
-        scale_factor_x = None
-        scale_factor_y = None
-        skew_factor_x = None
-        skew_factor_y = None
-        mirror = None
-        skew_reference = 'center'
+        # Type of object for which to create the film
+        self.tf_type_obj_combo = RadioSet([{'label': _('Gerber'), 'value': 'grb'},
+                                           {'label': _('Geometry'), 'value': 'geo'}])
 
-        if self.film_scale_cb.get_value():
-            if self.film_scalex_entry.get_value() != 1.0:
-                scale_factor_x = self.film_scalex_entry.get_value()
-            if self.film_scaley_entry.get_value() != 1.0:
-                scale_factor_y = self.film_scaley_entry.get_value()
-        if self.film_skew_cb.get_value():
-            if self.film_skewx_entry.get_value() != 0.0:
-                skew_factor_x = self.film_skewx_entry.get_value()
-            if self.film_skewy_entry.get_value() != 0.0:
-                skew_factor_y = self.film_skewy_entry.get_value()
+        self.tf_type_obj_combo_label = QtWidgets.QLabel('<b>%s</b>:' % _("Object"))
+        self.tf_type_obj_combo_label.setToolTip(
+            _("Specify the type of object for which to create the film.\n"
+              "The object can be of type: Gerber or Geometry.\n"
+              "The selection here decide the type of objects that will be\n"
+              "in the Film Object combobox.")
+        )
+        grid0.addWidget(self.tf_type_obj_combo_label, 0, 0)
+        grid0.addWidget(self.tf_type_obj_combo, 0, 1)
 
-            skew_reference = self.film_skew_reference.get_value()
-        if self.film_mirror_cb.get_value():
-            if self.film_mirror_axis.get_value() != 'none':
-                mirror = self.film_mirror_axis.get_value()
+        # List of objects for which we can create the film
+        self.tf_object_combo = FCComboBox()
+        self.tf_object_combo.setModel(self.app.collection)
+        self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.tf_object_combo.is_last = True
 
-        border = float(self.boundary_entry.get_value())
+        grid0.addWidget(self.tf_object_combo, 1, 0, 1, 2)
 
-        if border is None:
-            border = 0
+        # Type of Box Object to be used as an envelope for film creation
+        # Within this we can create negative
+        self.tf_type_box_combo = RadioSet([{'label': _('Gerber'), 'value': 'grb'},
+                                           {'label': _('Geometry'), 'value': 'geo'}])
 
-        if ftype == 'svg':
-            filter_ext = "SVG Files (*.SVG);;"\
-                         "All Files (*.*)"
-        elif ftype == 'png':
-            filter_ext = "PNG Files (*.PNG);;" \
-                         "All Files (*.*)"
-        else:
-            filter_ext = "PDF Files (*.PDF);;" \
-                         "All Files (*.*)"
+        self.tf_type_box_combo_label = QtWidgets.QLabel(_("Box Type:"))
+        self.tf_type_box_combo_label.setToolTip(
+            _("Specify the type of object to be used as an container for\n"
+              "film creation. It can be: Gerber or Geometry type."
+              "The selection here decide the type of objects that will be\n"
+              "in the Box Object combobox.")
+        )
+        grid0.addWidget(self.tf_type_box_combo_label, 2, 0)
+        grid0.addWidget(self.tf_type_box_combo, 2, 1)
 
-        try:
-            filename, _f = FCFileSaveDialog.get_saved_filename(
-                caption=_("Export negative film"),
-                directory=self.app.get_last_save_folder() + '/' + name + '_film',
-                ext_filter=filter_ext)
-        except TypeError:
-            filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export negative film"))
+        # Box
+        self.tf_box_combo = FCComboBox()
+        self.tf_box_combo.setModel(self.app.collection)
+        self.tf_box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.tf_box_combo.is_last = True
 
-        filename = str(filename)
+        grid0.addWidget(self.tf_box_combo, 3, 0, 1, 2)
 
-        if str(filename) == "":
-            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))
-            return
-        else:
-            self.export_negative(name, boxname, filename, border,
-                                 scale_stroke_factor=factor,
-                                 scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
-                                 skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
-                                 skew_reference=skew_reference,
-                                 mirror=mirror, ftype=ftype
-                                 )
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 4, 0, 1, 2)
 
-    def export_negative(self, obj_name, box_name, filename, boundary,
-                        scale_stroke_factor=0.00,
-                        scale_factor_x=None, scale_factor_y=None,
-                        skew_factor_x=None, skew_factor_y=None, skew_reference='center',
-                        mirror=None,
-                        use_thread=True, ftype='svg'):
-        """
-        Exports a Geometry Object to an SVG file in negative.
+        self.film_adj_label = QtWidgets.QLabel('<b>%s</b>' % _("Film Adjustments"))
+        self.film_adj_label.setToolTip(
+            _("Sometime the printers will distort the print shape, especially the Laser types.\n"
+              "This section provide the tools to compensate for the print distortions.")
+        )
 
-        :param obj_name: the name of the FlatCAM object to be saved as SVG
-        :param box_name: the name of the FlatCAM object to be used as delimitation of the content to be saved
-        :param filename: Path to the SVG file to save to.
-        :param boundary: thickness of a black border to surround all the features
-        :param scale_stroke_factor: factor by which to change/scale the thickness of the features
-        :param scale_factor_x: factor to scale the svg geometry on the X axis
-        :param scale_factor_y: factor to scale the svg geometry on the Y axis
-        :param skew_factor_x: factor to skew the svg geometry on the X axis
-        :param skew_factor_y: factor to skew the svg geometry on the Y axis
-        :param skew_reference: reference to use for skew. Can be 'bottomleft', 'bottomright', 'topleft', 'topright' and
-        those are the 4 points of the bounding box of the geometry to be skewed.
-        :param mirror: can be 'x' or 'y' or 'both'. Axis on which to mirror the svg geometry
-        :param use_thread: if to be run in a separate thread; boolean
-        :param ftype: the type of file for saving the film: 'svg', 'png' or 'pdf'
-        :return:
-        """
-        self.app.defaults.report_usage("export_negative()")
+        grid0.addWidget(self.film_adj_label, 5, 0, 1, 2)
 
-        if filename is None:
-            filename = self.app.defaults["global_last_save_folder"]
+        # Scale Geometry
+        self.film_scale_cb = FCCheckBox('%s' % _("Scale Film geometry"))
+        self.film_scale_cb.setToolTip(
+            _("A value greater than 1 will stretch the film\n"
+              "while a value less than 1 will jolt it.")
+        )
+        self.film_scale_cb.setStyleSheet(
+            """
+            QCheckBox {font-weight: bold; color: black}
+            """
+        )
+        grid0.addWidget(self.film_scale_cb, 6, 0, 1, 2)
 
-        self.app.log.debug("export_svg() negative")
+        self.film_scalex_label = QtWidgets.QLabel('%s:' % _("X factor"))
+        self.film_scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.film_scalex_entry.set_range(-999.9999, 999.9999)
+        self.film_scalex_entry.set_precision(self.decimals)
+        self.film_scalex_entry.setSingleStep(0.01)
 
-        try:
-            obj = self.app.collection.get_by_name(str(obj_name))
-        except Exception:
-            # TODO: The return behavior has not been established... should raise exception?
-            return "Could not retrieve object: %s" % obj_name
+        grid0.addWidget(self.film_scalex_label, 7, 0)
+        grid0.addWidget(self.film_scalex_entry, 7, 1)
 
-        try:
-            box = self.app.collection.get_by_name(str(box_name))
-        except Exception:
-            # TODO: The return behavior has not been established... should raise exception?
-            return "Could not retrieve object: %s" % box_name
+        self.film_scaley_label = QtWidgets.QLabel('%s:' % _("Y factor"))
+        self.film_scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.film_scaley_entry.set_range(-999.9999, 999.9999)
+        self.film_scaley_entry.set_precision(self.decimals)
+        self.film_scaley_entry.setSingleStep(0.01)
 
-        if box is None:
-            self.app.inform.emit('[WARNING_NOTCL] %s: %s' % (_("No object Box. Using instead"), obj))
-            box = obj
+        grid0.addWidget(self.film_scaley_label, 8, 0)
+        grid0.addWidget(self.film_scaley_entry, 8, 1)
 
-        def make_negative_film():
-            exported_svg = obj.export_svg(scale_stroke_factor=scale_stroke_factor,
-                                          scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
-                                          skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
-                                          mirror=mirror
-                                          )
+        self.ois_scale = OptionalHideInputSection(self.film_scale_cb,
+                                                  [
+                                                      self.film_scalex_label,
+                                                      self.film_scalex_entry,
+                                                      self.film_scaley_label,
+                                                      self.film_scaley_entry
+                                                  ])
 
-            # Determine bounding area for svg export
-            bounds = box.bounds()
-            size = box.size()
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 9, 0, 1, 2)
 
-            uom = obj.units.lower()
+        # Skew Geometry
+        self.film_skew_cb = FCCheckBox('%s' % _("Skew Film geometry"))
+        self.film_skew_cb.setToolTip(
+            _("Positive values will skew to the right\n"
+              "while negative values will skew to the left.")
+        )
+        self.film_skew_cb.setStyleSheet(
+            """
+            QCheckBox {font-weight: bold; color: black}
+            """
+        )
+        grid0.addWidget(self.film_skew_cb, 10, 0, 1, 2)
 
-            # Convert everything to strings for use in the xml doc
-            svgwidth = str(size[0] + (2 * boundary))
-            svgheight = str(size[1] + (2 * boundary))
-            minx = str(bounds[0] - boundary)
-            miny = str(bounds[1] + boundary + size[1])
-            miny_rect = str(bounds[1] - boundary)
+        self.film_skewx_label = QtWidgets.QLabel('%s:' % _("X angle"))
+        self.film_skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.film_skewx_entry.set_range(-999.9999, 999.9999)
+        self.film_skewx_entry.set_precision(self.decimals)
+        self.film_skewx_entry.setSingleStep(0.01)
 
-            # Add a SVG Header and footer to the svg output from shapely
-            # The transform flips the Y Axis so that everything renders
-            # properly within svg apps such as inkscape
-            svg_header = '<svg xmlns="http://www.w3.org/2000/svg" ' \
-                         'version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" '
-            svg_header += 'width="' + svgwidth + uom + '" '
-            svg_header += 'height="' + svgheight + uom + '" '
-            svg_header += 'viewBox="' + minx + ' -' + miny + ' ' + svgwidth + ' ' + svgheight + '" '
-            svg_header += '>'
-            svg_header += '<g transform="scale(1,-1)">'
-            svg_footer = '</g> </svg>'
+        grid0.addWidget(self.film_skewx_label, 11, 0)
+        grid0.addWidget(self.film_skewx_entry, 11, 1)
 
-            # Change the attributes of the exported SVG
-            # We don't need stroke-width - wrong, we do when we have lines with certain width
-            # We set opacity to maximum
-            # We set the color to WHITE
-            root = ET.fromstring(exported_svg)
-            for child in root:
-                child.set('fill', '#FFFFFF')
-                child.set('opacity', '1.0')
-                child.set('stroke', '#FFFFFF')
+        self.film_skewy_label = QtWidgets.QLabel('%s:' % _("Y angle"))
+        self.film_skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.film_skewy_entry.set_range(-999.9999, 999.9999)
+        self.film_skewy_entry.set_precision(self.decimals)
+        self.film_skewy_entry.setSingleStep(0.01)
 
-            # first_svg_elem = 'rect x="' + minx + '" ' + 'y="' + miny_rect + '" '
-            # first_svg_elem += 'width="' + svgwidth + '" ' + 'height="' + svgheight + '" '
-            # first_svg_elem += 'fill="#000000" opacity="1.0" stroke-width="0.0"'
+        grid0.addWidget(self.film_skewy_label, 12, 0)
+        grid0.addWidget(self.film_skewy_entry, 12, 1)
 
-            first_svg_elem_tag = 'rect'
-            first_svg_elem_attribs = {
-                'x': minx,
-                'y': miny_rect,
-                'width': svgwidth,
-                'height': svgheight,
-                'id': 'neg_rect',
-                'style': 'fill:#000000;opacity:1.0;stroke-width:0.0'
-            }
+        self.film_skew_ref_label = QtWidgets.QLabel('%s:' % _("Reference"))
+        self.film_skew_ref_label.setToolTip(
+            _("The reference point to be used as origin for the skew.\n"
+              "It can be one of the four points of the geometry bounding box.")
+        )
+        self.film_skew_reference = RadioSet([{'label': _('Bottom Left'), 'value': 'bottomleft'},
+                                             {'label': _('Top Left'), 'value': 'topleft'},
+                                             {'label': _('Bottom Right'), 'value': 'bottomright'},
+                                             {'label': _('Top right'), 'value': 'topright'}],
+                                            orientation='vertical',
+                                            stretch=False)
 
-            root.insert(0, ET.Element(first_svg_elem_tag, first_svg_elem_attribs))
-            exported_svg = ET.tostring(root)
+        grid0.addWidget(self.film_skew_ref_label, 13, 0)
+        grid0.addWidget(self.film_skew_reference, 13, 1)
 
-            svg_elem = svg_header + str(exported_svg) + svg_footer
+        self.ois_skew = OptionalHideInputSection(self.film_skew_cb,
+                                                 [
+                                                     self.film_skewx_label,
+                                                     self.film_skewx_entry,
+                                                     self.film_skewy_label,
+                                                     self.film_skewy_entry,
+                                                     self.film_skew_ref_label,
+                                                     self.film_skew_reference
+                                                 ])
 
-            # Parse the xml through a xml parser just to add line feeds
-            # and to make it look more pretty for the output
-            doc = parse_xml_string(svg_elem)
-            doc_final = doc.toprettyxml()
+        separator_line1 = QtWidgets.QFrame()
+        separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line1, 14, 0, 1, 2)
 
-            if ftype == 'svg':
-                try:
-                    with open(filename, 'w') as fp:
-                        fp.write(doc_final)
-                except PermissionError:
-                    self.app.inform.emit('[WARNING] %s' %
-                                         _("Permission denied, saving not possible.\n"
-                                           "Most likely another app is holding the file open and not accessible."))
-                    return 'fail'
-            elif ftype == 'png':
-                try:
-                    doc_final = StringIO(doc_final)
-                    drawing = svg2rlg(doc_final)
-                    renderPM.drawToFile(drawing, filename, 'PNG')
-                except Exception as e:
-                    log.debug("FilmTool.export_negative() --> PNG output --> %s" % str(e))
-                    return 'fail'
-            else:
-                try:
-                    if self.units == 'INCH':
-                        unit = inch
-                    else:
-                        unit = mm
+        # Mirror Geometry
+        self.film_mirror_cb = FCCheckBox('%s' % _("Mirror Film geometry"))
+        self.film_mirror_cb.setToolTip(
+            _("Mirror the film geometry on the selected axis or on both.")
+        )
+        self.film_mirror_cb.setStyleSheet(
+            """
+            QCheckBox {font-weight: bold; color: black}
+            """
+        )
+        grid0.addWidget(self.film_mirror_cb, 15, 0, 1, 2)
 
-                    doc_final = StringIO(doc_final)
-                    drawing = svg2rlg(doc_final)
+        self.film_mirror_axis = RadioSet([{'label': _('None'), 'value': 'none'},
+                                          {'label': _('X'), 'value': 'x'},
+                                          {'label': _('Y'), 'value': 'y'},
+                                          {'label': _('Both'), 'value': 'both'}],
+                                         stretch=False)
+        self.film_mirror_axis_label = QtWidgets.QLabel('%s:' % _("Mirror axis"))
 
-                    p_size = self.pagesize_combo.get_value()
-                    if p_size == 'Bounds':
-                        renderPDF.drawToFile(drawing, filename)
-                    else:
-                        if self.orientation_radio.get_value() == 'p':
-                            page_size = portrait(self.pagesize[p_size])
-                        else:
-                            page_size = landscape(self.pagesize[p_size])
+        grid0.addWidget(self.film_mirror_axis_label, 16, 0)
+        grid0.addWidget(self.film_mirror_axis, 16, 1)
 
-                        my_canvas = canvas.Canvas(filename, pagesize=page_size)
-                        my_canvas.translate(bounds[0] * unit, bounds[1] * unit)
-                        renderPDF.draw(drawing, my_canvas, 0, 0)
-                        my_canvas.save()
-                except Exception as e:
-                    log.debug("FilmTool.export_negative() --> PDF output --> %s" % str(e))
-                    return 'fail'
+        self.ois_mirror = OptionalHideInputSection(self.film_mirror_cb,
+                                                   [
+                                                       self.film_mirror_axis_label,
+                                                       self.film_mirror_axis
+                                                   ])
 
-            if self.app.defaults["global_open_style"] is False:
-                self.app.file_opened.emit("SVG", filename)
-            self.app.file_saved.emit("SVG", filename)
-            self.app.inform.emit('[success] %s: %s' % (_("Film file exported to"), filename))
+        separator_line2 = QtWidgets.QFrame()
+        separator_line2.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line2, 17, 0, 1, 2)
 
-        if use_thread is True:
-            proc = self.app.proc_container.new(_("Generating Film ... Please wait."))
+        self.film_param_label = QtWidgets.QLabel('<b>%s</b>' % _("Film Parameters"))
 
-            def job_thread_film(app_obj):
-                try:
-                    make_negative_film()
-                except Exception:
-                    proc.done()
-                    return
-                proc.done()
+        grid0.addWidget(self.film_param_label, 18, 0, 1, 2)
 
-            self.app.worker_task.emit({'fcn': job_thread_film, 'params': [self]})
-        else:
-            make_negative_film()
+        # Scale Stroke size
+        self.film_scale_stroke_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.film_scale_stroke_entry.set_range(-999.9999, 999.9999)
+        self.film_scale_stroke_entry.setSingleStep(0.01)
+        self.film_scale_stroke_entry.set_precision(self.decimals)
 
-    def export_positive(self, obj_name, box_name, filename,
-                        scale_stroke_factor=0.00,
-                        scale_factor_x=None, scale_factor_y=None,
-                        skew_factor_x=None, skew_factor_y=None, skew_reference='center',
-                        mirror=None,  orientation_val='p', pagesize_val='A4', color_val='black', opacity_val=1.0,
-                        use_thread=True, ftype='svg'):
+        self.film_scale_stroke_label = QtWidgets.QLabel('%s:' % _("Scale Stroke"))
+        self.film_scale_stroke_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_stroke_label, 19, 0)
+        grid0.addWidget(self.film_scale_stroke_entry, 19, 1)
 
-        """
-        Exports a Geometry Object to an SVG file in positive black.
+        # Film Type
+        self.film_type = RadioSet([{'label': _('Positive'), 'value': 'pos'},
+                                   {'label': _('Negative'), 'value': 'neg'}],
+                                  stretch=False)
+        self.film_type_label = QtWidgets.QLabel(_("Film Type:"))
+        self.film_type_label.setToolTip(
+            _("Generate a Positive black film or a Negative film.\n"
+              "Positive means that it will print the features\n"
+              "with black on a white canvas.\n"
+              "Negative means that it will print the features\n"
+              "with white on a black canvas.\n"
+              "The Film format is SVG.")
+        )
+        grid0.addWidget(self.film_type_label, 21, 0)
+        grid0.addWidget(self.film_type, 21, 1)
+
+        # Boundary for negative film generation
+        self.boundary_entry = FCDoubleSpinner(callback=self.confirmation_message)
+        self.boundary_entry.set_range(-999.9999, 999.9999)
+        self.boundary_entry.setSingleStep(0.01)
+        self.boundary_entry.set_precision(self.decimals)
+
+        self.boundary_label = QtWidgets.QLabel('%s:' % _("Border"))
+        self.boundary_label.setToolTip(
+            _("Specify a border around the object.\n"
+              "Only for negative film.\n"
+              "It helps if we use as a Box Object the same \n"
+              "object as in Film Object. It will create a thick\n"
+              "black bar around the actual print allowing for a\n"
+              "better delimitation of the outline features which are of\n"
+              "white color like the rest and which may confound with the\n"
+              "surroundings if not for this border.")
+        )
+        grid0.addWidget(self.boundary_label, 22, 0)
+        grid0.addWidget(self.boundary_entry, 22, 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, 23, 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 the holes in pads.")
+        )
+        self.exc_combo = FCComboBox()
+        self.exc_combo.setModel(self.app.collection)
+        self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
+        self.exc_combo.is_last = True
+        self.exc_combo.obj_type = "Excellon"
+
+        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(callback=self.confirmation_message)
+        self.punch_size_spinner.set_range(0, 999.9999)
+        self.punch_size_spinner.setSingleStep(0.1)
+        self.punch_size_spinner.set_precision(self.decimals)
 
-        :param obj_name:            the name of the FlatCAM object to be saved
-        :param box_name:            the name of the FlatCAM object to be used as delimitation of the content to be saved
-        :param filename:            Path to the file to save to.
-        :param scale_stroke_factor: factor by which to change/scale the thickness of the features
-        :param scale_factor_x:      factor to scale the geometry on the X axis
-        :param scale_factor_y:      factor to scale the geometry on the Y axis
-        :param skew_factor_x:       factor to skew the geometry on the X axis
-        :param skew_factor_y:       factor to skew the geometry on the Y axis
-        :param skew_reference:      reference to use for skew. Can be 'bottomleft', 'bottomright', 'topleft',
-        'topright' and those are the 4 points of the bounding box of the geometry to be skewed.
-        :param mirror:              can be 'x' or 'y' or 'both'. Axis on which to mirror the svg geometry
-        :param orientation_val:
-        :param pagesize_val:
-        :param color_val:
-        :param opacity_val:
-        :param use_thread:          if to be run in a separate thread; boolean
-        :param ftype:               the type of file for saving the film: 'svg', 'png' or 'pdf'
+        punch_grid.addWidget(self.punch_size_label, 2, 0)
+        punch_grid.addWidget(self.punch_size_spinner, 2, 1)
 
-        :return:
-        """
-        self.app.defaults.report_usage("export_positive()")
+        self.punch_size_label.hide()
+        self.punch_size_spinner.hide()
 
-        if filename is None:
-            filename = self.app.defaults["global_last_save_folder"]
+        grid1 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid1)
+        grid1.setColumnStretch(0, 0)
+        grid1.setColumnStretch(1, 1)
 
-        self.app.log.debug("export_svg() black")
+        separator_line3 = QtWidgets.QFrame()
+        separator_line3.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line3.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid1.addWidget(separator_line3, 0, 0, 1, 2)
 
-        try:
-            obj = self.app.collection.get_by_name(str(obj_name))
-        except Exception:
-            # TODO: The return behavior has not been established... should raise exception?
-            return "Could not retrieve object: %s" % obj_name
+        # File type
+        self.file_type_radio = RadioSet([{'label': _('SVG'), 'value': 'svg'},
+                                         {'label': _('PNG'), 'value': 'png'},
+                                         {'label': _('PDF'), 'value': 'pdf'}
+                                         ], stretch=False)
 
-        try:
-            box = self.app.collection.get_by_name(str(box_name))
-        except Exception:
-            # TODO: The return behavior has not been established... should raise exception?
-            return "Could not retrieve object: %s" % box_name
+        self.file_type_label = QtWidgets.QLabel(_("Film Type:"))
+        self.file_type_label.setToolTip(
+            _("The file type of the saved film. Can be:\n"
+              "- 'SVG' -> open-source vectorial format\n"
+              "- 'PNG' -> raster image\n"
+              "- 'PDF' -> portable document format")
+        )
+        grid1.addWidget(self.file_type_label, 1, 0)
+        grid1.addWidget(self.file_type_radio, 1, 1)
 
-        if box is None:
-            self.inform.emit('[WARNING_NOTCL] %s: %s' % (_("No object Box. Using instead"), obj))
-            box = obj
+        # Page orientation
+        self.orientation_label = QtWidgets.QLabel('%s:' % _("Page Orientation"))
+        self.orientation_label.setToolTip(_("Can be:\n"
+                                            "- Portrait\n"
+                                            "- Landscape"))
 
-        p_size = pagesize_val
-        orientation = orientation_val
-        color = color_val
-        transparency_level = opacity_val
+        self.orientation_radio = RadioSet([{'label': _('Portrait'), 'value': 'p'},
+                                           {'label': _('Landscape'), 'value': 'l'},
+                                           ], stretch=False)
 
-        def make_positive_film(p_size, orientation, color, transparency_level):
-            log.debug("FilmTool.export_positive().make_positive_film()")
+        grid1.addWidget(self.orientation_label, 2, 0)
+        grid1.addWidget(self.orientation_radio, 2, 1)
 
-            exported_svg = obj.export_svg(scale_stroke_factor=scale_stroke_factor,
-                                          scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
-                                          skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
-                                          mirror=mirror
-                                          )
+        # Page Size
+        self.pagesize_label = QtWidgets.QLabel('%s:' % _("Page Size"))
+        self.pagesize_label.setToolTip(_("A selection of standard ISO 216 page sizes."))
 
-            # Change the attributes of the exported SVG
-            # We don't need stroke-width
-            # We set opacity to maximum
-            # We set the colour to WHITE
-            root = ET.fromstring(exported_svg)
-            for child in root:
-                child.set('fill', str(color))
-                child.set('opacity', str(transparency_level))
-                child.set('stroke', str(color))
+        self.pagesize_combo = FCComboBox()
 
-            exported_svg = ET.tostring(root)
+        self.pagesize = {}
+        self.pagesize.update(
+            {
+                'Bounds': None,
+                'A0': (841 * mm, 1189 * mm),
+                'A1': (594 * mm, 841 * mm),
+                'A2': (420 * mm, 594 * mm),
+                'A3': (297 * mm, 420 * mm),
+                'A4': (210 * mm, 297 * mm),
+                'A5': (148 * mm, 210 * mm),
+                'A6': (105 * mm, 148 * mm),
+                'A7': (74 * mm, 105 * mm),
+                'A8': (52 * mm, 74 * mm),
+                'A9': (37 * mm, 52 * mm),
+                'A10': (26 * mm, 37 * mm),
+
+                'B0': (1000 * mm, 1414 * mm),
+                'B1': (707 * mm, 1000 * mm),
+                'B2': (500 * mm, 707 * mm),
+                'B3': (353 * mm, 500 * mm),
+                'B4': (250 * mm, 353 * mm),
+                'B5': (176 * mm, 250 * mm),
+                'B6': (125 * mm, 176 * mm),
+                'B7': (88 * mm, 125 * mm),
+                'B8': (62 * mm, 88 * mm),
+                'B9': (44 * mm, 62 * mm),
+                'B10': (31 * mm, 44 * mm),
+
+                'C0': (917 * mm, 1297 * mm),
+                'C1': (648 * mm, 917 * mm),
+                'C2': (458 * mm, 648 * mm),
+                'C3': (324 * mm, 458 * mm),
+                'C4': (229 * mm, 324 * mm),
+                'C5': (162 * mm, 229 * mm),
+                'C6': (114 * mm, 162 * mm),
+                'C7': (81 * mm, 114 * mm),
+                'C8': (57 * mm, 81 * mm),
+                'C9': (40 * mm, 57 * mm),
+                'C10': (28 * mm, 40 * mm),
 
-            # Determine bounding area for svg export
-            bounds = box.bounds()
-            size = box.size()
+                # American paper sizes
+                'LETTER': (8.5 * inch, 11 * inch),
+                'LEGAL': (8.5 * inch, 14 * inch),
+                'ELEVENSEVENTEEN': (11 * inch, 17 * inch),
 
-            # This contain the measure units
-            uom = obj.units.lower()
+                # From https://en.wikipedia.org/wiki/Paper_size
+                'JUNIOR_LEGAL': (5 * inch, 8 * inch),
+                'HALF_LETTER': (5.5 * inch, 8 * inch),
+                'GOV_LETTER': (8 * inch, 10.5 * inch),
+                'GOV_LEGAL': (8.5 * inch, 13 * inch),
+                'LEDGER': (17 * inch, 11 * inch),
+            }
+        )
 
-            # Define a boundary around SVG of about 1.0mm (~39mils)
-            if uom in "mm":
-                boundary = 1.0
-            else:
-                boundary = 0.0393701
+        page_size_list = list(self.pagesize.keys())
+        self.pagesize_combo.addItems(page_size_list)
 
-            # Convert everything to strings for use in the xml doc
-            svgwidth = str(size[0] + (2 * boundary))
-            svgheight = str(size[1] + (2 * boundary))
-            minx = str(bounds[0] - boundary)
-            miny = str(bounds[1] + boundary + size[1])
+        grid1.addWidget(self.pagesize_label, 3, 0)
+        grid1.addWidget(self.pagesize_combo, 3, 1)
 
-            # Add a SVG Header and footer to the svg output from shapely
-            # The transform flips the Y Axis so that everything renders
-            # properly within svg apps such as inkscape
-            svg_header = '<svg xmlns="http://www.w3.org/2000/svg" ' \
-                         'version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" '
-            svg_header += 'width="' + svgwidth + uom + '" '
-            svg_header += 'height="' + svgheight + uom + '" '
-            svg_header += 'viewBox="' + minx + ' -' + miny + ' ' + svgwidth + ' ' + svgheight + '" '
-            svg_header += '>'
-            svg_header += '<g transform="scale(1,-1)">'
-            svg_footer = '</g> </svg>'
+        self.on_film_type(val='hide')
 
-            svg_elem = str(svg_header) + str(exported_svg) + str(svg_footer)
+        # Buttons
+        self.film_object_button = QtWidgets.QPushButton(_("Save Film"))
+        self.film_object_button.setToolTip(
+            _("Create a Film for the selected object, within\n"
+              "the specified box. Does not create a new \n "
+              "FlatCAM object, but directly save it in the\n"
+              "selected format.")
+        )
+        self.film_object_button.setStyleSheet("""
+                               QPushButton
+                               {
+                                   font-weight: bold;
+                               }
+                               """)
+        grid1.addWidget(self.film_object_button, 4, 0, 1, 2)
 
-            # Parse the xml through a xml parser just to add line feeds
-            # and to make it look more pretty for the output
-            doc = parse_xml_string(svg_elem)
-            doc_final = doc.toprettyxml()
+        self.layout.addStretch()
 
-            if ftype == 'svg':
-                try:
-                    with open(filename, 'w') as fp:
-                        fp.write(doc_final)
-                except PermissionError:
-                    self.app.inform.emit('[WARNING] %s' %
-                                         _("Permission denied, saving not possible.\n"
-                                           "Most likely another app is holding the file open and not accessible."))
-                    return 'fail'
-            elif ftype == 'png':
-                try:
-                    doc_final = StringIO(doc_final)
-                    drawing = svg2rlg(doc_final)
-                    renderPM.drawToFile(drawing, filename, 'PNG')
-                except Exception as e:
-                    log.debug("FilmTool.export_positive() --> PNG output --> %s" % str(e))
-                    return 'fail'
-            else:
-                try:
-                    if self.units == 'IN':
-                        unit = inch
-                    else:
-                        unit = mm
+        # ## Reset Tool
+        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
+        self.reset_button.setToolTip(
+            _("Will reset the tool parameters.")
+        )
+        self.reset_button.setStyleSheet("""
+                               QPushButton
+                               {
+                                   font-weight: bold;
+                               }
+                               """)
+        self.layout.addWidget(self.reset_button)
 
-                    doc_final = StringIO(doc_final)
-                    drawing = svg2rlg(doc_final)
+        # #################################### FINSIHED GUI ###########################
+        # #############################################################################
 
-                    if p_size == 'Bounds':
-                        renderPDF.drawToFile(drawing, filename)
-                    else:
-                        if orientation == 'p':
-                            page_size = portrait(self.pagesize[p_size])
-                        else:
-                            page_size = landscape(self.pagesize[p_size])
+    def on_film_type(self, val):
+        type_of_film = val
 
-                        my_canvas = canvas.Canvas(filename, pagesize=page_size)
-                        my_canvas.translate(bounds[0] * unit, bounds[1] * unit)
-                        renderPDF.draw(drawing, my_canvas, 0, 0)
-                        my_canvas.save()
-                except Exception as e:
-                    log.debug("FilmTool.export_positive() --> PDF output --> %s" % str(e))
-                    return 'fail'
+        if type_of_film == 'neg':
+            self.boundary_label.show()
+            self.boundary_entry.show()
+            self.punch_cb.set_value(False)  # required so the self.punch_frame it's hidden also by the signal emitted
+            self.punch_cb.hide()
+        else:
+            self.boundary_label.hide()
+            self.boundary_entry.hide()
+            self.punch_cb.show()
 
-            if self.app.defaults["global_open_style"] is False:
-                self.app.file_opened.emit("SVG", filename)
-            self.app.file_saved.emit("SVG", filename)
-            self.app.inform.emit('[success] %s: %s' % (_("Film file exported to"), filename))
+    def on_file_type(self, val):
+        if val == 'pdf':
+            self.orientation_label.show()
+            self.orientation_radio.show()
+            self.pagesize_label.show()
+            self.pagesize_combo.show()
+        else:
+            self.orientation_label.hide()
+            self.orientation_radio.hide()
+            self.pagesize_label.hide()
+            self.pagesize_combo.hide()
 
-        if use_thread is True:
-            proc = self.app.proc_container.new(_("Generating Film ... Please wait."))
+    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()
 
-            def job_thread_film(app_obj):
-                try:
-                    make_positive_film(p_size=p_size, orientation=orientation, color=color,
-                                       transparency_level=transparency_level)
-                except Exception:
-                    proc.done()
-                    return
-                proc.done()
+        if val == 'pad' and self.tf_type_obj_combo.get_value() == 'geo':
+            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."))
 
-            self.app.worker_task.emit({'fcn': job_thread_film, 'params': [self]})
+    def confirmation_message(self, accepted, minval, maxval):
+        if accepted is False:
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
+                                                                                  self.decimals,
+                                                                                  minval,
+                                                                                  self.decimals,
+                                                                                  maxval), False)
         else:
-            make_positive_film(p_size=p_size, orientation=orientation, color=color,
-                               transparency_level=transparency_level)
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
 
-    def reset_fields(self):
-        self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.tf_box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+    def confirmation_message_int(self, accepted, minval, maxval):
+        if accepted is False:
+            self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
+                                            (_("Edited value is out of range"), minval, maxval), False)
+        else:
+            self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)