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

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

Beta
Marius Stanciu 6 лет назад
Родитель
Сommit
d0366af5d3
47 измененных файлов с 2467 добавлено и 1747 удалено
  1. 176 84
      FlatCAMApp.py
  2. 42 21
      FlatCAMObj.py
  3. 43 0
      README.md
  4. 14 8
      camlib.py
  5. 1 0
      config/configuration.txt
  6. 484 225
      flatcamEditors/FlatCAMExcEditor.py
  7. 4 3
      flatcamEditors/FlatCAMGrbEditor.py
  8. 212 10
      flatcamGUI/FlatCAMGUI.py
  9. 5 2
      flatcamGUI/PlotCanvas.py
  10. BIN
      flatcamGUI/VisPyData/data/fonts/opensans-regular.ttf
  11. BIN
      flatcamGUI/VisPyData/data/freetype/freetype253.dll
  12. BIN
      flatcamGUI/VisPyData/data/freetype/freetype253_x64.dll
  13. BIN
      locale/de/LC_MESSAGES/strings.mo
  14. 199 186
      locale/de/LC_MESSAGES/strings.po
  15. BIN
      locale/en/LC_MESSAGES/strings.mo
  16. 223 209
      locale/en/LC_MESSAGES/strings.po
  17. BIN
      locale/es/LC_MESSAGES/strings.mo
  18. 199 186
      locale/es/LC_MESSAGES/strings.po
  19. BIN
      locale/pt_BR/LC_MESSAGES/strings.mo
  20. 199 186
      locale/pt_BR/LC_MESSAGES/strings.po
  21. BIN
      locale/ro/LC_MESSAGES/strings.mo
  22. 199 186
      locale/ro/LC_MESSAGES/strings.po
  23. BIN
      locale/ru/LC_MESSAGES/strings.mo
  24. 220 207
      locale/ru/LC_MESSAGES/strings.po
  25. 245 234
      locale_template/strings.pot
  26. 2 0
      make_win.py
  27. BIN
      share/aero_arc.png
  28. BIN
      share/aero_array.png
  29. BIN
      share/aero_buffer.png
  30. BIN
      share/aero_circle.png
  31. BIN
      share/aero_circle_geo.png
  32. BIN
      share/aero_disc.png
  33. BIN
      share/aero_drill.png
  34. BIN
      share/aero_drill_array.png
  35. BIN
      share/aero_path1.png
  36. BIN
      share/aero_path2.png
  37. BIN
      share/aero_path3.png
  38. BIN
      share/aero_path4.png
  39. BIN
      share/aero_path5.png
  40. BIN
      share/aero_semidisc.png
  41. BIN
      share/aero_slot.png
  42. BIN
      share/aero_text.png
  43. BIN
      share/backup24.png
  44. BIN
      share/backup_export24.png
  45. BIN
      share/backup_import24.png
  46. BIN
      share/slot26.png
  47. BIN
      share/slot_array26.png

+ 176 - 84
FlatCAMApp.py

@@ -14,6 +14,7 @@ import random
 import simplejson as json
 import lzma
 import threading
+import shutil
 
 from stat import S_IREAD, S_IRGRP, S_IROTH
 import subprocess
@@ -98,7 +99,7 @@ class App(QtCore.QObject):
     # Version and VERSION DATE ###########
     # ####################################
     version = 8.94
-    version_date = "2019/08/31"
+    version_date = "2019/08/17"
     beta = True
 
     # current date now
@@ -194,9 +195,11 @@ class App(QtCore.QObject):
 
         self.main_thread = QtWidgets.QApplication.instance().thread()
 
-        # ################ ##
-        # # ## OS-specific # ##
-        # ################ ##
+        # #######################
+        # # ## OS-specific ######
+        # #######################
+
+        portable = False
 
         # Folder for user settings.
         if sys.platform == 'win32':
@@ -206,7 +209,28 @@ class App(QtCore.QObject):
             else:
                 App.log.debug("Win64!")
 
-            self.data_path = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, None, 0) + '\FlatCAM'
+            config_file = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '\\config\\configuration.txt'
+            try:
+                with open(config_file, 'r') as f:
+                    try:
+                        for line in f:
+                            param = str(line).rpartition('=')
+                            if param[0] == 'portable':
+                                try:
+                                    portable = eval(param[2])
+                                except NameError:
+                                    portable = False
+                    except Exception as e:
+                        log.debug('App.__init__() -->%s' % str(e))
+                        return
+            except FileNotFoundError:
+                pass
+
+            if portable is False:
+                self.data_path = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, None, 0) + '\\FlatCAM'
+            else:
+                self.data_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '\\config'
+
             self.os = 'windows'
         else:  # Linux/Unix/MacOS
             self.data_path = os.path.expanduser('~') + '/.FlatCAM'
@@ -336,6 +360,7 @@ class App(QtCore.QObject):
             "global_tolerance": self.ui.general_defaults_form.general_app_group.tol_entry,
 
             "global_open_style": self.ui.general_defaults_form.general_app_group.open_style_cb,
+            "global_delete_confirmation": self.ui.general_defaults_form.general_app_group.delete_conf_cb,
 
             "global_compression_level": self.ui.general_defaults_form.general_app_group.compress_combo,
             "global_save_compressed": self.ui.general_defaults_form.general_app_group.save_type_cb,
@@ -444,6 +469,7 @@ class App(QtCore.QObject):
             "excellon_exp_integer": self.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry,
             "excellon_exp_decimals": self.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry,
             "excellon_exp_zeros": self.ui.excellon_defaults_form.excellon_exp_group.zeros_radio,
+            "excellon_exp_slot_type": self.ui.excellon_defaults_form.excellon_exp_group.slot_type_radio,
 
             # Excellon Editor
             "excellon_editor_sel_limit": self.ui.excellon_defaults_form.excellon_editor_group.sel_limit_entry,
@@ -455,6 +481,25 @@ class App(QtCore.QObject):
             "excellon_editor_circ_dir": self.ui.excellon_defaults_form.excellon_editor_group.drill_circular_dir_radio,
             "excellon_editor_circ_angle":
                 self.ui.excellon_defaults_form.excellon_editor_group.drill_circular_angle_entry,
+            # Excellon Slots
+            "excellon_editor_slot_direction":
+                self.ui.excellon_defaults_form.excellon_editor_group.slot_axis_radio,
+            "excellon_editor_slot_angle":
+                self.ui.excellon_defaults_form.excellon_editor_group.slot_angle_spinner,
+            "excellon_editor_slot_length":
+                self.ui.excellon_defaults_form.excellon_editor_group.slot_length_entry,
+            # Excellon Slots
+            "excellon_editor_slot_array_size":
+                self.ui.excellon_defaults_form.excellon_editor_group.slot_array_size_entry,
+            "excellon_editor_slot_lin_dir": self.ui.excellon_defaults_form.excellon_editor_group.slot_array_axis_radio,
+            "excellon_editor_slot_lin_pitch":
+                self.ui.excellon_defaults_form.excellon_editor_group.slot_array_pitch_entry,
+            "excellon_editor_slot_lin_angle":
+                self.ui.excellon_defaults_form.excellon_editor_group.slot_array_angle_entry,
+            "excellon_editor_slot_circ_dir":
+                self.ui.excellon_defaults_form.excellon_editor_group.slot_array_circular_dir_radio,
+            "excellon_editor_slot_circ_angle":
+                self.ui.excellon_defaults_form.excellon_editor_group.slot_array_circular_angle_entry,
 
             # Geometry General
             "geometry_plot": self.ui.geometry_defaults_form.geometry_gen_group.plot_cb,
@@ -649,6 +694,7 @@ class App(QtCore.QObject):
             "global_worker_number": 2,
             "global_tolerance": 0.01,
             "global_open_style": True,
+            "global_delete_confirmation": True,
             "global_compression_level": 3,
             "global_save_compressed": True,
 
@@ -717,16 +763,16 @@ class App(QtCore.QObject):
             "gerber_plot": True,
             "gerber_solid": True,
             "gerber_multicolored": False,
-            "gerber_isotooldia": 0.016,
+            "gerber_isotooldia": 0.00787402,
             "gerber_isopasses": 1,
-            "gerber_isooverlap": 0.15,
+            "gerber_isooverlap": 0.00393701,
 
             # Gerber Options
             "gerber_combine_passes": False,
             "gerber_milling_type": "cl",
-            "gerber_noncoppermargin": 0.1,
+            "gerber_noncoppermargin": 0.00393701,
             "gerber_noncopperrounded": False,
-            "gerber_bboxmargin": 0.1,
+            "gerber_bboxmargin": 0.00393701,
             "gerber_bboxrounded": False,
             "gerber_circle_steps": 128,
             "gerber_use_buffer_for_union": True,
@@ -759,9 +805,9 @@ class App(QtCore.QObject):
             "excellon_search_time": 3,
 
             # Excellon Options
-            "excellon_drillz": -0.1,
-            "excellon_travelz": 0.1,
-            "excellon_feedrate": 3.0,
+            "excellon_drillz": -0.0590551,
+            "excellon_travelz": 0.0787402,
+            "excellon_feedrate": 3.14961,
             "excellon_spindlespeed": None,
             "excellon_spindledir": 'CW',
             "excellon_dwell": False,
@@ -769,8 +815,8 @@ class App(QtCore.QObject):
             "excellon_toolchange": False,
             "excellon_toolchangez": 0.5,
             "excellon_ppname_e": 'default',
-            "excellon_tooldia": 0.016,
-            "excellon_slot_tooldia": 0.016,
+            "excellon_tooldia": 0.0314961,
+            "excellon_slot_tooldia": 0.0708661,
             "excellon_gcode_type": "drills",
 
             # Excellon Advanced Options
@@ -778,9 +824,9 @@ class App(QtCore.QObject):
             "excellon_toolchangexy": "0.0, 0.0",
             "excellon_startz": None,
             "excellon_endz": 0.5,
-            "excellon_feedrate_rapid": 3.0,
+            "excellon_feedrate_rapid": 3.14961,
             "excellon_z_pdepth": -0.02,
-            "excellon_feedrate_probe": 3.0,
+            "excellon_feedrate_probe": 3.14961,
             "excellon_f_plunge": False,
             "excellon_f_retract": False,
 
@@ -790,6 +836,7 @@ class App(QtCore.QObject):
             "excellon_exp_integer": 2,
             "excellon_exp_decimals": 4,
             "excellon_exp_zeros": 'LZ',
+            "excellon_exp_slot_type": 'routing',
 
             # Excellon Editor
             "excellon_editor_sel_limit": 30,
@@ -800,23 +847,34 @@ class App(QtCore.QObject):
             "excellon_editor_lin_angle": 0.0,
             "excellon_editor_circ_dir": 'CW',
             "excellon_editor_circ_angle": 12,
+            # Excellon Slots
+            "excellon_editor_slot_direction": 'X',
+            "excellon_editor_slot_angle": 0.0,
+            "excellon_editor_slot_length": 5.0,
+            # Excellon Slot Array
+            "excellon_editor_slot_array_size": 5,
+            "excellon_editor_slot_lin_dir":  'X',
+            "excellon_editor_slot_lin_pitch": 0.1,
+            "excellon_editor_slot_lin_angle": 0.0,
+            "excellon_editor_slot_circ_dir": 'CW',
+            "excellon_editor_slot_circ_angle": 0.0,
 
             # Geometry General
             "geometry_plot": True,
             "geometry_circle_steps": 128,
-            "geometry_cnctooldia": "0.016",
+            "geometry_cnctooldia": "0.0944882",
 
             # Geometry Options
-            "geometry_cutz": -0.002,
+            "geometry_cutz": -0.0944882,
             "geometry_vtipdia": 0.1,
             "geometry_vtipangle": 30,
             "geometry_multidepth": False,
-            "geometry_depthperpass": 0.002,
-            "geometry_travelz": 0.1,
+            "geometry_depthperpass": 0.0314961,
+            "geometry_travelz": 0.0787402,
             "geometry_toolchange": False,
             "geometry_toolchangez": 0.5,
-            "geometry_feedrate": 3.0,
-            "geometry_feedrate_z": 3.0,
+            "geometry_feedrate": 3.14961,
+            "geometry_feedrate_z": 3.14961,
             "geometry_spindlespeed": None,
             "geometry_spindledir": 'CW',
             "geometry_dwell": False,
@@ -827,11 +885,11 @@ class App(QtCore.QObject):
             "geometry_toolchangexy": "0.0, 0.0",
             "geometry_startz": None,
             "geometry_endz": 0.5,
-            "geometry_feedrate_rapid": 3.0,
+            "geometry_feedrate_rapid": 3.14961,
             "geometry_extracut": False,
             "geometry_z_pdepth": -0.02,
             "geometry_f_plunge": False,
-            "geometry_feedrate_probe": 3.0,
+            "geometry_feedrate_probe": 3.14961,
             "geometry_segx": 0.0,
             "geometry_segy": 0.0,
 
@@ -859,7 +917,7 @@ class App(QtCore.QObject):
 
             "tools_ncctools": "0.0393701, 0.019685",
             "tools_nccoverlap": 0.015748,
-            "tools_nccmargin": 0.00393701,
+            "tools_nccmargin": 0.0393701,
             "tools_nccmethod": "seed",
             "tools_nccconnect": True,
             "tools_ncccontour": True,
@@ -868,15 +926,15 @@ class App(QtCore.QObject):
             "tools_ncc_offset_value": 0.0000,
             "tools_nccref": 'itself',
 
-            "tools_cutouttooldia": 0.00393701,
+            "tools_cutouttooldia": 0.0944882,
             "tools_cutoutkind": "single",
             "tools_cutoutmargin": 0.00393701,
-            "tools_cutoutgapsize": 0.005905512,
-            "tools_gaps_ff": "8",
+            "tools_cutoutgapsize": 0.15748,
+            "tools_gaps_ff": "4",
             "tools_cutout_convexshape": False,
 
-            "tools_painttooldia": 0.07,
-            "tools_paintoverlap": 0.15,
+            "tools_painttooldia": 0.023622,
+            "tools_paintoverlap": 0.015748,
             "tools_paintmargin": 0.0,
             "tools_paintmethod": "seed",
             "tools_selectmethod": "single",
@@ -1405,6 +1463,10 @@ class App(QtCore.QObject):
         self.ui.menufilesaveprojectas.triggered.connect(self.on_file_saveprojectas)
         self.ui.menufilesaveprojectcopy.triggered.connect(lambda: self.on_file_saveprojectas(make_copy=True))
         self.ui.menufilesavedefaults.triggered.connect(self.on_file_savedefaults)
+
+        self.ui.menufileexportpref.triggered.connect(self.on_export_preferences)
+        self.ui.menufileimportpref.triggered.connect(self.on_import_preferences)
+
         self.ui.menufile_exit.triggered.connect(self.final_save)
 
         self.ui.menueditedit.triggered.connect(lambda: self.object2editor())
@@ -2092,6 +2154,23 @@ class App(QtCore.QObject):
                 except Exception as e:
                     log.debug("Could not open FlatCAM Script file as App parameter due: %s" % str(e))
 
+    @staticmethod
+    def copy_and_overwrite(from_path, to_path):
+        """
+        From here:
+        https://stackoverflow.com/questions/12683834/how-to-copy-directory-recursively-in-python-and-overwrite-all
+        :param from_path: source path
+        :param to_path: destination path
+        :return: None
+        """
+        if os.path.exists(to_path):
+            shutil.rmtree(to_path)
+        try:
+            shutil.copytree(from_path, to_path)
+        except FileNotFoundError:
+            from_new_path = os.path.dirname(os.path.realpath(__file__)) + '\\flatcamGUI\\VisPyData\\data'
+            shutil.copytree(from_new_path, to_path)
+
     def set_ui_title(self, name):
         self.ui.setWindowTitle('FlatCAM %s %s - %s    %s' %
                                (self.version,
@@ -2296,6 +2375,12 @@ class App(QtCore.QObject):
             # store the Geometry Editor Toolbar visibility before entering in the Editor
             self.geo_editor.toolbar_old_state = True if self.ui.geo_edit_toolbar.isVisible() else False
 
+            # we set the notebook to hidden
+            self.ui.splitter.setSizes([0, 1])
+
+            # set call source to the Editor we go into
+            self.call_source = 'geo_editor'
+
             if edited_object.multigeo is True:
                 edited_tools = [int(x.text()) for x in edited_object.ui.geo_tools_table.selectedItems()]
                 if len(edited_tools) > 1:
@@ -2317,16 +2402,9 @@ class App(QtCore.QObject):
             else:
                 self.geo_editor.edit_fcgeometry(edited_object)
 
-            # we set the notebook to hidden
-            self.ui.splitter.setSizes([0, 1])
-
-            # set call source to the Editor we go into
-            self.call_source = 'geo_editor'
-
         elif isinstance(edited_object, FlatCAMExcellon):
             # store the Excellon Editor Toolbar visibility before entering in the Editor
             self.exc_editor.toolbar_old_state = True if self.ui.exc_edit_toolbar.isVisible() else False
-            self.exc_editor.edit_fcexcellon(edited_object)
 
             # set call source to the Editor we go into
             self.call_source = 'exc_editor'
@@ -2334,10 +2412,11 @@ class App(QtCore.QObject):
             if self.ui.splitter.sizes()[0] == 0:
                 self.ui.splitter.setSizes([1, 1])
 
+            self.exc_editor.edit_fcexcellon(edited_object)
+
         elif isinstance(edited_object, FlatCAMGerber):
             # store the Gerber Editor Toolbar visibility before entering in the Editor
             self.grb_editor.toolbar_old_state = True if self.ui.grb_edit_toolbar.isVisible() else False
-            self.grb_editor.edit_fcgerber(edited_object)
 
             # set call source to the Editor we go into
             self.call_source = 'grb_editor'
@@ -2345,7 +2424,9 @@ class App(QtCore.QObject):
             if self.ui.splitter.sizes()[0] == 0:
                 self.ui.splitter.setSizes([1, 1])
 
-        # # make sure that we can't select another object while in Editor Mode:
+            self.grb_editor.edit_fcgerber(edited_object)
+
+        # make sure that we can't select another object while in Editor Mode:
         # self.collection.view.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
         self.ui.project_frame.setDisabled(True)
 
@@ -4894,33 +4975,52 @@ class App(QtCore.QObject):
         """
         self.report_usage("on_delete()")
 
+        response = None
+        bt_ok = None
+
         # Make sure that the deletion will happen only after the Editor is no longer active otherwise we might delete
         # a geometry object before we update it.
-        if self.geo_editor.editor_active is False and self.exc_editor.editor_active is False:
-            if self.collection.get_active():
-                self.log.debug("App.on_delete()")
-
-                while self.collection.get_active():
-                    obj_active = self.collection.get_active()
-                    # if the deleted object is FlatCAMGerber then make sure to delete the possible mark shapes
-                    if isinstance(obj_active, FlatCAMGerber):
-                        for el in obj_active.mark_shapes:
-                            obj_active.mark_shapes[el].clear(update=True)
-                            obj_active.mark_shapes[el].enabled = False
-                            obj_active.mark_shapes[el] = None
-                    elif isinstance(obj_active, FlatCAMCNCjob):
-                        try:
-                            obj_active.annotation.clear(update=True)
-                            obj_active.annotation.enabled = False
-                        except AttributeError:
-                            pass
-                    self.delete_first_selected()
+        if self.geo_editor.editor_active is False and self.exc_editor.editor_active is False \
+                and self.grb_editor.editor_active is False:
+            if self.defaults["global_delete_confirmation"] is True:
+                msgbox = QtWidgets.QMessageBox()
+                msgbox.setWindowTitle(_("Delete objects"))
+                msgbox.setWindowIcon(QtGui.QIcon('share/deleteshape32.png'))
+                # msgbox.setText(_("<B>Delete FlatCAM objects ...</B>"))
+                msgbox.setText(_("Are you sure you want to permanently delete\n"
+                                 "the selected objects?"))
+                bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.AcceptRole)
+                bt_cancel = msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.RejectRole)
 
-                self.inform.emit(_("Object(s) deleted ..."))
-                # make sure that the selection shape is deleted, too
-                self.delete_selection_shape()
-            else:
-                self.inform.emit(_("Failed. No object(s) selected..."))
+                msgbox.setDefaultButton(bt_ok)
+                msgbox.exec_()
+                response = msgbox.clickedButton()
+
+            if response == bt_ok or self.defaults["global_delete_confirmation"] is False:
+                if self.collection.get_active():
+                    self.log.debug("App.on_delete()")
+
+                    while self.collection.get_active():
+                        obj_active = self.collection.get_active()
+                        # if the deleted object is FlatCAMGerber then make sure to delete the possible mark shapes
+                        if isinstance(obj_active, FlatCAMGerber):
+                            for el in obj_active.mark_shapes:
+                                obj_active.mark_shapes[el].clear(update=True)
+                                obj_active.mark_shapes[el].enabled = False
+                                obj_active.mark_shapes[el] = None
+                        elif isinstance(obj_active, FlatCAMCNCjob):
+                            try:
+                                obj_active.annotation.clear(update=True)
+                                obj_active.annotation.enabled = False
+                            except AttributeError:
+                                pass
+                        self.delete_first_selected()
+
+                    self.inform.emit(_("Object(s) deleted ..."))
+                    # make sure that the selection shape is deleted, too
+                    self.delete_selection_shape()
+                else:
+                    self.inform.emit(_("Failed. No object(s) selected..."))
         else:
             self.inform.emit(_("Save the work in Editor and try again ..."))
 
@@ -4933,16 +5033,12 @@ class App(QtCore.QObject):
             self.log.debug("Nothing selected for deletion")
             return
 
-        # Remove plot
-        # self.plotcanvas.figure.delaxes(self.collection.get_active().axes)
-        # self.plotcanvas.auto_adjust_axes()
+        # Remove from dictionary
+        self.collection.delete_active()
 
         # Clear form
         self.setup_component_editor()
 
-        # Remove from dictionary
-        self.collection.delete_active()
-
         self.inform.emit("Object deleted: %s" % name)
 
     def on_set_origin(self):
@@ -5572,6 +5668,7 @@ class App(QtCore.QObject):
         self.plotcanvas.vispy_canvas.update()           # TODO: Need update canvas?
         self.on_zoom_fit(None)
         self.collection.update_view()
+        # self.inform.emit(_("Plots updated ..."))
 
     # TODO: Rework toolbar 'clear', 'replot' functions
     def on_toolbar_replot(self):
@@ -7491,6 +7588,7 @@ class App(QtCore.QObject):
         efract = self.defaults["excellon_exp_decimals"]
         ezeros = self.defaults["excellon_exp_zeros"]
         eformat = self.defaults["excellon_exp_format"]
+        slot_type = self.defaults["excellon_exp_slot_type"]
 
         fc_units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
         if fc_units == 'MM':
@@ -7510,7 +7608,7 @@ class App(QtCore.QObject):
                 header += ';Created on : %s' % time_str + '\n'
 
                 if eformat == 'dec':
-                    has_slots, excellon_code = obj.export_excellon(ewhole, efract, factor=factor)
+                    has_slots, excellon_code = obj.export_excellon(ewhole, efract, factor=factor, slot_type=slot_type)
                     header += eunits + '\n'
 
                     for tool in obj.tools:
@@ -7525,7 +7623,8 @@ class App(QtCore.QObject):
                 else:
                     if ezeros == 'LZ':
                         has_slots, excellon_code = obj.export_excellon(ewhole, efract,
-                                                                       form='ndec', e_zeros='LZ', factor=factor)
+                                                                       form='ndec', e_zeros='LZ', factor=factor,
+                                                                       slot_type=slot_type)
                         header += '%s,%s\n' % (eunits, 'LZ')
                         header += format_exc
 
@@ -7540,7 +7639,8 @@ class App(QtCore.QObject):
                                                                               dec=4)
                     else:
                         has_slots, excellon_code = obj.export_excellon(ewhole, efract,
-                                                                       form='ndec', e_zeros='TZ', factor=factor)
+                                                                       form='ndec', e_zeros='TZ', factor=factor,
+                                                                       slot_type=slot_type)
                         header += '%s,%s\n' % (eunits, 'TZ')
                         header += format_exc
 
@@ -8806,7 +8906,8 @@ The normal flow when working in FlatCAM is the following:</span></p>
         log.debug("Enabling plots ...")
         self.inform.emit(_("Working ..."))
         for obj in objects:
-            obj.options['plot'] = True
+            if obj.options['plot'] is False:
+                obj.options['plot'] = True
         self.plots_updated.emit()
 
     def disable_plots(self, objects):
@@ -8820,20 +8921,11 @@ The normal flow when working in FlatCAM is the following:</span></p>
         if not self.collection.get_selected():
             return
 
-        # if at least one object is visible then do the disable
-        exit_flag = True
-        for obj in objects:
-            if obj.options['plot'] is True:
-                exit_flag = False
-                break
-
-        if exit_flag:
-            return
-
         log.debug("Disabling plots ...")
         self.inform.emit(_("Working ..."))
         for obj in objects:
-            obj.options['plot'] = False
+            if obj.options['plot'] is True:
+                obj.options['plot'] = False
         self.plots_updated.emit()
 
     def toggle_plots(self, objects):

+ 42 - 21
FlatCAMObj.py

@@ -2237,7 +2237,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             item[0] = str(item[0])
         return table_tools_items
 
-    def export_excellon(self, whole, fract, e_zeros=None, form='dec', factor=1):
+    def export_excellon(self, whole, fract, e_zeros=None, form='dec', factor=1, slot_type='routing'):
         """
         Returns two values, first is a boolean , if 1 then the file has slots and second contain the Excellon code
         :return: has_slots and Excellon_code
@@ -2303,6 +2303,8 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             if self.slots:
                 has_slots = 1
                 for tool in self.tools:
+                    excellon_code += 'G05\n'
+
                     if int(tool) < 10:
                         excellon_code += 'T0' + str(tool) + '\n'
                     else:
@@ -2314,13 +2316,17 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
                             start_slot_y = slot['start'].y * factor
                             stop_slot_x = slot['stop'].x * factor
                             stop_slot_y = slot['stop'].y * factor
-
-                            excellon_code += "G00X{:.{dec}f}Y{:.{dec}f}\nM15\n".format(start_slot_x,
-                                                                                       start_slot_y,
-                                                                                       dec=fract)
-                            excellon_code += "G00X{:.{dec}f}Y{:.{dec}f}\nM16\n".format(stop_slot_x,
-                                                                                       stop_slot_y,
-                                                                                       dec=fract)
+                            if slot_type == 'routing':
+                                excellon_code += "G00X{:.{dec}f}Y{:.{dec}f}\nM15\n".format(start_slot_x,
+                                                                                           start_slot_y,
+                                                                                           dec=fract)
+                                excellon_code += "G01X{:.{dec}f}Y{:.{dec}f}\nM16\n".format(stop_slot_x,
+                                                                                           stop_slot_y,
+                                                                                           dec=fract)
+                            elif slot_type == 'drilling':
+                                excellon_code += "X{:.{dec}f}Y{:.{dec}f}G85X{:.{dec}f}Y{:.{dec}f}\nG05\n".format(
+                                    start_slot_x, start_slot_y, stop_slot_x, stop_slot_y, dec=fract
+                                )
 
                         elif e_zeros == 'LZ' and tool == slot['tool']:
                             start_slot_x = slot['start'].x * factor
@@ -2352,10 +2358,16 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
                             stop_slot_x_formatted = stop_x_whole + stop_slot_x_formatted[2]
                             stop_slot_y_formatted = stop_y_whole + stop_slot_y_formatted[2]
 
-                            excellon_code += "G00X{xstart}Y{ystart}\nM15\n".format(xstart=start_slot_x_formatted,
-                                                                                   ystart=start_slot_y_formatted)
-                            excellon_code += "G00X{xstop}Y{ystop}\nM16\n".format(xstop=stop_slot_x_formatted,
-                                                                                 ystop=stop_slot_y_formatted)
+                            if slot_type == 'routing':
+                                excellon_code += "G00X{xstart}Y{ystart}\nM15\n".format(xstart=start_slot_x_formatted,
+                                                                                       ystart=start_slot_y_formatted)
+                                excellon_code += "G01X{xstop}Y{ystop}\nM16\n".format(xstop=stop_slot_x_formatted,
+                                                                                     ystop=stop_slot_y_formatted)
+                            elif slot_type == 'drilling':
+                                excellon_code += "{xstart}Y{ystart}G85X{xstop}Y{ystop}\nG05\n".format(
+                                    xstart=start_slot_x_formatted, ystart=start_slot_y_formatted,
+                                    xstop=stop_slot_x_formatted, ystop=stop_slot_y_formatted
+                                )
                         elif tool == slot['tool']:
                             start_slot_x = slot['start'].x * factor
                             start_slot_y = slot['start'].y * factor
@@ -2374,10 +2386,16 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
                             stop_slot_x_formatted.ljust(length, '0')
                             stop_slot_y_formatted.ljust(length, '0')
 
-                            excellon_code += "G00X{xstart}Y{ystart}\nM15\n".format(xstart=start_slot_x_formatted,
-                                                                                   ystart=start_slot_y_formatted)
-                            excellon_code += "G00X{xstop}Y{ystop}\nM16\n".format(xstop=stop_slot_x_formatted,
-                                                                                 ystop=stop_slot_y_formatted)
+                            if slot_type == 'routing':
+                                excellon_code += "G00X{xstart}Y{ystart}\nM15\n".format(xstart=start_slot_x_formatted,
+                                                                                       ystart=start_slot_y_formatted)
+                                excellon_code += "G01X{xstop}Y{ystop}\nM16\n".format(xstop=stop_slot_x_formatted,
+                                                                                     ystop=stop_slot_y_formatted)
+                            elif slot_type == 'drilling':
+                                excellon_code += "{xstart}Y{ystart}G85X{xstop}Y{ystop}\nG05\n".format(
+                                    xstart=start_slot_x_formatted, ystart=start_slot_y_formatted,
+                                    xstop=stop_slot_x_formatted, ystop=stop_slot_y_formatted
+                                )
         except Exception as e:
             log.debug(str(e))
 
@@ -5345,7 +5363,9 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         # from predecessors.
         self.ser_attrs += ['options', 'kind', 'cnc_tools', 'multitool']
 
-        self.annotation = self.app.plotcanvas.new_text_group()
+        self.text_col = self.app.plotcanvas.new_text_collection()
+        self.text_col.enabled = True
+        self.annotation = self.app.plotcanvas.new_text_group(collection=self.text_col)
 
     def build_ui(self):
         self.ui_disconnect()
@@ -5967,15 +5987,16 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
             self.annotation.clear(update=True)
 
         if self.ui.annotation_cb.get_value() and self.ui.plot_cb.get_value():
-            self.annotation.enabled = True
+            self.text_col.enabled = True
         else:
-            self.annotation.enabled = False
+            self.text_col.enabled = False
+        self.annotation.redraw()
 
     def on_annotation_change(self):
         if self.ui.annotation_cb.get_value():
-            self.app.plotcanvas.text_collection.enabled = True
+            self.text_col.enabled = True
         else:
-            self.app.plotcanvas.text_collection.enabled = False
+            self.text_col.enabled = False
         # kind = self.ui.cncplot_method_combo.get_value()
         # self.plot(kind=kind)
         self.annotation.redraw()

+ 43 - 0
README.md

@@ -9,10 +9,53 @@ CAD program, and create G-Code for Isolation routing.
 
 =================================================
 
+17.08.2019
+
+- updated the translations for the new strings
+- RELEASE 8.94
+
+16.08.2019
+
+- working in Excellon Editor to Tool Resize to consider the slots, too
+- fixed a weird error that created a crash in the following scenario: create a new excellon, edit it, add some drills/slots, delete it without saving, create a new excellon, try to edit and a crash is issued due of a wrapped C++ error
+- fixed bug selection in Excellon editor that caused not to select the corresponding row (tool dia) in the tool table when a selection rectangle selected an even number of geometric elements
+- updated the default values to more convenient ones
+- remade the enable/disable plots functions to work only where it needs to (no sense in disabling a plot already disabled)
+- made sure that if multi depth is choosed when creating GCode then if the multidepth is more than the depth of cut only one cut is made (to the depth of cut)
+- each CNCJob object has now it's own text_collection for the annotations which allow for the individual enabling and disabling of the annotations
+- added new menu category in File -> Backup with two menu entries that duplicate the functions of the export/import preferences buttons from the bottom of the Preferences window
+- in Excellon Editor fixed the display of the number of slots in the Tool Table after the resize done with the Resize tool
+- in Excellon Editor -> Resize tool, made sure that when the slot is resized, it's length remain the same, because the tool should influence only the 'thickness' of the slot. Since I don't know anything but the geometry and tool diameters (old and new), this is only an approximation and computationally intensive
+- in Excellon Editor -> remade the Tool edit made by editing the diameter values in the Tools Table to work for slots too
+- In Excellon Editor -> fixed bug that caused incorrect display of the relative coordinates in the status bar
+
+15.08.2019
+
+- added Edit -> Preferences GUI and storage for the Excellon Editor Add Slots
+- added a confirmation message for objects delete and a setting to activate it in Edit -> Preferences -> Global
+- merged pull request from Mike Smith which fix an application crash when attempting to open a not-a-FlatCAM-project file as project
+- merged pull request from Mike Smith that add support for a new SVG element: <use>
+- stored inside FlatCAM app the VisPy data files and at the first start the application will try to copy those files to the APPDATA (roaming) folder in case of running under Windows OS
+- created a configuration file in the root/config/configuration.txt with a configuration line for portability. Set portable to True to run the app as portable
+- working on the Slots Array in Excellon Editor - building the GUI
+- added a failsafe path to the source folder from which to copy the VisPy data
+- fixed the GUI for Slot Arrays in Excellon Editor
+- finished the Slot Array tool in Excellon Editor
+- added the key shortcut handlers for Add Slot and Add Slot Array tools in Excellon Editor
+- started to work on the Resize tool for the case of Excellon slots in Excellon Editor
+- final fix for the VisPy data files; the defaults files are saved to the Config folder when the app is set to be portable
+- added the Slot Type parameter for exporting Excellon in Edit -> Preferences -> Excellon -> Export Excellon. Now the Excellon object can be exported also with drilled slot command G85
+- fixed bug in Excellon export when there are no zero suppression (coordinates with decimals)
+
 14.08.2019
 
 - fixed the loading of Excellon with slots and the saving of edited Excellon object in regard of slots, in Excellon Editor
 - fixed the Delete tool, Select tool in Excellon Editor to work for Slots too
+- changes in the way the edited Excellon with added slots is saved
+- added more icons and cursor in Excellon Editor for Slots related functions
+- in Excellon Editor fixed the selection issue which in a certain step created a failure in the Copy and Move tools.
+- in Excellon Editor fixed the selection with key modifier pressed
+- edited the mouse cursors and saved them without included thumbnail in a bid to remove some CRC warnings made by libpng
 
 13.08.2019
 

+ 14 - 8
camlib.py

@@ -3979,10 +3979,10 @@ class Excellon(Geometry):
                         ':' + str(self.excellon_format_lower_in))
                     continue
 
-                #### Body ## ##
+                # ### Body ####
                 if not in_header:
 
-                    # ## Tool change # ##
+                    # ## Tool change ###
                     match = self.toolsel_re.search(eline)
                     if match:
                         current_tool = str(int(match.group(1)))
@@ -4022,7 +4022,7 @@ class Excellon(Geometry):
 
                         continue
 
-                    # ## Allegro Type Tool change # ##
+                    # ## Allegro Type Tool change ###
                     if allegro_warning is True:
                         match = self.absinc_re.search(eline)
                         match1 = self.stop_re.search(eline)
@@ -4114,7 +4114,7 @@ class Excellon(Geometry):
                             )
                             continue
 
-                        # Slot coordinates with period: Use literally. # ##
+                        # Slot coordinates with period: Use literally. ###
                         # get the coordinates for slot start and for slot stop into variables
                         start_coords_period = self.coordsperiod_re.search(start_coords_match)
                         stop_coords_period = self.coordsperiod_re.search(stop_coords_match)
@@ -4274,7 +4274,6 @@ class Excellon(Geometry):
                     if match:
                         # signal that there are drill operations
                         self.defaults['excellon_drills'] = True
-
                         try:
                             x = float(match.group(1))
                             repeating_x = current_x
@@ -4346,7 +4345,7 @@ class Excellon(Geometry):
                             # log.debug("{:15} {:8} {:8}".format(eline, x, y))
                             continue
 
-                #### Header ## ##
+                # ### Header ####
                 if in_header:
 
                     # ## Tool definitions # ##
@@ -4484,7 +4483,7 @@ class Excellon(Geometry):
         match = self.leadingzeros_re.search(number_str)
         nr_length = len(match.group(1)) + len(match.group(2))
         try:
-            if self.zeros == "L" or self.zeros == "LZ":
+            if self.zeros == "L" or self.zeros == "LZ": # Leading
                 # With leading zeros, when you type in a coordinate,
                 # the leading zeros must always be included.  Trailing zeros
                 # are unneeded and may be left off. The CNC-7 will automatically add them.
@@ -4498,7 +4497,7 @@ class Excellon(Geometry):
                 else:
                     result = float(number_str) / (10 ** (float(nr_length) - float(self.excellon_format_upper_mm)))
                 return result
-            else:  # Trailing
+            elif self.zeros == "T" or self.zeros == "TZ":  # Trailing
                 # You must show all zeros to the right of the number and can omit
                 # all zeros to the left of the number. The CNC-7 will count the number
                 # of digits you typed and automatically fill in the missing zeros.
@@ -4510,6 +4509,9 @@ class Excellon(Geometry):
                 else:   # Metric is 000.000
                     result = float(number_str) / (10 ** (float(self.excellon_format_lower_mm)))
                 return result
+            else: # None - the numbers are in decimal format
+                return float(number_str)
+
         except Exception as e:
             log.error("Aborted. Operation could not be completed due of %s" % str(e))
             return
@@ -5660,6 +5662,10 @@ class CNCjob(Geometry):
                                  "There will be no cut, skipping %s file") % self.options['name'])
             return 'fail'
 
+        # made sure that depth_per_cut is no more then the z_cut
+        if self.z_cut < self.z_depthpercut:
+            self.z_depthpercut = self.z_cut
+
         if self.z_move is None:
             self.app.inform.emit(_("[ERROR_NOTCL] Travel Z parameter is None or zero."))
             return 'fail'

+ 1 - 0
config/configuration.txt

@@ -0,0 +1 @@
+portable=False

Разница между файлами не показана из-за своего большого размера
+ 484 - 225
flatcamEditors/FlatCAMExcEditor.py


+ 4 - 3
flatcamEditors/FlatCAMGrbEditor.py

@@ -4205,9 +4205,10 @@ class FlatCAMGrbEditor(QtCore.QObject):
         # select the aperture code of the selected geometry, in the tool table
         self.apertures_table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
         for aper in sel_aperture:
-            for row in range(self.apertures_table.rowCount()):
-                if str(aper) == self.apertures_table.item(row, 1).text():
-                    self.apertures_table.selectRow(row)
+            for row_to_sel in range(self.apertures_table.rowCount()):
+                if str(aper) == self.apertures_table.item(row_to_sel, 1).text():
+                    if row_to_sel not in set(index.row() for index in self.apertures_table.selectedIndexes()):
+                        self.apertures_table.selectRow(row_to_sel)
                     self.last_aperture_selected = aper
         self.apertures_table.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
 

+ 212 - 10
flatcamGUI/FlatCAMGUI.py

@@ -194,12 +194,27 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.menufile.addSeparator()
 
         # Save Defaults
-        self.menufilesavedefaults = QtWidgets.QAction(QtGui.QIcon('share/defaults.png'), _('Save &Defaults'), self)
+        self.menufilesavedefaults = QtWidgets.QAction(QtGui.QIcon('share/defaults.png'), _('Save Preferences'), self)
         self.menufile.addAction(self.menufilesavedefaults)
 
         # Separator
         self.menufile.addSeparator()
 
+        self.menufile_backup = self.menufile.addMenu(QtGui.QIcon('share/backup24.png'), _('Backup'))
+
+        # Import Preferences
+        self.menufileimportpref = QtWidgets.QAction(QtGui.QIcon('share/backup_import24.png'),
+                                                    _('Import Preferences from file ...'), self)
+        self.menufile_backup.addAction(self.menufileimportpref)
+
+        # Export Preferences
+        self.menufileexportpref = QtWidgets.QAction(QtGui.QIcon('share/backup_export24.png'),
+                                                    _('Export Preferences to file ...'), self)
+        self.menufile_backup.addAction(self.menufileexportpref)
+
+        # Separator
+        self.menufile.addSeparator()
+
         self.menufile_save = self.menufile.addMenu(QtGui.QIcon('share/save_as.png'), _('Save'))
 
         # Save Project
@@ -450,8 +465,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.exc_editor_menu.addSeparator()
 
         self.exc_add_array_slot_menuitem = self.exc_editor_menu.addAction(
-            QtGui.QIcon('share/rectangle32.png'), _('Add Slot Array\tQ'))
-        self.exc_add_slot_menuitem = self.exc_editor_menu.addAction(QtGui.QIcon('share/plus16.png'),
+            QtGui.QIcon('share/slot_array26.png'), _('Add Slot Array\tQ'))
+        self.exc_add_slot_menuitem = self.exc_editor_menu.addAction(QtGui.QIcon('share/slot26.png'),
                                                                      _('Add Slot\tW'))
         self.exc_editor_menu.addSeparator()
 
@@ -660,9 +675,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.add_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/plus16.png'), _('Add Drill Hole'))
         self.add_drill_array_btn = self.exc_edit_toolbar.addAction(
             QtGui.QIcon('share/addarray16.png'), _('Add Drill Hole Array'))
-        self.add_slot_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/plus16.png'), _('Add Slot'))
+        self.add_slot_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/slot26.png'), _('Add Slot'))
         self.add_slot_array_btn = self.exc_edit_toolbar.addAction(
-            QtGui.QIcon('share/addarray16.png'), _('Add Slot Array'))
+            QtGui.QIcon('share/slot_array26.png'), _('Add Slot Array'))
         self.resize_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/resize16.png'), _('Resize Drill'))
         self.exc_edit_toolbar.addSeparator()
 
@@ -1674,8 +1689,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.drill = self.e_editor_cmenu.addAction(QtGui.QIcon('share/drill32.png'), _("Add Drill"))
         self.drill_array = self.e_editor_cmenu.addAction(QtGui.QIcon('share/addarray32.png'), _("Add Drill Array"))
         self.e_editor_cmenu.addSeparator()
-        self.slot = self.e_editor_cmenu.addAction(QtGui.QIcon('share/drill32.png'), _("Add Slot"))
-        self.slot_array = self.e_editor_cmenu.addAction(QtGui.QIcon('share/addarray32.png'), _("Add Slot Array"))
+        self.slot = self.e_editor_cmenu.addAction(QtGui.QIcon('share/slot26.png'), _("Add Slot"))
+        self.slot_array = self.e_editor_cmenu.addAction(QtGui.QIcon('share/slot_array26.png'), _("Add Slot Array"))
         self.e_editor_cmenu.addSeparator()
         self.drill_resize= self.e_editor_cmenu.addAction(QtGui.QIcon('share/resize16.png'), _("Resize Drill"))
 
@@ -1955,9 +1970,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.add_drill_array_btn = self.exc_edit_toolbar.addAction(
             QtGui.QIcon('share/addarray16.png'), _('Add Drill Hole Array'))
         self.resize_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/resize16.png'), _('Resize Drill'))
-        self.add_slot_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/plus16.png'), _('Add Slot'))
+        self.add_slot_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/slot26.png'), _('Add Slot'))
         self.add_slot_array_btn = self.exc_edit_toolbar.addAction(
-            QtGui.QIcon('share/addarray16.png'), _('Add Slot Array'))
+            QtGui.QIcon('share/slot_array26.png'), _('Add Slot Array'))
         self.exc_edit_toolbar.addSeparator()
 
         self.copy_drill_btn = self.exc_edit_toolbar.addAction(QtGui.QIcon('share/copy32.png'), _('Copy Drill'))
@@ -3057,6 +3072,18 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                         self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. Nothing selected to move."))
                     return
 
+                # Add Array of Slote Hole Tool
+                if key == QtCore.Qt.Key_Q or key == 'Q':
+                    self.app.exc_editor.launched_from_shortcuts = True
+                    self.app.inform.emit("Click on target point.")
+                    self.app.ui.add_slot_array_btn.setChecked(True)
+
+                    self.app.exc_editor.x = self.app.mouse[0]
+                    self.app.exc_editor.y = self.app.mouse[1]
+
+                    self.app.exc_editor.select_tool('slot_array')
+                    return
+
                 # Resize Tool
                 if key == QtCore.Qt.Key_R or key == 'R':
                     self.app.exc_editor.launched_from_shortcuts = True
@@ -3090,6 +3117,18 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                     self.app.on_zoom_fit(None)
                     return
 
+                # Add Slot Hole Tool
+                if key == QtCore.Qt.Key_W or key == 'W':
+                    self.app.exc_editor.launched_from_shortcuts = True
+                    self.app.inform.emit(_("Click on target point."))
+                    self.app.ui.add_slot_btn.setChecked(True)
+
+                    self.app.exc_editor.x = self.app.mouse[0]
+                    self.app.exc_editor.y = self.app.mouse[1]
+
+                    self.app.exc_editor.select_tool('slot_add')
+                    return
+
                 # Propagate to tool
                 response = None
                 if self.app.exc_editor.active_tool is not None:
@@ -4062,7 +4101,7 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         # to the main layout of this TAB
         self.layout.addLayout(self.form_box)
 
-        # Save compressed project CB
+        # Open behavior
         self.open_style_cb = FCCheckBox(_('"Open" behavior'))
         self.open_style_cb.setToolTip(
             _("When checked the path for the last saved file is used when saving files,\n"
@@ -4073,6 +4112,15 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         # self.advanced_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
         self.layout.addWidget(self.open_style_cb)
 
+        # Delete confirmation
+        self.delete_conf_cb = FCCheckBox(_('Delete object confirmation'))
+        self.delete_conf_cb.setToolTip(
+            _("When checked the application will ask for user confirmation\n"
+              "whenever the Delete object(s) event is triggered, either by\n"
+              "menu shortcut or key shortcut.")
+        )
+        self.layout.addWidget(self.delete_conf_cb)
+
         # Save compressed project CB
         self.save_type_cb = FCCheckBox(_('Save Compressed Project'))
         self.save_type_cb.setToolTip(
@@ -5113,6 +5161,29 @@ class ExcellonExpPrefGroupUI(OptionsGroupUI):
 
         form.addRow(self.zeros_label, self.zeros_radio)
 
+        # Slot type
+        self.slot_type_label = QtWidgets.QLabel(_('<b>Slot type</b>:'))
+        self.slot_type_label.setAlignment(QtCore.Qt.AlignLeft)
+        self.slot_type_label.setToolTip(
+            _("This sets how the slots will be exported.\n"
+              "If ROUTED then the slots will be routed\n"
+              "using M15/M16 commands.\n"
+              "If DRILLED(G85) the slots will be exported\n"
+              "using the Drilled slot command (G85).")
+        )
+
+        self.slot_type_radio = RadioSet([{'label': _('Routed'), 'value': 'routing'},
+                                         {'label': _('Drilled(G85)'), 'value': 'drilling'}])
+        self.slot_type_radio.setToolTip(
+            _("This sets how the slots will be exported.\n"
+              "If ROUTED then the slots will be routed\n"
+              "using M15/M16 commands.\n"
+              "If DRILLED(G85) the slots will be exported\n"
+              "using the Drilled slot command (G85).")
+        )
+
+        form.addRow(self.slot_type_label, self.slot_type_radio)
+
         self.layout.addStretch()
         self.format_radio.activated_custom.connect(self.optimization_selection)
 
@@ -5245,6 +5316,137 @@ class ExcellonEditorPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(self.drill_circular_angle_label, 9, 0)
         grid0.addWidget(self.drill_circular_angle_entry, 9, 1)
 
+        # ##### SLOTS #####
+        # #################
+        self.drill_array_circ_label = QtWidgets.QLabel(_('<b>Slots:</b>'))
+        grid0.addWidget(self.drill_array_circ_label, 10, 0, 1, 2)
+
+        # Slot length
+        self.slot_length_label = QtWidgets.QLabel(_('Length:'))
+        self.slot_length_label.setToolTip(
+            _("Length = The length of the slot.")
+        )
+        self.slot_length_label.setMinimumWidth(100)
+
+        self.slot_length_entry = LengthEntry()
+        grid0.addWidget(self.slot_length_label, 11, 0)
+        grid0.addWidget(self.slot_length_entry, 11, 1)
+
+        # Slot direction
+        self.slot_axis_label = QtWidgets.QLabel(_('Direction:'))
+        self.slot_axis_label.setToolTip(
+            _("Direction on which the slot is oriented:\n"
+              "- 'X' - horizontal axis \n"
+              "- 'Y' - vertical axis or \n"
+              "- 'Angle' - a custom angle for the slot inclination")
+        )
+        self.slot_axis_label.setMinimumWidth(100)
+
+        self.slot_axis_radio = RadioSet([{'label': _('X'), 'value': 'X'},
+                                         {'label': _('Y'), 'value': 'Y'},
+                                         {'label': _('Angle'), 'value': 'A'}])
+        grid0.addWidget(self.slot_axis_label, 12, 0)
+        grid0.addWidget(self.slot_axis_radio, 12, 1)
+
+        # Slot custom angle
+        self.slot_angle_label = QtWidgets.QLabel(_('Angle:'))
+        self.slot_angle_label.setToolTip(
+            _("Angle at which the slot is placed.\n"
+              "The precision is of max 2 decimals.\n"
+              "Min value is: -359.99 degrees.\n"
+              "Max value is:  360.00 degrees.")
+        )
+        self.slot_angle_label.setMinimumWidth(100)
+
+        self.slot_angle_spinner = FCDoubleSpinner()
+        self.slot_angle_spinner.set_precision(2)
+        self.slot_angle_spinner.setWrapping(True)
+        self.slot_angle_spinner.setRange(-359.99, 360.00)
+        self.slot_angle_spinner.setSingleStep(1.0)
+        grid0.addWidget(self.slot_angle_label, 13, 0)
+        grid0.addWidget(self.slot_angle_spinner, 13, 1)
+
+        # #### SLOTS ARRAY #######
+        # ########################
+
+        self.slot_array_linear_label = QtWidgets.QLabel(_('<b>Linear Slot Array:</b>'))
+        grid0.addWidget(self.slot_array_linear_label, 14, 0, 1, 2)
+
+        # Number of slot holes in a drill array
+        self.slot_array_size_label = QtWidgets.QLabel(_('Nr of slots:'))
+        self.drill_array_size_label.setToolTip(
+            _("Specify how many slots to be in the array.")
+        )
+        # self.slot_array_size_label.setMinimumWidth(100)
+
+        self.slot_array_size_entry = LengthEntry()
+
+        grid0.addWidget(self.slot_array_size_label, 15, 0)
+        grid0.addWidget(self.slot_array_size_entry, 15, 1)
+
+        # Linear Slot Array direction
+        self.slot_array_axis_label = QtWidgets.QLabel(_('Linear Dir.:'))
+        self.slot_array_axis_label.setToolTip(
+            _("Direction on which the linear array is oriented:\n"
+              "- 'X' - horizontal axis \n"
+              "- 'Y' - vertical axis or \n"
+              "- 'Angle' - a custom angle for the array inclination")
+        )
+        # self.slot_axis_label.setMinimumWidth(100)
+        self.slot_array_axis_radio = RadioSet([{'label': _('X'), 'value': 'X'},
+                                               {'label': _('Y'), 'value': 'Y'},
+                                               {'label': _('Angle'), 'value': 'A'}])
+
+        grid0.addWidget(self.slot_array_axis_label, 16, 0)
+        grid0.addWidget(self.slot_array_axis_radio, 16, 1)
+
+        # Linear Slot Array pitch distance
+        self.slot_array_pitch_label = QtWidgets.QLabel(_('Pitch:'))
+        self.slot_array_pitch_label.setToolTip(
+            _("Pitch = Distance between elements of the array.")
+        )
+        # self.drill_pitch_label.setMinimumWidth(100)
+        self.slot_array_pitch_entry = LengthEntry()
+
+        grid0.addWidget(self.slot_array_pitch_label, 17, 0)
+        grid0.addWidget(self.slot_array_pitch_entry, 17, 1)
+
+        # Linear Slot Array custom angle
+        self.slot_array_angle_label = QtWidgets.QLabel(_('Angle:'))
+        self.slot_array_angle_label.setToolTip(
+            _("Angle at which each element in circular array is placed.")
+        )
+        self.slot_array_angle_entry = LengthEntry()
+
+        grid0.addWidget(self.slot_array_angle_label, 18, 0)
+        grid0.addWidget(self.slot_array_angle_entry, 18, 1)
+
+        self.slot_array_circ_label = QtWidgets.QLabel(_('<b>Circular Slot Array:</b>'))
+        grid0.addWidget(self.slot_array_circ_label, 19, 0, 1, 2)
+
+        # Circular Slot Array direction
+        self.slot_array_circular_direction_label = QtWidgets.QLabel(_('Circular Dir.:'))
+        self.slot_array_circular_direction_label.setToolTip(
+            _("Direction for circular array.\n"
+              "Can be CW = clockwise or CCW = counter clockwise.")
+        )
+
+        self.slot_array_circular_dir_radio = RadioSet([{'label': _('CW'), 'value': 'CW'},
+                                                       {'label': _('CCW'), 'value': 'CCW'}])
+
+        grid0.addWidget(self.slot_array_circular_direction_label, 20, 0)
+        grid0.addWidget(self.slot_array_circular_dir_radio, 20, 1)
+
+        # Circular Slot Array Angle
+        self.slot_array_circular_angle_label = QtWidgets.QLabel(_('Circ. Angle:'))
+        self.slot_array_circular_angle_label.setToolTip(
+            _("Angle at which each element in circular array is placed.")
+        )
+        self.slot_array_circular_angle_entry = LengthEntry()
+
+        grid0.addWidget(self.slot_array_circular_angle_label, 21, 0)
+        grid0.addWidget(self.slot_array_circular_angle_entry, 21, 1)
+
         self.layout.addStretch()
 
 

+ 5 - 2
flatcamGUI/PlotCanvas.py

@@ -183,8 +183,11 @@ class PlotCanvas(QtCore.QObject):
         c.antialias = 0
         return c
 
-    def new_text_group(self):
-        return TextGroup(self.text_collection)
+    def new_text_group(self, collection=None):
+        if collection:
+            return TextGroup(collection)
+        else:
+            return TextGroup(self.text_collection)
 
     def new_text_collection(self, **kwargs):
         return TextCollection(parent=self.vispy_canvas.view.scene, **kwargs)

BIN
flatcamGUI/VisPyData/data/fonts/opensans-regular.ttf


BIN
flatcamGUI/VisPyData/data/freetype/freetype253.dll


BIN
flatcamGUI/VisPyData/data/freetype/freetype253_x64.dll


BIN
locale/de/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 199 - 186
locale/de/LC_MESSAGES/strings.po


BIN
locale/en/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 223 - 209
locale/en/LC_MESSAGES/strings.po


BIN
locale/es/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 199 - 186
locale/es/LC_MESSAGES/strings.po


BIN
locale/pt_BR/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 199 - 186
locale/pt_BR/LC_MESSAGES/strings.po


BIN
locale/ro/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 199 - 186
locale/ro/LC_MESSAGES/strings.po


BIN
locale/ru/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 220 - 207
locale/ru/LC_MESSAGES/strings.po


Разница между файлами не показана из-за своего большого размера
+ 245 - 234
locale_template/strings.pot


+ 2 - 0
make_win.py

@@ -55,6 +55,8 @@ if platform.architecture()[0] == '64bit':
 include_files.append(("locale", "lib/locale"))
 include_files.append(("postprocessors", "lib/postprocessors"))
 include_files.append(("share", "lib/share"))
+include_files.append(("flatcamGUI/VisPyData", "lib/vispy"))
+include_files.append(("config", "lib/config"))
 
 include_files.append(("README.md", "README.md"))
 include_files.append(("LICENSE", "LICENSE"))

BIN
share/aero_arc.png


BIN
share/aero_array.png


BIN
share/aero_buffer.png


BIN
share/aero_circle.png


BIN
share/aero_circle_geo.png


BIN
share/aero_disc.png


BIN
share/aero_drill.png


BIN
share/aero_drill_array.png


BIN
share/aero_path1.png


BIN
share/aero_path2.png


BIN
share/aero_path3.png


BIN
share/aero_path4.png


BIN
share/aero_path5.png


BIN
share/aero_semidisc.png


BIN
share/aero_slot.png


BIN
share/aero_text.png


BIN
share/backup24.png


BIN
share/backup_export24.png


BIN
share/backup_import24.png


BIN
share/slot26.png


BIN
share/slot_array26.png


Некоторые файлы не были показаны из-за большого количества измененных файлов