Explorar el Código

Merge remote-tracking branch 'remotes/jpcgt/flatcam/Beta' into Beta

camellan hace 6 años
padre
commit
762e460614
Se han modificado 42 ficheros con 2728 adiciones y 2083 borrados
  1. 32 29
      FlatCAMApp.py
  2. 216 97
      FlatCAMObj.py
  3. 32 0
      README.md
  4. 106 72
      camlib.py
  5. 75 34
      flatcamEditors/FlatCAMGeoEditor.py
  6. 10 7
      flatcamEditors/FlatCAMGrbEditor.py
  7. 38 3
      flatcamEditors/FlatCAMTextEditor.py
  8. 78 4
      flatcamGUI/GUIElements.py
  9. 38 38
      flatcamGUI/ObjectUI.py
  10. 10 10
      flatcamGUI/PreferencesUI.py
  11. 2 2
      flatcamParsers/ParseHPGL2.py
  12. 54 28
      flatcamTools/ToolNonCopperClear.py
  13. 169 63
      flatcamTools/ToolPaint.py
  14. BIN
      locale/de/LC_MESSAGES/strings.mo
  15. 183 183
      locale/de/LC_MESSAGES/strings.po
  16. BIN
      locale/en/LC_MESSAGES/strings.mo
  17. 208 208
      locale/en/LC_MESSAGES/strings.po
  18. BIN
      locale/es/LC_MESSAGES/strings.mo
  19. 183 183
      locale/es/LC_MESSAGES/strings.po
  20. BIN
      locale/fr/LC_MESSAGES/strings.mo
  21. 183 183
      locale/fr/LC_MESSAGES/strings.po
  22. BIN
      locale/pt_BR/LC_MESSAGES/strings.mo
  23. 185 185
      locale/pt_BR/LC_MESSAGES/strings.po
  24. BIN
      locale/ro/LC_MESSAGES/strings.mo
  25. 186 186
      locale/ro/LC_MESSAGES/strings.po
  26. 207 207
      locale/ru/LC_MESSAGES/strings.po
  27. 213 215
      locale_template/strings.pot
  28. 6 4
      preprocessors/Berta_CNC.py
  29. 10 10
      preprocessors/ISEL_CNC.py
  30. 136 0
      preprocessors/ISEL_ICP_CNC.py
  31. 13 13
      preprocessors/Paste_1.py
  32. 17 13
      preprocessors/Repetier.py
  33. 4 3
      preprocessors/Roland_MDX_20.py
  34. 12 8
      preprocessors/Toolchange_Custom.py
  35. 18 16
      preprocessors/Toolchange_Probe_MACH3.py
  36. 18 16
      preprocessors/Toolchange_manual.py
  37. 14 11
      preprocessors/default.py
  38. 16 13
      preprocessors/grbl_11.py
  39. 11 10
      preprocessors/grbl_laser.py
  40. 19 7
      preprocessors/hpgl.py
  41. 12 12
      preprocessors/line_xyz.py
  42. 14 10
      preprocessors/marlin.py

+ 32 - 29
FlatCAMApp.py

@@ -140,8 +140,8 @@ class App(QtCore.QObject):
     # ##########################################################################
     # ##########################################################################
     # ################## Version and VERSION DATE ##############################
     # ################## Version and VERSION DATE ##############################
     # ##########################################################################
     # ##########################################################################
-    version = 8.99
-    version_date = "2019/12/15"
+    version = 8.991
+    version_date = "2019/12/30"
     beta = True
     beta = True
     engine = '3D'
     engine = '3D'
 
 
@@ -237,6 +237,9 @@ class App(QtCore.QObject):
     # should be disconnected after use so it can be reused
     # should be disconnected after use so it can be reused
     replot_signal = pyqtSignal(list)
     replot_signal = pyqtSignal(list)
 
 
+    # signal emitted when jumping
+    jump_signal = pyqtSignal(tuple)
+
     def __init__(self, user_defaults=True):
     def __init__(self, user_defaults=True):
         """
         """
         Starts the application.
         Starts the application.
@@ -3810,7 +3813,7 @@ class App(QtCore.QObject):
 
 
         if 'version' not in defaults or defaults['version'] != self.defaults['version']:
         if 'version' not in defaults or defaults['version'] != self.defaults['version']:
             for k, v in defaults.items():
             for k, v in defaults.items():
-                if k in self.defaults:
+                if k in self.defaults and k != 'version':
                     self.defaults[k] = v
                     self.defaults[k] = v
 
 
             # delete old factory defaults
             # delete old factory defaults
@@ -7355,10 +7358,6 @@ class App(QtCore.QObject):
         """
         """
         self.report_usage("on_jump_to()")
         self.report_usage("on_jump_to()")
 
 
-        # if self.is_legacy is True:
-        #     self.inform.emit(_("Not available with the current Graphic Engine Legacy(2D)."))
-        #     return
-
         if not custom_location:
         if not custom_location:
             dia_box_location = None
             dia_box_location = None
 
 
@@ -7372,17 +7371,29 @@ class App(QtCore.QObject):
             else:
             else:
                 dia_box_location = None
                 dia_box_location = None
 
 
-            dia_box = Dialog_box(title=_("Jump to ..."),
-                                 label=_("Enter the coordinates in format X,Y:"),
-                                 icon=QtGui.QIcon(self.resource_location + '/jump_to16.png'),
-                                 initial_text=dia_box_location)
+            # dia_box = Dialog_box(title=_("Jump to ..."),
+            #                      label=_("Enter the coordinates in format X,Y:"),
+            #                      icon=QtGui.QIcon(self.resource_location + '/jump_to16.png'),
+            #                      initial_text=dia_box_location)
+
+            dia_box = DialogBoxRadio(title=_("Jump to ..."),
+                                     label=_("Enter the coordinates in format X,Y:"),
+                                     icon=QtGui.QIcon(self.resource_location + '/jump_to16.png'),
+                                     initial_text=dia_box_location)
 
 
             if dia_box.ok is True:
             if dia_box.ok is True:
                 try:
                 try:
                     location = eval(dia_box.location)
                     location = eval(dia_box.location)
+
                     if not isinstance(location, tuple):
                     if not isinstance(location, tuple):
                         self.inform.emit(_("Wrong coordinates. Enter coordinates in format: X,Y"))
                         self.inform.emit(_("Wrong coordinates. Enter coordinates in format: X,Y"))
                         return
                         return
+
+                    if dia_box.reference == 'rel':
+                        rel_x = self.mouse[0] + location[0]
+                        rel_y = self.mouse[1] + location[1]
+                        location = (rel_x, rel_y)
+
                 except Exception:
                 except Exception:
                     return
                     return
             else:
             else:
@@ -7390,6 +7401,8 @@ class App(QtCore.QObject):
         else:
         else:
             location = custom_location
             location = custom_location
 
 
+        self.jump_signal.emit(location)
+
         units = self.defaults['units'].upper()
         units = self.defaults['units'].upper()
 
 
         if fit_center:
         if fit_center:
@@ -9962,11 +9975,11 @@ class App(QtCore.QObject):
 
 
         flt = "All Files (*.*)"
         flt = "All Files (*.*)"
         if obj.kind == 'gerber':
         if obj.kind == 'gerber':
-            flt = "Gerber Files (*.GBR);;All Files (*.*)"
+            flt = "Gerber Files (*.GBR);;PDF Files (*.PDF);;All Files (*.*)"
         elif obj.kind == 'excellon':
         elif obj.kind == 'excellon':
-            flt = "Excellon Files (*.DRL);;All Files (*.*)"
+            flt = "Excellon Files (*.DRL);;PDF Files (*.PDF);;All Files (*.*)"
         elif obj.kind == 'cncjob':
         elif obj.kind == 'cncjob':
-            "GCode Files (*.NC);;All Files (*.*)"
+            flt = "GCode Files (*.NC);;PDF Files (*.PDF);;All Files (*.*)"
 
 
         self.source_editor_tab = TextEditor(app=self, plain_text=True)
         self.source_editor_tab = TextEditor(app=self, plain_text=True)
 
 
@@ -10241,13 +10254,6 @@ class App(QtCore.QObject):
             self.inform.emit('[WARNING_NOTCL] %s' % _("Save Project cancelled."))
             self.inform.emit('[WARNING_NOTCL] %s' % _("Save Project cancelled."))
             return
             return
 
 
-        try:
-            f = open(filename, 'r')
-            f.close()
-        except IOError:
-            self.inform.emit('[ERROR_NOTCL] %s' % _("The object is used by another application."))
-            return
-
         if use_thread is True:
         if use_thread is True:
             self.worker_task.emit({'fcn': self.save_project,
             self.worker_task.emit({'fcn': self.save_project,
                                    'params': [filename, quit_action]})
                                    'params': [filename, quit_action]})
@@ -12154,14 +12160,14 @@ class App(QtCore.QObject):
                     g = json.dumps(d, default=to_dict, indent=2, sort_keys=True).encode('utf-8')
                     g = json.dumps(d, default=to_dict, indent=2, sort_keys=True).encode('utf-8')
                     # # Write
                     # # Write
                     f.write(g)
                     f.write(g)
-                self.inform.emit('[success] %s: %s' %
-                                 (_("Project saved to"), filename))
+                self.inform.emit('[success] %s: %s' % (_("Project saved to"), filename))
             else:
             else:
                 # Open file
                 # Open file
                 try:
                 try:
                     f = open(filename, 'w')
                     f = open(filename, 'w')
                 except IOError:
                 except IOError:
                     App.log.error("Failed to open file for saving: %s", filename)
                     App.log.error("Failed to open file for saving: %s", filename)
+                    self.inform.emit('[ERROR_NOTCL] %s' % _("The object is used by another application."))
                     return
                     return
 
 
                 # Write
                 # Write
@@ -12175,8 +12181,7 @@ class App(QtCore.QObject):
                 except IOError:
                 except IOError:
                     if silent is False:
                     if silent is False:
                         self.inform.emit('[ERROR_NOTCL] %s: %s %s' %
                         self.inform.emit('[ERROR_NOTCL] %s: %s %s' %
-                                         (_("Failed to verify project file"), filename, _("Retry to save it."))
-                                         )
+                                         (_("Failed to verify project file"), filename, _("Retry to save it.")))
                     return
                     return
 
 
                 try:
                 try:
@@ -12184,8 +12189,7 @@ class App(QtCore.QObject):
                 except Exception:
                 except Exception:
                     if silent is False:
                     if silent is False:
                         self.inform.emit('[ERROR_NOTCL] %s: %s %s' %
                         self.inform.emit('[ERROR_NOTCL] %s: %s %s' %
-                                         (_("Failed to parse saved project file"), filename, _("Retry to save it."))
-                                         )
+                                         (_("Failed to parse saved project file"), filename, _("Retry to save it.")))
                     f.close()
                     f.close()
                     return
                     return
                 saved_f.close()
                 saved_f.close()
@@ -12196,8 +12200,7 @@ class App(QtCore.QObject):
                                          (_("Project saved to"), filename))
                                          (_("Project saved to"), filename))
                     else:
                     else:
                         self.inform.emit('[ERROR_NOTCL] %s: %s %s' %
                         self.inform.emit('[ERROR_NOTCL] %s: %s %s' %
-                                         (_("Failed to parse saved project file"), filename, _("Retry to save it."))
-                                         )
+                                         (_("Failed to parse saved project file"), filename, _("Retry to save it.")))
 
 
                 tb_settings = QSettings("Open Source", "FlatCAM")
                 tb_settings = QSettings("Open Source", "FlatCAM")
                 lock_state = self.ui.lock_action.isChecked()
                 lock_state = self.ui.lock_action.isChecked()

+ 216 - 97
FlatCAMObj.py

@@ -1288,6 +1288,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             def iso_init(geo_obj, app_obj):
             def iso_init(geo_obj, app_obj):
                 # Propagate options
                 # Propagate options
                 geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
                 geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
+                geo_obj.tool_type = self.ui.tool_type_radio.get_value().upper()
+
                 geo_obj.solid_geometry = []
                 geo_obj.solid_geometry = []
                 for i in range(passes):
                 for i in range(passes):
                     iso_offset = dia * ((2 * i + 1) / 2.0) - (i * (overlap / 100) * dia)
                     iso_offset = dia * ((2 * i + 1) / 2.0) - (i * (overlap / 100) * dia)
@@ -1417,6 +1419,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
                 def iso_init(geo_obj, app_obj):
                 def iso_init(geo_obj, app_obj):
                     # Propagate options
                     # Propagate options
                     geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
                     geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
+                    geo_obj.tool_type = self.ui.tool_type_radio.get_value().upper()
 
 
                     # if milling type is climb then the move is counter-clockwise around features
                     # if milling type is climb then the move is counter-clockwise around features
                     mill_t = 1 if milling_type == 'cl' else 0
                     mill_t = 1 if milling_type == 'cl' else 0
@@ -3603,6 +3606,9 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         self.old_toolchangeg_state = self.app.defaults["geometry_toolchange"]
         self.old_toolchangeg_state = self.app.defaults["geometry_toolchange"]
         self.units_found = self.app.defaults['units']
         self.units_found = self.app.defaults['units']
 
 
+        # this variable can be updated by the Object that generates the geometry
+        self.tool_type = 'C1'
+
         # Attributes to be included in serialization
         # Attributes to be included in serialization
         # Always append to it because it carries contents
         # Always append to it because it carries contents
         # from predecessors.
         # from predecessors.
@@ -3745,11 +3751,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             self.ui.geo_tools_table.setColumnHidden(6, False)
             self.ui.geo_tools_table.setColumnHidden(6, False)
 
 
         self.set_tool_offset_visibility(selected_row)
         self.set_tool_offset_visibility(selected_row)
-        self.ui_connect()
 
 
         # HACK: for whatever reasons the name in Selected tab is reverted to the original one after a successful rename
         # HACK: for whatever reasons the name in Selected tab is reverted to the original one after a successful rename
         # done in the collection view but only for Geometry objects. Perhaps some references remains. Should be fixed.
         # done in the collection view but only for Geometry objects. Perhaps some references remains. Should be fixed.
         self.ui.name_entry.set_value(self.options['name'])
         self.ui.name_entry.set_value(self.options['name'])
+        self.ui_connect()
 
 
     def set_ui(self, ui):
     def set_ui(self, ui):
         FlatCAMObj.set_ui(self, ui)
         FlatCAMObj.set_ui(self, ui)
@@ -3859,7 +3865,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                         'offset': 'Path',
                         'offset': 'Path',
                         'offset_value': 0.0,
                         'offset_value': 0.0,
                         'type': _('Rough'),
                         'type': _('Rough'),
-                        'tool_type': 'C1',
+                        'tool_type': self.tool_type,
                         'data': new_data,
                         'data': new_data,
                         'solid_geometry': self.solid_geometry
                         'solid_geometry': self.solid_geometry
                     }
                     }
@@ -3883,7 +3889,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         self.ui.tool_offset_entry.hide()
         self.ui.tool_offset_entry.hide()
         self.ui.tool_offset_lbl.hide()
         self.ui.tool_offset_lbl.hide()
 
 
-        # used to store the state of the mpass_cb if the selected postproc for geometry is hpgl
+        # used to store the state of the mpass_cb if the selected preprocessor for geometry is hpgl
         self.old_pp_state = self.default_data['multidepth']
         self.old_pp_state = self.default_data['multidepth']
         self.old_toolchangeg_state = self.default_data['toolchange']
         self.old_toolchangeg_state = self.default_data['toolchange']
 
 
@@ -3934,7 +3940,6 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         self.ui.paint_tool_button.clicked.connect(lambda: self.app.paint_tool.run(toggle=False))
         self.ui.paint_tool_button.clicked.connect(lambda: self.app.paint_tool.run(toggle=False))
         self.ui.generate_ncc_button.clicked.connect(lambda: self.app.ncclear_tool.run(toggle=False))
         self.ui.generate_ncc_button.clicked.connect(lambda: self.app.ncclear_tool.run(toggle=False))
         self.ui.pp_geometry_name_cb.activated.connect(self.on_pp_changed)
         self.ui.pp_geometry_name_cb.activated.connect(self.on_pp_changed)
-        self.ui.addtool_entry.returnPressed.connect(lambda: self.on_tool_add())
 
 
         self.ui.tipdia_entry.valueChanged.connect(self.update_cutz)
         self.ui.tipdia_entry.valueChanged.connect(self.update_cutz)
         self.ui.tipangle_entry.valueChanged.connect(self.update_cutz)
         self.ui.tipangle_entry.valueChanged.connect(self.update_cutz)
@@ -4010,6 +4015,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
 
         # I use lambda's because the connected functions have parameters that could be used in certain scenarios
         # I use lambda's because the connected functions have parameters that could be used in certain scenarios
         self.ui.addtool_btn.clicked.connect(lambda: self.on_tool_add())
         self.ui.addtool_btn.clicked.connect(lambda: self.on_tool_add())
+        self.ui.addtool_entry.returnPressed.connect(self.on_tool_add)
 
 
         self.ui.copytool_btn.clicked.connect(lambda: self.on_tool_copy())
         self.ui.copytool_btn.clicked.connect(lambda: self.on_tool_copy())
         self.ui.deltool_btn.clicked.connect(lambda: self.on_tool_delete())
         self.ui.deltool_btn.clicked.connect(lambda: self.on_tool_delete())
@@ -4062,6 +4068,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         except (TypeError, AttributeError):
         except (TypeError, AttributeError):
             pass
             pass
 
 
+        try:
+            self.ui.addtool_entry.returnPressed.disconnect()
+        except (TypeError, AttributeError):
+            pass
+
         try:
         try:
             self.ui.copytool_btn.clicked.disconnect()
             self.ui.copytool_btn.clicked.disconnect()
         except (TypeError, AttributeError):
         except (TypeError, AttributeError):
@@ -4103,59 +4114,26 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
 
         self.units = self.app.defaults['units'].upper()
         self.units = self.app.defaults['units'].upper()
 
 
-        # if a Tool diameter entered is a char instead a number the final message of Tool adding is changed
-        # because the Default value for Tool is used.
-        change_message = False
-
         if dia is not None:
         if dia is not None:
             tooldia = dia
             tooldia = dia
         else:
         else:
-            try:
-                tooldia = float(self.ui.addtool_entry.get_value())
-            except ValueError:
-                # try to convert comma to decimal point. if it's still not working error message and return
-                try:
-                    tooldia = float(self.ui.addtool_entry.get_value().replace(',', '.'))
-                except ValueError:
-                    change_message = True
-                    tooldia = float(self.options["cnctooldia"][0])
-
-            if tooldia is None:
-                self.build_ui()
-                self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                     _("Please enter the desired tool diameter in Float format."))
-                return
+            tooldia = float(self.ui.addtool_entry.get_value())
 
 
         # construct a list of all 'tooluid' in the self.tools
         # construct a list of all 'tooluid' in the self.tools
-        tool_uid_list = []
-        for tooluid_key in self.tools:
-            tool_uid_item = int(tooluid_key)
-            tool_uid_list.append(tool_uid_item)
+        # tool_uid_list = list()
+        # for tooluid_key in self.tools:
+        #     tool_uid_list.append(int(tooluid_key))
+        tool_uid_list = [int(tooluid_key) for tooluid_key in self.tools]
 
 
         # find maximum from the temp_uid, add 1 and this is the new 'tooluid'
         # find maximum from the temp_uid, add 1 and this is the new 'tooluid'
-        if not tool_uid_list:
-            max_uid = 0
-        else:
-            max_uid = max(tool_uid_list)
+        max_uid = max(tool_uid_list) if tool_uid_list else 0
         self.tooluid = max_uid + 1
         self.tooluid = max_uid + 1
 
 
         tooldia = float('%.*f' % (self.decimals, tooldia))
         tooldia = float('%.*f' % (self.decimals, tooldia))
 
 
         # here we actually add the new tool; if there is no tool in the tool table we add a tool with default data
         # here we actually add the new tool; if there is no tool in the tool table we add a tool with default data
         # otherwise we add a tool with data copied from last tool
         # otherwise we add a tool with data copied from last tool
-        if not self.tools:
-            self.tools.update({
-                self.tooluid: {
-                    'tooldia': tooldia,
-                    'offset': 'Path',
-                    'offset_value': 0.0,
-                    'type': _('Rough'),
-                    'tool_type': 'C1',
-                    'data': deepcopy(self.default_data),
-                    'solid_geometry': self.solid_geometry
-                }
-            })
-        else:
+        if self.tools:
             last_data = self.tools[max_uid]['data']
             last_data = self.tools[max_uid]['data']
             last_offset = self.tools[max_uid]['offset']
             last_offset = self.tools[max_uid]['offset']
             last_offset_value = self.tools[max_uid]['offset_value']
             last_offset_value = self.tools[max_uid]['offset_value']
@@ -4179,6 +4157,18 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                     'solid_geometry': deepcopy(last_solid_geometry)
                     'solid_geometry': deepcopy(last_solid_geometry)
                 }
                 }
             })
             })
+        else:
+            self.tools.update({
+                self.tooluid: {
+                    'tooldia': tooldia,
+                    'offset': 'Path',
+                    'offset_value': 0.0,
+                    'type': _('Rough'),
+                    'tool_type': 'C1',
+                    'data': deepcopy(self.default_data),
+                    'solid_geometry': self.solid_geometry
+                }
+            })
 
 
         self.tools[self.tooluid]['data']['name'] = self.options['name']
         self.tools[self.tooluid]['data']['name'] = self.options['name']
 
 
@@ -4192,12 +4182,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             pass
             pass
         self.ser_attrs.append('tools')
         self.ser_attrs.append('tools')
 
 
-        if change_message is False:
-            self.app.inform.emit('[success] %s' % _("Tool added in Tool Table."))
-        else:
-            change_message = False
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("Default Tool added. Wrong value format entered."))
+        self.app.inform.emit('[success] %s' % _("Tool added in Tool Table."))
         self.build_ui()
         self.build_ui()
 
 
         # if there is no tool left in the Tools Table, enable the parameters GUI
         # if there is no tool left in the Tools Table, enable the parameters GUI
@@ -5735,12 +5720,17 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             self.tools = deepcopy(temp_tools_dict)
             self.tools = deepcopy(temp_tools_dict)
 
 
         # if there is a value in the new tool field then convert that one too
         # if there is a value in the new tool field then convert that one too
+        try:
+            self.ui.addtool_entry.returnPressed.disconnect()
+        except TypeError:
+            pass
         tooldia = self.ui.addtool_entry.get_value()
         tooldia = self.ui.addtool_entry.get_value()
         if tooldia:
         if tooldia:
             tooldia *= factor
             tooldia *= factor
             tooldia = float('%.*f' % (self.decimals, tooldia))
             tooldia = float('%.*f' % (self.decimals, tooldia))
 
 
             self.ui.addtool_entry.set_value(tooldia)
             self.ui.addtool_entry.set_value(tooldia)
+        self.ui.addtool_entry.returnPressed.connect(self.on_tool_add)
 
 
         return factor
         return factor
 
 
@@ -6621,9 +6611,12 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         :param to_file: if False then no actual file is saved but the app will know that a file was created
         :param to_file: if False then no actual file is saved but the app will know that a file was created
         :return: None
         :return: None
         """
         """
-        gcode = ''
-        roland = False
-        hpgl = False
+        # gcode = ''
+        # roland = False
+        # hpgl = False
+        # isel_icp = False
+
+        include_header = True
 
 
         try:
         try:
             if self.special_group:
             if self.special_group:
@@ -6635,58 +6628,184 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         except AttributeError:
         except AttributeError:
             pass
             pass
 
 
-        # detect if using Roland preprocessor
-        try:
-            for key in self.cnc_tools:
-                if self.cnc_tools[key]['data']['ppname_g'] == 'Roland_MDX_20':
-                    roland = True
-                    break
-                if self.cnc_tools[key]['data']['ppname_g'] == 'hpgl':
-                    hpgl = True
-                    break
-        except Exception as e:
-            try:
-                for key in self.cnc_tools:
-                    if self.cnc_tools[key]['data']['ppname_e'] == 'Roland_MDX_20':
-                        roland = True
-                        break
-            except Exception as e:
-                pass
+        # if this dict is not empty then the object is a Geometry object
+        if self.cnc_tools:
+            first_key = next(iter(self.cnc_tools))
+            include_header = self.app.preprocessors[self.cnc_tools[first_key]['data']['ppname_g']].include_header
+
+        # if this dict is not empty then the object is an Excellon object
+        if self.exc_cnc_tools:
+            first_key = next(iter(self.exc_cnc_tools))
+            include_header = self.app.preprocessors[self.exc_cnc_tools[first_key]['data']['ppname_e']].include_header
+
+        # # detect if using Roland preprocessor
+        # try:
+        #     for key in self.cnc_tools:
+        #         if self.cnc_tools[key]['data']['ppname_g'] == 'Roland_MDX_20':
+        #             roland = True
+        #             break
+        # except Exception:
+        #     try:
+        #         for key in self.cnc_tools:
+        #             if self.cnc_tools[key]['data']['ppname_e'] == 'Roland_MDX_20':
+        #                 roland = True
+        #                 break
+        #     except Exception:
+        #         pass
+        #
+        # # detect if using HPGL preprocessor
+        # try:
+        #     for key in self.cnc_tools:
+        #         if self.cnc_tools[key]['data']['ppname_g'] == 'hpgl':
+        #             hpgl = True
+        #             break
+        # except Exception:
+        #     try:
+        #         for key in self.cnc_tools:
+        #             if self.cnc_tools[key]['data']['ppname_e'] == 'hpgl':
+        #                 hpgl = True
+        #                 break
+        #     except Exception:
+        #         pass
+        #
+        # # detect if using ISEL_ICP_CNC preprocessor
+        # try:
+        #     for key in self.cnc_tools:
+        #         if 'ISEL_ICP' in self.cnc_tools[key]['data']['ppname_g'].upper():
+        #             isel_icp = True
+        #             break
+        # except Exception:
+        #     try:
+        #         for key in self.cnc_tools:
+        #             if 'ISEL_ICP' in self.cnc_tools[key]['data']['ppname_e'].upper():
+        #                 isel_icp = True
+        #                 break
+        #     except Exception:
+        #         pass
 
 
         # do not add gcode_header when using the Roland preprocessor, add it for every other preprocessor
         # do not add gcode_header when using the Roland preprocessor, add it for every other preprocessor
-        if roland is False and hpgl is False:
-            gcode = self.gcode_header()
-
-        # detect if using multi-tool and make the Gcode summation correctly for each case
-        if self.multitool is True:
-            for tooluid_key in self.cnc_tools:
-                for key, value in self.cnc_tools[tooluid_key].items():
-                    if key == 'gcode':
-                        gcode += value
-                        break
-        else:
-            gcode += self.gcode
+        # if roland is False and hpgl is False and isel_icp is False:
+        #     gcode = self.gcode_header()
 
 
-        if roland is True:
-            g = preamble + gcode + postamble
-        elif hpgl is True:
-            g = self.gcode_header() + preamble + gcode + postamble
+        # do not add gcode_header when using the Roland, HPGL or ISEP_ICP_CNC preprocessor (or any other preprocessor
+        # that has the include_header attribute set as False, add it for every other preprocessor
+        # if include_header:
+        #     gcode = self.gcode_header()
+        # else:
+        #     gcode = ''
+
+        # # detect if using multi-tool and make the Gcode summation correctly for each case
+        # if self.multitool is True:
+        #     for tooluid_key in self.cnc_tools:
+        #         for key, value in self.cnc_tools[tooluid_key].items():
+        #             if key == 'gcode':
+        #                 gcode += value
+        #                 break
+        # else:
+        #     gcode += self.gcode
+
+        # if roland is True:
+        #     g = preamble + gcode + postamble
+        # elif hpgl is True:
+        #     g = self.gcode_header() + preamble + gcode + postamble
+        # else:
+        #     # fix so the preamble gets inserted in between the comments header and the actual start of GCODE
+        #     g_idx = gcode.rfind('G20')
+        #
+        #     # if it did not find 'G20' then search for 'G21'
+        #     if g_idx == -1:
+        #         g_idx = gcode.rfind('G21')
+        #
+        #     # if it did not find 'G20' and it did not find 'G21' then there is an error and return
+        #     # but only when the preprocessor is not ISEL_ICP who is allowed not to have the G20/G21 command
+        #     if g_idx == -1 and isel_icp is False:
+        #         self.app.inform.emit('[ERROR_NOTCL] %s' % _("G-code does not have a units code: either G20 or G21"))
+        #         return
+        #
+        #     footer = self.app.defaults['cncjob_footer']
+        #     end_gcode = self.gcode_footer() if footer is True else ''
+        #     g = gcode[:g_idx] + preamble + '\n' + gcode[g_idx:] + postamble + end_gcode
+
+        gcode = ''
+        if include_header is False:
+            g = preamble
+            # detect if using multi-tool and make the Gcode summation correctly for each case
+            if self.multitool is True:
+                for tooluid_key in self.cnc_tools:
+                    for key, value in self.cnc_tools[tooluid_key].items():
+                        if key == 'gcode':
+                            gcode += value
+                            break
+            else:
+                gcode += self.gcode
+
+            g = g + gcode + postamble
         else:
         else:
+            # search for the GCode beginning which is usually a G20 or G21
             # fix so the preamble gets inserted in between the comments header and the actual start of GCODE
             # fix so the preamble gets inserted in between the comments header and the actual start of GCODE
-            g_idx = gcode.rfind('G20')
+            # g_idx = gcode.rfind('G20')
+            #
+            # # if it did not find 'G20' then search for 'G21'
+            # if g_idx == -1:
+            #     g_idx = gcode.rfind('G21')
+            #
+            # # if it did not find 'G20' and it did not find 'G21' then there is an error and return
+            # if g_idx == -1:
+            #     self.app.inform.emit('[ERROR_NOTCL] %s' % _("G-code does not have a units code: either G20 or G21"))
+            #     return
+
+            # detect if using multi-tool and make the Gcode summation correctly for each case
+            if self.multitool is True:
+                for tooluid_key in self.cnc_tools:
+                    for key, value in self.cnc_tools[tooluid_key].items():
+                        if key == 'gcode':
+                            gcode += value
+                            break
+            else:
+                gcode += self.gcode
 
 
-            # if it did not find 'G20' then search for 'G21'
-            if g_idx == -1:
-                g_idx = gcode.rfind('G21')
+            end_gcode = self.gcode_footer() if self.app.defaults['cncjob_footer'] is True else ''
 
 
-            # if it did not find 'G20' and it did not find 'G21' then there is an error and return
-            if g_idx == -1:
-                self.app.inform.emit('[ERROR_NOTCL] %s' % _("G-code does not have a units code: either G20 or G21"))
-                return
+            # detect if using a HPGL preprocessor
+            hpgl = False
+            if self.cnc_tools:
+                for key in self.cnc_tools:
+                    if 'ppname_g' in self.cnc_tools[key]['data']:
+                        if 'hpgl' in self.cnc_tools[key]['data']['ppname_g']:
+                            hpgl = True
+                            break
+            elif self.exc_cnc_tools:
+                for key in self.cnc_tools:
+                    if 'ppname_e' in self.cnc_tools[key]['data']:
+                        if 'hpgl' in self.cnc_tools[key]['data']['ppname_e']:
+                            hpgl = True
+                            break
 
 
-            footer = self.app.defaults['cncjob_footer']
-            end_gcode = self.gcode_footer() if footer is True else ''
-            g = gcode[:g_idx] + preamble + '\n' + gcode[g_idx:] + postamble + end_gcode
+            if hpgl:
+                processed_gcode = ''
+                pa_re = re.compile(r"^PA\s*(-?\d+\.\d*),?\s*(-?\d+\.\d*)*;?$")
+                for gline in gcode.splitlines():
+                    match = pa_re.search(gline)
+                    if match:
+                        x_int = int(float(match.group(1)))
+                        y_int = int(float(match.group(2)))
+                        new_line = 'PA%d,%d;\n' % (x_int, y_int)
+                        processed_gcode += new_line
+                    else:
+                        processed_gcode += gline + '\n'
+
+                gcode = processed_gcode
+                g = self.gcode_header() + '\n' + preamble + '\n' + gcode + postamble + end_gcode
+            else:
+                try:
+                    g_idx = gcode.index('G94')
+                    g = self.gcode_header() + gcode[:g_idx + 3] + '\n\n' + preamble + '\n' + \
+                        gcode[(g_idx + 3):] + postamble + end_gcode
+                except ValueError:
+                    self.app.inform.emit('[ERROR_NOTCL] %s' %
+                                         _("G-code does not have a G94 code and we will not include the code in the "
+                                           "'Prepend to GCode' text box"))
+                    g = self.gcode_header() + '\n' + gcode + postamble + end_gcode
 
 
         # if toolchange custom is used, replace M6 code with the code from the Toolchange Custom Text box
         # if toolchange custom is used, replace M6 code with the code from the Toolchange Custom Text box
         if self.ui.toolchange_cb.get_value() is True:
         if self.ui.toolchange_cb.get_value() is True:

+ 32 - 0
README.md

@@ -9,6 +9,34 @@ CAD program, and create G-Code for Isolation routing.
 
 
 =================================================
 =================================================
 
 
+17.12.2019
+
+- more optimizations in NCC Tool
+- optimizations in Paint Tool
+- maximum range for Cut Z is now zero to deal with the situation when using V-shape with tip-dia same value with cut width
+- modified QValidator in FCDoubleSpinner() GUI element to allow entering the minus sign when the range maximum is set as 0.0; also for positive numbers allowed entering the symbol plus
+- made sure that if in Gerber UI the isolation is made with a V-Shape tool then the tool type is automatically updated on the generated Geometry Object
+- added ability to save the Source File as PDF (still have to adjust the page size)
+- fixed the generate_from_geometry_2() method to use the default values in case the parameters are None
+- added ability to save the Source File as PDF - fixed page size and added line breaks
+- more mods to generate_from_geometry_2() method
+- fixed bug saving the FlatCAM project saying the file is used by another application
+
+16.12.2019
+
+- in Geometry Editor added support for Jump To function such as that it works within the Editor Tools themselves. For now it works only in absolute jumps
+- modified the Jump To method such that now allows relative jump from the current mouse location
+- fixed the Defaults upgrade overwriting the new version number with the old one
+- fixed issue with clear_polygon3() - the one who makes 'lines' and fixed the NCC Tool
+- some small changes in the FlatCAMGeometry.on_tool_add() method
+- made sure that in Geometry Editor the self.app.mouse attribute is updated with the current mouse position (x, y)
+- updated the preprocessor files
+- fixed the HPGL preprocessor
+- fixed the CNCJob geometry created with HPGL preprocessor
+- fixed GCode generated with HPGL preprocessor to output only integer coordinates
+- fixed the HPGL2 import parsing for absolute linear movements
+- fixed the line endings for setup_ubuntu.sh
+
 15.12.2019
 15.12.2019
 
 
 - fixed a bug that created a crash in special conditions; it's related to the QSettings in FlatCAMGui.py
 - fixed a bug that created a crash in special conditions; it's related to the QSettings in FlatCAMGui.py
@@ -17,6 +45,10 @@ CAD program, and create G-Code for Isolation routing.
 - updated the languages
 - updated the languages
 - fixed a typo
 - fixed a typo
 - fixed layout on first launch of the app
 - fixed layout on first launch of the app
+- fixed some issues with the recent preparation for dark icons resource usage
+- added a new preprocessor file contributed by Daniel Friderich and added fixes for it
+- modified the export_gcode() method and the preprocessors such that the preprocessors now have the information if to include the gcode header
+- updated all the translation PO files and the POT file
 - RELEASE 8.99
 - RELEASE 8.99
 
 
 14.12.2019
 14.12.2019

+ 106 - 72
camlib.py

@@ -1381,9 +1381,10 @@ class Geometry(object):
                     inner_edges.append(y)
                     inner_edges.append(y)
             # geoms += outer_edges + inner_edges
             # geoms += outer_edges + inner_edges
             for g in outer_edges + inner_edges:
             for g in outer_edges + inner_edges:
-                geoms.insert(g)
-                if prog_plot:
-                    self.plot_temp_shapes(g)
+                if g and not g.is_empty:
+                    geoms.insert(g)
+                    if prog_plot:
+                        self.plot_temp_shapes(g)
 
 
         if prog_plot:
         if prog_plot:
             self.temp_shapes.redraw()
             self.temp_shapes.redraw()
@@ -1395,7 +1396,9 @@ class Geometry(object):
         # Optimization: Reduce lifts
         # Optimization: Reduce lifts
         if connect:
         if connect:
             # log.debug("Reducing tool lifts...")
             # log.debug("Reducing tool lifts...")
-            geoms = Geometry.paint_connect(geoms, polygon_to_clear, tooldia, steps_per_circle)
+            geoms_conn = Geometry.paint_connect(geoms, polygon_to_clear, tooldia, steps_per_circle)
+            if geoms_conn:
+                return geoms_conn
 
 
         return geoms
         return geoms
 
 
@@ -1419,6 +1422,9 @@ class Geometry(object):
         """
         """
 
 
         # log.debug("camlib.clear_polygon3()")
         # log.debug("camlib.clear_polygon3()")
+        if not isinstance(polygon, Polygon):
+            log.debug("camlib.Geometry.clear_polygon3() --> Not a Polygon but %s" % str(type(polygon)))
+            return None
 
 
         # ## The toolpaths
         # ## The toolpaths
         # Index first and last points in paths
         # Index first and last points in paths
@@ -1433,41 +1439,43 @@ class Geometry(object):
         # Bounding box
         # Bounding box
         left, bot, right, top = polygon.bounds
         left, bot, right, top = polygon.bounds
 
 
-        margin_poly = polygon.buffer(-tooldia / 1.99999999, (int(steps_per_circle)))
+        try:
+            margin_poly = polygon.buffer(-tooldia / 1.99999999, (int(steps_per_circle)))
+        except Exception as e:
+            log.debug("camlib.Geometry.clear_polygon3() --> Could not buffer the Polygon")
+            return None
 
 
         # First line
         # First line
-        y = top - tooldia / 1.99999999
-        while y > bot + tooldia / 1.999999999:
-            if self.app.abort_flag:
-                # graceful abort requested by the user
-                raise FlatCAMApp.GracefulException
+        try:
+            y = top - tooldia / 1.99999999
+            while y > bot + tooldia / 1.999999999:
+                if self.app.abort_flag:
+                    # graceful abort requested by the user
+                    raise FlatCAMApp.GracefulException
 
 
-            # provide the app with a way to process the GUI events when in a blocking loop
-            QtWidgets.QApplication.processEvents()
+                # provide the app with a way to process the GUI events when in a blocking loop
+                QtWidgets.QApplication.processEvents()
+
+                line = LineString([(left, y), (right, y)])
+                line = line.intersection(margin_poly)
+                lines_trimmed.append(line)
+                y -= tooldia * (1 - overlap)
+                if prog_plot:
+                    self.plot_temp_shapes(line)
+                    self.temp_shapes.redraw()
 
 
+            # Last line
+            y = bot + tooldia / 2
             line = LineString([(left, y), (right, y)])
             line = LineString([(left, y), (right, y)])
             line = line.intersection(margin_poly)
             line = line.intersection(margin_poly)
-            lines_trimmed.append(line)
-            y -= tooldia * (1 - overlap)
-            if prog_plot:
-                self.plot_temp_shapes(line)
-                self.temp_shapes.redraw()
-
-        # Last line
-        y = bot + tooldia / 2
-        line = LineString([(left, y), (right, y)])
-        line = line.intersection(margin_poly)
-        for ll in line:
-            lines_trimmed.append(ll)
-            if prog_plot:
-                self.plot_temp_shapes(line)
-
-        # Combine
-        # linesgeo = unary_union(lines)
-
-        # Trim to the polygon
-        # margin_poly = polygon.buffer(-tooldia / 1.99999999, (int(steps_per_circle)))
-        # lines_trimmed = linesgeo.intersection(margin_poly)
+
+            for ll in line:
+                lines_trimmed.append(ll)
+                if prog_plot:
+                    self.plot_temp_shapes(line)
+        except Exception as e:
+            log.debug('camlib.Geometry.clear_polygon3() Processing poly --> %s' % str(e))
+            return None
 
 
         if prog_plot:
         if prog_plot:
             self.temp_shapes.redraw()
             self.temp_shapes.redraw()
@@ -1477,27 +1485,33 @@ class Geometry(object):
         # Add lines to storage
         # Add lines to storage
         try:
         try:
             for line in lines_trimmed:
             for line in lines_trimmed:
-                geoms.insert(line)
+                if isinstance(line, LineString) or isinstance(line, LinearRing):
+                    geoms.insert(line)
+                else:
+                    log.debug("camlib.Geometry.clear_polygon3(). Not a line: %s" % str(type(line)))
         except TypeError:
         except TypeError:
             # in case lines_trimmed are not iterable (Linestring, LinearRing)
             # in case lines_trimmed are not iterable (Linestring, LinearRing)
             geoms.insert(lines_trimmed)
             geoms.insert(lines_trimmed)
 
 
         # Add margin (contour) to storage
         # Add margin (contour) to storage
         if contour:
         if contour:
-            if isinstance(margin_poly, Polygon):
-                geoms.insert(margin_poly.exterior)
-                if prog_plot:
-                    self.plot_temp_shapes(margin_poly.exterior)
-                for ints in margin_poly.interiors:
-                    geoms.insert(ints)
-                    if prog_plot:
-                        self.plot_temp_shapes(ints)
-            elif isinstance(margin_poly, MultiPolygon):
+            try:
                 for poly in margin_poly:
                 for poly in margin_poly:
-                    geoms.insert(poly.exterior)
+                    if isinstance(poly, Polygon) and not poly.is_empty:
+                        geoms.insert(poly.exterior)
+                        if prog_plot:
+                            self.plot_temp_shapes(poly.exterior)
+                        for ints in poly.interiors:
+                            geoms.insert(ints)
+                            if prog_plot:
+                                self.plot_temp_shapes(ints)
+            except TypeError:
+                if isinstance(margin_poly, Polygon) and not margin_poly.is_empty:
+                    marg_ext = margin_poly.exterior
+                    geoms.insert(marg_ext)
                     if prog_plot:
                     if prog_plot:
-                        self.plot_temp_shapes(poly.exterior)
-                    for ints in poly.interiors:
+                        self.plot_temp_shapes(margin_poly.exterior)
+                    for ints in margin_poly.interiors:
                         geoms.insert(ints)
                         geoms.insert(ints)
                         if prog_plot:
                         if prog_plot:
                             self.plot_temp_shapes(ints)
                             self.plot_temp_shapes(ints)
@@ -1508,7 +1522,9 @@ class Geometry(object):
         # Optimization: Reduce lifts
         # Optimization: Reduce lifts
         if connect:
         if connect:
             # log.debug("Reducing tool lifts...")
             # log.debug("Reducing tool lifts...")
-            geoms = Geometry.paint_connect(geoms, polygon, tooldia, steps_per_circle)
+            geoms_conn = Geometry.paint_connect(geoms, polygon, tooldia, steps_per_circle)
+            if geoms_conn:
+                return geoms_conn
 
 
         return geoms
         return geoms
 
 
@@ -1581,8 +1597,14 @@ class Geometry(object):
         optimized_paths.get_points = get_pts
         optimized_paths.get_points = get_pts
         path_count = 0
         path_count = 0
         current_pt = (0, 0)
         current_pt = (0, 0)
-        pt, geo = storage.nearest(current_pt)
+        try:
+            pt, geo = storage.nearest(current_pt)
+        except StopIteration:
+            log.debug("camlib.Geometry.paint_connect(). Storage empty")
+            return None
+
         storage.remove(geo)
         storage.remove(geo)
+
         geo = LineString(geo)
         geo = LineString(geo)
         current_pt = geo.coords[-1]
         current_pt = geo.coords[-1]
         try:
         try:
@@ -1592,6 +1614,7 @@ class Geometry(object):
 
 
                 pt, candidate = storage.nearest(current_pt)
                 pt, candidate = storage.nearest(current_pt)
                 storage.remove(candidate)
                 storage.remove(candidate)
+
                 candidate = LineString(candidate)
                 candidate = LineString(candidate)
 
 
                 # If last point in geometry is the nearest
                 # If last point in geometry is the nearest
@@ -2448,11 +2471,17 @@ class CNCjob(Geometry):
                     except KeyError:
                     except KeyError:
                         z_off = 0
                         z_off = 0
 
 
+                    default_data = dict()
+                    for k, v in list(self.options.items()):
+                        default_data[k] = deepcopy(v)
+
                     self.exc_cnc_tools[it[1]] = dict()
                     self.exc_cnc_tools[it[1]] = dict()
                     self.exc_cnc_tools[it[1]]['tool'] = it[0]
                     self.exc_cnc_tools[it[1]]['tool'] = it[0]
                     self.exc_cnc_tools[it[1]]['nr_drills'] = drill_no
                     self.exc_cnc_tools[it[1]]['nr_drills'] = drill_no
                     self.exc_cnc_tools[it[1]]['nr_slots'] = slot_no
                     self.exc_cnc_tools[it[1]]['nr_slots'] = slot_no
                     self.exc_cnc_tools[it[1]]['offset_z'] = z_off
                     self.exc_cnc_tools[it[1]]['offset_z'] = z_off
+                    self.exc_cnc_tools[it[1]]['data'] = default_data
+
                     self.exc_cnc_tools[it[1]]['solid_geometry'] = deepcopy(sol_geo)
                     self.exc_cnc_tools[it[1]]['solid_geometry'] = deepcopy(sol_geo)
 
 
         self.app.inform.emit(_("Creating a list of points to drill..."))
         self.app.inform.emit(_("Creating a list of points to drill..."))
@@ -3275,14 +3304,12 @@ class CNCjob(Geometry):
         return self.gcode
         return self.gcode
 
 
     def generate_from_geometry_2(
     def generate_from_geometry_2(
-            self, geometry, append=True,
-            tooldia=None, offset=0.0, tolerance=0,
-            z_cut=1.0, z_move=2.0,
-            feedrate=2.0, feedrate_z=2.0, feedrate_rapid=30,
-            spindlespeed=None, spindledir='CW', dwell=False, dwelltime=1.0,
+            self, geometry, append=True, tooldia=None, offset=0.0, tolerance=0, z_cut=None, z_move=None,
+            feedrate=None, feedrate_z=None, feedrate_rapid=None,
+            spindlespeed=None, spindledir='CW', dwell=False, dwelltime=None,
             multidepth=False, depthpercut=None,
             multidepth=False, depthpercut=None,
-            toolchange=False, toolchangez=1.0, toolchangexy="0.0, 0.0",
-            extracut=False, extracut_length=0.1, startz=None, endz=2.0,
+            toolchange=False, toolchangez=None, toolchangexy="0.0, 0.0",
+            extracut=False, extracut_length=None, startz=None, endz=None,
             pp_geometry_name=None, tool_no=1):
             pp_geometry_name=None, tool_no=1):
         """
         """
         Second algorithm to generate from Geometry.
         Second algorithm to generate from Geometry.
@@ -3377,27 +3404,31 @@ class CNCjob(Geometry):
         log.debug("%d paths" % len(flat_geometry))
         log.debug("%d paths" % len(flat_geometry))
 
 
         try:
         try:
-            self.tooldia = float(tooldia) if tooldia else None
+            self.tooldia = float(tooldia) if tooldia else self.app.defaults["geometry_cnctooldia"]
         except ValueError:
         except ValueError:
-            self.tooldia = [float(el) for el in tooldia.split(',') if el != ''] if tooldia else None
+            self.tooldia = [float(el) for el in tooldia.split(',') if el != ''] if tooldia is not None else \
+                self.app.defaults["geometry_cnctooldia"]
 
 
-        self.z_cut = float(z_cut) if z_cut is not None else None
-        self.z_move = float(z_move) if z_move is not None else None
+        self.z_cut = float(z_cut) if z_cut is not None else self.app.defaults["geometry_cutz"]
+        self.z_move = float(z_move) if z_move is not None else self.app.defaults["geometry_travelz"]
 
 
-        self.feedrate = float(feedrate) if feedrate else None
-        self.z_feedrate = float(feedrate_z) if feedrate_z is not None else None
-        self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else None
+        self.feedrate = float(feedrate) if feedrate is not None else self.app.defaults["geometry_feedrate"]
+        self.z_feedrate = float(feedrate_z) if feedrate_z is not None else self.app.defaults["geometry_feedrate_z"]
+        self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid is not None else \
+            self.app.defaults["geometry_feedrate_rapid"]
 
 
         self.spindlespeed = int(spindlespeed) if spindlespeed != 0 else None
         self.spindlespeed = int(spindlespeed) if spindlespeed != 0 else None
         self.spindledir = spindledir
         self.spindledir = spindledir
         self.dwell = dwell
         self.dwell = dwell
-        self.dwelltime = float(dwelltime) if dwelltime else None
+        self.dwelltime = float(dwelltime) if dwelltime is not None else self.app.defaults["geometry_dwelltime"]
 
 
-        self.startz = float(startz) if startz is not None else None
-        self.z_end = float(endz) if endz is not None else None
-        self.z_depthpercut = float(depthpercut) if depthpercut else None
+        self.startz = float(startz) if startz is not None else self.app.defaults["geometry_startz"]
+        self.z_end = float(endz) if endz is not None else self.app.defaults["geometry_endz"]
+        self.z_depthpercut = float(depthpercut) if depthpercut is not None else 0.0
         self.multidepth = multidepth
         self.multidepth = multidepth
-        self.z_toolchange = float(toolchangez) if toolchangez is not None else None
+        self.z_toolchange = float(toolchangez) if toolchangez is not None else self.app.defaults["geometry_toolchangez"]
+        self.extracut_length = float(extracut_length) if extracut_length is not None else \
+            self.app.defaults["geometry_extracut_length"]
 
 
         try:
         try:
             if toolchangexy == '':
             if toolchangexy == '':
@@ -3457,7 +3488,10 @@ class CNCjob(Geometry):
                 return 'fail'
                 return 'fail'
 
 
         # made sure that depth_per_cut is no more then the z_cut
         # made sure that depth_per_cut is no more then the z_cut
-        if abs(self.z_cut) < self.z_depthpercut:
+        try:
+            if abs(self.z_cut) < self.z_depthpercut:
+                self.z_depthpercut = abs(self.z_cut)
+        except TypeError:
             self.z_depthpercut = abs(self.z_cut)
             self.z_depthpercut = abs(self.z_cut)
 
 
         # ## Index first and last points in paths
         # ## Index first and last points in paths
@@ -3572,7 +3606,7 @@ class CNCjob(Geometry):
                 if not multidepth:
                 if not multidepth:
                     # calculate the cut distance
                     # calculate the cut distance
                     total_cut += geo.length
                     total_cut += geo.length
-                    self.gcode += self.create_gcode_single_pass(geo, extracut, extracut_length, tolerance,
+                    self.gcode += self.create_gcode_single_pass(geo, extracut, self.extracut_length, tolerance,
                                                                 old_point=current_pt)
                                                                 old_point=current_pt)
 
 
                 # --------- Multi-pass ---------
                 # --------- Multi-pass ---------
@@ -3587,7 +3621,7 @@ class CNCjob(Geometry):
 
 
                     total_cut += (geo.length * nr_cuts)
                     total_cut += (geo.length * nr_cuts)
 
 
-                    self.gcode += self.create_gcode_multi_pass(geo, extracut, extracut_length, tolerance,
+                    self.gcode += self.create_gcode_multi_pass(geo, extracut, self.extracut_length, tolerance,
                                                                postproc=p, old_point=current_pt)
                                                                postproc=p, old_point=current_pt)
 
 
                 # calculate the travel distance
                 # calculate the travel distance
@@ -3916,8 +3950,8 @@ class CNCjob(Geometry):
             match_pa = re.search(r"^PA(\s*-?\d+\.\d+?),(\s*\s*-?\d+\.\d+?)*;$", gline)
             match_pa = re.search(r"^PA(\s*-?\d+\.\d+?),(\s*\s*-?\d+\.\d+?)*;$", gline)
             if match_pa:
             if match_pa:
                 command['G'] = 0
                 command['G'] = 0
-                command['X'] = float(match_pa.group(1).replace(" ", ""))
-                command['Y'] = float(match_pa.group(2).replace(" ", ""))
+                command['X'] = float(match_pa.group(1).replace(" ", "")) / 40
+                command['Y'] = float(match_pa.group(2).replace(" ", "")) / 40
             match_pen = re.search(r"^(P[U|D])", gline)
             match_pen = re.search(r"^(P[U|D])", gline)
             if match_pen:
             if match_pen:
                 if match_pen.group(1) == 'PU':
                 if match_pen.group(1) == 'PU':

+ 75 - 34
flatcamEditors/FlatCAMGeoEditor.py

@@ -1880,7 +1880,10 @@ class DrawTool(object):
         return ""
         return ""
 
 
     def on_key(self, key):
     def on_key(self, key):
-        return None
+
+        # Jump to coords
+        if key == QtCore.Qt.Key_J or key == 'J':
+            self.draw_app.app.on_jump_to()
 
 
     def utility_geometry(self, data=None):
     def utility_geometry(self, data=None):
         return None
         return None
@@ -1934,13 +1937,17 @@ class FCCircle(FCShapeTool):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
         self.name = 'circle'
         self.name = 'circle'
 
 
+        self.draw_app = draw_app
+
         try:
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
             QtGui.QGuiApplication.restoreOverrideCursor()
         except Exception:
         except Exception:
             pass
             pass
-        self.cursor = QtGui.QCursor(QtGui.QPixmap(self.app.resource_location + '/aero_circle_geo.png'))
+        self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_circle_geo.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
 
 
+        self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
+
         self.draw_app.app.inform.emit(_("Click on Center point ..."))
         self.draw_app.app.inform.emit(_("Click on Center point ..."))
         self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
         self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
 
 
@@ -1977,8 +1984,10 @@ class FCCircle(FCShapeTool):
         radius = distance(p1, p2)
         radius = distance(p1, p2)
         self.geometry = DrawToolShape(Point(p1).buffer(radius, int(self.steps_per_circ / 4)))
         self.geometry = DrawToolShape(Point(p1).buffer(radius, int(self.steps_per_circ / 4)))
         self.complete = True
         self.complete = True
-        self.draw_app.app.inform.emit('[success] %s' %
-                                      _("Done. Adding Circle completed."))
+
+        self.draw_app.app.jump_signal.disconnect()
+
+        self.draw_app.app.inform.emit('[success] %s' % _("Done. Adding Circle completed."))
 
 
 
 
 class FCArc(FCShapeTool):
 class FCArc(FCShapeTool):
@@ -1986,11 +1995,13 @@ class FCArc(FCShapeTool):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
         self.name = 'arc'
         self.name = 'arc'
 
 
+        self.draw_app = draw_app
+
         try:
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
             QtGui.QGuiApplication.restoreOverrideCursor()
         except Exception:
         except Exception:
             pass
             pass
-        self.cursor = QtGui.QCursor(QtGui.QPixmap(self.app.resource_location + '/aero_arc.png'))
+        self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_arc.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
 
 
         self.draw_app.app.inform.emit(_("Click on Center point ..."))
         self.draw_app.app.inform.emit(_("Click on Center point ..."))
@@ -2006,6 +2017,8 @@ class FCArc(FCShapeTool):
         # 132 = p1, p3, p2
         # 132 = p1, p3, p2
         self.mode = "c12"  # Center, p1, p2
         self.mode = "c12"  # Center, p1, p2
 
 
+        self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
+
         self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
         self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
 
 
     def click(self, point):
     def click(self, point):
@@ -2040,6 +2053,10 @@ class FCArc(FCShapeTool):
             self.direction = 'cw' if self.direction == 'ccw' else 'ccw'
             self.direction = 'cw' if self.direction == 'ccw' else 'ccw'
             return _('Direction: %s') % self.direction.upper()
             return _('Direction: %s') % self.direction.upper()
 
 
+        # Jump to coords
+        if key == QtCore.Qt.Key_J or key == 'J':
+            self.draw_app.app.on_jump_to()
+
         if key == 'M' or key == QtCore.Qt.Key_M:
         if key == 'M' or key == QtCore.Qt.Key_M:
             # delete the possible points made before this action; we want to start anew
             # delete the possible points made before this action; we want to start anew
             self.points[:] = []
             self.points[:] = []
@@ -2192,8 +2209,10 @@ class FCArc(FCShapeTool):
             self.geometry = DrawToolShape(LineString(arc(center, radius, startangle, stopangle,
             self.geometry = DrawToolShape(LineString(arc(center, radius, startangle, stopangle,
                                                          self.direction, self.steps_per_circ)))
                                                          self.direction, self.steps_per_circ)))
         self.complete = True
         self.complete = True
-        self.draw_app.app.inform.emit('[success] %s' %
-                                      _("Done. Arc completed."))
+
+        self.draw_app.app.jump_signal.disconnect()
+
+        self.draw_app.app.inform.emit('[success] %s' % _("Done. Arc completed."))
 
 
 
 
 class FCRectangle(FCShapeTool):
 class FCRectangle(FCShapeTool):
@@ -2204,14 +2223,17 @@ class FCRectangle(FCShapeTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
         self.name = 'rectangle'
         self.name = 'rectangle'
+        self.draw_app = draw_app
 
 
         try:
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
             QtGui.QGuiApplication.restoreOverrideCursor()
         except Exception:
         except Exception:
             pass
             pass
-        self.cursor = QtGui.QCursor(QtGui.QPixmap(self.app.resource_location + '/aero.png'))
+        self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
 
 
+        self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
+
         self.draw_app.app.inform.emit(_("Click on 1st corner ..."))
         self.draw_app.app.inform.emit(_("Click on 1st corner ..."))
 
 
     def click(self, point):
     def click(self, point):
@@ -2246,8 +2268,9 @@ class FCRectangle(FCShapeTool):
         # self.geometry = LinearRing([p1, (p2[0], p1[1]), p2, (p1[0], p2[1])])
         # self.geometry = LinearRing([p1, (p2[0], p1[1]), p2, (p1[0], p2[1])])
         self.geometry = DrawToolShape(Polygon([p1, (p2[0], p1[1]), p2, (p1[0], p2[1])]))
         self.geometry = DrawToolShape(Polygon([p1, (p2[0], p1[1]), p2, (p1[0], p2[1])]))
         self.complete = True
         self.complete = True
-        self.draw_app.app.inform.emit('[success] %s' %
-                                      _("Done. Rectangle completed."))
+
+        self.draw_app.app.jump_signal.disconnect()
+        self.draw_app.app.inform.emit('[success] %s' % _("Done. Rectangle completed."))
 
 
 
 
 class FCPolygon(FCShapeTool):
 class FCPolygon(FCShapeTool):
@@ -2258,14 +2281,17 @@ class FCPolygon(FCShapeTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
         self.name = 'polygon'
         self.name = 'polygon'
+        self.draw_app = draw_app
 
 
         try:
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
             QtGui.QGuiApplication.restoreOverrideCursor()
         except Exception:
         except Exception:
             pass
             pass
-        self.cursor = QtGui.QCursor(QtGui.QPixmap(self.app.resource_location + '/aero.png'))
+        self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
 
 
+        self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
+
         self.draw_app.app.inform.emit(_("Click on 1st corner ..."))
         self.draw_app.app.inform.emit(_("Click on 1st corner ..."))
 
 
     def click(self, point):
     def click(self, point):
@@ -2301,10 +2327,16 @@ class FCPolygon(FCShapeTool):
         self.geometry = DrawToolShape(Polygon(self.points))
         self.geometry = DrawToolShape(Polygon(self.points))
         self.draw_app.in_action = False
         self.draw_app.in_action = False
         self.complete = True
         self.complete = True
-        self.draw_app.app.inform.emit('[success] %s' %
-                                      _("Done. Polygon completed."))
+
+        self.draw_app.app.jump_signal.disconnect()
+
+        self.draw_app.app.inform.emit('[success] %s' % _("Done. Polygon completed."))
 
 
     def on_key(self, key):
     def on_key(self, key):
+        # Jump to coords
+        if key == QtCore.Qt.Key_J or key == 'J':
+            self.draw_app.app.on_jump_to()
+
         if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
         if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
             if len(self.points) > 0:
             if len(self.points) > 0:
                 self.points = self.points[0:-1]
                 self.points = self.points[0:-1]
@@ -2321,14 +2353,17 @@ class FCPath(FCPolygon):
     """
     """
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         FCPolygon.__init__(self, draw_app)
         FCPolygon.__init__(self, draw_app)
+        self.draw_app = draw_app
 
 
         try:
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
             QtGui.QGuiApplication.restoreOverrideCursor()
         except Exception:
         except Exception:
             pass
             pass
-        self.cursor = QtGui.QCursor(QtGui.QPixmap(self.app.resource_location + '/aero_path5.png'))
+        self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_path5.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
 
 
+        self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
+
     def make(self):
     def make(self):
         self.geometry = DrawToolShape(LineString(self.points))
         self.geometry = DrawToolShape(LineString(self.points))
         self.name = 'path'
         self.name = 'path'
@@ -2340,6 +2375,9 @@ class FCPath(FCPolygon):
 
 
         self.draw_app.in_action = False
         self.draw_app.in_action = False
         self.complete = True
         self.complete = True
+
+        self.draw_app.app.jump_signal.disconnect()
+
         self.draw_app.app.inform.emit('[success] %s' % _("Done. Path completed."))
         self.draw_app.app.inform.emit('[success] %s' % _("Done. Path completed."))
 
 
     def utility_geometry(self, data=None):
     def utility_geometry(self, data=None):
@@ -2351,6 +2389,10 @@ class FCPath(FCPolygon):
         return None
         return None
 
 
     def on_key(self, key):
     def on_key(self, key):
+        # Jump to coords
+        if key == QtCore.Qt.Key_J or key == 'J':
+            self.draw_app.app.on_jump_to()
+
         if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
         if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
             if len(self.points) > 0:
             if len(self.points) > 0:
                 self.points = self.points[0:-1]
                 self.points = self.points[0:-1]
@@ -2365,6 +2407,7 @@ class FCSelect(DrawTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
         self.name = 'select'
         self.name = 'select'
+        self.draw_app = draw_app
 
 
         try:
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
             QtGui.QGuiApplication.restoreOverrideCursor()
@@ -2443,12 +2486,11 @@ class FCExplode(FCShapeTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         FCShapeTool.__init__(self, draw_app)
         FCShapeTool.__init__(self, draw_app)
         self.name = 'explode'
         self.name = 'explode'
-
         self.draw_app = draw_app
         self.draw_app = draw_app
 
 
         try:
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
             QtGui.QGuiApplication.restoreOverrideCursor()
-        except Exception as e:
+        except Exception:
             pass
             pass
 
 
         self.storage = self.draw_app.storage
         self.storage = self.draw_app.storage
@@ -2457,8 +2499,7 @@ class FCExplode(FCShapeTool):
 
 
         self.draw_app.active_tool = self
         self.draw_app.active_tool = self
         if len(self.draw_app.get_selected()) == 0:
         if len(self.draw_app.get_selected()) == 0:
-            self.draw_app.app.inform.emit('[WARNING_NOTCL] %s...' %
-                                          _("No shape selected. Select a shape to explode"))
+            self.draw_app.app.inform.emit('[WARNING_NOTCL] %s...' % ("No shape selected. Select a shape to explode"))
         else:
         else:
             self.make()
             self.make()
 
 
@@ -2498,6 +2539,7 @@ class FCMove(FCShapeTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         FCShapeTool.__init__(self, draw_app)
         FCShapeTool.__init__(self, draw_app)
         self.name = 'move'
         self.name = 'move'
+        self.draw_app = draw_app
 
 
         try:
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
             QtGui.QGuiApplication.restoreOverrideCursor()
@@ -2673,24 +2715,22 @@ class FCCopy(FCMove):
         self.geometry = [DrawToolShape(affinity.translate(geom.geo, xoff=dx, yoff=dy))
         self.geometry = [DrawToolShape(affinity.translate(geom.geo, xoff=dx, yoff=dy))
                          for geom in self.draw_app.get_selected()]
                          for geom in self.draw_app.get_selected()]
         self.complete = True
         self.complete = True
-        self.draw_app.app.inform.emit('[success] %s' %
-                                      _("Done. Geometry(s) Copy completed."))
+        self.draw_app.app.inform.emit('[success] %s' % _("Done. Geometry(s) Copy completed."))
 
 
 
 
 class FCText(FCShapeTool):
 class FCText(FCShapeTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         FCShapeTool.__init__(self, draw_app)
         FCShapeTool.__init__(self, draw_app)
         self.name = 'text'
         self.name = 'text'
+        self.draw_app = draw_app
 
 
         try:
         try:
             QtGui.QGuiApplication.restoreOverrideCursor()
             QtGui.QGuiApplication.restoreOverrideCursor()
         except Exception:
         except Exception:
             pass
             pass
-        self.cursor = QtGui.QCursor(QtGui.QPixmap(self.app.resource_location + '/aero_text.png'))
+        self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_text.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
 
 
-        # self.shape_buffer = self.draw_app.shape_buffer
-        self.draw_app = draw_app
         self.app = draw_app.app
         self.app = draw_app.app
 
 
         self.draw_app.app.inform.emit(_("Click on 1st corner ..."))
         self.draw_app.app.inform.emit(_("Click on 1st corner ..."))
@@ -2762,8 +2802,7 @@ class FCBuffer(FCShapeTool):
 
 
     def on_buffer(self):
     def on_buffer(self):
         if not self.draw_app.selected:
         if not self.draw_app.selected:
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("Buffer cancelled. No shape selected."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Buffer cancelled. No shape selected."))
             return
             return
 
 
         try:
         try:
@@ -2876,6 +2915,7 @@ class FCEraser(FCShapeTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
         self.name = 'eraser'
         self.name = 'eraser'
+        self.draw_app = draw_app
 
 
         self.origin = None
         self.origin = None
         self.destination = None
         self.destination = None
@@ -2982,8 +3022,6 @@ class FCPaint(FCShapeTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         FCShapeTool.__init__(self, draw_app)
         FCShapeTool.__init__(self, draw_app)
         self.name = 'paint'
         self.name = 'paint'
-
-        # self.shape_buffer = self.draw_app.shape_buffer
         self.draw_app = draw_app
         self.draw_app = draw_app
         self.app = draw_app.app
         self.app = draw_app.app
 
 
@@ -2997,7 +3035,6 @@ class FCTransform(FCShapeTool):
         FCShapeTool.__init__(self, draw_app)
         FCShapeTool.__init__(self, draw_app)
         self.name = 'transformation'
         self.name = 'transformation'
 
 
-        # self.shape_buffer = self.draw_app.shape_buffer
         self.draw_app = draw_app
         self.draw_app = draw_app
         self.app = draw_app.app
         self.app = draw_app.app
 
 
@@ -3798,6 +3835,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
 
         self.snap_x = x
         self.snap_x = x
         self.snap_y = y
         self.snap_y = y
+        self.app.mouse = [x, y]
 
 
         # update the position label in the infobar since the APP mouse event handlers are disconnected
         # update the position label in the infobar since the APP mouse event handlers are disconnected
         self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
         self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
@@ -3815,12 +3853,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         if event.button == 1 and event_is_dragging and isinstance(self.active_tool, FCEraser):
         if event.button == 1 and event_is_dragging and isinstance(self.active_tool, FCEraser):
             pass
             pass
         else:
         else:
-            # ### Utility geometry (animated) ###
-            geo = self.active_tool.utility_geometry(data=(x, y))
-            if isinstance(geo, DrawToolShape) and geo.geo is not None:
-                # Remove any previous utility shape
-                self.tool_shape.clear(update=True)
-                self.draw_utility_geometry(geo=geo)
+            self.update_utility_geometry(data=(x, y))
 
 
         # ### Selection area on canvas section ###
         # ### Selection area on canvas section ###
         dx = pos[0] - self.pos[0]
         dx = pos[0] - self.pos[0]
@@ -3837,6 +3870,14 @@ class FlatCAMGeoEditor(QtCore.QObject):
         else:
         else:
             self.app.selection_type = None
             self.app.selection_type = None
 
 
+    def update_utility_geometry(self, data):
+        # ### Utility geometry (animated) ###
+        geo = self.active_tool.utility_geometry(data=data)
+        if isinstance(geo, DrawToolShape) and geo.geo is not None:
+            # Remove any previous utility shape
+            self.tool_shape.clear(update=True)
+            self.draw_utility_geometry(geo=geo)
+
     def on_geo_click_release(self, event):
     def on_geo_click_release(self, event):
         if self.app.is_legacy is False:
         if self.app.is_legacy is False:
             event_pos = event.pos
             event_pos = event.pos

+ 10 - 7
flatcamEditors/FlatCAMGrbEditor.py

@@ -4530,13 +4530,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: " 
         self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: " 
                                                "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
                                                "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
 
 
-        # # ## Utility geometry (animated)
-        geo = self.active_tool.utility_geometry(data=(x, y))
-
-        if isinstance(geo, DrawToolShape) and geo.geo is not None:
-            # Remove any previous utility shape
-            self.tool_shape.clear(update=True)
-            self.draw_utility_geometry(geo=geo)
+        self.update_utility_geometry(data=(x, y))
 
 
         # # ## Selection area on canvas section # ##
         # # ## Selection area on canvas section # ##
         if event_is_dragging == 1 and event.button == 1:
         if event_is_dragging == 1 and event.button == 1:
@@ -4558,6 +4552,15 @@ class FlatCAMGrbEditor(QtCore.QObject):
         else:
         else:
             self.app.selection_type = None
             self.app.selection_type = None
 
 
+    def update_utility_geometry(self, data):
+        # # ## Utility geometry (animated)
+        geo = self.active_tool.utility_geometry(data=data)
+
+        if isinstance(geo, DrawToolShape) and geo.geo is not None:
+            # Remove any previous utility shape
+            self.tool_shape.clear(update=True)
+            self.draw_utility_geometry(geo=geo)
+
     def draw_utility_geometry(self, geo):
     def draw_utility_geometry(self, geo):
         if type(geo.geo) == list:
         if type(geo.geo) == list:
             for el in geo.geo:
             for el in geo.geo:

+ 38 - 3
flatcamEditors/FlatCAMTextEditor.py

@@ -8,6 +8,12 @@
 from flatcamGUI.GUIElements import *
 from flatcamGUI.GUIElements import *
 from PyQt5 import QtPrintSupport
 from PyQt5 import QtPrintSupport
 
 
+from reportlab.platypus import SimpleDocTemplate, Paragraph
+from reportlab.lib.styles import getSampleStyleSheet
+from reportlab.lib.units import inch, mm
+
+from io import StringIO
+
 import gettext
 import gettext
 import FlatCAMTranslation as fcTranslate
 import FlatCAMTranslation as fcTranslate
 import builtins
 import builtins
@@ -217,9 +223,38 @@ class TextEditor(QtWidgets.QWidget):
         else:
         else:
             try:
             try:
                 my_gcode = self.code_editor.toPlainText()
                 my_gcode = self.code_editor.toPlainText()
-                with open(filename, 'w') as f:
-                    for line in my_gcode:
-                        f.write(line)
+                if filename.rpartition('.')[2].lower() == 'pdf':
+                    page_size = (
+                        self.app.plotcanvas.pagesize_dict[self.app.defaults['global_workspaceT']][0] * mm,
+                        self.app.plotcanvas.pagesize_dict[self.app.defaults['global_workspaceT']][1] * mm
+                    )
+
+                    # add new line after each line
+                    lined_gcode = my_gcode.replace("\n", "<br />")
+
+                    styles = getSampleStyleSheet()
+                    styleN = styles['Normal']
+                    styleH = styles['Heading1']
+                    story = []
+
+                    doc = SimpleDocTemplate(
+                        filename,
+                        pagesize=page_size,
+                        bottomMargin=0.4 * inch,
+                        topMargin=0.6 * inch,
+                        rightMargin=0.8 * inch,
+                        leftMargin=0.8 * inch)
+
+                    P = Paragraph(lined_gcode, styleN)
+                    story.append(P)
+
+                    doc.build(
+                        story,
+                    )
+                else:
+                    with open(filename, 'w') as f:
+                        for line in my_gcode:
+                            f.write(line)
             except FileNotFoundError:
             except FileNotFoundError:
                 self.app.inform.emit('[WARNING] %s' % _("No such file or directory"))
                 self.app.inform.emit('[WARNING] %s' % _("No such file or directory"))
                 return
                 return

+ 78 - 4
flatcamGUI/GUIElements.py

@@ -21,8 +21,16 @@ import re
 import logging
 import logging
 import html
 import html
 
 
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
 log = logging.getLogger('base')
 log = logging.getLogger('base')
 
 
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
 EDIT_SIZE_HINT = 70
 EDIT_SIZE_HINT = 70
 
 
 
 
@@ -624,7 +632,7 @@ class FCDoubleSpinner(QtWidgets.QDoubleSpinBox):
         # by default don't allow the minus sign to be entered as the default for QDoubleSpinBox is the positive range
         # by default don't allow the minus sign to be entered as the default for QDoubleSpinBox is the positive range
         # between 0.00 and 99.00 (2 decimals)
         # between 0.00 and 99.00 (2 decimals)
         self.lineEdit().setValidator(
         self.lineEdit().setValidator(
-            QtGui.QRegExpValidator(QtCore.QRegExp("[0-9]*[.,]?[0-9]{%d}" % self.decimals()), self))
+            QtGui.QRegExpValidator(QtCore.QRegExp("\+?[0-9]*[.,]?[0-9]{%d}" % self.decimals()), self))
 
 
         if suffix:
         if suffix:
             self.setSuffix(' %s' % str(suffix))
             self.setSuffix(' %s' % str(suffix))
@@ -710,15 +718,15 @@ class FCDoubleSpinner(QtWidgets.QDoubleSpinBox):
         self.setDecimals(val)
         self.setDecimals(val)
 
 
         # make sure that the user can't type more decimals than the set precision
         # make sure that the user can't type more decimals than the set precision
-        if self.minimum() < 0 or self.maximum() < 0:
+        if self.minimum() < 0 or self.maximum() <= 0:
             self.lineEdit().setValidator(
             self.lineEdit().setValidator(
                 QtGui.QRegExpValidator(QtCore.QRegExp("-?[0-9]*[.,]?[0-9]{%d}" % self.decimals()), self))
                 QtGui.QRegExpValidator(QtCore.QRegExp("-?[0-9]*[.,]?[0-9]{%d}" % self.decimals()), self))
         else:
         else:
             self.lineEdit().setValidator(
             self.lineEdit().setValidator(
-                QtGui.QRegExpValidator(QtCore.QRegExp("[0-9]*[.,]?[0-9]{%d}" % self.decimals()), self))
+                QtGui.QRegExpValidator(QtCore.QRegExp("\+?[0-9]*[.,]?[0-9]{%d}" % self.decimals()), self))
 
 
     def set_range(self, min_val, max_val):
     def set_range(self, min_val, max_val):
-        if min_val < 0 or max_val < 0:
+        if min_val < 0 or max_val <= 0:
             self.lineEdit().setValidator(
             self.lineEdit().setValidator(
                 QtGui.QRegExpValidator(QtCore.QRegExp("-?[0-9]*[.,]?[0-9]{%d}" % self.decimals()), self))
                 QtGui.QRegExpValidator(QtCore.QRegExp("-?[0-9]*[.,]?[0-9]{%d}" % self.decimals()), self))
 
 
@@ -2230,6 +2238,72 @@ class Dialog_box(QtWidgets.QWidget):
             self.readyToEdit = True
             self.readyToEdit = True
 
 
 
 
+class DialogBoxRadio(QtWidgets.QDialog):
+    def __init__(self, title=None, label=None, icon=None, initial_text=None):
+        """
+
+        :param title: string with the window title
+        :param label: string with the message inside the dialog box
+        """
+        super(DialogBoxRadio, self).__init__()
+        if initial_text is None:
+            self.location = str((0, 0))
+        else:
+            self.location = initial_text
+
+        self.ok = False
+
+        self.setWindowIcon(icon)
+        self.setWindowTitle(str(title))
+
+        self.form = QtWidgets.QFormLayout(self)
+
+        self.form.addRow(QtWidgets.QLabel(''))
+
+        self.wdg_label = QtWidgets.QLabel('<b>%s</b>' % str(label))
+        self.form.addRow(self.wdg_label)
+
+        self.ref_label = QtWidgets.QLabel('%s:' % _("Reference"))
+        self.ref_label.setToolTip(
+            _("The reference can be:\n"
+              "- Absolute -> the reference point is point (0,0)\n"
+              "- Relative -> the reference point is the mouse position before Jump")
+        )
+        self.ref_radio = RadioSet([
+            {"label": _("Abs"), "value": "abs"},
+            {"label": _("Relative"), "value": "rel"}
+        ], orientation='horizontal', stretch=False)
+        self.ref_radio.set_value('abs')
+        self.form.addRow(self.ref_label, self.ref_radio)
+
+        self.loc_label = QtWidgets.QLabel('<b>%s:</b>' % _("Location"))
+        self.loc_label.setToolTip(
+            _("The Location value is a tuple (x,y).\n"
+              "If the reference is Absolute then the Jump will be at the position (x,y).\n"
+              "If the reference is Relative then the Jump will be at the (x,y) distance\n"
+              "from the current mouse location point.")
+        )
+        self.lineEdit = EvalEntry()
+        self.lineEdit.setText(str(self.location).replace('(', '').replace(')', ''))
+        self.form.addRow(self.loc_label, self.lineEdit)
+
+        self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel,
+                                                     Qt.Horizontal, parent=self)
+        self.form.addRow(self.button_box)
+
+        self.button_box.accepted.connect(self.accept)
+        self.button_box.rejected.connect(self.reject)
+
+        self.readyToEdit = True
+
+        if self.exec_() == QtWidgets.QDialog.Accepted:
+            self.ok = True
+            self.location = self.lineEdit.text()
+            self.reference = self.ref_radio.get_value()
+        else:
+            self.ok = False
+
+
 class _BrowserTextEdit(QTextEdit):
 class _BrowserTextEdit(QTextEdit):
 
 
     def __init__(self, version):
     def __init__(self, version):

+ 38 - 38
flatcamGUI/ObjectUI.py

@@ -348,7 +348,7 @@ class GerberObjectUI(ObjectUI):
               "below the copper surface.")
               "below the copper surface.")
         )
         )
         self.cutz_spinner = FCDoubleSpinner()
         self.cutz_spinner = FCDoubleSpinner()
-        self.cutz_spinner.set_range(-9999.9999, -0.0001)
+        self.cutz_spinner.set_range(-9999.9999, 0.0000)
         self.cutz_spinner.set_precision(self.decimals)
         self.cutz_spinner.set_precision(self.decimals)
         self.cutz_spinner.setSingleStep(0.1)
         self.cutz_spinner.setSingleStep(0.1)
         self.cutz_spinner.setWrapping(True)
         self.cutz_spinner.setWrapping(True)
@@ -381,7 +381,7 @@ class GerberObjectUI(ObjectUI):
         )
         )
         passlabel.setMinimumWidth(90)
         passlabel.setMinimumWidth(90)
         self.iso_width_entry = FCSpinner()
         self.iso_width_entry = FCSpinner()
-        self.iso_width_entry.setRange(1, 999)
+        self.iso_width_entry.set_range(1, 999)
         grid1.addWidget(passlabel, 5, 0)
         grid1.addWidget(passlabel, 5, 0)
         grid1.addWidget(self.iso_width_entry, 5, 1, 1, 2)
         grid1.addWidget(self.iso_width_entry, 5, 1, 1, 2)
 
 
@@ -394,7 +394,7 @@ class GerberObjectUI(ObjectUI):
         self.iso_overlap_entry = FCDoubleSpinner(suffix='%')
         self.iso_overlap_entry = FCDoubleSpinner(suffix='%')
         self.iso_overlap_entry.set_precision(self.decimals)
         self.iso_overlap_entry.set_precision(self.decimals)
         self.iso_overlap_entry.setWrapping(True)
         self.iso_overlap_entry.setWrapping(True)
-        self.iso_overlap_entry.setRange(0.0000, 99.9999)
+        self.iso_overlap_entry.set_range(0.0000, 99.9999)
         self.iso_overlap_entry.setSingleStep(0.1)
         self.iso_overlap_entry.setSingleStep(0.1)
         grid1.addWidget(overlabel, 6, 0)
         grid1.addWidget(overlabel, 6, 0)
         grid1.addWidget(self.iso_overlap_entry, 6, 1, 1, 2)
         grid1.addWidget(self.iso_overlap_entry, 6, 1, 1, 2)
@@ -827,9 +827,9 @@ class ExcellonObjectUI(ObjectUI):
         self.cutz_entry.set_precision(self.decimals)
         self.cutz_entry.set_precision(self.decimals)
 
 
         if machinist_setting == 0:
         if machinist_setting == 0:
-            self.cutz_entry.setRange(-9999.9999, -0.000001)
+            self.cutz_entry.set_range(-9999.9999, 0.0000)
         else:
         else:
-            self.cutz_entry.setRange(-9999.9999, 9999.9999)
+            self.cutz_entry.set_range(-9999.9999, 9999.9999)
 
 
         self.cutz_entry.setSingleStep(0.1)
         self.cutz_entry.setSingleStep(0.1)
 
 
@@ -846,9 +846,9 @@ class ExcellonObjectUI(ObjectUI):
         self.travelz_entry.set_precision(self.decimals)
         self.travelz_entry.set_precision(self.decimals)
 
 
         if machinist_setting == 0:
         if machinist_setting == 0:
-            self.travelz_entry.setRange(0.00001, 9999.9999)
+            self.travelz_entry.set_range(0.00001, 9999.9999)
         else:
         else:
-            self.travelz_entry.setRange(-9999.9999, 9999.9999)
+            self.travelz_entry.set_range(-9999.9999, 9999.9999)
 
 
         self.travelz_entry.setSingleStep(0.1)
         self.travelz_entry.setSingleStep(0.1)
 
 
@@ -873,9 +873,9 @@ class ExcellonObjectUI(ObjectUI):
         self.toolchangez_entry.set_precision(self.decimals)
         self.toolchangez_entry.set_precision(self.decimals)
 
 
         if machinist_setting == 0:
         if machinist_setting == 0:
-            self.toolchangez_entry.setRange(0.0, 9999.9999)
+            self.toolchangez_entry.set_range(0.0, 9999.9999)
         else:
         else:
-            self.toolchangez_entry.setRange(-9999.9999, 9999.9999)
+            self.toolchangez_entry.set_range(-9999.9999, 9999.9999)
 
 
         self.toolchangez_entry.setSingleStep(0.1)
         self.toolchangez_entry.setSingleStep(0.1)
 
 
@@ -883,7 +883,7 @@ class ExcellonObjectUI(ObjectUI):
         self.ois_tcz_e = OptionalInputSection(self.toolchange_cb, [self.toolchangez_entry])
         self.ois_tcz_e = OptionalInputSection(self.toolchange_cb, [self.toolchangez_entry])
 
 
         # Start move Z:
         # Start move Z:
-        self.estartz_label = QtWidgets.QLabel('%s:' % _("Start move Z"))
+        self.estartz_label = QtWidgets.QLabel('%s:' % _("Start Z"))
         self.estartz_label.setToolTip(
         self.estartz_label.setToolTip(
             _("Height of the tool just after start.\n"
             _("Height of the tool just after start.\n"
               "Delete the value if you don't need this feature.")
               "Delete the value if you don't need this feature.")
@@ -903,9 +903,9 @@ class ExcellonObjectUI(ObjectUI):
         self.eendz_entry.set_precision(self.decimals)
         self.eendz_entry.set_precision(self.decimals)
 
 
         if machinist_setting == 0:
         if machinist_setting == 0:
-            self.eendz_entry.setRange(0.0, 9999.9999)
+            self.eendz_entry.set_range(0.0, 9999.9999)
         else:
         else:
-            self.eendz_entry.setRange(-9999.9999, 9999.9999)
+            self.eendz_entry.set_range(-9999.9999, 9999.9999)
 
 
         self.eendz_entry.setSingleStep(0.1)
         self.eendz_entry.setSingleStep(0.1)
 
 
@@ -922,7 +922,7 @@ class ExcellonObjectUI(ObjectUI):
         grid1.addWidget(frlabel, 6, 0)
         grid1.addWidget(frlabel, 6, 0)
         self.feedrate_entry = FCDoubleSpinner()
         self.feedrate_entry = FCDoubleSpinner()
         self.feedrate_entry.set_precision(self.decimals)
         self.feedrate_entry.set_precision(self.decimals)
-        self.feedrate_entry.setRange(0.0, 9999.9999)
+        self.feedrate_entry.set_range(0.0, 9999.9999)
         self.feedrate_entry.setSingleStep(0.1)
         self.feedrate_entry.setSingleStep(0.1)
 
 
         grid1.addWidget(self.feedrate_entry, 6, 1)
         grid1.addWidget(self.feedrate_entry, 6, 1)
@@ -939,7 +939,7 @@ class ExcellonObjectUI(ObjectUI):
         grid1.addWidget(self.feedrate_rapid_label, 7, 0)
         grid1.addWidget(self.feedrate_rapid_label, 7, 0)
         self.feedrate_rapid_entry = FCDoubleSpinner()
         self.feedrate_rapid_entry = FCDoubleSpinner()
         self.feedrate_rapid_entry.set_precision(self.decimals)
         self.feedrate_rapid_entry.set_precision(self.decimals)
-        self.feedrate_rapid_entry.setRange(0.0, 9999.9999)
+        self.feedrate_rapid_entry.set_range(0.0, 9999.9999)
         self.feedrate_rapid_entry.setSingleStep(0.1)
         self.feedrate_rapid_entry.setSingleStep(0.1)
 
 
         grid1.addWidget(self.feedrate_rapid_entry, 7, 1)
         grid1.addWidget(self.feedrate_rapid_entry, 7, 1)
@@ -967,7 +967,7 @@ class ExcellonObjectUI(ObjectUI):
         )
         )
         self.dwelltime_entry = FCDoubleSpinner()
         self.dwelltime_entry = FCDoubleSpinner()
         self.dwelltime_entry.set_precision(self.decimals)
         self.dwelltime_entry.set_precision(self.decimals)
-        self.dwelltime_entry.setRange(0.0, 9999.9999)
+        self.dwelltime_entry.set_range(0.0, 9999.9999)
         self.dwelltime_entry.setSingleStep(0.1)
         self.dwelltime_entry.setSingleStep(0.1)
 
 
         self.dwelltime_entry.setToolTip(
         self.dwelltime_entry.setToolTip(
@@ -998,7 +998,7 @@ class ExcellonObjectUI(ObjectUI):
         grid1.addWidget(self.pdepth_label, 11, 0)
         grid1.addWidget(self.pdepth_label, 11, 0)
         self.pdepth_entry = FCDoubleSpinner()
         self.pdepth_entry = FCDoubleSpinner()
         self.pdepth_entry.set_precision(self.decimals)
         self.pdepth_entry.set_precision(self.decimals)
-        self.pdepth_entry.setRange(-9999.9999, 9999.9999)
+        self.pdepth_entry.set_range(-9999.9999, 9999.9999)
         self.pdepth_entry.setSingleStep(0.1)
         self.pdepth_entry.setSingleStep(0.1)
 
 
         grid1.addWidget(self.pdepth_entry, 11, 1)
         grid1.addWidget(self.pdepth_entry, 11, 1)
@@ -1013,7 +1013,7 @@ class ExcellonObjectUI(ObjectUI):
 
 
         self.feedrate_probe_entry = FCDoubleSpinner()
         self.feedrate_probe_entry = FCDoubleSpinner()
         self.feedrate_probe_entry.set_precision(self.decimals)
         self.feedrate_probe_entry.set_precision(self.decimals)
-        self.feedrate_probe_entry.setRange(0.0, 9999.9999)
+        self.feedrate_probe_entry.set_range(0.0, 9999.9999)
         self.feedrate_probe_entry.setSingleStep(0.1)
         self.feedrate_probe_entry.setSingleStep(0.1)
 
 
         grid1.addWidget(self.feedrate_probe_label, 12, 0)
         grid1.addWidget(self.feedrate_probe_label, 12, 0)
@@ -1077,7 +1077,7 @@ class ExcellonObjectUI(ObjectUI):
         )
         )
         self.tooldia_entry = FCDoubleSpinner()
         self.tooldia_entry = FCDoubleSpinner()
         self.tooldia_entry.set_precision(self.decimals)
         self.tooldia_entry.set_precision(self.decimals)
-        self.tooldia_entry.setRange(0.0, 9999.9999)
+        self.tooldia_entry.set_range(0.0, 9999.9999)
         self.tooldia_entry.setSingleStep(0.1)
         self.tooldia_entry.setSingleStep(0.1)
 
 
         self.generate_milling_button = QtWidgets.QPushButton(_('Mill Drills Geo'))
         self.generate_milling_button = QtWidgets.QPushButton(_('Mill Drills Geo'))
@@ -1104,7 +1104,7 @@ class ExcellonObjectUI(ObjectUI):
 
 
         self.slot_tooldia_entry = FCDoubleSpinner()
         self.slot_tooldia_entry = FCDoubleSpinner()
         self.slot_tooldia_entry.set_precision(self.decimals)
         self.slot_tooldia_entry.set_precision(self.decimals)
-        self.slot_tooldia_entry.setRange(0.0, 9999.9999)
+        self.slot_tooldia_entry.set_range(0.0, 9999.9999)
         self.slot_tooldia_entry.setSingleStep(0.1)
         self.slot_tooldia_entry.setSingleStep(0.1)
 
 
         self.generate_milling_slots_button = QtWidgets.QPushButton(_('Mill Slots Geo'))
         self.generate_milling_slots_button = QtWidgets.QPushButton(_('Mill Slots Geo'))
@@ -1286,7 +1286,7 @@ class GeometryObjectUI(ObjectUI):
         )
         )
         self.tool_offset_entry = FCDoubleSpinner()
         self.tool_offset_entry = FCDoubleSpinner()
         self.tool_offset_entry.set_precision(self.decimals)
         self.tool_offset_entry.set_precision(self.decimals)
-        self.tool_offset_entry.setRange(-9999.9999, 9999.9999)
+        self.tool_offset_entry.set_range(-9999.9999, 9999.9999)
         self.tool_offset_entry.setSingleStep(0.1)
         self.tool_offset_entry.setSingleStep(0.1)
 
 
         self.grid1.addWidget(self.tool_offset_lbl, 0, 0)
         self.grid1.addWidget(self.tool_offset_lbl, 0, 0)
@@ -1298,7 +1298,7 @@ class GeometryObjectUI(ObjectUI):
         )
         )
         self.addtool_entry = FCDoubleSpinner()
         self.addtool_entry = FCDoubleSpinner()
         self.addtool_entry.set_precision(self.decimals)
         self.addtool_entry.set_precision(self.decimals)
-        self.addtool_entry.setRange(0.00001, 9999.9999)
+        self.addtool_entry.set_range(0.00001, 9999.9999)
         self.addtool_entry.setSingleStep(0.1)
         self.addtool_entry.setSingleStep(0.1)
 
 
         self.addtool_btn = QtWidgets.QPushButton(_('Add'))
         self.addtool_btn = QtWidgets.QPushButton(_('Add'))
@@ -1379,7 +1379,7 @@ class GeometryObjectUI(ObjectUI):
         )
         )
         self.tipdia_entry = FCDoubleSpinner()
         self.tipdia_entry = FCDoubleSpinner()
         self.tipdia_entry.set_precision(self.decimals)
         self.tipdia_entry.set_precision(self.decimals)
-        self.tipdia_entry.setRange(0.00001, 9999.9999)
+        self.tipdia_entry.set_range(0.00001, 9999.9999)
         self.tipdia_entry.setSingleStep(0.1)
         self.tipdia_entry.setSingleStep(0.1)
 
 
         self.grid3.addWidget(self.tipdialabel, 1, 0)
         self.grid3.addWidget(self.tipdialabel, 1, 0)
@@ -1395,7 +1395,7 @@ class GeometryObjectUI(ObjectUI):
         )
         )
         self.tipangle_entry = FCDoubleSpinner()
         self.tipangle_entry = FCDoubleSpinner()
         self.tipangle_entry.set_precision(self.decimals)
         self.tipangle_entry.set_precision(self.decimals)
-        self.tipangle_entry.setRange(0.0, 180.0)
+        self.tipangle_entry.set_range(0.0, 180.0)
         self.tipangle_entry.setSingleStep(1)
         self.tipangle_entry.setSingleStep(1)
 
 
         self.grid3.addWidget(self.tipanglelabel, 2, 0)
         self.grid3.addWidget(self.tipanglelabel, 2, 0)
@@ -1413,9 +1413,9 @@ class GeometryObjectUI(ObjectUI):
         self.cutz_entry.set_precision(self.decimals)
         self.cutz_entry.set_precision(self.decimals)
 
 
         if machinist_setting == 0:
         if machinist_setting == 0:
-            self.cutz_entry.setRange(-9999.9999, -0.00001)
+            self.cutz_entry.set_range(-9999.9999, 0.0000)
         else:
         else:
-            self.cutz_entry.setRange(-9999.9999, 9999.9999)
+            self.cutz_entry.set_range(-9999.9999, 9999.9999)
 
 
         self.cutz_entry.setSingleStep(0.1)
         self.cutz_entry.setSingleStep(0.1)
 
 
@@ -1435,7 +1435,7 @@ class GeometryObjectUI(ObjectUI):
 
 
         self.maxdepth_entry = FCDoubleSpinner()
         self.maxdepth_entry = FCDoubleSpinner()
         self.maxdepth_entry.set_precision(self.decimals)
         self.maxdepth_entry.set_precision(self.decimals)
-        self.maxdepth_entry.setRange(0, 9999.9999)
+        self.maxdepth_entry.set_range(0, 9999.9999)
         self.maxdepth_entry.setSingleStep(0.1)
         self.maxdepth_entry.setSingleStep(0.1)
 
 
         self.maxdepth_entry.setToolTip(
         self.maxdepth_entry.setToolTip(
@@ -1458,9 +1458,9 @@ class GeometryObjectUI(ObjectUI):
         self.travelz_entry.set_precision(self.decimals)
         self.travelz_entry.set_precision(self.decimals)
 
 
         if machinist_setting == 0:
         if machinist_setting == 0:
-            self.travelz_entry.setRange(0.00001, 9999.9999)
+            self.travelz_entry.set_range(0.00001, 9999.9999)
         else:
         else:
-            self.travelz_entry.setRange(-9999.9999, 9999.9999)
+            self.travelz_entry.set_range(-9999.9999, 9999.9999)
 
 
         self.travelz_entry.setSingleStep(0.1)
         self.travelz_entry.setSingleStep(0.1)
 
 
@@ -1486,9 +1486,9 @@ class GeometryObjectUI(ObjectUI):
         self.toolchangez_entry.set_precision(self.decimals)
         self.toolchangez_entry.set_precision(self.decimals)
 
 
         if machinist_setting == 0:
         if machinist_setting == 0:
-            self.toolchangez_entry.setRange(0, 9999.9999)
+            self.toolchangez_entry.set_range(0, 9999.9999)
         else:
         else:
-            self.toolchangez_entry.setRange(-9999.9999, 9999.9999)
+            self.toolchangez_entry.set_range(-9999.9999, 9999.9999)
 
 
         self.toolchangez_entry.setSingleStep(0.1)
         self.toolchangez_entry.setSingleStep(0.1)
 
 
@@ -1518,9 +1518,9 @@ class GeometryObjectUI(ObjectUI):
         self.gendz_entry.set_precision(self.decimals)
         self.gendz_entry.set_precision(self.decimals)
 
 
         if machinist_setting == 0:
         if machinist_setting == 0:
-            self.gendz_entry.setRange(0, 9999.9999)
+            self.gendz_entry.set_range(0, 9999.9999)
         else:
         else:
-            self.gendz_entry.setRange(-9999.9999, 9999.9999)
+            self.gendz_entry.set_range(-9999.9999, 9999.9999)
 
 
         self.gendz_entry.setSingleStep(0.1)
         self.gendz_entry.setSingleStep(0.1)
 
 
@@ -1535,7 +1535,7 @@ class GeometryObjectUI(ObjectUI):
         )
         )
         self.cncfeedrate_entry = FCDoubleSpinner()
         self.cncfeedrate_entry = FCDoubleSpinner()
         self.cncfeedrate_entry.set_precision(self.decimals)
         self.cncfeedrate_entry.set_precision(self.decimals)
-        self.cncfeedrate_entry.setRange(0, 9999.9999)
+        self.cncfeedrate_entry.set_range(0, 9999.9999)
         self.cncfeedrate_entry.setSingleStep(0.1)
         self.cncfeedrate_entry.setSingleStep(0.1)
 
 
         self.grid3.addWidget(frlabel, 10, 0)
         self.grid3.addWidget(frlabel, 10, 0)
@@ -1550,7 +1550,7 @@ class GeometryObjectUI(ObjectUI):
         )
         )
         self.cncplunge_entry = FCDoubleSpinner()
         self.cncplunge_entry = FCDoubleSpinner()
         self.cncplunge_entry.set_precision(self.decimals)
         self.cncplunge_entry.set_precision(self.decimals)
-        self.cncplunge_entry.setRange(0, 9999.9999)
+        self.cncplunge_entry.set_range(0, 9999.9999)
         self.cncplunge_entry.setSingleStep(0.1)
         self.cncplunge_entry.setSingleStep(0.1)
 
 
         self.grid3.addWidget(frzlabel, 11, 0)
         self.grid3.addWidget(frzlabel, 11, 0)
@@ -1567,7 +1567,7 @@ class GeometryObjectUI(ObjectUI):
         )
         )
         self.cncfeedrate_rapid_entry = FCDoubleSpinner()
         self.cncfeedrate_rapid_entry = FCDoubleSpinner()
         self.cncfeedrate_rapid_entry.set_precision(self.decimals)
         self.cncfeedrate_rapid_entry.set_precision(self.decimals)
-        self.cncfeedrate_rapid_entry.setRange(0, 9999.9999)
+        self.cncfeedrate_rapid_entry.set_range(0, 9999.9999)
         self.cncfeedrate_rapid_entry.setSingleStep(0.1)
         self.cncfeedrate_rapid_entry.setSingleStep(0.1)
 
 
         self.grid3.addWidget(self.fr_rapidlabel, 12, 0)
         self.grid3.addWidget(self.fr_rapidlabel, 12, 0)
@@ -1627,7 +1627,7 @@ class GeometryObjectUI(ObjectUI):
         )
         )
         self.dwelltime_entry = FCDoubleSpinner()
         self.dwelltime_entry = FCDoubleSpinner()
         self.dwelltime_entry.set_precision(self.decimals)
         self.dwelltime_entry.set_precision(self.decimals)
-        self.dwelltime_entry.setRange(0, 9999.9999)
+        self.dwelltime_entry.set_range(0, 9999.9999)
         self.dwelltime_entry.setSingleStep(0.1)
         self.dwelltime_entry.setSingleStep(0.1)
 
 
         self.dwelltime_entry.setToolTip(
         self.dwelltime_entry.setToolTip(
@@ -1658,7 +1658,7 @@ class GeometryObjectUI(ObjectUI):
         )
         )
         self.pdepth_entry = FCDoubleSpinner()
         self.pdepth_entry = FCDoubleSpinner()
         self.pdepth_entry.set_precision(self.decimals)
         self.pdepth_entry.set_precision(self.decimals)
-        self.pdepth_entry.setRange(-9999.9999, 9999.9999)
+        self.pdepth_entry.set_range(-9999.9999, 9999.9999)
         self.pdepth_entry.setSingleStep(0.1)
         self.pdepth_entry.setSingleStep(0.1)
 
 
         self.grid3.addWidget(self.pdepth_label, 17, 0)
         self.grid3.addWidget(self.pdepth_label, 17, 0)
@@ -1674,7 +1674,7 @@ class GeometryObjectUI(ObjectUI):
         )
         )
         self.feedrate_probe_entry = FCDoubleSpinner()
         self.feedrate_probe_entry = FCDoubleSpinner()
         self.feedrate_probe_entry.set_precision(self.decimals)
         self.feedrate_probe_entry.set_precision(self.decimals)
-        self.feedrate_probe_entry.setRange(0.0, 9999.9999)
+        self.feedrate_probe_entry.set_range(0.0, 9999.9999)
         self.feedrate_probe_entry.setSingleStep(0.1)
         self.feedrate_probe_entry.setSingleStep(0.1)
 
 
         self.grid3.addWidget(self.feedrate_probe_label, 18, 0)
         self.grid3.addWidget(self.feedrate_probe_label, 18, 0)

+ 10 - 10
flatcamGUI/PreferencesUI.py

@@ -1682,7 +1682,7 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
         )
         )
         self.cutz_spinner = FCDoubleSpinner()
         self.cutz_spinner = FCDoubleSpinner()
         self.cutz_spinner.set_precision(self.decimals)
         self.cutz_spinner.set_precision(self.decimals)
-        self.cutz_spinner.set_range(-99.9999, -0.0001)
+        self.cutz_spinner.set_range(-99.9999, 0.0000)
         self.cutz_spinner.setSingleStep(0.1)
         self.cutz_spinner.setSingleStep(0.1)
         self.cutz_spinner.setWrapping(True)
         self.cutz_spinner.setWrapping(True)
 
 
@@ -2352,7 +2352,7 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
         self.cutz_entry = FCDoubleSpinner()
         self.cutz_entry = FCDoubleSpinner()
 
 
         if machinist_setting == 0:
         if machinist_setting == 0:
-            self.cutz_entry.set_range(-9999.9999, -0.000001)
+            self.cutz_entry.set_range(-9999.9999, 0.0000)
         else:
         else:
             self.cutz_entry.set_range(-9999.9999, 9999.9999)
             self.cutz_entry.set_range(-9999.9999, 9999.9999)
 
 
@@ -2584,7 +2584,7 @@ class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
         self.toolchangexy_entry = FCEntry()
         self.toolchangexy_entry = FCEntry()
         grid1.addWidget(self.toolchangexy_entry, 1, 1)
         grid1.addWidget(self.toolchangexy_entry, 1, 1)
 
 
-        startzlabel = QtWidgets.QLabel('%s:' % _('Start move Z'))
+        startzlabel = QtWidgets.QLabel('%s:' % _('Start Z'))
         startzlabel.setToolTip(
         startzlabel.setToolTip(
             _("Height of the tool just after start.\n"
             _("Height of the tool just after start.\n"
               "Delete the value if you don't need this feature.")
               "Delete the value if you don't need this feature.")
@@ -2617,7 +2617,7 @@ class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
         )
         )
         self.pdepth_entry = FCDoubleSpinner()
         self.pdepth_entry = FCDoubleSpinner()
         self.pdepth_entry.set_precision(self.decimals)
         self.pdepth_entry.set_precision(self.decimals)
-        self.pdepth_entry.set_range(-99999, -0.000001)
+        self.pdepth_entry.set_range(-99999.9999, 0.0000)
 
 
         grid1.addWidget(self.pdepth_label, 4, 0)
         grid1.addWidget(self.pdepth_label, 4, 0)
         grid1.addWidget(self.pdepth_entry, 4, 1)
         grid1.addWidget(self.pdepth_entry, 4, 1)
@@ -3196,7 +3196,7 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
         self.cutz_entry = FCDoubleSpinner()
         self.cutz_entry = FCDoubleSpinner()
 
 
         if machinist_setting == 0:
         if machinist_setting == 0:
-            self.cutz_entry.set_range(-9999.9999, -0.000001)
+            self.cutz_entry.set_range(-9999.9999, 0.0000)
         else:
         else:
             self.cutz_entry.set_range(-9999.9999, 9999.9999)
             self.cutz_entry.set_range(-9999.9999, 9999.9999)
 
 
@@ -3429,7 +3429,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
         grid1.addWidget(self.toolchangexy_entry, 1, 1)
         grid1.addWidget(self.toolchangexy_entry, 1, 1)
 
 
         # Start move Z
         # Start move Z
-        startzlabel = QtWidgets.QLabel('%s:' % _('Start move Z'))
+        startzlabel = QtWidgets.QLabel('%s:' % _('Start Z'))
         startzlabel.setToolTip(
         startzlabel.setToolTip(
             _("Height of the tool just after starting the work.\n"
             _("Height of the tool just after starting the work.\n"
               "Delete the value if you don't need this feature.")
               "Delete the value if you don't need this feature.")
@@ -3486,7 +3486,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
               "to probe. Negative value, in current units.")
               "to probe. Negative value, in current units.")
         )
         )
         self.pdepth_entry = FCDoubleSpinner()
         self.pdepth_entry = FCDoubleSpinner()
-        self.pdepth_entry.set_range(-99999, -0.000001)
+        self.pdepth_entry.set_range(-99999, 0.0000)
         self.pdepth_entry.set_precision(self.decimals)
         self.pdepth_entry.set_precision(self.decimals)
         self.pdepth_entry.setSingleStep(0.1)
         self.pdepth_entry.setSingleStep(0.1)
         self.pdepth_entry.setWrapping(True)
         self.pdepth_entry.setWrapping(True)
@@ -4052,7 +4052,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
         )
         )
         self.cutz_entry = FCDoubleSpinner()
         self.cutz_entry = FCDoubleSpinner()
         self.cutz_entry.set_precision(self.decimals)
         self.cutz_entry.set_precision(self.decimals)
-        self.cutz_entry.set_range(-9999.9999, -0.000001)
+        self.cutz_entry.set_range(-9999.9999, 0.0000)
         self.cutz_entry.setSingleStep(0.1)
         self.cutz_entry.setSingleStep(0.1)
 
 
         self.cutz_entry.setToolTip(
         self.cutz_entry.setToolTip(
@@ -4310,7 +4310,7 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
         self.cutz_entry.set_precision(self.decimals)
         self.cutz_entry.set_precision(self.decimals)
 
 
         if machinist_setting == 0:
         if machinist_setting == 0:
-            self.cutz_entry.setRange(-9999.9999, -0.00001)
+            self.cutz_entry.setRange(-9999.9999, 0.0000)
         else:
         else:
             self.cutz_entry.setRange(-9999.9999, 9999.9999)
             self.cutz_entry.setRange(-9999.9999, 9999.9999)
 
 
@@ -5119,7 +5119,7 @@ class ToolsCalculatorsPrefGroupUI(OptionsGroupUI):
 
 
         # ## Depth-of-cut Cut Z
         # ## Depth-of-cut Cut Z
         self.cut_z_entry = FCDoubleSpinner()
         self.cut_z_entry = FCDoubleSpinner()
-        self.cut_z_entry.set_range(-9999.9999, -0.00001)
+        self.cut_z_entry.set_range(-9999.9999, 0.0000)
         self.cut_z_entry.set_precision(self.decimals)
         self.cut_z_entry.set_precision(self.decimals)
         self.cut_z_entry.setSingleStep(0.01)
         self.cut_z_entry.setSingleStep(0.01)
 
 

+ 2 - 2
flatcamParsers/ParseHPGL2.py

@@ -111,9 +111,9 @@ class HPGL2:
         self.initialize_re = re.compile(r'^(IN);?$')
         self.initialize_re = re.compile(r'^(IN);?$')
 
 
         # Absolute linear interpolation
         # Absolute linear interpolation
-        self.abs_move_re = re.compile(r"^PA\s*(-?\d+\.?\d+?),?\s*(-?\d+\.?\d+?)*;?$")
+        self.abs_move_re = re.compile(r"^PA\s*(-?\d+\.?\d*),?\s*(-?\d+\.?\d*)*;?$")
         # Relative linear interpolation
         # Relative linear interpolation
-        self.rel_move_re = re.compile(r"^PR\s*(-?\d+\.\d+?),?\s*(-?\d+\.\d+?)*;?$")
+        self.rel_move_re = re.compile(r"^PR\s*(-?\d+\.?\d*),?\s*(-?\d+\.?\d*)*;?$")
 
 
         # Circular interpolation with radius
         # Circular interpolation with radius
         self.circ_re = re.compile(r"^CI\s*(\+?\d+\.?\d+?)?\s*;?\s*$")
         self.circ_re = re.compile(r"^CI\s*(\+?\d+\.?\d+?)?\s*;?\s*$")

+ 54 - 28
flatcamTools/ToolNonCopperClear.py

@@ -258,7 +258,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
         )
         )
         self.cutz_entry = FCDoubleSpinner()
         self.cutz_entry = FCDoubleSpinner()
         self.cutz_entry.set_precision(self.decimals)
         self.cutz_entry.set_precision(self.decimals)
-        self.cutz_entry.set_range(-99999, -0.00000000000001)
+        self.cutz_entry.set_range(-99999.9999, 0.0000)
 
 
         self.cutz_entry.setToolTip(
         self.cutz_entry.setToolTip(
            _("Depth of cut into material. Negative value.\n"
            _("Depth of cut into material. Negative value.\n"
@@ -1810,8 +1810,43 @@ class NonCopperClear(FlatCAMTool, Gerber):
                             if self.app.abort_flag:
                             if self.app.abort_flag:
                                 # graceful abort requested by the user
                                 # graceful abort requested by the user
                                 raise FlatCAMApp.GracefulException
                                 raise FlatCAMApp.GracefulException
-                            if p is not None:
+
+                            # clean the polygon
+                            p = p.buffer(0)
+
+                            if p is not None and p.is_valid:
+                                poly_processed = list()
                                 try:
                                 try:
+                                    for pol in p:
+                                        if pol is not None and isinstance(pol, Polygon):
+                                            if ncc_method == 'standard':
+                                                cp = self.clear_polygon(pol, tool,
+                                                                        self.grb_circle_steps,
+                                                                        overlap=overlap, contour=contour,
+                                                                        connect=connect,
+                                                                        prog_plot=prog_plot)
+                                            elif ncc_method == 'seed':
+                                                cp = self.clear_polygon2(pol, tool,
+                                                                         self.grb_circle_steps,
+                                                                         overlap=overlap, contour=contour,
+                                                                         connect=connect,
+                                                                         prog_plot=prog_plot)
+                                            else:
+                                                cp = self.clear_polygon3(pol, tool,
+                                                                         self.grb_circle_steps,
+                                                                         overlap=overlap, contour=contour,
+                                                                         connect=connect,
+                                                                         prog_plot=prog_plot)
+                                            if cp:
+                                                cleared_geo += list(cp.get_objects())
+                                                poly_processed.append(True)
+                                            else:
+                                                poly_processed.append(False)
+                                                log.warning("Polygon in MultiPolygon can not be cleared.")
+                                        else:
+                                            log.warning("Geo in Iterable can not be cleared beacuse it is not Polygon. "
+                                                        "It is: %s" % str(type(pol)))
+                                except TypeError:
                                     if isinstance(p, Polygon):
                                     if isinstance(p, Polygon):
                                         if ncc_method == 'standard':
                                         if ncc_method == 'standard':
                                             cp = self.clear_polygon(p, tool, self.grb_circle_steps,
                                             cp = self.clear_polygon(p, tool, self.grb_circle_steps,
@@ -1827,32 +1862,20 @@ class NonCopperClear(FlatCAMTool, Gerber):
                                                                      prog_plot=prog_plot)
                                                                      prog_plot=prog_plot)
                                         if cp:
                                         if cp:
                                             cleared_geo += list(cp.get_objects())
                                             cleared_geo += list(cp.get_objects())
-                                    elif isinstance(p, MultiPolygon):
-                                        for pol in p:
-                                            if pol is not None:
-                                                if ncc_method == 'standard':
-                                                    cp = self.clear_polygon(pol, tool,
-                                                                            self.grb_circle_steps,
-                                                                            overlap=overlap, contour=contour,
-                                                                            connect=connect,
-                                                                            prog_plot=prog_plot)
-                                                elif ncc_method == 'seed':
-                                                    cp = self.clear_polygon2(pol, tool,
-                                                                             self.grb_circle_steps,
-                                                                             overlap=overlap, contour=contour,
-                                                                             connect=connect,
-                                                                             prog_plot=prog_plot)
-                                                else:
-                                                    cp = self.clear_polygon3(pol, tool,
-                                                                             self.grb_circle_steps,
-                                                                             overlap=overlap, contour=contour,
-                                                                             connect=connect,
-                                                                             prog_plot=prog_plot)
-                                                if cp:
-                                                    cleared_geo += list(cp.get_objects())
-                                except Exception as e:
-                                    log.warning("Polygon can not be cleared. %s" % str(e))
+                                            poly_processed.append(True)
+                                        else:
+                                            poly_processed.append(False)
+                                            log.warning("Polygon can not be cleared.")
+                                    else:
+                                        log.warning("Geo can not be cleared because it is: %s" % str(type(p)))
+
+                                p_cleared = poly_processed.count(True)
+                                p_not_cleared = poly_processed.count(False)
+
+                                if p_not_cleared:
                                     app_obj.poly_not_cleared = True
                                     app_obj.poly_not_cleared = True
+
+                                if p_cleared == 0:
                                     continue
                                     continue
 
 
                                 pol_nr += 1
                                 pol_nr += 1
@@ -2182,7 +2205,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
                                 # graceful abort requested by the user
                                 # graceful abort requested by the user
                                 raise FlatCAMApp.GracefulException
                                 raise FlatCAMApp.GracefulException
 
 
-                            if p is not None:
+                            # clean the polygon
+                            p = p.buffer(0)
+
+                            if p is not None and p.is_valid:
                                 # provide the app with a way to process the GUI events when in a blocking loop
                                 # provide the app with a way to process the GUI events when in a blocking loop
                                 QtWidgets.QApplication.processEvents()
                                 QtWidgets.QApplication.processEvents()
 
 

+ 169 - 63
flatcamTools/ToolPaint.py

@@ -1450,8 +1450,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                     geo_obj.solid_geometry += list(cpoly.get_objects())
                     geo_obj.solid_geometry += list(cpoly.get_objects())
                     return cpoly
                     return cpoly
                 else:
                 else:
-                    app_obj.inform.emit('[ERROR_NOTCL] %s' %
-                                        _('Geometry could not be painted completely'))
+                    app_obj.inform.emit('[ERROR_NOTCL] %s' %  _('Geometry could not be painted completely'))
                     return None
                     return None
 
 
             current_uid = int(1)
             current_uid = int(1)
@@ -1769,62 +1768,162 @@ class ToolPaint(FlatCAMTool, Gerber):
 
 
                 pol_nr = 0
                 pol_nr = 0
                 for geo in painted_area:
                 for geo in painted_area:
-                    try:
-                        # Polygons are the only really paintable geometries, lines in theory have no area to be painted
-                        if not isinstance(geo, Polygon):
-                            continue
-                        poly_buf = geo.buffer(-paint_margin)
 
 
-                        if paint_method == "seed":
-                            # Type(cp) == FlatCAMRTreeStorage | None
-                            cp = self.clear_polygon2(poly_buf,
-                                                     tooldia=tool_dia,
-                                                     steps_per_circle=self.app.defaults["geometry_circle_steps"],
-                                                     overlap=over,
-                                                     contour=cont,
-                                                     connect=conn,
-                                                     prog_plot=prog_plot)
-
-                        elif paint_method == "lines":
-                            # Type(cp) == FlatCAMRTreeStorage | None
-                            cp = self.clear_polygon3(poly_buf,
-                                                     tooldia=tool_dia,
-                                                     steps_per_circle=self.app.defaults["geometry_circle_steps"],
-                                                     overlap=over,
-                                                     contour=cont,
-                                                     connect=conn,
-                                                     prog_plot=prog_plot)
-
-                        else:
-                            # Type(cp) == FlatCAMRTreeStorage | None
-                            cp = self.clear_polygon(poly_buf,
-                                                    tooldia=tool_dia,
-                                                    steps_per_circle=self.app.defaults["geometry_circle_steps"],
-                                                    overlap=over,
-                                                    contour=cont,
-                                                    connect=conn,
-                                                    prog_plot=prog_plot)
-
-                        if cp is not None:
-                            total_geometry += list(cp.get_objects())
-                    except FlatCAMApp.GracefulException:
-                        return "fail"
-                    except Exception as e:
-                        log.debug("Could not Paint the polygons. %s" % str(e))
-                        self.app.inform.emit('[ERROR] %s\n%s' %
-                                             (_("Could not do Paint All. Try a different combination of parameters. "
-                                                "Or a different Method of paint"),
-                                              str(e)))
-                        return "fail"
-
-                    pol_nr += 1
-                    disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
-                    # log.debug("Polygons cleared: %d" % pol_nr)
+                    # provide the app with a way to process the GUI events when in a blocking loop
+                    QtWidgets.QApplication.processEvents()
+
+                    if self.app.abort_flag:
+                        # graceful abort requested by the user
+                        raise FlatCAMApp.GracefulException
+
+                    # try to clean the Polygon but it may result into a MultiPolygon
+                    geo = geo.buffer(0)
+                    poly_buf = geo.buffer(-paint_margin)
+
+                    if geo is not None and geo.is_valid:
+                        poly_processed = list()
+                        try:
+                            for pol in poly_buf:
+                                if pol is not None and isinstance(pol, Polygon):
+                                    if paint_method == 'standard':
+                                        cp = self.clear_polygon(pol,
+                                                                tooldia=tool_dia,
+                                                                steps_per_circle=self.app.defaults[
+                                                                    "geometry_circle_steps"],
+                                                                overlap=over,
+                                                                contour=cont,
+                                                                connect=conn,
+                                                                prog_plot=prog_plot)
+                                    elif paint_method == 'seed':
+                                        cp = self.clear_polygon2(pol,
+                                                                 tooldia=tool_dia,
+                                                                 steps_per_circle=self.app.defaults[
+                                                                     "geometry_circle_steps"],
+                                                                 overlap=over,
+                                                                 contour=cont,
+                                                                 connect=conn,
+                                                                 prog_plot=prog_plot)
+                                    else:
+                                        cp = self.clear_polygon3(pol,
+                                                                 tooldia=tool_dia,
+                                                                 steps_per_circle=self.app.defaults[
+                                                                     "geometry_circle_steps"],
+                                                                 overlap=over,
+                                                                 contour=cont,
+                                                                 connect=conn,
+                                                                 prog_plot=prog_plot)
+                                    if cp:
+                                        total_geometry += list(cp.get_objects())
+                                        poly_processed.append(True)
+                                    else:
+                                        poly_processed.append(False)
+                                        log.warning("Polygon in MultiPolygon can not be cleared.")
+                                else:
+                                    log.warning("Geo in Iterable can not be cleared because it is not Polygon. "
+                                                "It is: %s" % str(type(pol)))
+                        except TypeError:
+                            if isinstance(poly_buf, Polygon):
+                                if paint_method == 'standard':
+                                    cp = self.clear_polygon(poly_buf,
+                                                            tooldia=tool_dia,
+                                                            steps_per_circle=self.app.defaults[
+                                                                "geometry_circle_steps"],
+                                                            overlap=over,
+                                                            contour=cont,
+                                                            connect=conn,
+                                                            prog_plot=prog_plot)
+                                elif paint_method == 'seed':
+                                    cp = self.clear_polygon2(poly_buf,
+                                                             tooldia=tool_dia,
+                                                             steps_per_circle=self.app.defaults[
+                                                                 "geometry_circle_steps"],
+                                                             overlap=over,
+                                                             contour=cont,
+                                                             connect=conn,
+                                                             prog_plot=prog_plot)
+                                else:
+                                    cp = self.clear_polygon3(poly_buf,
+                                                             tooldia=tool_dia,
+                                                             steps_per_circle=self.app.defaults[
+                                                                 "geometry_circle_steps"],
+                                                             overlap=over,
+                                                             contour=cont,
+                                                             connect=conn,
+                                                             prog_plot=prog_plot)
+                                if cp:
+                                    total_geometry += list(cp.get_objects())
+                                    poly_processed.append(True)
+                                else:
+                                    poly_processed.append(False)
+                                    log.warning("Polygon can not be cleared.")
+                            else:
+                                log.warning("Geo can not be cleared because it is: %s" % str(type(poly_buf)))
+
+                        p_cleared = poly_processed.count(True)
+                        p_not_cleared = poly_processed.count(False)
+
+                        if p_not_cleared:
+                            app_obj.poly_not_cleared = True
+
+                        if p_cleared == 0:
+                            continue
 
 
-                    if old_disp_number < disp_number <= 100:
-                        app_obj.proc_container.update_view_text(' %d%%' % disp_number)
-                        old_disp_number = disp_number
-                        # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number))
+                    # try:
+                    #     # Polygons are the only really paintable geometries, lines in theory have no area to be painted
+                    #     if not isinstance(geo, Polygon):
+                    #         continue
+                    #     poly_buf = geo.buffer(-paint_margin)
+                    #
+                    #     if paint_method == "seed":
+                    #         # Type(cp) == FlatCAMRTreeStorage | None
+                    #         cp = self.clear_polygon2(poly_buf,
+                    #                                  tooldia=tool_dia,
+                    #                                  steps_per_circle=self.app.defaults["geometry_circle_steps"],
+                    #                                  overlap=over,
+                    #                                  contour=cont,
+                    #                                  connect=conn,
+                    #                                  prog_plot=prog_plot)
+                    #
+                    #     elif paint_method == "lines":
+                    #         # Type(cp) == FlatCAMRTreeStorage | None
+                    #         cp = self.clear_polygon3(poly_buf,
+                    #                                  tooldia=tool_dia,
+                    #                                  steps_per_circle=self.app.defaults["geometry_circle_steps"],
+                    #                                  overlap=over,
+                    #                                  contour=cont,
+                    #                                  connect=conn,
+                    #                                  prog_plot=prog_plot)
+                    #
+                    #     else:
+                    #         # Type(cp) == FlatCAMRTreeStorage | None
+                    #         cp = self.clear_polygon(poly_buf,
+                    #                                 tooldia=tool_dia,
+                    #                                 steps_per_circle=self.app.defaults["geometry_circle_steps"],
+                    #                                 overlap=over,
+                    #                                 contour=cont,
+                    #                                 connect=conn,
+                    #                                 prog_plot=prog_plot)
+                    #
+                    #     if cp is not None:
+                    #         total_geometry += list(cp.get_objects())
+                    # except FlatCAMApp.GracefulException:
+                    #     return "fail"
+                    # except Exception as e:
+                    #     log.debug("Could not Paint the polygons. %s" % str(e))
+                    #     self.app.inform.emit('[ERROR] %s\n%s' %
+                    #                          (_("Could not do Paint All. Try a different combination of parameters. "
+                    #                             "Or a different Method of paint"),
+                    #                           str(e)))
+                    #     return "fail"
+
+                        pol_nr += 1
+                        disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
+                        # log.debug("Polygons cleared: %d" % pol_nr)
+
+                        if old_disp_number < disp_number <= 100:
+                            app_obj.proc_container.update_view_text(' %d%%' % disp_number)
+                            old_disp_number = disp_number
+                            # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number))
 
 
                 # add the solid_geometry to the current too in self.paint_tools (tools_storage)
                 # add the solid_geometry to the current too in self.paint_tools (tools_storage)
                 # dictionary and then reset the temporary list that stored that solid_geometry
                 # dictionary and then reset the temporary list that stored that solid_geometry
@@ -1837,17 +1936,24 @@ class ToolPaint(FlatCAMTool, Gerber):
             if self.app.defaults["tools_paint_plotting"] == 'progressive':
             if self.app.defaults["tools_paint_plotting"] == 'progressive':
                 self.temp_shapes.clear(update=True)
                 self.temp_shapes.clear(update=True)
 
 
+            # # delete tools with empty geometry
+            # keys_to_delete = []
+            # # look for keys in the tools_storage dict that have 'solid_geometry' values empty
+            # for uid in tools_storage:
+            #     # if the solid_geometry (type=list) is empty
+            #     if not tools_storage[uid]['solid_geometry']:
+            #         keys_to_delete.append(uid)
+            #
+            # # actual delete of keys from the tools_storage dict
+            # for k in keys_to_delete:
+            #     tools_storage.pop(k, None)
+
             # delete tools with empty geometry
             # delete tools with empty geometry
-            keys_to_delete = []
             # look for keys in the tools_storage dict that have 'solid_geometry' values empty
             # look for keys in the tools_storage dict that have 'solid_geometry' values empty
-            for uid in tools_storage:
+            for uid in list(tools_storage.keys()):
                 # if the solid_geometry (type=list) is empty
                 # if the solid_geometry (type=list) is empty
                 if not tools_storage[uid]['solid_geometry']:
                 if not tools_storage[uid]['solid_geometry']:
-                    keys_to_delete.append(uid)
-
-            # actual delete of keys from the tools_storage dict
-            for k in keys_to_delete:
-                tools_storage.pop(k, None)
+                    tools_storage.pop(uid, None)
 
 
             geo_obj.options["cnctooldia"] = str(tool_dia)
             geo_obj.options["cnctooldia"] = str(tool_dia)
             # this turn on the FlatCAMCNCJob plot for multiple tools
             # this turn on the FlatCAMCNCJob plot for multiple tools

BIN
locale/de/LC_MESSAGES/strings.mo


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 183 - 183
locale/de/LC_MESSAGES/strings.po


BIN
locale/en/LC_MESSAGES/strings.mo


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 208 - 208
locale/en/LC_MESSAGES/strings.po


BIN
locale/es/LC_MESSAGES/strings.mo


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 183 - 183
locale/es/LC_MESSAGES/strings.po


BIN
locale/fr/LC_MESSAGES/strings.mo


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 183 - 183
locale/fr/LC_MESSAGES/strings.po


BIN
locale/pt_BR/LC_MESSAGES/strings.mo


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 185 - 185
locale/pt_BR/LC_MESSAGES/strings.po


BIN
locale/ro/LC_MESSAGES/strings.mo


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 186 - 186
locale/ro/LC_MESSAGES/strings.po


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 207 - 207
locale/ru/LC_MESSAGES/strings.po


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 213 - 215
locale_template/strings.pot


+ 6 - 4
preprocessors/Berta_CNC.py

@@ -14,6 +14,8 @@ from FlatCAMPostProc import *
 
 
 
 
 class Berta_CNC(FlatCAMPostProc):
 class Berta_CNC(FlatCAMPostProc):
+
+    include_header = True
     coordinate_format = "%.*f"
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
     feedrate_format = '%.*f'
 
 
@@ -66,18 +68,19 @@ class Berta_CNC(FlatCAMPostProc):
 
 
         gcode += '(Spindle Speed: %s RPM)\n' % str(p['spindlespeed'])
         gcode += '(Spindle Speed: %s RPM)\n' % str(p['spindlespeed'])
 
 
-        gcode += '(Berta)\n'
-        gcode += 'G90 G94 G17 G91.1'
         gcode += (
         gcode += (
             # This line allow you to sets the machine to METRIC / INCH in the GUI
             # This line allow you to sets the machine to METRIC / INCH in the GUI
-            'G20\n' if p.units.upper() == 'IN' else 'G21\n')
+            'G20\n' if p.units.upper() == 'IN' else 'G21\n') + '\n'
         #        gcode += 'G21\n' # This line sets the machine to METRIC ONLY
         #        gcode += 'G21\n' # This line sets the machine to METRIC ONLY
         #        gcode += 'G20\n' # This line sets the machine to INCH ONLY
         #        gcode += 'G20\n' # This line sets the machine to INCH ONLY
+
+        gcode += 'G90 G17 G91.1\n'
         gcode += 'G64 P0.03\n'
         gcode += 'G64 P0.03\n'
         gcode += 'M110\n'
         gcode += 'M110\n'
         gcode += 'G54\n'
         gcode += 'G54\n'
         gcode += 'G0\n'
         gcode += 'G0\n'
         gcode += '(Berta)\n'
         gcode += '(Berta)\n'
+        gcode += 'G94\n'
 
 
         return gcode
         return gcode
 
 
@@ -97,7 +100,6 @@ class Berta_CNC(FlatCAMPostProc):
         z_toolchange = p.z_toolchange
         z_toolchange = p.z_toolchange
         toolchangexy = p.xy_toolchange
         toolchangexy = p.xy_toolchange
         f_plunge = p.f_plunge
         f_plunge = p.f_plunge
-        gcode = ''
 
 
         if toolchangexy is not None:
         if toolchangexy is not None:
             x_toolchange = toolchangexy[0]
             x_toolchange = toolchangexy[0]

+ 10 - 10
preprocessors/ISEL_CNC.py

@@ -1,16 +1,16 @@
-# ########################################################## ##
+# ##########################################################
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # http://flatcam.org                                       #
 # http://flatcam.org                                       #
 # File Author: Matthieu Berthomé                           #
 # File Author: Matthieu Berthomé                           #
 # Date: 5/26/2017                                          #
 # Date: 5/26/2017                                          #
 # MIT Licence                                              #
 # MIT Licence                                              #
-# ########################################################## ##
+# ##########################################################
 
 
 from FlatCAMPostProc import *
 from FlatCAMPostProc import *
 
 
 
 
 class ISEL_CNC(FlatCAMPostProc):
 class ISEL_CNC(FlatCAMPostProc):
-
+    include_header = True
     coordinate_format = "%.*f"
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
     feedrate_format = '%.*f'
 
 
@@ -71,15 +71,15 @@ class ISEL_CNC(FlatCAMPostProc):
 
 
     def startz_code(self, p):
     def startz_code(self, p):
         if p.startz is not None:
         if p.startz is not None:
-            return 'G00 Z' + self.coordinate_format%(p.coords_decimals, p.startz)
+            return 'G00 Z' + self.coordinate_format % (p.coords_decimals, p.startz)
         else:
         else:
             return ''
             return ''
 
 
     def lift_code(self, p):
     def lift_code(self, p):
-        return 'G00 Z' + self.coordinate_format%(p.coords_decimals, p.z_move)
+        return 'G00 Z' + self.coordinate_format % (p.coords_decimals, p.z_move)
 
 
     def down_code(self, p):
     def down_code(self, p):
-        return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut)
+        return 'G01 Z' + self.coordinate_format % (p.coords_decimals, p.z_cut)
 
 
     def toolchange_code(self, p):
     def toolchange_code(self, p):
         f_plunge = p.f_plunge
         f_plunge = p.f_plunge
@@ -130,17 +130,17 @@ M01""".format(tool=int(p.tool), toolC=toolC_formatted)
 
 
     def end_code(self, p):
     def end_code(self, p):
         coords_xy = p['xy_toolchange']
         coords_xy = p['xy_toolchange']
-        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.z_end) + "\n")
+        gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
 
         if coords_xy is not None:
         if coords_xy is not None:
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
         return gcode
 
 
     def feedrate_code(self, p):
     def feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
 
     def z_feedrate_code(self, p):
     def z_feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.z_feedrate))
 
 
     def spindle_code(self, p):
     def spindle_code(self, p):
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
@@ -153,5 +153,5 @@ M01""".format(tool=int(p.tool), toolC=toolC_formatted)
         if p.dwelltime:
         if p.dwelltime:
             return 'G4 P' + str(p.dwelltime)
             return 'G4 P' + str(p.dwelltime)
 
 
-    def spindle_stop_code(self,p):
+    def spindle_stop_code(self, p):
         return 'M05'
         return 'M05'

+ 136 - 0
preprocessors/ISEL_ICP_CNC.py

@@ -0,0 +1,136 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# http://flatcam.org                                       #
+# File Author: Matthieu Berthomé, Daniel Friderich         #
+# Date: 12/15/2019                                         #
+# MIT Licence                                              #
+# ##########################################################
+
+from FlatCAMPostProc import *
+
+
+class ISEL_ICP_CNC(FlatCAMPostProc):
+    include_header = False
+
+    def start_code(self, p):
+        units = ' ' + str(p['units']).lower()
+        coords_xy = p['xy_toolchange']
+        gcode = ''
+
+        xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
+        xmax = '%.*f' % (p.coords_decimals, p['options']['xmax'])
+        ymin = '%.*f' % (p.coords_decimals, p['options']['ymin'])
+        ymax = '%.*f' % (p.coords_decimals, p['options']['ymax'])
+
+        gcode += 'IMF_PBL flatcam\n\n'
+
+        if str(p['options']['type']) == 'Geometry':
+            gcode += '; TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + '\n'
+        gcode += '; Spindle Speed: %s RPM\n' % str(p['spindlespeed'])
+        gcode += '; Feedrate: ' + str(p['feedrate']) + units + '/min' + '\n'
+        if str(p['options']['type']) == 'Geometry':
+            gcode += '; Feedrate_Z: ' + str(p['z_feedrate']) + units + '/min' + '\n'
+        gcode += '\n'
+        gcode += '; Z_Cut: ' + str(p['z_cut']) + units + '\n'
+
+        if str(p['options']['type']) == 'Geometry':
+            if p['multidepth'] is True:
+                gcode += '; DepthPerCut: ' + str(p['z_depthpercut']) + units + ' <=>' + \
+                         str(math.ceil(abs(p['z_cut']) / p['z_depthpercut'])) + ' passes' + '\n'
+        gcode += '; Z_Move: ' + str(p['z_move']) + units + '\n'
+        gcode += '; Z Toolchange: ' + str(p['z_toolchange']) + units + '\n'
+        if coords_xy is not None:
+            gcode += '; X,Y Toolchange: ' + "%.*f, %.*f" % (p.decimals, coords_xy[0],
+                                                            p.decimals, coords_xy[1]) + units + '\n'
+        else:
+            gcode += '; X,Y Toolchange: ' + "None" + units + '\n'
+        gcode += '; Z Start: ' + str(p['startz']) + units + '\n'
+        gcode += '; Z End: ' + str(p['z_end']) + units + '\n'
+        gcode += '; Steps per circle: ' + str(p['steps_per_circle']) + '\n'
+        if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
+            gcode += '; Preprocessor Excellon: ' + str(p['pp_excellon_name']) + '\n'
+        else:
+            gcode += '; Preprocessor Geometry: ' + str(p['pp_geometry_name']) + '\n'
+        gcode += '\n'
+
+        gcode += '; X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + '\n'
+        gcode += '; Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + '\n'
+
+        return gcode
+
+    def startz_code(self, p):
+        if p.startz is not None:
+            return 'FASTABS Z' + str(int(p.startz * 1000))
+        else:
+            return ''
+
+    def lift_code(self, p):
+        return 'FASTABS Z' + str(int(p.z_move * 1000))
+
+    def down_code(self, p):
+        return 'MOVEABS Z' + str(int(p.z_cut * 1000))
+
+    def toolchange_code(self, p):
+        f_plunge = p.f_plunge
+        no_drills = 1
+
+        toolC_formatted = '%.*f' % (p.decimals, p.toolC)
+
+        if str(p['options']['type']) == 'Excellon':
+            for i in p['options']['Tools_in_use']:
+                if i[0] == p.tool:
+                    no_drills = i[2]
+
+            gcode = "GETTOOL {tool}\n; Changed to Tool Dia = {toolC}".format(tool=int(p.tool), t_drills=no_drills,
+                                                                             toolC=toolC_formatted)
+
+            if f_plunge is True:
+                gcode += '\nFASTABS Z' + str(int(p.z_move * 1000))
+            return gcode
+
+        else:
+            gcode = "GETTOOL {tool}\n; Changed to Tool Dia = {toolC}".format(tool=int(p.tool), toolC=toolC_formatted)
+
+            if f_plunge is True:
+                gcode += '\nFASTABS Z' + str(int(p.z_move * 1000))
+            return gcode
+
+    def up_to_zero_code(self, p):
+        return 'MOVEABS Z0'
+
+    def position_code(self, p):
+        return 'X' + str(int(p.x * 1000)) + ' Y' + str(int(p.y * 1000))
+
+    def rapid_code(self, p):
+        return ('FASTABS ' + self.position_code(p)).format(**p)
+
+    def linear_code(self, p):
+        return ('MOVEABS ' + self.position_code(p)).format(**p)
+
+    def end_code(self, p):
+        gcode = ''
+        gcode += 'WPCLEAR\n'
+        gcode += 'FASTABS Z0\n'
+        gcode += 'FASTABS X0 Y0\n'
+        gcode += 'PROGEND'
+        return gcode
+
+    def feedrate_code(self, p):
+        return 'VEL ' + str(int(p.feedrate / 60 * 1000))
+
+    def z_feedrate_code(self, p):
+        return 'VEL ' + str(int(p.z_feedrate / 60 * 1000))
+
+    def spindle_code(self, p):
+        sdir = {'CW': 'SPINDLE CW', 'CCW': 'SPINDLE CCW'}[p.spindledir]
+        if p.spindlespeed:
+            return '%s RPM%s' % (sdir, str(int(p.spindlespeed)))
+        else:
+            return sdir
+
+    def dwell_code(self, p):
+        if p.dwelltime:
+            return 'WAIT ' + str(int(p.dwelltime * 1000))
+
+    def spindle_stop_code(self, p):
+        return 'SPINDLE OFF'

+ 13 - 13
preprocessors/Paste_1.py

@@ -1,16 +1,17 @@
-# ########################################################## ##
+# ##########################################################
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # http://flatcam.org                                       #
 # http://flatcam.org                                       #
 # File Author: Marius Adrian Stanciu (c)                   #
 # File Author: Marius Adrian Stanciu (c)                   #
 # Date: 3/10/2019                                          #
 # Date: 3/10/2019                                          #
 # MIT Licence                                              #
 # MIT Licence                                              #
-# ########################################################## ##
+# ##########################################################
 
 
 from FlatCAMPostProc import *
 from FlatCAMPostProc import *
 
 
 
 
 class Paste_1(FlatCAMPostProc_Tools):
 class Paste_1(FlatCAMPostProc_Tools):
 
 
+    include_header = True
     coordinate_format = "%.*f"
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
     feedrate_format = '%.*f'
 
 
@@ -56,21 +57,20 @@ class Paste_1(FlatCAMPostProc_Tools):
         return gcode
         return gcode
 
 
     def lift_code(self, p):
     def lift_code(self, p):
-        return 'G00 Z' + self.coordinate_format%(p.coords_decimals, float(p['z_travel']))
+        return 'G00 Z' + self.coordinate_format % (p.coords_decimals, float(p['z_travel']))
 
 
     def down_z_start_code(self, p):
     def down_z_start_code(self, p):
-        return 'G01 Z' + self.coordinate_format%(p.coords_decimals, float(p['z_start']))
+        return 'G01 Z' + self.coordinate_format % (p.coords_decimals, float(p['z_start']))
 
 
     def lift_z_dispense_code(self, p):
     def lift_z_dispense_code(self, p):
-        return 'G01 Z' + self.coordinate_format%(p.coords_decimals, float(p['z_dispense']))
+        return 'G01 Z' + self.coordinate_format % (p.coords_decimals, float(p['z_dispense']))
 
 
     def down_z_stop_code(self, p):
     def down_z_stop_code(self, p):
-        return 'G01 Z' + self.coordinate_format%(p.coords_decimals, float(p['z_stop']))
+        return 'G01 Z' + self.coordinate_format % (p.coords_decimals, float(p['z_stop']))
 
 
     def toolchange_code(self, p):
     def toolchange_code(self, p):
         z_toolchange = float(p['z_toolchange'])
         z_toolchange = float(p['z_toolchange'])
         toolchangexy = [float(eval(a)) for a in p['xy_toolchange'].split(",") if a != '']
         toolchangexy = [float(eval(a)) for a in p['xy_toolchange'].split(",") if a != '']
-        gcode = ''
 
 
         if toolchangexy is not None:
         if toolchangexy is not None:
             x_toolchange = toolchangexy[0]
             x_toolchange = toolchangexy[0]
@@ -116,27 +116,27 @@ G00 Z{z_toolchange}
 
 
     def rapid_code(self, p):
     def rapid_code(self, p):
         return ('G00 ' + self.position_code(p)).format(**p) + '\nG00 Z' + \
         return ('G00 ' + self.position_code(p)).format(**p) + '\nG00 Z' + \
-               self.coordinate_format%(p.coords_decimals, float(p['z_travel']))
+               self.coordinate_format % (p.coords_decimals, float(p['z_travel']))
 
 
     def linear_code(self, p):
     def linear_code(self, p):
         return ('G01 ' + self.position_code(p)).format(**p)
         return ('G01 ' + self.position_code(p)).format(**p)
 
 
     def end_code(self, p):
     def end_code(self, p):
         coords_xy = [float(eval(a)) for a in p['xy_toolchange'].split(",") if a != '']
         coords_xy = [float(eval(a)) for a in p['xy_toolchange'].split(",") if a != '']
-        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, float(p['z_toolchange'])) + "\n")
+        gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, float(p['z_toolchange'])) + "\n")
 
 
         if coords_xy is not None:
         if coords_xy is not None:
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
         return gcode
 
 
     def feedrate_xy_code(self, p):
     def feedrate_xy_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, float(p['frxy'])))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, float(p['frxy'])))
 
 
     def z_feedrate_code(self, p):
     def z_feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, float(p['frz'])))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, float(p['frz'])))
 
 
     def feedrate_z_dispense_code(self, p):
     def feedrate_z_dispense_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, float(p['frz_dispense'])))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, float(p['frz_dispense'])))
 
 
     def spindle_fwd_code(self, p):
     def spindle_fwd_code(self, p):
         if p.spindlespeed:
         if p.spindlespeed:
@@ -150,7 +150,7 @@ G00 Z{z_toolchange}
         else:
         else:
             return 'M04'
             return 'M04'
 
 
-    def spindle_off_code(self,p):
+    def spindle_off_code(self, p):
         return 'M05'
         return 'M05'
 
 
     def dwell_fwd_code(self, p):
     def dwell_fwd_code(self, p):

+ 17 - 13
preprocessors/Repetier.py

@@ -1,16 +1,17 @@
-# ########################################################## ##
+# ##########################################################
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # http://flatcam.org                                       #
 # http://flatcam.org                                       #
 # File Author: Marius Adrian Stanciu (c)                   #
 # File Author: Marius Adrian Stanciu (c)                   #
 # Date: 3/10/2019                                          #
 # Date: 3/10/2019                                          #
 # MIT Licence                                              #
 # MIT Licence                                              #
-# ########################################################## ##
+# ##########################################################
 
 
 from FlatCAMPostProc import *
 from FlatCAMPostProc import *
 
 
 
 
 class Repetier(FlatCAMPostProc):
 class Repetier(FlatCAMPostProc):
 
 
+    include_header = True
     coordinate_format = "%.*f"
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
     feedrate_format = '%.*f'
     feedrate_rapid_format = feedrate_format
     feedrate_rapid_format = feedrate_format
@@ -66,6 +67,7 @@ class Repetier(FlatCAMPostProc):
 
 
         gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n"
         gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n"
         gcode += 'G90\n'
         gcode += 'G90\n'
+        gcode += 'G94\n'
 
 
         return gcode
         return gcode
 
 
@@ -76,20 +78,22 @@ class Repetier(FlatCAMPostProc):
             return ''
             return ''
 
 
     def lift_code(self, p):
     def lift_code(self, p):
-        return 'G0 Z' + self.coordinate_format%(p.coords_decimals, p.z_move) + " " + self.feedrate_rapid_code(p)
+        return 'G0 Z' + self.coordinate_format % (p.coords_decimals, p.z_move) + " " + self.feedrate_rapid_code(p)
 
 
     def down_code(self, p):
     def down_code(self, p):
-        return 'G1 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut) + " " + self.inline_z_feedrate_code(p)
+        return 'G1 Z' + self.coordinate_format % (p.coords_decimals, p.z_cut) + " " + self.inline_z_feedrate_code(p)
 
 
     def toolchange_code(self, p):
     def toolchange_code(self, p):
         z_toolchange = p.z_toolchange
         z_toolchange = p.z_toolchange
         toolchangexy = p.xy_toolchange
         toolchangexy = p.xy_toolchange
         f_plunge = p.f_plunge
         f_plunge = p.f_plunge
-        gcode = ''
 
 
         if toolchangexy is not None:
         if toolchangexy is not None:
             x_toolchange = toolchangexy[0]
             x_toolchange = toolchangexy[0]
             y_toolchange = toolchangexy[1]
             y_toolchange = toolchangexy[1]
+        else:
+            x_toolchange = 0.0
+            y_toolchange = 0.0
 
 
         no_drills = 1
         no_drills = 1
 
 
@@ -151,7 +155,7 @@ G0 Z{z_toolchange}
 M84
 M84
 @pause Change to tool T{tool} with Tool Dia = {toolC}
 @pause Change to tool T{tool} with Tool Dia = {toolC}
 G0 Z{z_toolchange}
 G0 Z{z_toolchange}
-""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchange),
+""".format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            tool=int(p.tool),
            tool=int(p.tool),
            toolC=toolC_formatted)
            toolC=toolC_formatted)
 
 
@@ -175,7 +179,7 @@ G0 Z{z_toolchange}
 
 
     def end_code(self, p):
     def end_code(self, p):
         coords_xy = p['xy_toolchange']
         coords_xy = p['xy_toolchange']
-        gcode = ('G0 Z' + self.feedrate_format %(p.fr_decimals, p.z_end) + " " + self.feedrate_rapid_code(p) + "\n")
+        gcode = ('G0 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + " " + self.feedrate_rapid_code(p) + "\n")
 
 
         if coords_xy is not None:
         if coords_xy is not None:
             gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
             gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
@@ -183,21 +187,21 @@ G0 Z{z_toolchange}
         return gcode
         return gcode
 
 
     def feedrate_code(self, p):
     def feedrate_code(self, p):
-        return 'G1 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
+        return 'G1 F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
 
     def inline_feedrate_code(self, p):
     def inline_feedrate_code(self, p):
-        return 'F' + self.feedrate_format %(p.fr_decimals, p.feedrate)
+        return 'F' + self.feedrate_format % (p.fr_decimals, p.feedrate)
 
 
     def inline_z_feedrate_code(self, p):
     def inline_z_feedrate_code(self, p):
-        return 'F' + self.feedrate_format %(p.fr_decimals, p.z_feedrate)
+        return 'F' + self.feedrate_format % (p.fr_decimals, p.z_feedrate)
 
 
     def z_feedrate_code(self, p):
     def z_feedrate_code(self, p):
-        return 'G1 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
+        return 'G1 F' + str(self.feedrate_format % (p.fr_decimals, p.z_feedrate))
 
 
     def feedrate_rapid_code(self, p):
     def feedrate_rapid_code(self, p):
         return 'F' + self.feedrate_rapid_format % (p.fr_decimals, p.feedrate_rapid)
         return 'F' + self.feedrate_rapid_format % (p.fr_decimals, p.feedrate_rapid)
 
 
-    def spindle_code(self,p):
+    def spindle_code(self, p):
         if p.spindlespeed:
         if p.spindlespeed:
             return 'M106 S%d' % p.spindlespeed
             return 'M106 S%d' % p.spindlespeed
         else:
         else:
@@ -207,5 +211,5 @@ G0 Z{z_toolchange}
         if p.dwelltime:
         if p.dwelltime:
             return 'G4 P' + str(p.dwelltime)
             return 'G4 P' + str(p.dwelltime)
 
 
-    def spindle_stop_code(self,p):
+    def spindle_stop_code(self, p):
         return 'M107'
         return 'M107'

+ 4 - 3
preprocessors/Roland_MDX_20.py

@@ -1,10 +1,10 @@
-# ########################################################## ##
+# ##########################################################
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # http://flatcam.org                                       #
 # http://flatcam.org                                       #
 # File Author: Marius Adrian Stanciu (c)                   #
 # File Author: Marius Adrian Stanciu (c)                   #
 # Date: 3/10/2019                                          #
 # Date: 3/10/2019                                          #
 # MIT Licence                                              #
 # MIT Licence                                              #
-# ########################################################## ##
+# ##########################################################
 
 
 from FlatCAMPostProc import *
 from FlatCAMPostProc import *
 
 
@@ -13,6 +13,7 @@ from FlatCAMPostProc import *
 # the same) to contain the following keyword, case-sensitive: 'Roland' without the quotes.
 # the same) to contain the following keyword, case-sensitive: 'Roland' without the quotes.
 class Roland_MDX_20(FlatCAMPostProc):
 class Roland_MDX_20(FlatCAMPostProc):
 
 
+    include_header = False
     coordinate_format = "%.1f"
     coordinate_format = "%.1f"
     feedrate_format = '%.1f'
     feedrate_format = '%.1f'
     feedrate_rapid_format = '%.1f'
     feedrate_rapid_format = '%.1f'
@@ -123,5 +124,5 @@ class Roland_MDX_20(FlatCAMPostProc):
     def dwell_code(self, p):
     def dwell_code(self, p):
         return''
         return''
 
 
-    def spindle_stop_code(self,p):
+    def spindle_stop_code(self, p):
         return '!MC0'
         return '!MC0'

+ 12 - 8
preprocessors/Toolchange_Custom.py

@@ -1,16 +1,17 @@
-# ########################################################## ##
+# ##########################################################
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # http://flatcam.org                                       #
 # http://flatcam.org                                       #
 # File Author: Marius Adrian Stanciu (c)                   #
 # File Author: Marius Adrian Stanciu (c)                   #
 # Date: 3/10/2019                                          #
 # Date: 3/10/2019                                          #
 # MIT Licence                                              #
 # MIT Licence                                              #
-# ########################################################## ##
+# ##########################################################
 
 
 from FlatCAMPostProc import *
 from FlatCAMPostProc import *
 
 
 
 
 class Toolchange_Custom(FlatCAMPostProc):
 class Toolchange_Custom(FlatCAMPostProc):
 
 
+    include_header = True
     coordinate_format = "%.*f"
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
     feedrate_format = '%.*f'
 
 
@@ -74,10 +75,10 @@ class Toolchange_Custom(FlatCAMPostProc):
         return ''
         return ''
 
 
     def lift_code(self, p):
     def lift_code(self, p):
-        return 'G00 Z' + self.coordinate_format%(p.coords_decimals, p.z_move)
+        return 'G00 Z' + self.coordinate_format % (p.coords_decimals, p.z_move)
 
 
     def down_code(self, p):
     def down_code(self, p):
-        return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut)
+        return 'G01 Z' + self.coordinate_format % (p.coords_decimals, p.z_cut)
 
 
     def toolchange_code(self, p):
     def toolchange_code(self, p):
         z_toolchange = p.z_toolchange
         z_toolchange = p.z_toolchange
@@ -88,6 +89,9 @@ class Toolchange_Custom(FlatCAMPostProc):
         if toolchangexy is not None:
         if toolchangexy is not None:
             x_toolchange = toolchangexy[0]
             x_toolchange = toolchangexy[0]
             y_toolchange = toolchangexy[1]
             y_toolchange = toolchangexy[1]
+        else:
+            x_toolchange = 0.0
+            y_toolchange = 0.0
 
 
         no_drills = 1
         no_drills = 1
 
 
@@ -142,17 +146,17 @@ M6
 
 
     def end_code(self, p):
     def end_code(self, p):
         coords_xy = p['xy_toolchange']
         coords_xy = p['xy_toolchange']
-        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.z_end) + "\n")
+        gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
 
         if coords_xy is not None:
         if coords_xy is not None:
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
         return gcode
 
 
     def feedrate_code(self, p):
     def feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
 
     def z_feedrate_code(self, p):
     def z_feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.z_feedrate))
 
 
     def spindle_code(self, p):
     def spindle_code(self, p):
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
@@ -165,5 +169,5 @@ M6
         if p.dwelltime:
         if p.dwelltime:
             return 'G4 P' + str(p.dwelltime)
             return 'G4 P' + str(p.dwelltime)
 
 
-    def spindle_stop_code(self,p):
+    def spindle_stop_code(self, p):
         return 'M05'
         return 'M05'

+ 18 - 16
preprocessors/Toolchange_Probe_MACH3.py

@@ -1,16 +1,17 @@
-# ########################################################## ##
+# ##########################################################
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # http://flatcam.org                                       #
 # http://flatcam.org                                       #
 # File Author: Marius Adrian Stanciu (c)                   #
 # File Author: Marius Adrian Stanciu (c)                   #
 # Date: 3/10/2019                                          #
 # Date: 3/10/2019                                          #
 # MIT Licence                                              #
 # MIT Licence                                              #
-# ########################################################## ##
+# ##########################################################
 
 
 from FlatCAMPostProc import *
 from FlatCAMPostProc import *
 
 
 
 
 class Toolchange_Probe_MACH3(FlatCAMPostProc):
 class Toolchange_Probe_MACH3(FlatCAMPostProc):
 
 
+    include_header = True
     coordinate_format = "%.*f"
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
     feedrate_format = '%.*f'
 
 
@@ -69,14 +70,14 @@ class Toolchange_Probe_MACH3(FlatCAMPostProc):
         gcode += 'G90\n'
         gcode += 'G90\n'
         gcode += 'G17\n'
         gcode += 'G17\n'
         gcode += 'G94\n'
         gcode += 'G94\n'
-        gcode += '(MSG, WARNING: Make sure you do zero on all axis. ' \
-                 'For Z axis, since it will be probed, make a rough estimate and do a zero.)\n'
-        gcode += 'M0'
 
 
         return gcode
         return gcode
 
 
     def startz_code(self, p):
     def startz_code(self, p):
-        return ''
+        g = '(MSG, WARNING: Make sure you do zero on all axis. ' \
+            'For Z axis, since it will be probed, make a rough estimate and do a zero.)\n'
+        g += 'M0'
+        return g
 
 
     def lift_code(self, p):
     def lift_code(self, p):
         return 'G00 Z' + self.coordinate_format%(p.coords_decimals, p.z_move)
         return 'G00 Z' + self.coordinate_format%(p.coords_decimals, p.z_move)
@@ -89,11 +90,12 @@ class Toolchange_Probe_MACH3(FlatCAMPostProc):
         toolchangexy = p.xy_toolchange
         toolchangexy = p.xy_toolchange
         f_plunge = p.f_plunge
         f_plunge = p.f_plunge
 
 
-        gcode = ''
-
         if toolchangexy is not None:
         if toolchangexy is not None:
             x_toolchange = toolchangexy[0]
             x_toolchange = toolchangexy[0]
             y_toolchange = toolchangexy[1]
             y_toolchange = toolchangexy[1]
+        else:
+            x_toolchange = 0.0
+            y_toolchange = 0.0
 
 
         no_drills = 1
         no_drills = 1
 
 
@@ -131,7 +133,7 @@ M0
            z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
-           feedrate_probe=str(self.feedrate_format %(p.fr_decimals, p.feedrate_probe)),
+           feedrate_probe=str(self.feedrate_format % (p.fr_decimals, p.feedrate_probe)),
            feedrate_probe_slow=str(self.feedrate_format % (p.fr_decimals, (p.feedrate_probe / 2))),
            feedrate_probe_slow=str(self.feedrate_format % (p.fr_decimals, (p.feedrate_probe / 2))),
            z_pdepth=self.coordinate_format % (p.coords_decimals, p.z_pdepth),
            z_pdepth=self.coordinate_format % (p.coords_decimals, p.z_pdepth),
            tool=int(p.tool),
            tool=int(p.tool),
@@ -158,7 +160,7 @@ M0
 """.format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
 """.format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
-           feedrate_probe=str(self.feedrate_format %(p.fr_decimals, p.feedrate_probe)),
+           feedrate_probe=str(self.feedrate_format % (p.fr_decimals, p.feedrate_probe)),
            feedrate_probe_slow=str(self.feedrate_format % (p.fr_decimals, (p.feedrate_probe / 2))),
            feedrate_probe_slow=str(self.feedrate_format % (p.fr_decimals, (p.feedrate_probe / 2))),
            z_pdepth=self.coordinate_format % (p.coords_decimals, p.z_pdepth),
            z_pdepth=self.coordinate_format % (p.coords_decimals, p.z_pdepth),
            tool=int(p.tool),
            tool=int(p.tool),
@@ -194,7 +196,7 @@ M0
            z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
-           feedrate_probe=str(self.feedrate_format %(p.fr_decimals, p.feedrate_probe)),
+           feedrate_probe=str(self.feedrate_format % (p.fr_decimals, p.feedrate_probe)),
            feedrate_probe_slow=str(self.feedrate_format % (p.fr_decimals, (p.feedrate_probe / 2))),
            feedrate_probe_slow=str(self.feedrate_format % (p.fr_decimals, (p.feedrate_probe / 2))),
            z_pdepth=self.coordinate_format % (p.coords_decimals, p.z_pdepth),
            z_pdepth=self.coordinate_format % (p.coords_decimals, p.z_pdepth),
            tool=int(p.tool),
            tool=int(p.tool),
@@ -220,7 +222,7 @@ M0
 """.format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
 """.format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
            z_in_between=self.coordinate_format % (p.coords_decimals, p.z_move / 2),
-           feedrate_probe=str(self.feedrate_format %(p.fr_decimals, p.feedrate_probe)),
+           feedrate_probe=str(self.feedrate_format % (p.fr_decimals, p.feedrate_probe)),
            feedrate_probe_slow=str(self.feedrate_format % (p.fr_decimals, (p.feedrate_probe / 2))),
            feedrate_probe_slow=str(self.feedrate_format % (p.fr_decimals, (p.feedrate_probe / 2))),
            z_pdepth=self.coordinate_format % (p.coords_decimals, p.z_pdepth),
            z_pdepth=self.coordinate_format % (p.coords_decimals, p.z_pdepth),
            tool=int(p.tool),
            tool=int(p.tool),
@@ -245,17 +247,17 @@ M0
 
 
     def end_code(self, p):
     def end_code(self, p):
         coords_xy = p['xy_toolchange']
         coords_xy = p['xy_toolchange']
-        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.z_end) + "\n")
+        gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
 
         if coords_xy is not None:
         if coords_xy is not None:
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
         return gcode
 
 
     def feedrate_code(self, p):
     def feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
 
     def z_feedrate_code(self, p):
     def z_feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.z_feedrate))
 
 
     def spindle_code(self, p):
     def spindle_code(self, p):
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
@@ -268,5 +270,5 @@ M0
         if p.dwelltime:
         if p.dwelltime:
             return 'G4 P' + str(p.dwelltime)
             return 'G4 P' + str(p.dwelltime)
 
 
-    def spindle_stop_code(self,p):
+    def spindle_stop_code(self, p):
         return 'M05'
         return 'M05'

+ 18 - 16
preprocessors/Toolchange_manual.py

@@ -1,16 +1,17 @@
-# ########################################################## ##
+# ##########################################################
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # http://flatcam.org                                       #
 # http://flatcam.org                                       #
 # File Author: Marius Adrian Stanciu (c)                   #
 # File Author: Marius Adrian Stanciu (c)                   #
 # Date: 3/10/2019                                          #
 # Date: 3/10/2019                                          #
 # MIT Licence                                              #
 # MIT Licence                                              #
-# ########################################################## ##
+# ##########################################################
 
 
 from FlatCAMPostProc import *
 from FlatCAMPostProc import *
 
 
 
 
 class Toolchange_manual(FlatCAMPostProc):
 class Toolchange_manual(FlatCAMPostProc):
 
 
+    include_header = True
     coordinate_format = "%.*f"
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
     feedrate_format = '%.*f'
 
 
@@ -84,11 +85,13 @@ class Toolchange_manual(FlatCAMPostProc):
         z_toolchange = p.z_toolchange
         z_toolchange = p.z_toolchange
         toolchangexy = p.xy_toolchange
         toolchangexy = p.xy_toolchange
         f_plunge = p.f_plunge
         f_plunge = p.f_plunge
-        gcode = ''
 
 
         if toolchangexy is not None:
         if toolchangexy is not None:
             x_toolchange = toolchangexy[0]
             x_toolchange = toolchangexy[0]
             y_toolchange = toolchangexy[1]
             y_toolchange = toolchangexy[1]
+        else:
+            x_toolchange = 0.0
+            y_toolchange = 0.0
 
 
         no_drills = 1
         no_drills = 1
 
 
@@ -116,9 +119,9 @@ M0
 G00 Z{z_toolchange}
 G00 Z{z_toolchange}
 (MSG, Now the tool can be tightened more securely.)
 (MSG, Now the tool can be tightened more securely.)
 M0
 M0
-""".format(x_toolchange=self.coordinate_format%(p.coords_decimals, x_toolchange),
-           y_toolchange=self.coordinate_format%(p.coords_decimals, y_toolchange),
-           z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchange),
+""".format(x_toolchange=self.coordinate_format % (p.coords_decimals, x_toolchange),
+           y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
+           z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            tool=int(p.tool),
            tool=int(p.tool),
            t_drills=no_drills,
            t_drills=no_drills,
            toolC=toolC_formatted)
            toolC=toolC_formatted)
@@ -137,7 +140,7 @@ G00 Z{z_toolchange}
 (MSG, Now the tool can be tightened more securely.)
 (MSG, Now the tool can be tightened more securely.)
 M0
 M0
 """.format(
 """.format(
-           z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchange),
+           z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            tool=int(p.tool),
            tool=int(p.tool),
            t_drills=no_drills,
            t_drills=no_drills,
            toolC=toolC_formatted)
            toolC=toolC_formatted)
@@ -161,9 +164,9 @@ M0
 G00 Z{z_toolchange}
 G00 Z{z_toolchange}
 (MSG, Now the tool can be tightened more securely.)
 (MSG, Now the tool can be tightened more securely.)
 M0
 M0
-""".format(x_toolchange=self.coordinate_format%(p.coords_decimals, x_toolchange),
-           y_toolchange=self.coordinate_format%(p.coords_decimals, y_toolchange),
-           z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchange),
+""".format(x_toolchange=self.coordinate_format % (p.coords_decimals, x_toolchange),
+           y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
+           z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            tool=int(p.tool),
            tool=int(p.tool),
            toolC=toolC_formatted)
            toolC=toolC_formatted)
             else:
             else:
@@ -179,8 +182,7 @@ M0
 G00 Z{z_toolchange}
 G00 Z{z_toolchange}
 (MSG, Now the tool can be tightened more securely.)
 (MSG, Now the tool can be tightened more securely.)
 M0
 M0
-""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchange),
-           tool=int(p.tool),
+""".format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange), tool=int(p.tool),
            toolC=toolC_formatted)
            toolC=toolC_formatted)
 
 
             if f_plunge is True:
             if f_plunge is True:
@@ -202,7 +204,7 @@ M0
 
 
     def end_code(self, p):
     def end_code(self, p):
         coords_xy = p['xy_toolchange']
         coords_xy = p['xy_toolchange']
-        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.z_end) + "\n")
+        gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
         if coords_xy is not None:
         if coords_xy is not None:
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         else:
         else:
@@ -210,10 +212,10 @@ M0
         return gcode
         return gcode
 
 
     def feedrate_code(self, p):
     def feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
 
     def z_feedrate_code(self, p):
     def z_feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.z_feedrate))
 
 
     def spindle_code(self, p):
     def spindle_code(self, p):
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
@@ -226,5 +228,5 @@ M0
         if p.dwelltime:
         if p.dwelltime:
             return 'G4 P' + str(p.dwelltime)
             return 'G4 P' + str(p.dwelltime)
 
 
-    def spindle_stop_code(self,p):
+    def spindle_stop_code(self, p):
         return 'M05'
         return 'M05'

+ 14 - 11
preprocessors/default.py

@@ -1,16 +1,17 @@
-# ########################################################## ##
+# ##########################################################
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # http://flatcam.org                                       #
 # http://flatcam.org                                       #
 # File Author: Matthieu Berthomé                           #
 # File Author: Matthieu Berthomé                           #
 # Date: 5/26/2017                                          #
 # Date: 5/26/2017                                          #
 # MIT Licence                                              #
 # MIT Licence                                              #
-# ########################################################## ##
+# ##########################################################
 
 
 from FlatCAMPostProc import *
 from FlatCAMPostProc import *
 
 
 
 
 class default(FlatCAMPostProc):
 class default(FlatCAMPostProc):
 
 
+    include_header = True
     coordinate_format = "%.*f"
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
     feedrate_format = '%.*f'
 
 
@@ -71,25 +72,27 @@ class default(FlatCAMPostProc):
 
 
     def startz_code(self, p):
     def startz_code(self, p):
         if p.startz is not None:
         if p.startz is not None:
-            return 'G00 Z' + self.coordinate_format%(p.coords_decimals, p.startz)
+            return 'G00 Z' + self.coordinate_format % (p.coords_decimals, p.startz)
         else:
         else:
             return ''
             return ''
 
 
     def lift_code(self, p):
     def lift_code(self, p):
-        return 'G00 Z' + self.coordinate_format%(p.coords_decimals, p.z_move)
+        return 'G00 Z' + self.coordinate_format % (p.coords_decimals, p.z_move)
 
 
     def down_code(self, p):
     def down_code(self, p):
-        return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut)
+        return 'G01 Z' + self.coordinate_format % (p.coords_decimals, p.z_cut)
 
 
     def toolchange_code(self, p):
     def toolchange_code(self, p):
         z_toolchange = p.z_toolchange
         z_toolchange = p.z_toolchange
         toolchangexy = p.xy_toolchange
         toolchangexy = p.xy_toolchange
         f_plunge = p.f_plunge
         f_plunge = p.f_plunge
-        gcode = ''
 
 
         if toolchangexy is not None:
         if toolchangexy is not None:
             x_toolchange = toolchangexy[0]
             x_toolchange = toolchangexy[0]
             y_toolchange = toolchangexy[1]
             y_toolchange = toolchangexy[1]
+        else:
+            x_toolchange = 0.0
+            y_toolchange = 0.0
 
 
         no_drills = 1
         no_drills = 1
 
 
@@ -163,7 +166,7 @@ M6
 (MSG, Change to Tool Dia = {toolC})
 (MSG, Change to Tool Dia = {toolC})
 M0
 M0
 G00 Z{z_toolchange}
 G00 Z{z_toolchange}
-""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchange),
+""".format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            tool=int(p.tool),
            tool=int(p.tool),
            toolC=toolC_formatted)
            toolC=toolC_formatted)
 
 
@@ -186,17 +189,17 @@ G00 Z{z_toolchange}
 
 
     def end_code(self, p):
     def end_code(self, p):
         coords_xy = p['xy_toolchange']
         coords_xy = p['xy_toolchange']
-        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.z_end) + "\n")
+        gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
 
         if coords_xy is not None:
         if coords_xy is not None:
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
         return gcode
 
 
     def feedrate_code(self, p):
     def feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
 
     def z_feedrate_code(self, p):
     def z_feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.z_feedrate))
 
 
     def spindle_code(self, p):
     def spindle_code(self, p):
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
@@ -209,5 +212,5 @@ G00 Z{z_toolchange}
         if p.dwelltime:
         if p.dwelltime:
             return 'G4 P' + str(p.dwelltime)
             return 'G4 P' + str(p.dwelltime)
 
 
-    def spindle_stop_code(self,p):
+    def spindle_stop_code(self, p):
         return 'M05'
         return 'M05'

+ 16 - 13
preprocessors/grbl_11.py

@@ -1,16 +1,17 @@
-# ########################################################## ##
+# ##########################################################
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # http://flatcam.org                                       #
 # http://flatcam.org                                       #
 # File Author: Matthieu Berthomé                           #
 # File Author: Matthieu Berthomé                           #
 # Date: 5/26/2017                                          #
 # Date: 5/26/2017                                          #
 # MIT Licence                                              #
 # MIT Licence                                              #
-# ########################################################## ##
+# ##########################################################
 
 
 from FlatCAMPostProc import *
 from FlatCAMPostProc import *
 
 
 
 
 class grbl_11(FlatCAMPostProc):
 class grbl_11(FlatCAMPostProc):
 
 
+    include_header = True
     coordinate_format = "%.*f"
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
     feedrate_format = '%.*f'
 
 
@@ -63,32 +64,34 @@ class grbl_11(FlatCAMPostProc):
 
 
         gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n"
         gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n"
         gcode += 'G90\n'
         gcode += 'G90\n'
-        gcode += 'G94\n'
         gcode += 'G17\n'
         gcode += 'G17\n'
+        gcode += 'G94\n'
 
 
         return gcode
         return gcode
 
 
     def startz_code(self, p):
     def startz_code(self, p):
         if p.startz is not None:
         if p.startz is not None:
-            return 'G00 Z' + self.coordinate_format%(p.coords_decimals, p.startz)
+            return 'G00 Z' + self.coordinate_format % (p.coords_decimals, p.startz)
         else:
         else:
             return ''
             return ''
 
 
     def lift_code(self, p):
     def lift_code(self, p):
-        return 'G00 Z' + self.coordinate_format%(p.coords_decimals, p.z_move)
+        return 'G00 Z' + self.coordinate_format % (p.coords_decimals, p.z_move)
 
 
     def down_code(self, p):
     def down_code(self, p):
-        return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut)
+        return 'G01 Z' + self.coordinate_format % (p.coords_decimals, p.z_cut)
 
 
     def toolchange_code(self, p):
     def toolchange_code(self, p):
         z_toolchange = p.z_toolchange
         z_toolchange = p.z_toolchange
         toolchangexy = p.xy_toolchange
         toolchangexy = p.xy_toolchange
         f_plunge = p.f_plunge
         f_plunge = p.f_plunge
-        gcode = ''
 
 
         if toolchangexy is not None:
         if toolchangexy is not None:
             x_toolchange = toolchangexy[0]
             x_toolchange = toolchangexy[0]
             y_toolchange = toolchangexy[1]
             y_toolchange = toolchangexy[1]
+        else:
+            x_toolchange = 0.0
+            y_toolchange = 0.0
 
 
         no_drills = 1
         no_drills = 1
 
 
@@ -161,7 +164,7 @@ M6
 (MSG, Change to Tool Dia = {toolC})
 (MSG, Change to Tool Dia = {toolC})
 M0
 M0
 G00 Z{z_toolchange}
 G00 Z{z_toolchange}
-""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchange),
+""".format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            tool=int(p.tool),
            tool=int(p.tool),
            toolC=toolC_formatted)
            toolC=toolC_formatted)
 
 
@@ -181,21 +184,21 @@ G00 Z{z_toolchange}
 
 
     def linear_code(self, p):
     def linear_code(self, p):
         return ('G01 ' + self.position_code(p)).format(**p) + \
         return ('G01 ' + self.position_code(p)).format(**p) + \
-               ' F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
+               ' F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
 
     def end_code(self, p):
     def end_code(self, p):
         coords_xy = p['xy_toolchange']
         coords_xy = p['xy_toolchange']
-        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.z_end) + "\n")
+        gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
 
         if coords_xy is not None:
         if coords_xy is not None:
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
         return gcode
 
 
     def feedrate_code(self, p):
     def feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
 
     def z_feedrate_code(self, p):
     def z_feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.z_feedrate))
 
 
     def spindle_code(self, p):
     def spindle_code(self, p):
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
@@ -208,5 +211,5 @@ G00 Z{z_toolchange}
         if p.dwelltime:
         if p.dwelltime:
             return 'G4 P' + str(p.dwelltime)
             return 'G4 P' + str(p.dwelltime)
 
 
-    def spindle_stop_code(self,p):
+    def spindle_stop_code(self, p):
         return 'M05'
         return 'M05'

+ 11 - 10
preprocessors/grbl_laser.py

@@ -1,10 +1,10 @@
-# ########################################################## ##
+# ##########################################################
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # http://flatcam.org                                       #
 # http://flatcam.org                                       #
 # File Author: Matthieu Berthomé                           #
 # File Author: Matthieu Berthomé                           #
 # Date: 5/26/2017                                          #
 # Date: 5/26/2017                                          #
 # MIT Licence                                              #
 # MIT Licence                                              #
-# ########################################################## ##
+# ##########################################################
 
 
 from FlatCAMPostProc import *
 from FlatCAMPostProc import *
 
 
@@ -14,6 +14,7 @@ from FlatCAMPostProc import *
 
 
 class grbl_laser(FlatCAMPostProc):
 class grbl_laser(FlatCAMPostProc):
 
 
+    include_header = True
     coordinate_format = "%.*f"
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
     feedrate_format = '%.*f'
 
 
@@ -34,15 +35,15 @@ class grbl_laser(FlatCAMPostProc):
         if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
         if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
             gcode += '(Preprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n'
             gcode += '(Preprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n'
         else:
         else:
-            gcode += '(Preprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n'
-        gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n" + '\n'
+            gcode += '(Preprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'
 
 
         gcode += '(X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + ')\n'
         gcode += '(X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + ')\n'
         gcode += '(Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + ')\n\n'
         gcode += '(Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + ')\n\n'
 
 
+        gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n"
         gcode += 'G90\n'
         gcode += 'G90\n'
-        gcode += 'G94\n'
         gcode += 'G17\n'
         gcode += 'G17\n'
+        gcode += 'G94\n'
 
 
         return gcode
         return gcode
 
 
@@ -73,21 +74,21 @@ class grbl_laser(FlatCAMPostProc):
 
 
     def linear_code(self, p):
     def linear_code(self, p):
         return ('G01 ' + self.position_code(p)).format(**p) + \
         return ('G01 ' + self.position_code(p)).format(**p) + \
-               ' F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
+               ' F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
 
     def end_code(self, p):
     def end_code(self, p):
         coords_xy = p['xy_toolchange']
         coords_xy = p['xy_toolchange']
-        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.z_end) + "\n")
+        gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
 
         if coords_xy is not None:
         if coords_xy is not None:
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
         return gcode
 
 
     def feedrate_code(self, p):
     def feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
 
     def z_feedrate_code(self, p):
     def z_feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.z_feedrate))
 
 
     def spindle_code(self, p):
     def spindle_code(self, p):
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
@@ -99,5 +100,5 @@ class grbl_laser(FlatCAMPostProc):
     def dwell_code(self, p):
     def dwell_code(self, p):
         return ''
         return ''
 
 
-    def spindle_stop_code(self,p):
+    def spindle_stop_code(self, p):
         return 'M05'
         return 'M05'

+ 19 - 7
preprocessors/hpgl.py

@@ -1,10 +1,10 @@
-# ########################################################## ##
+# ##########################################################
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # http://flatcam.org                                       #
 # http://flatcam.org                                       #
 # File Author: Marius Adrian Stanciu (c)                   #
 # File Author: Marius Adrian Stanciu (c)                   #
 # Date: 3/10/2019                                          #
 # Date: 3/10/2019                                          #
 # MIT Licence                                              #
 # MIT Licence                                              #
-# ########################################################## ##
+# ##########################################################
 
 
 from FlatCAMPostProc import *
 from FlatCAMPostProc import *
 
 
@@ -12,11 +12,12 @@ from FlatCAMPostProc import *
 # for Roland Preprocessors it is mandatory for the preprocessor name (python file and class name, both of them must be
 # for Roland Preprocessors it is mandatory for the preprocessor name (python file and class name, both of them must be
 # the same) to contain the following keyword, case-sensitive: 'Roland' without the quotes.
 # the same) to contain the following keyword, case-sensitive: 'Roland' without the quotes.
 class hpgl(FlatCAMPostProc):
 class hpgl(FlatCAMPostProc):
-
+    include_header = True
     coordinate_format = "%.*f"
     coordinate_format = "%.*f"
 
 
     def start_code(self, p):
     def start_code(self, p):
-        gcode = 'IN;'
+        gcode = 'IN;\n'
+        gcode += 'PU;'
         return gcode
         return gcode
 
 
     def startz_code(self, p):
     def startz_code(self, p):
@@ -49,8 +50,19 @@ class hpgl(FlatCAMPostProc):
             y = p.y
             y = p.y
 
 
         # we need to have the coordinates as multiples of 0.025mm
         # we need to have the coordinates as multiples of 0.025mm
-        x = round(x / 0.025) * 25 / 1000
-        y = round(y / 0.025) * 25 / 1000
+        x = round(x * 40)
+        y = round(y * 40)
+
+        # constrain the x and y values within the domain of valid values: [-32767 ... 32768]
+        if x <= -32767:
+            x = -32767
+        if x >= 32768:
+            x = 32768
+
+        if y <= -32767:
+            y = -32767
+        if y >= 32768:
+            y = 32768
 
 
         return ('PA' + self.coordinate_format + ',' + self.coordinate_format + ';') % \
         return ('PA' + self.coordinate_format + ',' + self.coordinate_format + ';') % \
                (p.coords_decimals, x, p.coords_decimals, y)
                (p.coords_decimals, x, p.coords_decimals, y)
@@ -80,5 +92,5 @@ class hpgl(FlatCAMPostProc):
     def dwell_code(self, p):
     def dwell_code(self, p):
         return ''
         return ''
 
 
-    def spindle_stop_code(self,p):
+    def spindle_stop_code(self, p):
         return ''
         return ''

+ 12 - 12
preprocessors/line_xyz.py

@@ -1,16 +1,17 @@
-# ########################################################## ##
+# ##########################################################
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # FlatCAM: 2D Post-processing for Manufacturing            #
 # http://flatcam.org                                       #
 # http://flatcam.org                                       #
 # File Author: Marius Adrian Stanciu (c)                   #
 # File Author: Marius Adrian Stanciu (c)                   #
 # Date: 3/10/2019                                          #
 # Date: 3/10/2019                                          #
 # MIT Licence                                              #
 # MIT Licence                                              #
-# ########################################################## ##
+# ##########################################################
 
 
 from FlatCAMPostProc import *
 from FlatCAMPostProc import *
 
 
 
 
 class line_xyz(FlatCAMPostProc):
 class line_xyz(FlatCAMPostProc):
 
 
+    include_header = True
     coordinate_format = "%.*f"
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
     feedrate_format = '%.*f'
 
 
@@ -69,9 +70,9 @@ class line_xyz(FlatCAMPostProc):
 
 
     def startz_code(self, p):
     def startz_code(self, p):
         if p.startz is not None:
         if p.startz is not None:
-            g = 'G00 ' + 'X' + self.coordinate_format%(p.coords_decimals, p.x) + \
-                ' Y' + self.coordinate_format%(p.coords_decimals, p.y) + \
-                ' Z' + self.coordinate_format%(p.coords_decimals, p.startz)
+            g = 'G00 ' + 'X' + self.coordinate_format % (p.coords_decimals, p.x) + \
+                ' Y' + self.coordinate_format % (p.coords_decimals, p.y) + \
+                ' Z' + self.coordinate_format % (p.coords_decimals, p.startz)
             return g
             return g
         else:
         else:
             return ''
             return ''
@@ -92,7 +93,6 @@ class line_xyz(FlatCAMPostProc):
         z_toolchange = p.z_toolchange
         z_toolchange = p.z_toolchange
         xy_toolchange = p.xy_toolchange
         xy_toolchange = p.xy_toolchange
         f_plunge = p.f_plunge
         f_plunge = p.f_plunge
-        gcode = ''
 
 
         if xy_toolchange is not None:
         if xy_toolchange is not None:
             x_toolchange = xy_toolchange[0]
             x_toolchange = xy_toolchange[0]
@@ -122,7 +122,7 @@ G00 X{x_toolchange} Y{x_toolchange} Z{z_toolchange}
 T{tool}
 T{tool}
 M6
 M6
 (MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
 (MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
-M0""".format(x_toolchange=self.coordinate_format%(p.coords_decimals, x_toolchange),
+M0""".format(x_toolchange=self.coordinate_format % (p.coords_decimals, x_toolchange),
              y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
              y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
              z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
              z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
              tool=int(p.tool),
              tool=int(p.tool),
@@ -131,7 +131,7 @@ M0""".format(x_toolchange=self.coordinate_format%(p.coords_decimals, x_toolchang
 
 
             if f_plunge is True:
             if f_plunge is True:
                 gcode += """\nG00 X{x_toolchange} Y{x_toolchange} Z{z_move}""".format(
                 gcode += """\nG00 X{x_toolchange} Y{x_toolchange} Z{z_move}""".format(
-                    x_toolchange=self.coordinate_format%(p.coords_decimals, x_toolchange),
+                    x_toolchange=self.coordinate_format % (p.coords_decimals, x_toolchange),
                     y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
                     y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
                     z_move=self.coordinate_format % (p.coords_decimals, p.z_move))
                     z_move=self.coordinate_format % (p.coords_decimals, p.z_move))
             return gcode
             return gcode
@@ -142,7 +142,7 @@ G00 X{x_toolchange} Y{x_toolchange} Z{z_toolchange}
 T{tool}
 T{tool}
 M6    
 M6    
 (MSG, Change to Tool Dia = {toolC})
 (MSG, Change to Tool Dia = {toolC})
-M0""".format(x_toolchange=self.coordinate_format%(p.coords_decimals, x_toolchange),
+M0""".format(x_toolchange=self.coordinate_format % (p.coords_decimals, x_toolchange),
              y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
              y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
              z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
              z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
              tool=int(p.tool),
              tool=int(p.tool),
@@ -185,10 +185,10 @@ M0""".format(x_toolchange=self.coordinate_format%(p.coords_decimals, x_toolchang
         return g
         return g
 
 
     def feedrate_code(self, p):
     def feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
 
     def z_feedrate_code(self, p):
     def z_feedrate_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
+        return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.z_feedrate))
 
 
     def spindle_code(self, p):
     def spindle_code(self, p):
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
         sdir = {'CW': 'M03', 'CCW': 'M04'}[p.spindledir]
@@ -201,5 +201,5 @@ M0""".format(x_toolchange=self.coordinate_format%(p.coords_decimals, x_toolchang
         if p.dwelltime:
         if p.dwelltime:
             return 'G4 P' + str(p.dwelltime)
             return 'G4 P' + str(p.dwelltime)
 
 
-    def spindle_stop_code(self,p):
+    def spindle_stop_code(self, p):
         return 'M05'
         return 'M05'

+ 14 - 10
preprocessors/marlin.py

@@ -11,6 +11,7 @@ from FlatCAMPostProc import *
 
 
 class marlin(FlatCAMPostProc):
 class marlin(FlatCAMPostProc):
 
 
+    include_header = True
     coordinate_format = "%.*f"
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
     feedrate_format = '%.*f'
     feedrate_rapid_format = feedrate_format
     feedrate_rapid_format = feedrate_format
@@ -66,6 +67,7 @@ class marlin(FlatCAMPostProc):
 
 
         gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n"
         gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n"
         gcode += 'G90\n'
         gcode += 'G90\n'
+        gcode += 'G94\n'
 
 
         return gcode
         return gcode
 
 
@@ -76,20 +78,22 @@ class marlin(FlatCAMPostProc):
             return ''
             return ''
 
 
     def lift_code(self, p):
     def lift_code(self, p):
-        return 'G0 Z' + self.coordinate_format%(p.coords_decimals, p.z_move) + " " + self.feedrate_rapid_code(p)
+        return 'G0 Z' + self.coordinate_format % (p.coords_decimals, p.z_move) + " " + self.feedrate_rapid_code(p)
 
 
     def down_code(self, p):
     def down_code(self, p):
-        return 'G1 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut) + " " + self.inline_z_feedrate_code(p)
+        return 'G1 Z' + self.coordinate_format % (p.coords_decimals, p.z_cut) + " " + self.inline_z_feedrate_code(p)
 
 
     def toolchange_code(self, p):
     def toolchange_code(self, p):
         z_toolchange = p.z_toolchange
         z_toolchange = p.z_toolchange
         toolchangexy = p.xy_toolchange
         toolchangexy = p.xy_toolchange
         f_plunge = p.f_plunge
         f_plunge = p.f_plunge
-        gcode = ''
 
 
         if toolchangexy is not None:
         if toolchangexy is not None:
             x_toolchange = toolchangexy[0]
             x_toolchange = toolchangexy[0]
             y_toolchange = toolchangexy[1]
             y_toolchange = toolchangexy[1]
+        else:
+            x_toolchange = 0.0
+            y_toolchange = 0.0
 
 
         no_drills = 1
         no_drills = 1
 
 
@@ -162,7 +166,7 @@ M6
 ;MSG, Change to Tool Dia = {toolC}
 ;MSG, Change to Tool Dia = {toolC}
 M0
 M0
 G0 Z{z_toolchange}
 G0 Z{z_toolchange}
-""".format(z_toolchange=self.coordinate_format%(p.coords_decimals, z_toolchange),
+""".format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            tool=int(p.tool),
            tool=int(p.tool),
            toolC=toolC_formatted)
            toolC=toolC_formatted)
 
 
@@ -185,7 +189,7 @@ G0 Z{z_toolchange}
 
 
     def end_code(self, p):
     def end_code(self, p):
         coords_xy = p['xy_toolchange']
         coords_xy = p['xy_toolchange']
-        gcode = ('G0 Z' + self.feedrate_format %(p.fr_decimals, p.z_end) + " " + self.feedrate_rapid_code(p) + "\n")
+        gcode = ('G0 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + " " + self.feedrate_rapid_code(p) + "\n")
 
 
         if coords_xy is not None:
         if coords_xy is not None:
             gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
             gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
@@ -193,16 +197,16 @@ G0 Z{z_toolchange}
         return gcode
         return gcode
 
 
     def feedrate_code(self, p):
     def feedrate_code(self, p):
-        return 'G1 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
+        return 'G1 F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
 
     def inline_feedrate_code(self, p):
     def inline_feedrate_code(self, p):
-        return 'F' + self.feedrate_format %(p.fr_decimals, p.feedrate)
+        return 'F' + self.feedrate_format % (p.fr_decimals, p.feedrate)
 
 
     def inline_z_feedrate_code(self, p):
     def inline_z_feedrate_code(self, p):
-        return 'F' + self.feedrate_format %(p.fr_decimals, p.z_feedrate)
+        return 'F' + self.feedrate_format % (p.fr_decimals, p.z_feedrate)
 
 
     def z_feedrate_code(self, p):
     def z_feedrate_code(self, p):
-        return 'G1 F' + str(self.feedrate_format %(p.fr_decimals, p.z_feedrate))
+        return 'G1 F' + str(self.feedrate_format % (p.fr_decimals, p.z_feedrate))
 
 
     def feedrate_rapid_code(self, p):
     def feedrate_rapid_code(self, p):
         return 'F' + self.feedrate_rapid_format % (p.fr_decimals, p.feedrate_rapid)
         return 'F' + self.feedrate_rapid_format % (p.fr_decimals, p.feedrate_rapid)
@@ -218,5 +222,5 @@ G0 Z{z_toolchange}
         if p.dwelltime:
         if p.dwelltime:
             return 'G4 P' + str(p.dwelltime)
             return 'G4 P' + str(p.dwelltime)
 
 
-    def spindle_stop_code(self,p):
+    def spindle_stop_code(self, p):
         return 'M5'
         return 'M5'

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio