Explorar el Código

Merged in marius_stanciu/flatcam_beta/Beta (pull request #270)

Beta 8.991 - enhancements
Marius Stanciu hace 6 años
padre
commit
637cba109c

+ 248 - 24
FlatCAMApp.py

@@ -31,6 +31,7 @@ from reportlab.pdfgen import canvas
 from reportlab.graphics import renderPM
 from reportlab.graphics import renderPM
 from reportlab.lib.units import inch, mm
 from reportlab.lib.units import inch, mm
 from reportlab.lib.pagesizes import landscape, portrait
 from reportlab.lib.pagesizes import landscape, portrait
+from svglib.svglib import svg2rlg
 
 
 from contextlib import contextmanager
 from contextlib import contextmanager
 import gc
 import gc
@@ -416,6 +417,10 @@ class App(QtCore.QObject):
             "global_stats": dict(),
             "global_stats": dict(),
             "global_tabs_detachable": True,
             "global_tabs_detachable": True,
             "global_jump_ref": 'abs',
             "global_jump_ref": 'abs',
+            "global_tpdf_tmargin": 15.0,
+            "global_tpdf_bmargin": 10.0,
+            "global_tpdf_lmargin": 20.0,
+            "global_tpdf_rmargin": 20.0,
 
 
             # General
             # General
             "global_graphic_engine": '3D',
             "global_graphic_engine": '3D',
@@ -978,6 +983,10 @@ class App(QtCore.QObject):
 
 
         self.current_units = self.defaults['units']
         self.current_units = self.defaults['units']
 
 
+        # store here the current self.defaults so it can be restored if Preferences changes are cancelled
+        self.current_defaults = dict()
+        self.current_defaults.update(self.defaults)
+
         # #############################################################################
         # #############################################################################
         # ##################### CREATE MULTIPROCESSING POOL ###########################
         # ##################### CREATE MULTIPROCESSING POOL ###########################
         # #############################################################################
         # #############################################################################
@@ -1085,6 +1094,11 @@ class App(QtCore.QObject):
             "global_bookmarks_limit": self.ui.general_defaults_form.general_app_group.bm_limit_spinner,
             "global_bookmarks_limit": self.ui.general_defaults_form.general_app_group.bm_limit_spinner,
             "global_machinist_setting": self.ui.general_defaults_form.general_app_group.machinist_cb,
             "global_machinist_setting": self.ui.general_defaults_form.general_app_group.machinist_cb,
 
 
+            "global_tpdf_tmargin": self.ui.general_defaults_form.general_app_group.tmargin_entry,
+            "global_tpdf_bmargin": self.ui.general_defaults_form.general_app_group.bmargin_entry,
+            "global_tpdf_lmargin": self.ui.general_defaults_form.general_app_group.lmargin_entry,
+            "global_tpdf_rmargin": self.ui.general_defaults_form.general_app_group.rmargin_entry,
+
             # General GUI Preferences
             # General GUI Preferences
             "global_gridx": self.ui.general_defaults_form.general_gui_group.gridx_entry,
             "global_gridx": self.ui.general_defaults_form.general_gui_group.gridx_entry,
             "global_gridy": self.ui.general_defaults_form.general_gui_group.gridy_entry,
             "global_gridy": self.ui.general_defaults_form.general_gui_group.gridy_entry,
@@ -1800,10 +1814,11 @@ class App(QtCore.QObject):
 
 
         self.ui.menufileexportdxf.triggered.connect(self.on_file_exportdxf)
         self.ui.menufileexportdxf.triggered.connect(self.on_file_exportdxf)
 
 
+        self.ui.menufile_print.triggered.connect(lambda: self.on_file_save_objects_pdf(use_thread=True))
+
         self.ui.menufilesaveproject.triggered.connect(self.on_file_saveproject)
         self.ui.menufilesaveproject.triggered.connect(self.on_file_saveproject)
         self.ui.menufilesaveprojectas.triggered.connect(self.on_file_saveprojectas)
         self.ui.menufilesaveprojectas.triggered.connect(self.on_file_saveprojectas)
         self.ui.menufilesaveprojectcopy.triggered.connect(lambda: self.on_file_saveprojectas(make_copy=True))
         self.ui.menufilesaveprojectcopy.triggered.connect(lambda: self.on_file_saveprojectas(make_copy=True))
-        self.ui.menufilesave_object_pdf.triggered.connect(self.on_file_save_object_pdf)
         self.ui.menufilesavedefaults.triggered.connect(self.on_file_savedefaults)
         self.ui.menufilesavedefaults.triggered.connect(self.on_file_savedefaults)
 
 
         self.ui.menufileexportpref.triggered.connect(self.on_export_preferences)
         self.ui.menufileexportpref.triggered.connect(self.on_export_preferences)
@@ -2940,17 +2955,25 @@ class App(QtCore.QObject):
             except Exception as e:
             except Exception as e:
                 log.debug("App.defaults_read_form() --> %s" % str(e))
                 log.debug("App.defaults_read_form() --> %s" % str(e))
 
 
-    def defaults_write_form(self, factor=None, fl_units=None):
+    def defaults_write_form(self, factor=None, fl_units=None, source_dict=None):
         """
         """
         Will set the values for all the GUI elements in Preferences GUI based on the values found in the
         Will set the values for all the GUI elements in Preferences GUI based on the values found in the
         self.defaults dictionary.
         self.defaults dictionary.
 
 
         :param factor: will apply a factor to the values that written in the GUI elements
         :param factor: will apply a factor to the values that written in the GUI elements
         :param fl_units: current measuring units in FlatCAM: Metric or Inch
         :param fl_units: current measuring units in FlatCAM: Metric or Inch
+        :param source_dict: the repository of options, usually is the self.defaults
         :return: None
         :return: None
         """
         """
-        for option in self.defaults:
-            self.defaults_write_form_field(option, factor=factor, units=fl_units)
+
+        options_storage = self.defaults if source_dict is None else source_dict
+
+        for option in options_storage:
+            if source_dict:
+                self.defaults_write_form_field(option, factor=factor, units=fl_units, defaults_dict=source_dict)
+            else:
+                self.defaults_write_form_field(option, factor=factor, units=fl_units)
+
             # try:
             # try:
             #     self.defaults_form_fields[option].set_value(self.defaults[option])
             #     self.defaults_form_fields[option].set_value(self.defaults[option])
             # except KeyError:
             # except KeyError:
@@ -2958,7 +2981,7 @@ class App(QtCore.QObject):
             #     # TODO: Rethink this?
             #     # TODO: Rethink this?
             #     pass
             #     pass
 
 
-    def defaults_write_form_field(self, field, factor=None, units=None):
+    def defaults_write_form_field(self, field, factor=None, units=None, defaults_dict=None):
         """
         """
         Basically it is the worker in the self.defaults_write_form()
         Basically it is the worker in the self.defaults_write_form()
 
 
@@ -2967,21 +2990,23 @@ class App(QtCore.QObject):
         :param units: current FLatCAM measuring units
         :param units: current FLatCAM measuring units
         :return: None, it updates GUI elements
         :return: None, it updates GUI elements
         """
         """
+
+        def_dict = self.defaults if defaults_dict is None else defaults_dict
         try:
         try:
             if factor is None:
             if factor is None:
                 if units is None:
                 if units is None:
-                    self.defaults_form_fields[field].set_value(self.defaults[field])
+                    self.defaults_form_fields[field].set_value(def_dict[field])
                 elif units == 'IN' and (field == 'global_gridx' or field == 'global_gridy'):
                 elif units == 'IN' and (field == 'global_gridx' or field == 'global_gridy'):
-                    self.defaults_form_fields[field].set_value(self.defaults[field])
+                    self.defaults_form_fields[field].set_value(def_dict[field])
                 elif units == 'MM' and (field == 'global_gridx' or field == 'global_gridy'):
                 elif units == 'MM' and (field == 'global_gridx' or field == 'global_gridy'):
-                    self.defaults_form_fields[field].set_value(self.defaults[field])
+                    self.defaults_form_fields[field].set_value(def_dict[field])
             else:
             else:
                 if units is None:
                 if units is None:
-                    self.defaults_form_fields[field].set_value(self.defaults[field] * factor)
+                    self.defaults_form_fields[field].set_value(def_dict[field] * factor)
                 elif units == 'IN' and (field == 'global_gridx' or field == 'global_gridy'):
                 elif units == 'IN' and (field == 'global_gridx' or field == 'global_gridy'):
-                    self.defaults_form_fields[field].set_value((self.defaults[field] * factor))
+                    self.defaults_form_fields[field].set_value((def_dict[field] * factor))
                 elif units == 'MM' and (field == 'global_gridx' or field == 'global_gridy'):
                 elif units == 'MM' and (field == 'global_gridx' or field == 'global_gridy'):
-                    self.defaults_form_fields[field].set_value((self.defaults[field] * factor))
+                    self.defaults_form_fields[field].set_value((def_dict[field] * factor))
         except KeyError:
         except KeyError:
             # self.log.debug("defaults_write_form(): No field for: %s" % option)
             # self.log.debug("defaults_write_form(): No field for: %s" % option)
             # TODO: Rethink this?
             # TODO: Rethink this?
@@ -3891,6 +3916,10 @@ class App(QtCore.QObject):
                                  _("Failed to parse defaults file."))
                                  _("Failed to parse defaults file."))
                 return
                 return
             self.defaults.update(defaults_from_file)
             self.defaults.update(defaults_from_file)
+            # update the dict that is used to restore the values in the defaults form if Cancel is clicked in the
+            # Preferences window
+            self.current_defaults.update(defaults_from_file)
+
             self.on_preferences_edited()
             self.on_preferences_edited()
             self.inform.emit('[success] %s: %s' %
             self.inform.emit('[success] %s: %s' %
                              (_("Imported Defaults from"), filename))
                              (_("Imported Defaults from"), filename))
@@ -5764,14 +5793,15 @@ class App(QtCore.QObject):
                       "tools_cr_trace_size_val", "tools_cr_c2c_val", "tools_cr_c2o_val", "tools_cr_s2s_val",
                       "tools_cr_trace_size_val", "tools_cr_c2c_val", "tools_cr_c2o_val", "tools_cr_s2s_val",
                       "tools_cr_s2sm_val", "tools_cr_s2o_val", "tools_cr_sm2sm_val", "tools_cr_ri_val",
                       "tools_cr_s2sm_val", "tools_cr_s2o_val", "tools_cr_sm2sm_val", "tools_cr_ri_val",
                       "tools_cr_h2h_val", "tools_cr_dh_val", "tools_fiducials_dia", "tools_fiducials_margin",
                       "tools_cr_h2h_val", "tools_cr_dh_val", "tools_fiducials_dia", "tools_fiducials_margin",
-                      "tools_fiducials_mode", "tools_fiducials_second_pos", "tools_fiducials_type",
                       "tools_fiducials_line_thickness",
                       "tools_fiducials_line_thickness",
                       "tools_copper_thieving_clearance", "tools_copper_thieving_margin",
                       "tools_copper_thieving_clearance", "tools_copper_thieving_margin",
                       "tools_copper_thieving_dots_dia", "tools_copper_thieving_dots_spacing",
                       "tools_copper_thieving_dots_dia", "tools_copper_thieving_dots_spacing",
                       "tools_copper_thieving_squares_size", "tools_copper_thieving_squares_spacing",
                       "tools_copper_thieving_squares_size", "tools_copper_thieving_squares_spacing",
                       "tools_copper_thieving_lines_size", "tools_copper_thieving_lines_spacing",
                       "tools_copper_thieving_lines_size", "tools_copper_thieving_lines_spacing",
                       "tools_copper_thieving_rb_margin", "tools_copper_thieving_rb_thickness",
                       "tools_copper_thieving_rb_margin", "tools_copper_thieving_rb_thickness",
-                      'global_gridx', 'global_gridy', 'global_snap_max', "global_tolerance"]
+
+                      'global_gridx', 'global_gridy', 'global_snap_max', "global_tolerance",
+                      'global_tpdf_bmargin', 'global_tpdf_tmargin', 'global_tpdf_rmargin', 'global_tpdf_lmargin']
 
 
         def scale_defaults(sfactor):
         def scale_defaults(sfactor):
             for dim in dimensions:
             for dim in dimensions:
@@ -5796,6 +5826,7 @@ class App(QtCore.QObject):
                         tools_diameters = [eval(a) for a in tools_string if a != '']
                         tools_diameters = [eval(a) for a in tools_string if a != '']
                     except Exception as e:
                     except Exception as e:
                         log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
                         log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
+                        continue
 
 
                     self.defaults['geometry_cnctooldia'] = ''
                     self.defaults['geometry_cnctooldia'] = ''
                     for t in range(len(tools_diameters)):
                     for t in range(len(tools_diameters)):
@@ -5808,6 +5839,7 @@ class App(QtCore.QObject):
                         ncctools = [eval(a) for a in tools_string if a != '']
                         ncctools = [eval(a) for a in tools_string if a != '']
                     except Exception as e:
                     except Exception as e:
                         log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
                         log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
+                        continue
 
 
                     self.defaults['tools_ncctools'] = ''
                     self.defaults['tools_ncctools'] = ''
                     for t in range(len(ncctools)):
                     for t in range(len(ncctools)):
@@ -5820,6 +5852,7 @@ class App(QtCore.QObject):
                         sptools = [eval(a) for a in tools_string if a != '']
                         sptools = [eval(a) for a in tools_string if a != '']
                     except Exception as e:
                     except Exception as e:
                         log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
                         log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
+                        continue
 
 
                     self.defaults['tools_solderpaste_tools'] = ""
                     self.defaults['tools_solderpaste_tools'] = ""
                     for t in range(len(sptools)):
                     for t in range(len(sptools)):
@@ -5839,6 +5872,7 @@ class App(QtCore.QObject):
                             val = float(self.defaults[dim]) * sfactor
                             val = float(self.defaults[dim]) * sfactor
                         except Exception as e:
                         except Exception as e:
                             log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
                             log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
+                            continue
 
 
                         self.defaults[dim] = float('%.*f' % (self.decimals, val))
                         self.defaults[dim] = float('%.*f' % (self.decimals, val))
                     else:
                     else:
@@ -5847,6 +5881,7 @@ class App(QtCore.QObject):
                             val = float(self.defaults[dim]) * sfactor
                             val = float(self.defaults[dim]) * sfactor
                         except Exception as e:
                         except Exception as e:
                             log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
                             log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
+                            continue
 
 
                         self.defaults[dim] = float('%.*f' % (self.decimals, val))
                         self.defaults[dim] = float('%.*f' % (self.decimals, val))
                 else:
                 else:
@@ -5855,7 +5890,8 @@ class App(QtCore.QObject):
                         try:
                         try:
                             val = float(self.defaults[dim]) * sfactor
                             val = float(self.defaults[dim]) * sfactor
                         except Exception as e:
                         except Exception as e:
-                            log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
+                            log.debug('App.on_toggle_units().scale_defaults() --> Value: %s %s' % (str(dim), str(e)))
+                            continue
 
 
                         self.defaults[dim] = val
                         self.defaults[dim] = val
 
 
@@ -7029,6 +7065,9 @@ class App(QtCore.QObject):
 
 
         self.inform.emit('%s' % _("Preferences applied."))
         self.inform.emit('%s' % _("Preferences applied."))
 
 
+        # make sure we update the self.current_defaults dict used to undo changes to self.defaults
+        self.current_defaults.update(self.defaults)
+
         if save_to_file:
         if save_to_file:
             self.save_defaults(silent=False)
             self.save_defaults(silent=False)
             # load the defaults so they are updated into the app
             # load the defaults so they are updated into the app
@@ -7067,7 +7106,18 @@ class App(QtCore.QObject):
         except TypeError:
         except TypeError:
             pass
             pass
 
 
-        self.defaults_write_form()
+        try:
+            self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.disconnect()
+        except (TypeError, AttributeError):
+            pass
+        self.defaults_write_form(source_dict=self.current_defaults)
+        self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect(
+            lambda: self.on_toggle_units(no_pref=False))
+        self.defaults.update(self.current_defaults)
+
+        # shared_items = {k: self.defaults[k] for k in self.defaults if k in self.current_defaults and
+        #                 self.defaults[k] == self.current_defaults[k]}
+        # print(len(self.defaults), len(shared_items))
 
 
         # Preferences save, update the color of the Preferences Tab text
         # Preferences save, update the color of the Preferences Tab text
         for idx in range(self.ui.plot_tab_area.count()):
         for idx in range(self.ui.plot_tab_area.count()):
@@ -7797,8 +7847,7 @@ class App(QtCore.QObject):
                     pass
                     pass
 
 
     def on_preferences_edited(self):
     def on_preferences_edited(self):
-        self.inform.emit('[WARNING_NOTCL] %s' %
-                         _("Preferences edited but not saved."))
+        self.inform.emit('[WARNING_NOTCL] %s' % _("Preferences edited but not saved."))
 
 
         for idx in range(self.ui.plot_tab_area.count()):
         for idx in range(self.ui.plot_tab_area.count()):
             if self.ui.plot_tab_area.tabText(idx) == _("Preferences"):
             if self.ui.plot_tab_area.tabText(idx) == _("Preferences"):
@@ -10275,19 +10324,26 @@ class App(QtCore.QObject):
         self.set_ui_title(name=self.project_filename)
         self.set_ui_title(name=self.project_filename)
         self.should_we_save = False
         self.should_we_save = False
 
 
-    def on_file_save_object_pdf(self, use_thread=True):
+    def on_file_save_objects_pdf(self, use_thread=True):
         self.date = str(datetime.today()).rpartition('.')[0]
         self.date = str(datetime.today()).rpartition('.')[0]
         self.date = ''.join(c for c in self.date if c not in ':-')
         self.date = ''.join(c for c in self.date if c not in ':-')
         self.date = self.date.replace(' ', '_')
         self.date = self.date.replace(' ', '_')
 
 
         try:
         try:
-            obj_active = self.collection.get_active()
-            obj_name = _(str(obj_active.options['name']))
+            obj_selection = self.collection.get_selected()
+            if len(obj_selection) == 1:
+                obj_name = str(obj_selection[0].options['name'])
+            else:
+                obj_name = _("FlatCAM objects print")
         except AttributeError as err:
         except AttributeError as err:
             log.debug("App.on_file_save_object_pdf() --> %s" % str(err))
             log.debug("App.on_file_save_object_pdf() --> %s" % str(err))
             self.inform.emit('[ERROR_NOTCL] %s' % _("No object selected."))
             self.inform.emit('[ERROR_NOTCL] %s' % _("No object selected."))
             return
             return
 
 
+        if not obj_selection:
+            self.inform.emit('[ERROR_NOTCL] %s' % _("No object selected."))
+            return
+
         filter_ = "PDF File (*.PDF);; All Files (*.*)"
         filter_ = "PDF File (*.PDF);; All Files (*.*)"
         try:
         try:
             filename, _f = QtWidgets.QFileDialog.getSaveFileName(
             filename, _f = QtWidgets.QFileDialog.getSaveFileName(
@@ -10307,17 +10363,185 @@ class App(QtCore.QObject):
             return
             return
 
 
         if use_thread is True:
         if use_thread is True:
-            self.worker_task.emit({'fcn': self.save_pdf, 'params': [filename, obj_name]})
+            proc = self.proc_container.new(_("Printing PDF ... Please wait."))
+            self.worker_task.emit({'fcn': self.save_pdf, 'params': [filename, obj_selection]})
         else:
         else:
-            self.save_pdf(filename, obj_name)
+            self.save_pdf(filename, obj_selection)
 
 
         # self.save_project(filename)
         # self.save_project(filename)
         if self.defaults["global_open_style"] is False:
         if self.defaults["global_open_style"] is False:
             self.file_opened.emit("pdf", filename)
             self.file_opened.emit("pdf", filename)
         self.file_saved.emit("pdf", filename)
         self.file_saved.emit("pdf", filename)
 
 
-    def save_pdf(self, file_name, obj_name):
-        self.film_tool.export_positive(obj_name=obj_name, box_name=obj_name, filename=file_name, ftype='pdf')
+    def save_pdf(self, file_name, obj_selection):
+
+        p_size = self.defaults['global_workspaceT']
+        orientation = self.defaults['global_workspace_orientation']
+        color = 'black'
+        transparency_level = 1.0
+
+        self.pagesize = dict()
+        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),
+
+                # American paper sizes
+                'LETTER': (8.5*inch, 11*inch),
+                'LEGAL': (8.5*inch, 14*inch),
+                'ELEVENSEVENTEEN': (11*inch, 17*inch),
+
+                # 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),
+            }
+        )
+
+        exported_svg = list()
+        for obj in obj_selection:
+            svg_obj = obj.export_svg(scale_stroke_factor=0.0,
+                                     scale_factor_x=None, scale_factor_y=None,
+                                     skew_factor_x=None, skew_factor_y=None,
+                                     mirror=None)
+
+            if obj.kind.lower() == 'gerber':
+                color = self.defaults["global_plot_fill"][:-2]
+            elif obj.kind.lower() == 'excellon':
+                color = '#C40000'
+            elif obj.kind.lower() == 'geometry':
+                color = self.defaults["global_draw_color"]
+
+            # 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(svg_obj)
+            for child in root:
+                child.set('fill', str(color))
+                child.set('opacity', str(transparency_level))
+                child.set('stroke', str(color))
+
+            exported_svg.append(ET.tostring(root))
+
+        xmin = Inf
+        ymin = Inf
+        xmax = -Inf
+        ymax = -Inf
+
+        for obj in obj_selection:
+            try:
+                gxmin, gymin, gxmax, gymax = obj.bounds()
+                xmin = min([xmin, gxmin])
+                ymin = min([ymin, gymin])
+                xmax = max([xmax, gxmax])
+                ymax = max([ymax, gymax])
+            except Exception as e:
+                log.warning("DEV WARNING: Tried to get bounds of empty geometry in App.save_pdf(). %s" % str(e))
+
+        # Determine bounding area for svg export
+        bounds = [xmin, ymin, xmax, ymax]
+        size = bounds[2] - bounds[0], bounds[3] - bounds[1]
+
+        # This contain the measure units
+        uom = obj_selection[0].units.lower()
+
+        # Define a boundary around SVG of about 1.0mm (~39mils)
+        if uom in "mm":
+            boundary = 1.0
+        else:
+            boundary = 0.0393701
+
+        # 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])
+
+        # 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>'
+
+        svg_elem = str(svg_header)
+        for svg_item in exported_svg:
+            svg_elem += str(svg_item)
+        svg_elem += str(svg_footer)
+
+        # 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()
+
+        try:
+            if self.defaults['units'].upper() == 'IN':
+                unit = inch
+            else:
+                unit = mm
+
+            doc_final = StringIO(doc_final)
+            drawing = svg2rlg(doc_final)
+
+            if p_size == 'Bounds':
+                renderPDF.drawToFile(drawing, file_name)
+            else:
+                if orientation == 'p':
+                    page_size = portrait(self.pagesize[p_size])
+                else:
+                    page_size = landscape(self.pagesize[p_size])
+
+                my_canvas = canvas.Canvas(file_name, 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("App.save_pdf() --> PDF output --> %s" % str(e))
+            return 'fail'
+
+        self.inform.emit('[success] %s: %s' % (_("PDF file saved to"), file_name))
 
 
     def export_svg(self, obj_name, filename, scale_stroke_factor=0.00):
     def export_svg(self, obj_name, filename, scale_stroke_factor=0.00):
         """
         """

+ 3 - 0
FlatCAMObj.py

@@ -981,6 +981,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
 
 
         def geo_init(geo_obj, app_obj):
         def geo_init(geo_obj, app_obj):
             assert isinstance(geo_obj, FlatCAMGeometry)
             assert isinstance(geo_obj, FlatCAMGeometry)
+            if isinstance(self.solid_geometry, list):
+                self.solid_geometry = cascaded_union(self.solid_geometry)
+
             bounding_box = self.solid_geometry.envelope.buffer(float(self.options["noncoppermargin"]))
             bounding_box = self.solid_geometry.envelope.buffer(float(self.options["noncoppermargin"]))
             if not self.options["noncopperrounded"]:
             if not self.options["noncopperrounded"]:
                 bounding_box = bounding_box.envelope
                 bounding_box = bounding_box.envelope

+ 10 - 0
README.md

@@ -9,9 +9,19 @@ CAD program, and create G-Code for Isolation routing.
 
 
 =================================================
 =================================================
 
 
+20.12.2019
+
+- fixed a rare issue in the generation of non-copper-region geometry started from the Gerber Object UI (selected tab)
+- Print function is now printing a PDF file for a selection of objects in the colors from canvas 
+
 19.12.2019
 19.12.2019
 
 
 - in 2-Sided Tool added a way to calculate the bounding box values for a selection of objects, and also the centroid
 - in 2-Sided Tool added a way to calculate the bounding box values for a selection of objects, and also the centroid
+- in 2-Sided Tool fixed the Reset Tool button handler to reset the bounds value too; changed a string
+- added Preferences values for PDF margins when saving text in Code Editor as PDF
+- when clicking Cancel in Preferences now the values are reverted to what they used to be before opening Preferences tab and start changing values
+- starting to work to a general Print function; for now it will generate PDF files; currently it works only for one object not for a selection
+- added shortcut key CTRL+P for printing to PDF method
 
 
 18.12.2019
 18.12.2019
 
 

+ 17 - 6
flatcamEditors/FlatCAMTextEditor.py

@@ -196,7 +196,7 @@ class TextEditor(QtWidgets.QWidget):
             _filter_ = filt
             _filter_ = filt
         else:
         else:
             _filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \
             _filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \
-                       "All Files (*.*)"
+                       "PDF Files (*.pdf);;All Files (*.*)"
 
 
         if name:
         if name:
             obj_name = name
             obj_name = name
@@ -206,7 +206,7 @@ class TextEditor(QtWidgets.QWidget):
             except AttributeError:
             except AttributeError:
                 obj_name = 'file'
                 obj_name = 'file'
                 if filt is None:
                 if filt is None:
-                    _filter_ = "FlatConfig Files (*.FlatConfig);;All Files (*.*)"
+                    _filter_ = "FlatConfig Files (*.FlatConfig);;PDF Files (*.pdf);;All Files (*.*)"
 
 
         try:
         try:
             filename = str(QtWidgets.QFileDialog.getSaveFileName(
             filename = str(QtWidgets.QFileDialog.getSaveFileName(
@@ -237,13 +237,24 @@ class TextEditor(QtWidgets.QWidget):
                     styleH = styles['Heading1']
                     styleH = styles['Heading1']
                     story = []
                     story = []
 
 
+                    if self.app.defaults['units'].lower() == 'mm':
+                        bmargin = self.app.defaults['global_tpdf_bmargin'] * mm
+                        tmargin = self.app.defaults['global_tpdf_tmargin'] * mm
+                        rmargin = self.app.defaults['global_tpdf_rmargin'] * mm
+                        lmargin = self.app.defaults['global_tpdf_lmargin'] * mm
+                    else:
+                        bmargin = self.app.defaults['global_tpdf_bmargin'] * inch
+                        tmargin = self.app.defaults['global_tpdf_tmargin'] * inch
+                        rmargin = self.app.defaults['global_tpdf_rmargin'] * inch
+                        lmargin = self.app.defaults['global_tpdf_lmargin'] * inch
+
                     doc = SimpleDocTemplate(
                     doc = SimpleDocTemplate(
                         filename,
                         filename,
                         pagesize=page_size,
                         pagesize=page_size,
-                        bottomMargin=0.4 * inch,
-                        topMargin=0.6 * inch,
-                        rightMargin=0.8 * inch,
-                        leftMargin=0.8 * inch)
+                        bottomMargin=bmargin,
+                        topMargin=tmargin,
+                        rightMargin=rmargin,
+                        leftMargin=lmargin)
 
 
                     P = Paragraph(lined_gcode, styleN)
                     P = Paragraph(lined_gcode, styleN)
                     story.append(P)
                     story.append(P)

+ 20 - 14
flatcamGUI/FlatCAMGUI.py

@@ -240,6 +240,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
 
         # Separator
         # Separator
         self.menufile.addSeparator()
         self.menufile.addSeparator()
+        self.menufile_print = QtWidgets.QAction(
+            QtGui.QIcon(self.app.resource_location + '/printer32.png'), '%s\tCTRL+P' % _('Print (PDF)'))
+        self.menufile.addAction(self.menufile_print)
 
 
         self.menufile_save = self.menufile.addMenu(QtGui.QIcon(self.app.resource_location + '/save_as.png'), _('Save'))
         self.menufile_save = self.menufile.addMenu(QtGui.QIcon(self.app.resource_location + '/save_as.png'), _('Save'))
 
 
@@ -260,11 +263,6 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
 
         self.menufile_save.addSeparator()
         self.menufile_save.addSeparator()
 
 
-        # Save Object PDF
-        self.menufilesave_object_pdf = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/pdf32.png'),
-                                                         _('Save Object as PDF ...'), self)
-        self.menufile_save.addAction(self.menufilesave_object_pdf)
-
         # Separator
         # Separator
         self.menufile.addSeparator()
         self.menufile.addSeparator()
 
 
@@ -1382,17 +1380,21 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                         <td>&nbsp;%s</td>
                         <td>&nbsp;%s</td>
                     </tr>
                     </tr>
                     <tr height="20">
                     <tr height="20">
-                        <td height="20"><strong>CTRL+N</strong></td>
+                        <td height="20"><strong>CTRL+M</strong></td>
                         <td>&nbsp;%s</td>
                         <td>&nbsp;%s</td>
                     </tr>
                     </tr>
                     <tr height="20">
                     <tr height="20">
-                        <td height="20"><strong>CTRL+M</strong></td>
+                        <td height="20"><strong>CTRL+N</strong></td>
                         <td>&nbsp;%s</td>
                         <td>&nbsp;%s</td>
-                    </tr>
+                    </tr>                   
                     <tr height="20">
                     <tr height="20">
                         <td height="20"><strong>CTRL+O</strong></td>
                         <td height="20"><strong>CTRL+O</strong></td>
                         <td>&nbsp;%s</td>
                         <td>&nbsp;%s</td>
                     </tr>
                     </tr>
+                    <tr height="20">
+                        <td height="20"><strong>CTRL+P</strong></td>
+                        <td>&nbsp;%s</td>
+                    </tr> 
                     <tr height="20">
                     <tr height="20">
                         <td height="20"><strong>CTRL+Q</strong></td>
                         <td height="20"><strong>CTRL+Q</strong></td>
                         <td>&nbsp;%s</td>
                         <td>&nbsp;%s</td>
@@ -1579,8 +1581,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
 
                 # CTRL section
                 # CTRL section
                 _("Select All"), _("Copy Obj"), _("Open Tools Database"),
                 _("Select All"), _("Copy Obj"), _("Open Tools Database"),
-                _("Open Excellon File"), _("Open Gerber File"), _("New Project"), _("Distance Tool"),
-                _("Open Project"), _("PDF Import Tool"), _("Save Project As"), _("Toggle Plot Area"),
+                _("Open Excellon File"), _("Open Gerber File"), _("Distance Tool"), _("New Project"),
+                _("Open Project"), _("Print (PDF)"), _("PDF Import Tool"), _("Save Project As"), _("Toggle Plot Area"),
 
 
                 # SHIFT section
                 # SHIFT section
                 _("Copy Obj_Name"),
                 _("Copy Obj_Name"),
@@ -2671,18 +2673,22 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                 if key == QtCore.Qt.Key_G:
                 if key == QtCore.Qt.Key_G:
                     self.app.on_fileopengerber()
                     self.app.on_fileopengerber()
 
 
-                # Create New Project
-                if key == QtCore.Qt.Key_N:
-                    self.app.on_file_new_click()
-
                 # Distance Tool
                 # Distance Tool
                 if key == QtCore.Qt.Key_M:
                 if key == QtCore.Qt.Key_M:
                     self.app.distance_tool.run()
                     self.app.distance_tool.run()
 
 
+                # Create New Project
+                if key == QtCore.Qt.Key_N:
+                    self.app.on_file_new_click()
+
                 # Open Project
                 # Open Project
                 if key == QtCore.Qt.Key_O:
                 if key == QtCore.Qt.Key_O:
                     self.app.on_file_openproject()
                     self.app.on_file_openproject()
 
 
+                # Open Project
+                if key == QtCore.Qt.Key_P:
+                    self.app.on_file_save_objects_pdf(use_thread=True)
+
                 # PDF Import
                 # PDF Import
                 if key == QtCore.Qt.Key_Q:
                 if key == QtCore.Qt.Key_Q:
                     self.app.pdf_tool.run()
                     self.app.pdf_tool.run()

+ 70 - 0
flatcamGUI/PreferencesUI.py

@@ -1330,6 +1330,76 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
 
 
         grid0.addWidget(self.machinist_cb, 21, 0, 1, 2)
         grid0.addWidget(self.machinist_cb, 21, 0, 1, 2)
 
 
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        self.layout.addWidget(separator_line)
+
+        self.layout.addWidget(QtWidgets.QLabel(''))
+
+        grid1 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid1)
+        grid1.setColumnStretch(0, 0)
+        grid1.setColumnStretch(1, 1)
+
+        self.pdf_param_label = QtWidgets.QLabel('<B>%s:</b>' % _("Text to PDF parameters"))
+        self.pdf_param_label.setToolTip(
+            _("Used when saving text in Code Editor or in FlatCAM Document objects.")
+        )
+        grid1.addWidget(self.pdf_param_label, 0, 0, 1, 2)
+
+        # Top Margin value
+        self.tmargin_entry = FCDoubleSpinner()
+        self.tmargin_entry.set_precision(self.decimals)
+        self.tmargin_entry.set_range(0.0000, 9999.9999)
+
+        self.tmargin_label = QtWidgets.QLabel('%s:' % _("Top Margin"))
+        self.tmargin_label.setToolTip(
+            _("Distance between text body and the top of the PDF file.")
+        )
+
+        grid1.addWidget(self.tmargin_label, 1, 0)
+        grid1.addWidget(self.tmargin_entry, 1, 1)
+
+        # Bottom Margin value
+        self.bmargin_entry = FCDoubleSpinner()
+        self.bmargin_entry.set_precision(self.decimals)
+        self.bmargin_entry.set_range(0.0000, 9999.9999)
+
+        self.bmargin_label = QtWidgets.QLabel('%s:' % _("Bottom Margin"))
+        self.bmargin_label.setToolTip(
+            _("Distance between text body and the bottom of the PDF file.")
+        )
+
+        grid1.addWidget(self.bmargin_label, 2, 0)
+        grid1.addWidget(self.bmargin_entry, 2, 1)
+
+        # Left Margin value
+        self.lmargin_entry = FCDoubleSpinner()
+        self.lmargin_entry.set_precision(self.decimals)
+        self.lmargin_entry.set_range(0.0000, 9999.9999)
+
+        self.lmargin_label = QtWidgets.QLabel('%s:' % _("Left Margin"))
+        self.lmargin_label.setToolTip(
+            _("Distance between text body and the left of the PDF file.")
+        )
+
+        grid1.addWidget(self.lmargin_label, 3, 0)
+        grid1.addWidget(self.lmargin_entry, 3, 1)
+
+        # Right Margin value
+        self.rmargin_entry = FCDoubleSpinner()
+        self.rmargin_entry.set_precision(self.decimals)
+        self.rmargin_entry.set_range(0.0000, 9999.9999)
+
+        self.rmargin_label = QtWidgets.QLabel('%s:' % _("Right Margin"))
+        self.rmargin_label.setToolTip(
+            _("Distance between text body and the right of the PDF file.")
+        )
+
+        grid1.addWidget(self.rmargin_label, 4, 0)
+        grid1.addWidget(self.rmargin_entry, 4, 1)
+
         self.layout.addStretch()
         self.layout.addStretch()
 
 
         if sys.platform != 'win32':
         if sys.platform != 'win32':

+ 14 - 6
flatcamTools/ToolDblSided.py

@@ -281,13 +281,12 @@ class DblSidedTool(FlatCAMTool):
         self.drill_dia.set_precision(self.decimals)
         self.drill_dia.set_precision(self.decimals)
         self.drill_dia.set_range(0.0000, 9999.9999)
         self.drill_dia.set_range(0.0000, 9999.9999)
 
 
-        self.dd_label = QtWidgets.QLabel('%s:' % _("Drill dia"))
-        self.dd_label.setToolTip(
+        self.drill_dia.setToolTip(
             _("Diameter of the drill for the "
             _("Diameter of the drill for the "
               "alignment holes.")
               "alignment holes.")
         )
         )
-        grid0.addWidget(self.dd_label, 1, 0)
-        grid0.addWidget(self.drill_dia, 1, 1)
+
+        grid0.addWidget(self.drill_dia, 1, 0, 1, 2)
 
 
         # ## Buttons
         # ## Buttons
         self.create_alignment_hole_button = QtWidgets.QPushButton(_("Create Excellon Object"))
         self.create_alignment_hole_button = QtWidgets.QPushButton(_("Create Excellon Object"))
@@ -309,6 +308,8 @@ class DblSidedTool(FlatCAMTool):
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
         self.layout.addWidget(separator_line)
         self.layout.addWidget(separator_line)
 
 
+        self.layout.addWidget(QtWidgets.QLabel(''))
+
         grid1 = QtWidgets.QGridLayout()
         grid1 = QtWidgets.QGridLayout()
         self.layout.addLayout(grid1)
         self.layout.addLayout(grid1)
         grid1.setColumnStretch(0, 0)
         grid1.setColumnStretch(0, 0)
@@ -384,7 +385,7 @@ class DblSidedTool(FlatCAMTool):
         grid1.addWidget(self.center_entry, 5, 1)
         grid1.addWidget(self.center_entry, 5, 1)
 
 
         # Calculate Bounding box
         # Calculate Bounding box
-        self.calculate_bb_button = QtWidgets.QPushButton(_("Calculate Bounding Box"))
+        self.calculate_bb_button = QtWidgets.QPushButton(_("Calculate Bounds Values"))
         self.calculate_bb_button.setToolTip(
         self.calculate_bb_button.setToolTip(
             _("Calculate the enveloping rectangular shape coordinates,\n"
             _("Calculate the enveloping rectangular shape coordinates,\n"
               "for the selection of objects.\n"
               "for the selection of objects.\n"
@@ -419,7 +420,6 @@ class DblSidedTool(FlatCAMTool):
         self.mirror_geo_button.clicked.connect(self.on_mirror_geo)
         self.mirror_geo_button.clicked.connect(self.on_mirror_geo)
         self.add_point_button.clicked.connect(self.on_point_add)
         self.add_point_button.clicked.connect(self.on_point_add)
         self.add_drill_point_button.clicked.connect(self.on_drill_add)
         self.add_drill_point_button.clicked.connect(self.on_drill_add)
-        self.reset_button.clicked.connect(self.reset_fields)
         self.box_combo_type.currentIndexChanged.connect(self.on_combo_box_type)
         self.box_combo_type.currentIndexChanged.connect(self.on_combo_box_type)
 
 
         self.axis_location.group_toggle_fn = self.on_toggle_pointbox
         self.axis_location.group_toggle_fn = self.on_toggle_pointbox
@@ -427,6 +427,8 @@ class DblSidedTool(FlatCAMTool):
         self.create_alignment_hole_button.clicked.connect(self.on_create_alignment_holes)
         self.create_alignment_hole_button.clicked.connect(self.on_create_alignment_holes)
         self.calculate_bb_button.clicked.connect(self.on_bbox_coordinates)
         self.calculate_bb_button.clicked.connect(self.on_bbox_coordinates)
 
 
+        self.reset_button.clicked.connect(self.set_tool_ui)
+
         self.drill_values = ""
         self.drill_values = ""
 
 
     def install(self, icon=None, separator=None, **kwargs):
     def install(self, icon=None, separator=None, **kwargs):
@@ -469,6 +471,12 @@ class DblSidedTool(FlatCAMTool):
         self.axis_location.set_value(self.app.defaults["tools_2sided_axis_loc"])
         self.axis_location.set_value(self.app.defaults["tools_2sided_axis_loc"])
         self.drill_dia.set_value(self.app.defaults["tools_2sided_drilldia"])
         self.drill_dia.set_value(self.app.defaults["tools_2sided_drilldia"])
 
 
+        self.xmin_entry.set_value(0.0)
+        self.ymin_entry.set_value(0.0)
+        self.xmax_entry.set_value(0.0)
+        self.ymax_entry.set_value(0.0)
+        self.center_entry.set_value('')
+
     def on_combo_box_type(self):
     def on_combo_box_type(self):
         obj_type = self.box_combo_type.currentIndex()
         obj_type = self.box_combo_type.currentIndex()
         self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
         self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))

BIN
share/printer16.png


BIN
share/printer32.png