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

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

Beta 8.991 - enhancements
Marius Stanciu 6 лет назад
Родитель
Сommit
637cba109c

+ 248 - 24
FlatCAMApp.py

@@ -31,6 +31,7 @@ from reportlab.pdfgen import canvas
 from reportlab.graphics import renderPM
 from reportlab.lib.units import inch, mm
 from reportlab.lib.pagesizes import landscape, portrait
+from svglib.svglib import svg2rlg
 
 from contextlib import contextmanager
 import gc
@@ -416,6 +417,10 @@ class App(QtCore.QObject):
             "global_stats": dict(),
             "global_tabs_detachable": True,
             "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
             "global_graphic_engine": '3D',
@@ -978,6 +983,10 @@ class App(QtCore.QObject):
 
         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 ###########################
         # #############################################################################
@@ -1085,6 +1094,11 @@ class App(QtCore.QObject):
             "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_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
             "global_gridx": self.ui.general_defaults_form.general_gui_group.gridx_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.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.menufilesaveprojectas.triggered.connect(self.on_file_saveprojectas)
         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.menufileexportpref.triggered.connect(self.on_export_preferences)
@@ -2940,17 +2955,25 @@ class App(QtCore.QObject):
             except Exception as 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
         self.defaults dictionary.
 
         :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 source_dict: the repository of options, usually is the self.defaults
         :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:
             #     self.defaults_form_fields[option].set_value(self.defaults[option])
             # except KeyError:
@@ -2958,7 +2981,7 @@ class App(QtCore.QObject):
             #     # TODO: Rethink this?
             #     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()
 
@@ -2967,21 +2990,23 @@ class App(QtCore.QObject):
         :param units: current FLatCAM measuring units
         :return: None, it updates GUI elements
         """
+
+        def_dict = self.defaults if defaults_dict is None else defaults_dict
         try:
             if factor 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'):
-                    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'):
-                    self.defaults_form_fields[field].set_value(self.defaults[field])
+                    self.defaults_form_fields[field].set_value(def_dict[field])
             else:
                 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'):
-                    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'):
-                    self.defaults_form_fields[field].set_value((self.defaults[field] * factor))
+                    self.defaults_form_fields[field].set_value((def_dict[field] * factor))
         except KeyError:
             # self.log.debug("defaults_write_form(): No field for: %s" % option)
             # TODO: Rethink this?
@@ -3891,6 +3916,10 @@ class App(QtCore.QObject):
                                  _("Failed to parse defaults file."))
                 return
             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.inform.emit('[success] %s: %s' %
                              (_("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_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_fiducials_mode", "tools_fiducials_second_pos", "tools_fiducials_type",
                       "tools_fiducials_line_thickness",
                       "tools_copper_thieving_clearance", "tools_copper_thieving_margin",
                       "tools_copper_thieving_dots_dia", "tools_copper_thieving_dots_spacing",
                       "tools_copper_thieving_squares_size", "tools_copper_thieving_squares_spacing",
                       "tools_copper_thieving_lines_size", "tools_copper_thieving_lines_spacing",
                       "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):
             for dim in dimensions:
@@ -5796,6 +5826,7 @@ class App(QtCore.QObject):
                         tools_diameters = [eval(a) for a in tools_string if a != '']
                     except Exception as e:
                         log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
+                        continue
 
                     self.defaults['geometry_cnctooldia'] = ''
                     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 != '']
                     except Exception as e:
                         log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
+                        continue
 
                     self.defaults['tools_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 != '']
                     except Exception as e:
                         log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
+                        continue
 
                     self.defaults['tools_solderpaste_tools'] = ""
                     for t in range(len(sptools)):
@@ -5839,6 +5872,7 @@ class App(QtCore.QObject):
                             val = float(self.defaults[dim]) * sfactor
                         except Exception as e:
                             log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
+                            continue
 
                         self.defaults[dim] = float('%.*f' % (self.decimals, val))
                     else:
@@ -5847,6 +5881,7 @@ class App(QtCore.QObject):
                             val = float(self.defaults[dim]) * sfactor
                         except Exception as e:
                             log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
+                            continue
 
                         self.defaults[dim] = float('%.*f' % (self.decimals, val))
                 else:
@@ -5855,7 +5890,8 @@ class App(QtCore.QObject):
                         try:
                             val = float(self.defaults[dim]) * sfactor
                         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
 
@@ -7029,6 +7065,9 @@ class App(QtCore.QObject):
 
         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:
             self.save_defaults(silent=False)
             # load the defaults so they are updated into the app
@@ -7067,7 +7106,18 @@ class App(QtCore.QObject):
         except TypeError:
             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
         for idx in range(self.ui.plot_tab_area.count()):
@@ -7797,8 +7847,7 @@ class App(QtCore.QObject):
                     pass
 
     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()):
             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.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 = ''.join(c for c in self.date if c not in ':-')
         self.date = self.date.replace(' ', '_')
 
         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:
             log.debug("App.on_file_save_object_pdf() --> %s" % str(err))
             self.inform.emit('[ERROR_NOTCL] %s' % _("No object selected."))
             return
 
+        if not obj_selection:
+            self.inform.emit('[ERROR_NOTCL] %s' % _("No object selected."))
+            return
+
         filter_ = "PDF File (*.PDF);; All Files (*.*)"
         try:
             filename, _f = QtWidgets.QFileDialog.getSaveFileName(
@@ -10307,17 +10363,185 @@ class App(QtCore.QObject):
             return
 
         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:
-            self.save_pdf(filename, obj_name)
+            self.save_pdf(filename, obj_selection)
 
         # self.save_project(filename)
         if self.defaults["global_open_style"] is False:
             self.file_opened.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):
         """

+ 3 - 0
FlatCAMObj.py

@@ -981,6 +981,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
 
         def geo_init(geo_obj, app_obj):
             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"]))
             if not self.options["noncopperrounded"]:
                 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
 
 - 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
 

+ 17 - 6
flatcamEditors/FlatCAMTextEditor.py

@@ -196,7 +196,7 @@ class TextEditor(QtWidgets.QWidget):
             _filter_ = filt
         else:
             _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:
             obj_name = name
@@ -206,7 +206,7 @@ class TextEditor(QtWidgets.QWidget):
             except AttributeError:
                 obj_name = 'file'
                 if filt is None:
-                    _filter_ = "FlatConfig Files (*.FlatConfig);;All Files (*.*)"
+                    _filter_ = "FlatConfig Files (*.FlatConfig);;PDF Files (*.pdf);;All Files (*.*)"
 
         try:
             filename = str(QtWidgets.QFileDialog.getSaveFileName(
@@ -237,13 +237,24 @@ class TextEditor(QtWidgets.QWidget):
                     styleH = styles['Heading1']
                     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(
                         filename,
                         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)
                     story.append(P)

+ 20 - 14
flatcamGUI/FlatCAMGUI.py

@@ -240,6 +240,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
         # Separator
         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'))
 
@@ -260,11 +263,6 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
         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
         self.menufile.addSeparator()
 
@@ -1382,17 +1380,21 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                         <td>&nbsp;%s</td>
                     </tr>
                     <tr height="20">
-                        <td height="20"><strong>CTRL+N</strong></td>
+                        <td height="20"><strong>CTRL+M</strong></td>
                         <td>&nbsp;%s</td>
                     </tr>
                     <tr height="20">
-                        <td height="20"><strong>CTRL+M</strong></td>
+                        <td height="20"><strong>CTRL+N</strong></td>
                         <td>&nbsp;%s</td>
-                    </tr>
+                    </tr>                   
                     <tr height="20">
                         <td height="20"><strong>CTRL+O</strong></td>
                         <td>&nbsp;%s</td>
                     </tr>
+                    <tr height="20">
+                        <td height="20"><strong>CTRL+P</strong></td>
+                        <td>&nbsp;%s</td>
+                    </tr> 
                     <tr height="20">
                         <td height="20"><strong>CTRL+Q</strong></td>
                         <td>&nbsp;%s</td>
@@ -1579,8 +1581,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
                 # CTRL section
                 _("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
                 _("Copy Obj_Name"),
@@ -2671,18 +2673,22 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                 if key == QtCore.Qt.Key_G:
                     self.app.on_fileopengerber()
 
-                # Create New Project
-                if key == QtCore.Qt.Key_N:
-                    self.app.on_file_new_click()
-
                 # Distance Tool
                 if key == QtCore.Qt.Key_M:
                     self.app.distance_tool.run()
 
+                # Create New Project
+                if key == QtCore.Qt.Key_N:
+                    self.app.on_file_new_click()
+
                 # Open Project
                 if key == QtCore.Qt.Key_O:
                     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
                 if key == QtCore.Qt.Key_Q:
                     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)
 
+        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()
 
         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_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 "
               "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
         self.create_alignment_hole_button = QtWidgets.QPushButton(_("Create Excellon Object"))
@@ -309,6 +308,8 @@ class DblSidedTool(FlatCAMTool):
         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)
@@ -384,7 +385,7 @@ class DblSidedTool(FlatCAMTool):
         grid1.addWidget(self.center_entry, 5, 1)
 
         # 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(
             _("Calculate the enveloping rectangular shape coordinates,\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.add_point_button.clicked.connect(self.on_point_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.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.calculate_bb_button.clicked.connect(self.on_bbox_coordinates)
 
+        self.reset_button.clicked.connect(self.set_tool_ui)
+
         self.drill_values = ""
 
     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.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):
         obj_type = self.box_combo_type.currentIndex()
         self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))

BIN
share/printer16.png


BIN
share/printer32.png