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

- finished work in ToolSolderPaste

Marius Stanciu 7 лет назад
Родитель
Сommit
d453c31bf5
11 измененных файлов с 442 добавлено и 273 удалено
  1. 9 6
      FlatCAMApp.py
  2. 43 14
      FlatCAMGUI.py
  3. 23 5
      FlatCAMObj.py
  4. 72 0
      FlatCAMPostProc.py
  5. 1 0
      README.md
  6. 135 106
      camlib.py
  7. 1 7
      flatcamTools/ToolCutOut.py
  8. 0 2
      flatcamTools/ToolNonCopperClear.py
  9. 80 10
      flatcamTools/ToolSolderPaste.py
  10. 2 2
      flatcamTools/__init__.py
  11. 76 121
      postprocessors/Paste_1.py

+ 9 - 6
FlatCAMApp.py

@@ -489,8 +489,11 @@ class App(QtCore.QObject):
             "tools_solderpaste_z_dispense": self.tools_defaults_form.tools_solderpaste_group.z_dispense_entry,
             "tools_solderpaste_z_dispense": self.tools_defaults_form.tools_solderpaste_group.z_dispense_entry,
             "tools_solderpaste_z_stop": self.tools_defaults_form.tools_solderpaste_group.z_stop_entry,
             "tools_solderpaste_z_stop": self.tools_defaults_form.tools_solderpaste_group.z_stop_entry,
             "tools_solderpaste_z_travel": self.tools_defaults_form.tools_solderpaste_group.z_travel_entry,
             "tools_solderpaste_z_travel": self.tools_defaults_form.tools_solderpaste_group.z_travel_entry,
+            "tools_solderpaste_z_toolchange": self.tools_defaults_form.tools_solderpaste_group.z_toolchange_entry,
+            "tools_solderpaste_xy_toolchange": self.tools_defaults_form.tools_solderpaste_group.xy_toolchange_entry,
             "tools_solderpaste_frxy": self.tools_defaults_form.tools_solderpaste_group.frxy_entry,
             "tools_solderpaste_frxy": self.tools_defaults_form.tools_solderpaste_group.frxy_entry,
             "tools_solderpaste_frz": self.tools_defaults_form.tools_solderpaste_group.frz_entry,
             "tools_solderpaste_frz": self.tools_defaults_form.tools_solderpaste_group.frz_entry,
+            "tools_solderpaste_frz_dispense": self.tools_defaults_form.tools_solderpaste_group.frz_dispense_entry,
             "tools_solderpaste_speedfwd": self.tools_defaults_form.tools_solderpaste_group.speedfwd_entry,
             "tools_solderpaste_speedfwd": self.tools_defaults_form.tools_solderpaste_group.speedfwd_entry,
             "tools_solderpaste_dwellfwd": self.tools_defaults_form.tools_solderpaste_group.dwellfwd_entry,
             "tools_solderpaste_dwellfwd": self.tools_defaults_form.tools_solderpaste_group.dwellfwd_entry,
             "tools_solderpaste_speedrev": self.tools_defaults_form.tools_solderpaste_group.speedrev_entry,
             "tools_solderpaste_speedrev": self.tools_defaults_form.tools_solderpaste_group.speedrev_entry,
@@ -499,16 +502,13 @@ class App(QtCore.QObject):
 
 
         }
         }
 
 
-
         #############################
         #############################
         #### LOAD POSTPROCESSORS ####
         #### LOAD POSTPROCESSORS ####
         #############################
         #############################
 
 
-
         self.postprocessors = load_postprocessors(self)
         self.postprocessors = load_postprocessors(self)
 
 
         for name in list(self.postprocessors.keys()):
         for name in list(self.postprocessors.keys()):
-
             # 'Paste' postprocessors are to be used only in the Solder Paste Dispensing Tool
             # 'Paste' postprocessors are to be used only in the Solder Paste Dispensing Tool
             if name.partition('_')[0] == 'Paste':
             if name.partition('_')[0] == 'Paste':
                 self.tools_defaults_form.tools_solderpaste_group.pp_combo.addItem(name)
                 self.tools_defaults_form.tools_solderpaste_group.pp_combo.addItem(name)
@@ -743,13 +743,16 @@ class App(QtCore.QObject):
             "tools_solderpaste_z_dispense": 0.01,
             "tools_solderpaste_z_dispense": 0.01,
             "tools_solderpaste_z_stop": 0.005,
             "tools_solderpaste_z_stop": 0.005,
             "tools_solderpaste_z_travel": 0.1,
             "tools_solderpaste_z_travel": 0.1,
+            "tools_solderpaste_z_toolchange": 1.0,
+            "tools_solderpaste_xy_toolchange": "0.0, 0.0",
             "tools_solderpaste_frxy": 3.0,
             "tools_solderpaste_frxy": 3.0,
             "tools_solderpaste_frz": 3.0,
             "tools_solderpaste_frz": 3.0,
+            "tools_solderpaste_frz_dispense": 1.0,
             "tools_solderpaste_speedfwd": 20,
             "tools_solderpaste_speedfwd": 20,
             "tools_solderpaste_dwellfwd": 1,
             "tools_solderpaste_dwellfwd": 1,
             "tools_solderpaste_speedrev": 10,
             "tools_solderpaste_speedrev": 10,
             "tools_solderpaste_dwellrev": 1,
             "tools_solderpaste_dwellrev": 1,
-            "tools_solderpaste_pp": ''
+            "tools_solderpaste_pp": 'Paste_1'
         })
         })
 
 
         ###############################
         ###############################
@@ -1623,14 +1626,14 @@ class App(QtCore.QObject):
         self.film_tool = Film(self)
         self.film_tool = Film(self)
         self.film_tool.install(icon=QtGui.QIcon('share/film16.png'))
         self.film_tool.install(icon=QtGui.QIcon('share/film16.png'))
 
 
-        self.paste_tool = ToolSolderPaste(self)
+        self.paste_tool = SolderPaste(self)
         self.paste_tool.install(icon=QtGui.QIcon('share/solderpastebis32.png'), separator=True)
         self.paste_tool.install(icon=QtGui.QIcon('share/solderpastebis32.png'), separator=True)
 
 
         self.move_tool = ToolMove(self)
         self.move_tool = ToolMove(self)
         self.move_tool.install(icon=QtGui.QIcon('share/move16.png'), pos=self.ui.menuedit,
         self.move_tool.install(icon=QtGui.QIcon('share/move16.png'), pos=self.ui.menuedit,
                                before=self.ui.menueditorigin)
                                before=self.ui.menueditorigin)
 
 
-        self.cutout_tool = ToolCutOut(self)
+        self.cutout_tool = CutOut(self)
         self.cutout_tool.install(icon=QtGui.QIcon('share/cut16.png'), pos=self.ui.menutool,
         self.cutout_tool.install(icon=QtGui.QIcon('share/cut16.png'), pos=self.ui.menutool,
                                  before=self.measurement_tool.menuAction)
                                  before=self.measurement_tool.menuAction)
 
 

+ 43 - 14
FlatCAMGUI.py

@@ -5224,14 +5224,33 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         grid0.addWidget(self.z_travel_label, 5, 0)
         grid0.addWidget(self.z_travel_label, 5, 0)
         grid0.addWidget(self.z_travel_entry, 5, 1)
         grid0.addWidget(self.z_travel_entry, 5, 1)
 
 
+        # Z toolchange location
+        self.z_toolchange_entry = FCEntry()
+        self.z_toolchange_label = QtWidgets.QLabel("Z Toolchange:")
+        self.z_toolchange_label.setToolTip(
+            "The height (Z) for tool (nozzle) change."
+        )
+        grid0.addWidget(self.z_toolchange_label, 6, 0)
+        grid0.addWidget(self.z_toolchange_entry, 6, 1)
+
+        # X,Y Toolchange location
+        self.xy_toolchange_entry = FCEntry()
+        self.xy_toolchange_label = QtWidgets.QLabel("XY Toolchange:")
+        self.xy_toolchange_label.setToolTip(
+            "The X,Y location for tool (nozzle) change.\n"
+            "The format is (x, y) where x and y are real numbers."
+        )
+        grid0.addWidget(self.xy_toolchange_label, 7, 0)
+        grid0.addWidget(self.xy_toolchange_entry, 7, 1)
+
         # Feedrate X-Y
         # Feedrate X-Y
         self.frxy_entry = FCEntry()
         self.frxy_entry = FCEntry()
         self.frxy_label = QtWidgets.QLabel("Feedrate X-Y:")
         self.frxy_label = QtWidgets.QLabel("Feedrate X-Y:")
         self.frxy_label.setToolTip(
         self.frxy_label.setToolTip(
             "Feedrate (speed) while moving on the X-Y plane."
             "Feedrate (speed) while moving on the X-Y plane."
         )
         )
-        grid0.addWidget(self.frxy_label, 6, 0)
-        grid0.addWidget(self.frxy_entry, 6, 1)
+        grid0.addWidget(self.frxy_label, 8, 0)
+        grid0.addWidget(self.frxy_entry, 8, 1)
 
 
         # Feedrate Z
         # Feedrate Z
         self.frz_entry = FCEntry()
         self.frz_entry = FCEntry()
@@ -5240,8 +5259,18 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
             "Feedrate (speed) while moving vertically\n"
             "Feedrate (speed) while moving vertically\n"
             "(on Z plane)."
             "(on Z plane)."
         )
         )
-        grid0.addWidget(self.frz_label, 7, 0)
-        grid0.addWidget(self.frz_entry, 7, 1)
+        grid0.addWidget(self.frz_label, 9, 0)
+        grid0.addWidget(self.frz_entry, 9, 1)
+
+        # Feedrate Z Dispense
+        self.frz_dispense_entry = FCEntry()
+        self.frz_dispense_label = QtWidgets.QLabel("Feedrate Z Dispense:")
+        self.frz_dispense_label.setToolTip(
+            "Feedrate (speed) while moving up vertically\n"
+            " to Dispense position (on Z plane)."
+        )
+        grid0.addWidget(self.frz_dispense_label, 10, 0)
+        grid0.addWidget(self.frz_dispense_entry, 10, 1)
 
 
         # Spindle Speed Forward
         # Spindle Speed Forward
         self.speedfwd_entry = FCEntry()
         self.speedfwd_entry = FCEntry()
@@ -5250,8 +5279,8 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
             "The dispenser speed while pushing solder paste\n"
             "The dispenser speed while pushing solder paste\n"
             "through the dispenser nozzle."
             "through the dispenser nozzle."
         )
         )
-        grid0.addWidget(self.speedfwd_label, 8, 0)
-        grid0.addWidget(self.speedfwd_entry, 8, 1)
+        grid0.addWidget(self.speedfwd_label, 11, 0)
+        grid0.addWidget(self.speedfwd_entry, 11, 1)
 
 
         # Dwell Forward
         # Dwell Forward
         self.dwellfwd_entry = FCEntry()
         self.dwellfwd_entry = FCEntry()
@@ -5259,8 +5288,8 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         self.dwellfwd_label.setToolTip(
         self.dwellfwd_label.setToolTip(
             "Pause after solder dispensing."
             "Pause after solder dispensing."
         )
         )
-        grid0.addWidget(self.dwellfwd_label, 9, 0)
-        grid0.addWidget(self.dwellfwd_entry, 9, 1)
+        grid0.addWidget(self.dwellfwd_label, 12, 0)
+        grid0.addWidget(self.dwellfwd_entry, 12, 1)
 
 
         # Spindle Speed Reverse
         # Spindle Speed Reverse
         self.speedrev_entry = FCEntry()
         self.speedrev_entry = FCEntry()
@@ -5269,8 +5298,8 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
             "The dispenser speed while retracting solder paste\n"
             "The dispenser speed while retracting solder paste\n"
             "through the dispenser nozzle."
             "through the dispenser nozzle."
         )
         )
-        grid0.addWidget(self.speedrev_label, 10, 0)
-        grid0.addWidget(self.speedrev_entry, 10, 1)
+        grid0.addWidget(self.speedrev_label, 13, 0)
+        grid0.addWidget(self.speedrev_entry, 13, 1)
 
 
         # Dwell Reverse
         # Dwell Reverse
         self.dwellrev_entry = FCEntry()
         self.dwellrev_entry = FCEntry()
@@ -5279,8 +5308,8 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
             "Pause after solder paste dispenser retracted,\n"
             "Pause after solder paste dispenser retracted,\n"
             "to allow pressure equilibrium."
             "to allow pressure equilibrium."
         )
         )
-        grid0.addWidget(self.dwellrev_label, 11, 0)
-        grid0.addWidget(self.dwellrev_entry, 11, 1)
+        grid0.addWidget(self.dwellrev_label, 14, 0)
+        grid0.addWidget(self.dwellrev_entry, 14, 1)
 
 
         # Postprocessors
         # Postprocessors
         pp_label = QtWidgets.QLabel('PostProcessors:')
         pp_label = QtWidgets.QLabel('PostProcessors:')
@@ -5289,8 +5318,8 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         )
         )
 
 
         self.pp_combo = FCComboBox()
         self.pp_combo = FCComboBox()
-        grid0.addWidget(pp_label, 12, 0)
-        grid0.addWidget(self.pp_combo, 12, 1)
+        grid0.addWidget(pp_label, 15, 0)
+        grid0.addWidget(self.pp_combo, 15, 1)
 
 
         self.layout.addStretch()
         self.layout.addStretch()
 
 

+ 23 - 5
FlatCAMObj.py

@@ -4695,6 +4695,9 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
        '''
        '''
         self.exc_cnc_tools = {}
         self.exc_cnc_tools = {}
 
 
+        # flag to store if the CNCJob is part of a special group of CNCJob objects that can't be processed by the
+        # default engine of FlatCAM. They generated by some of tools and are special cases of CNCJob objects.
+        self. special_group = None
 
 
         # for now it show if the plot will be done for multi-tool CNCJob (True) or for single tool
         # for now it show if the plot will be done for multi-tool CNCJob (True) or for single tool
         # (like the one in the TCL Command), False
         # (like the one in the TCL Command), False
@@ -4944,11 +4947,22 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         preamble = str(self.ui.prepend_text.get_value())
         preamble = str(self.ui.prepend_text.get_value())
         postamble = str(self.ui.append_text.get_value())
         postamble = str(self.ui.append_text.get_value())
 
 
-        self.export_gcode(filename, preamble=preamble, postamble=postamble)
+        gc = self.export_gcode(filename, preamble=preamble, postamble=postamble)
+        if gc == 'fail':
+            return
+
         self.app.file_saved.emit("gcode", filename)
         self.app.file_saved.emit("gcode", filename)
         self.app.inform.emit("[success] Machine Code file saved to: %s" % filename)
         self.app.inform.emit("[success] Machine Code file saved to: %s" % filename)
 
 
     def on_modifygcode_button_click(self, *args):
     def on_modifygcode_button_click(self, *args):
+        preamble = str(self.ui.prepend_text.get_value())
+        postamble = str(self.ui.append_text.get_value())
+        gc = self.export_gcode(preamble=preamble, postamble=postamble, to_file=True)
+        if gc == 'fail':
+            return
+        else:
+            self.app.gcode_edited = gc
+
         # add the tab if it was closed
         # add the tab if it was closed
         self.app.ui.plot_tab_area.addTab(self.app.ui.cncjob_tab, "Code Editor")
         self.app.ui.plot_tab_area.addTab(self.app.ui.cncjob_tab, "Code Editor")
 
 
@@ -4959,10 +4973,6 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         # Switch plot_area to CNCJob tab
         # Switch plot_area to CNCJob tab
         self.app.ui.plot_tab_area.setCurrentWidget(self.app.ui.cncjob_tab)
         self.app.ui.plot_tab_area.setCurrentWidget(self.app.ui.cncjob_tab)
 
 
-        preamble = str(self.ui.prepend_text.get_value())
-        postamble = str(self.ui.append_text.get_value())
-        self.app.gcode_edited = self.export_gcode(preamble=preamble, postamble=postamble, to_file=True)
-
         # first clear previous text in text editor (if any)
         # first clear previous text in text editor (if any)
         self.app.ui.code_editor.clear()
         self.app.ui.code_editor.clear()
 
 
@@ -5076,6 +5086,14 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         roland = False
         roland = False
         hpgl = False
         hpgl = False
 
 
+        try:
+            if self.special_group:
+                self.app.inform.emit("[WARNING_NOTCL]This CNCJob object can't be processed because "
+                                     "it is a %s CNCJob object." % str(self.special_group))
+                return 'fail'
+        except AttributeError:
+            pass
+
         # detect if using Roland postprocessor
         # detect if using Roland postprocessor
         try:
         try:
             for key in self.cnc_tools:
             for key in self.cnc_tools:

+ 72 - 0
FlatCAMPostProc.py

@@ -20,6 +20,7 @@ class ABCPostProcRegister(ABCMeta):
             postprocessors[newclass.__name__] = newclass()  # here is your register function
             postprocessors[newclass.__name__] = newclass()  # here is your register function
         return newclass
         return newclass
 
 
+
 class FlatCAMPostProc(object, metaclass=ABCPostProcRegister):
 class FlatCAMPostProc(object, metaclass=ABCPostProcRegister):
     @abstractmethod
     @abstractmethod
     def start_code(self, p):
     def start_code(self, p):
@@ -65,6 +66,77 @@ class FlatCAMPostProc(object, metaclass=ABCPostProcRegister):
     def spindle_stop_code(self,p):
     def spindle_stop_code(self,p):
         pass
         pass
 
 
+
+class FlatCAMPostProc_Tools(object, metaclass=ABCPostProcRegister):
+    @abstractmethod
+    def start_code(self, p):
+        pass
+
+    @abstractmethod
+    def lift_code(self, p):
+        pass
+
+    @abstractmethod
+    def down_z_start_code(self, p):
+        pass
+
+    @abstractmethod
+    def lift_z_dispense_code(self, p):
+        pass
+
+    @abstractmethod
+    def down_z_stop_code(self, p):
+        pass
+
+    @abstractmethod
+    def toolchange_code(self, p):
+        pass
+
+    @abstractmethod
+    def rapid_code(self, p):
+        pass
+
+    @abstractmethod
+    def linear_code(self, p):
+        pass
+
+    @abstractmethod
+    def end_code(self, p):
+        pass
+
+    @abstractmethod
+    def feedrate_xy_code(self, p):
+        pass
+
+    @abstractmethod
+    def feedrate_z_code(self, p):
+        pass
+
+    @abstractmethod
+    def feedrate_z_dispense_code(self,p):
+        pass
+
+    @abstractmethod
+    def spindle_fwd_code(self,p):
+        pass
+
+    @abstractmethod
+    def spindle_rev_code(self,p):
+        pass
+
+    @abstractmethod
+    def spindle_off_code(self,p):
+        pass
+
+    @abstractmethod
+    def dwell_fwd_code(self,p):
+        pass
+
+    @abstractmethod
+    def dwell_rev_code(self,p):
+        pass
+
+
 def load_postprocessors(app):
 def load_postprocessors(app):
     postprocessors_path_search = [os.path.join(app.data_path,'postprocessors','*.py'),
     postprocessors_path_search = [os.path.join(app.data_path,'postprocessors','*.py'),
                                   os.path.join('postprocessors', '*.py')]
                                   os.path.join('postprocessors', '*.py')]

+ 1 - 0
README.md

@@ -19,6 +19,7 @@ CAD program, and create G-Code for Isolation routing.
 - added protection against trying to create a CNCJob from a solder_paste dispenser geometry. This one is different than the default Geometry and can be handled only by SolderPaste Tool.
 - added protection against trying to create a CNCJob from a solder_paste dispenser geometry. This one is different than the default Geometry and can be handled only by SolderPaste Tool.
 - ToolSolderPaste tools (nozzles) now have each it's own settings
 - ToolSolderPaste tools (nozzles) now have each it's own settings
 - creating the camlib functions for the ToolSolderPaste gcode generation functions
 - creating the camlib functions for the ToolSolderPaste gcode generation functions
+- finished work in ToolSolderPaste
 
 
 20.02.2019
 20.02.2019
 
 

+ 135 - 106
camlib.py

@@ -4503,6 +4503,9 @@ class CNCjob(Geometry):
         self.pp_excellon_name = pp_excellon_name
         self.pp_excellon_name = pp_excellon_name
         self.pp_excellon = self.app.postprocessors[self.pp_excellon_name]
         self.pp_excellon = self.app.postprocessors[self.pp_excellon_name]
 
 
+        self.pp_solderpaste_name = None
+
+
         # Controls if the move from Z_Toolchange to Z_Move is done fast with G0 or normally with G1
         # Controls if the move from Z_Toolchange to Z_Move is done fast with G0 or normally with G1
         self.f_plunge = None
         self.f_plunge = None
 
 
@@ -4527,6 +4530,8 @@ class CNCjob(Geometry):
         self.oldx = None
         self.oldx = None
         self.oldy = None
         self.oldy = None
 
 
+        self.tool = 0.0
+
         # 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 Geometry.
         # from Geometry.
@@ -5191,99 +5196,6 @@ class CNCjob(Geometry):
 
 
         return self.gcode
         return self.gcode
 
 
-    def generate_gcode_from_solderpaste_geo(self, **kwargs):
-        """
-               Algorithm to generate from multitool Geometry.
-
-               Algorithm description:
-               ----------------------
-               Uses RTree to find the nearest path to follow.
-
-               :return: Gcode string
-               """
-
-        log.debug("Generate_from_solderpaste_geometry()")
-
-        ## Index first and last points in paths
-        # What points to index.
-        def get_pts(o):
-            return [o.coords[0], o.coords[-1]]
-
-        self.gcode = ""
-
-        if not kwargs:
-            log.debug("camlib.generate_from_solderpaste_geo() --> No tool in the solderpaste geometry.")
-            self.app.inform.emit("[ERROR_NOTCL] There is no tool data in the SolderPaste geometry.")
-
-
-        # this is the tool diameter, it is used as such to accommodate the postprocessor who need the tool diameter
-        # given under the name 'toolC'
-
-        self.postdata['toolC'] = kwargs['tooldia']
-
-        # Initial G-Code
-        pp_solderpaste_name = kwargs['data']['tools_solderpaste_pp'] if kwargs['data']['tools_solderpaste_pp'] else \
-            self.app.defaults['tools_solderpaste_pp']
-        p = self.app.postprocessors[pp_solderpaste_name]
-
-        self.gcode = self.doformat(p.start_code)
-
-        ## Flatten the geometry. Only linear elements (no polygons) remain.
-        flat_geometry = self.flatten(kwargs['solid_geometry'], pathonly=True)
-        log.debug("%d paths" % len(flat_geometry))
-
-        # Create the indexed storage.
-        storage = FlatCAMRTreeStorage()
-        storage.get_points = get_pts
-
-        # Store the geometry
-        log.debug("Indexing geometry before generating G-Code...")
-        for shape in flat_geometry:
-            if shape is not None:
-                storage.insert(shape)
-
-        # kwargs length will tell actually the number of tools used so if we have more than one tools then
-        # we have toolchange event
-        if len(kwargs) > 1:
-            self.gcode += self.doformat(p.toolchange_code)
-        else:
-            self.gcode += self.doformat(p.lift_code, x=0, y=0)  # Move (up) to travel height
-
-        ## Iterate over geometry paths getting the nearest each time.
-        log.debug("Starting SolderPaste G-Code...")
-        path_count = 0
-        current_pt = (0, 0)
-
-        pt, geo = storage.nearest(current_pt)
-
-        try:
-            while True:
-                path_count += 1
-
-                # Remove before modifying, otherwise deletion will fail.
-                storage.remove(geo)
-
-                # If last point in geometry is the nearest but prefer the first one if last point == first point
-                # then reverse coordinates.
-                if pt != geo.coords[0] and pt == geo.coords[-1]:
-                    geo.coords = list(geo.coords)[::-1]
-
-                self.gcode += self.create_soldepaste_gcode(geo, p=p)
-                current_pt = geo.coords[-1]
-                pt, geo = storage.nearest(current_pt)  # Next
-
-        except StopIteration:  # Nothing found in storage.
-            pass
-
-        log.debug("Finishing SolderPste G-Code... %s paths traced." % path_count)
-
-        # Finish
-        self.gcode += self.doformat(p.lift_code)
-        self.gcode += self.doformat(p.end_code)
-
-        return self.gcode
-
-
     def generate_from_geometry_2(self, geometry, append=True,
     def generate_from_geometry_2(self, geometry, append=True,
                                  tooldia=None, offset=0.0, tolerance=0,
                                  tooldia=None, offset=0.0, tolerance=0,
                                  z_cut=1.0, z_move=2.0,
                                  z_cut=1.0, z_move=2.0,
@@ -5536,41 +5448,152 @@ class CNCjob(Geometry):
 
 
         return self.gcode
         return self.gcode
 
 
+    def generate_gcode_from_solderpaste_geo(self, **kwargs):
+        """
+               Algorithm to generate from multitool Geometry.
+
+               Algorithm description:
+               ----------------------
+               Uses RTree to find the nearest path to follow.
+
+               :return: Gcode string
+               """
+
+        log.debug("Generate_from_solderpaste_geometry()")
+
+        ## Index first and last points in paths
+        # What points to index.
+        def get_pts(o):
+            return [o.coords[0], o.coords[-1]]
+
+        self.gcode = ""
+
+        if not kwargs:
+            log.debug("camlib.generate_from_solderpaste_geo() --> No tool in the solderpaste geometry.")
+            self.app.inform.emit("[ERROR_NOTCL] There is no tool data in the SolderPaste geometry.")
+
+
+        # this is the tool diameter, it is used as such to accommodate the postprocessor who need the tool diameter
+        # given under the name 'toolC'
+
+        self.postdata['z_start'] = kwargs['data']['tools_solderpaste_z_start']
+        self.postdata['z_dispense'] = kwargs['data']['tools_solderpaste_z_dispense']
+        self.postdata['z_stop'] = kwargs['data']['tools_solderpaste_z_stop']
+        self.postdata['z_travel'] = kwargs['data']['tools_solderpaste_z_travel']
+        self.postdata['z_toolchange'] = kwargs['data']['tools_solderpaste_z_toolchange']
+        self.postdata['xy_toolchange'] = kwargs['data']['tools_solderpaste_xy_toolchange']
+        self.postdata['frxy'] = kwargs['data']['tools_solderpaste_frxy']
+        self.postdata['frz'] = kwargs['data']['tools_solderpaste_frz']
+        self.postdata['frz_dispense'] = kwargs['data']['tools_solderpaste_frz_dispense']
+        self.postdata['speedfwd'] = kwargs['data']['tools_solderpaste_speedfwd']
+        self.postdata['dwellfwd'] = kwargs['data']['tools_solderpaste_dwellfwd']
+        self.postdata['speedrev'] = kwargs['data']['tools_solderpaste_speedrev']
+        self.postdata['dwellrev'] = kwargs['data']['tools_solderpaste_dwellrev']
+        self.postdata['pp_solderpaste_name'] = kwargs['data']['tools_solderpaste_pp']
+
+        self.postdata['toolC'] = kwargs['tooldia']
+
+        self.pp_solderpaste_name = kwargs['data']['tools_solderpaste_pp'] if kwargs['data']['tools_solderpaste_pp'] \
+            else self.app.defaults['tools_solderpaste_pp']
+        p = self.app.postprocessors[self.pp_solderpaste_name]
+
+        ## Flatten the geometry. Only linear elements (no polygons) remain.
+        flat_geometry = self.flatten(kwargs['solid_geometry'], pathonly=True)
+        log.debug("%d paths" % len(flat_geometry))
+
+        # Create the indexed storage.
+        storage = FlatCAMRTreeStorage()
+        storage.get_points = get_pts
+
+        # Store the geometry
+        log.debug("Indexing geometry before generating G-Code...")
+        for shape in flat_geometry:
+            if shape is not None:
+                storage.insert(shape)
+
+        # Initial G-Code
+        self.gcode = self.doformat(p.start_code)
+        self.gcode += self.doformat(p.spindle_off_code)
+        self.gcode += self.doformat(p.toolchange_code)
+
+        ## Iterate over geometry paths getting the nearest each time.
+        log.debug("Starting SolderPaste G-Code...")
+        path_count = 0
+        current_pt = (0, 0)
+
+        pt, geo = storage.nearest(current_pt)
+
+        try:
+            while True:
+                path_count += 1
+
+                # Remove before modifying, otherwise deletion will fail.
+                storage.remove(geo)
+
+                # If last point in geometry is the nearest but prefer the first one if last point == first point
+                # then reverse coordinates.
+                if pt != geo.coords[0] and pt == geo.coords[-1]:
+                    geo.coords = list(geo.coords)[::-1]
+
+                self.gcode += self.create_soldepaste_gcode(geo, p=p)
+                current_pt = geo.coords[-1]
+                pt, geo = storage.nearest(current_pt)  # Next
+
+        except StopIteration:  # Nothing found in storage.
+            pass
+
+        log.debug("Finishing SolderPste G-Code... %s paths traced." % path_count)
+
+        # Finish
+        self.gcode += self.doformat(p.lift_code)
+        self.gcode += self.doformat(p.end_code)
+
+        return self.gcode
+
     def create_soldepaste_gcode(self, geometry, p):
     def create_soldepaste_gcode(self, geometry, p):
         gcode = ''
         gcode = ''
-        path = self.segment(geometry.coords)
+        path = geometry.coords
 
 
         if type(geometry) == LineString or type(geometry) == LinearRing:
         if type(geometry) == LineString or type(geometry) == LinearRing:
             # Move fast to 1st point
             # Move fast to 1st point
-            gcode += self.doformat(p.rapid_code)  # Move to first point
+            gcode += self.doformat(p.rapid_code, x=path[0][0], y=path[0][1])  # Move to first point
 
 
             # Move down to cutting depth
             # Move down to cutting depth
             gcode += self.doformat(p.feedrate_z_code)
             gcode += self.doformat(p.feedrate_z_code)
             gcode += self.doformat(p.down_z_start_code)
             gcode += self.doformat(p.down_z_start_code)
-            gcode += self.doformat(p.spindle_on_fwd_code) # Start dispensing
+            gcode += self.doformat(p.spindle_fwd_code) # Start dispensing
+            gcode += self.doformat(p.dwell_fwd_code)
+            gcode += self.doformat(p.feedrate_z_dispense_code)
+            gcode += self.doformat(p.lift_z_dispense_code)
             gcode += self.doformat(p.feedrate_xy_code)
             gcode += self.doformat(p.feedrate_xy_code)
 
 
             # Cutting...
             # Cutting...
             for pt in path[1:]:
             for pt in path[1:]:
-                gcode += self.doformat(p.linear_code)  # Linear motion to point
+                gcode += self.doformat(p.linear_code, x=pt[0], y=pt[1])  # Linear motion to point
 
 
             # Up to travelling height.
             # Up to travelling height.
             gcode += self.doformat(p.spindle_off_code) # Stop dispensing
             gcode += self.doformat(p.spindle_off_code) # Stop dispensing
-            gcode += self.doformat(p.spindle_on_rev_code)
+            gcode += self.doformat(p.spindle_rev_code)
             gcode += self.doformat(p.down_z_stop_code)
             gcode += self.doformat(p.down_z_stop_code)
             gcode += self.doformat(p.spindle_off_code)
             gcode += self.doformat(p.spindle_off_code)
+            gcode += self.doformat(p.dwell_rev_code)
+            gcode += self.doformat(p.feedrate_z_code)
             gcode += self.doformat(p.lift_code)
             gcode += self.doformat(p.lift_code)
         elif type(geometry) == Point:
         elif type(geometry) == Point:
-            gcode += self.doformat(p.linear_code)  # Move to first point
+            gcode += self.doformat(p.linear_code, x=path[0][0], y=path[0][1])  # Move to first point
 
 
-            gcode += self.doformat(p.feedrate_z_code)
+            gcode += self.doformat(p.feedrate_z_dispense_code)
             gcode += self.doformat(p.down_z_start_code)
             gcode += self.doformat(p.down_z_start_code)
-            gcode += self.doformat(p.spindle_on_fwd_code) # Start dispensing
-            # TODO A dwell time for dispensing?
+            gcode += self.doformat(p.spindle_fwd_code) # Start dispensing
+            gcode += self.doformat(p.dwell_fwd_code)
+            gcode += self.doformat(p.lift_z_dispense_code)
+
             gcode += self.doformat(p.spindle_off_code)  # Stop dispensing
             gcode += self.doformat(p.spindle_off_code)  # Stop dispensing
-            gcode += self.doformat(p.spindle_on_rev_code)
-            gcode += self.doformat(p.down_z_stop_code)
+            gcode += self.doformat(p.spindle_rev_code)
             gcode += self.doformat(p.spindle_off_code)
             gcode += self.doformat(p.spindle_off_code)
+            gcode += self.doformat(p.down_z_stop_code)
+            gcode += self.doformat(p.dwell_rev_code)
+            gcode += self.doformat(p.feedrate_z_code)
             gcode += self.doformat(p.lift_code)
             gcode += self.doformat(p.lift_code)
         return gcode
         return gcode
 
 
@@ -5685,7 +5708,8 @@ class CNCjob(Geometry):
                 else:
                 else:
                     command['Z'] = 0
                     command['Z'] = 0
 
 
-        elif 'grbl_laser' in self.pp_excellon_name or 'grbl_laser' in self.pp_geometry_name:
+        elif 'grbl_laser' in self.pp_excellon_name or 'grbl_laser' in self.pp_geometry_name or \
+                (self.pp_solderpaste_name is not None and 'Paste' in self.pp_solderpaste_name):
             match_lsr = re.search(r"X([\+-]?\d+.[\+-]?\d+)\s*Y([\+-]?\d+.[\+-]?\d+)", gline)
             match_lsr = re.search(r"X([\+-]?\d+.[\+-]?\d+)\s*Y([\+-]?\d+.[\+-]?\d+)", gline)
             if match_lsr:
             if match_lsr:
                 command['X'] = float(match_lsr.group(1).replace(" ", ""))
                 command['X'] = float(match_lsr.group(1).replace(" ", ""))
@@ -5699,7 +5723,12 @@ class CNCjob(Geometry):
                     command['Z'] = 1
                     command['Z'] = 1
                 else:
                 else:
                     command['Z'] = 0
                     command['Z'] = 0
-
+        elif self.pp_solderpaste is not None:
+            if 'Paste' in self.pp_solderpaste:
+                match_paste = re.search(r"X([\+-]?\d+.[\+-]?\d+)\s*Y([\+-]?\d+.[\+-]?\d+)", gline)
+                if match_paste:
+                    command['X'] = float(match_paste.group(1).replace(" ", ""))
+                    command['Y'] = float(match_paste.group(2).replace(" ", ""))
         else:
         else:
             match = re.search(r'^\s*([A-Z])\s*([\+\-\.\d\s]+)', gline)
             match = re.search(r'^\s*([A-Z])\s*([\+\-\.\d\s]+)', gline)
             while match:
             while match:

+ 1 - 7
flatcamTools/ToolCutOut.py

@@ -1,14 +1,9 @@
 from FlatCAMTool import FlatCAMTool
 from FlatCAMTool import FlatCAMTool
-from copy import copy,deepcopy
 from ObjectCollection import *
 from ObjectCollection import *
 from FlatCAMApp import *
 from FlatCAMApp import *
-from PyQt5 import QtGui, QtCore, QtWidgets
-from GUIElements import IntEntry, RadioSet, LengthEntry, FloatEntry
 
 
-from FlatCAMObj import FlatCAMGeometry, FlatCAMExcellon, FlatCAMGerber
 
 
-
-class ToolCutOut(FlatCAMTool):
+class CutOut(FlatCAMTool):
 
 
     toolName = "Cutout PCB"
     toolName = "Cutout PCB"
 
 
@@ -472,4 +467,3 @@ class ToolCutOut(FlatCAMTool):
 
 
     def reset_fields(self):
     def reset_fields(self):
         self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
         self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-

+ 0 - 2
flatcamTools/ToolNonCopperClear.py

@@ -1,7 +1,5 @@
 from FlatCAMTool import FlatCAMTool
 from FlatCAMTool import FlatCAMTool
 from copy import copy,deepcopy
 from copy import copy,deepcopy
-# from GUIElements import IntEntry, RadioSet, FCEntry
-# from FlatCAMObj import FlatCAMGeometry, FlatCAMExcellon, FlatCAMGerber
 from ObjectCollection import *
 from ObjectCollection import *
 import time
 import time
 
 

+ 80 - 10
flatcamTools/ToolSolderPaste.py

@@ -9,7 +9,7 @@ from FlatCAMCommon import LoudDict
 from FlatCAMObj import FlatCAMGeometry, FlatCAMExcellon, FlatCAMGerber
 from FlatCAMObj import FlatCAMGeometry, FlatCAMExcellon, FlatCAMGerber
 
 
 
 
-class ToolSolderPaste(FlatCAMTool):
+class SolderPaste(FlatCAMTool):
 
 
     toolName = "Solder Paste Tool"
     toolName = "Solder Paste Tool"
 
 
@@ -177,6 +177,23 @@ class ToolSolderPaste(FlatCAMTool):
         )
         )
         self.gcode_form_layout.addRow(self.z_travel_label, self.z_travel_entry)
         self.gcode_form_layout.addRow(self.z_travel_label, self.z_travel_entry)
 
 
+        # Z toolchange location
+        self.z_toolchange_entry = FCEntry()
+        self.z_toolchange_label = QtWidgets.QLabel("Z Toolchange:")
+        self.z_toolchange_label.setToolTip(
+            "The height (Z) for tool (nozzle) change."
+        )
+        self.gcode_form_layout.addRow(self.z_toolchange_label, self.z_toolchange_entry)
+
+        # X,Y Toolchange location
+        self.xy_toolchange_entry = FCEntry()
+        self.xy_toolchange_label = QtWidgets.QLabel("XY Toolchange:")
+        self.xy_toolchange_label.setToolTip(
+            "The X,Y location for tool (nozzle) change.\n"
+            "The format is (x, y) where x and y are real numbers."
+        )
+        self.gcode_form_layout.addRow(self.xy_toolchange_label, self.xy_toolchange_entry)
+
         # Feedrate X-Y
         # Feedrate X-Y
         self.frxy_entry = FCEntry()
         self.frxy_entry = FCEntry()
         self.frxy_label = QtWidgets.QLabel("Feedrate X-Y:")
         self.frxy_label = QtWidgets.QLabel("Feedrate X-Y:")
@@ -194,6 +211,15 @@ class ToolSolderPaste(FlatCAMTool):
         )
         )
         self.gcode_form_layout.addRow(self.frz_label, self.frz_entry)
         self.gcode_form_layout.addRow(self.frz_label, self.frz_entry)
 
 
+        # Feedrate Z Dispense
+        self.frz_dispense_entry = FCEntry()
+        self.frz_dispense_label = QtWidgets.QLabel("Feedrate Z Dispense:")
+        self.frz_dispense_label.setToolTip(
+            "Feedrate (speed) while moving up vertically\n"
+            " to Dispense position (on Z plane)."
+        )
+        self.gcode_form_layout.addRow(self.frz_dispense_label, self.frz_dispense_entry)
+
         # Spindle Speed Forward
         # Spindle Speed Forward
         self.speedfwd_entry = FCEntry()
         self.speedfwd_entry = FCEntry()
         self.speedfwd_label = QtWidgets.QLabel("Spindle Speed FWD:")
         self.speedfwd_label = QtWidgets.QLabel("Spindle Speed FWD:")
@@ -332,6 +358,8 @@ class ToolSolderPaste(FlatCAMTool):
         self.options = LoudDict()
         self.options = LoudDict()
         self.form_fields = {}
         self.form_fields = {}
 
 
+        self.units = ''
+
         ## Signals
         ## Signals
         self.addtool_btn.clicked.connect(self.on_tool_add)
         self.addtool_btn.clicked.connect(self.on_tool_add)
         self.deltool_btn.clicked.connect(self.on_tool_delete)
         self.deltool_btn.clicked.connect(self.on_tool_delete)
@@ -366,8 +394,11 @@ class ToolSolderPaste(FlatCAMTool):
             "tools_solderpaste_z_dispense": self.z_dispense_entry,
             "tools_solderpaste_z_dispense": self.z_dispense_entry,
             "tools_solderpaste_z_stop": self.z_stop_entry,
             "tools_solderpaste_z_stop": self.z_stop_entry,
             "tools_solderpaste_z_travel":  self.z_travel_entry,
             "tools_solderpaste_z_travel":  self.z_travel_entry,
+            "tools_solderpaste_z_toolchange": self.z_toolchange_entry,
+            "tools_solderpaste_xy_toolchange": self.xy_toolchange_entry,
             "tools_solderpaste_frxy":  self.frxy_entry,
             "tools_solderpaste_frxy":  self.frxy_entry,
             "tools_solderpaste_frz":  self.frz_entry,
             "tools_solderpaste_frz":  self.frz_entry,
+            "tools_solderpaste_frz_dispense": self.frz_dispense_entry,
             "tools_solderpaste_speedfwd": self.speedfwd_entry,
             "tools_solderpaste_speedfwd": self.speedfwd_entry,
             "tools_solderpaste_dwellfwd": self.dwellfwd_entry,
             "tools_solderpaste_dwellfwd": self.dwellfwd_entry,
             "tools_solderpaste_speedrev": self.speedrev_entry,
             "tools_solderpaste_speedrev": self.speedrev_entry,
@@ -707,7 +738,6 @@ class ToolSolderPaste(FlatCAMTool):
         self.ui_disconnect()
         self.ui_disconnect()
 
 
         deleted_tools_list = []
         deleted_tools_list = []
-
         if all:
         if all:
             self.tools.clear()
             self.tools.clear()
             self.build_ui()
             self.build_ui()
@@ -810,12 +840,17 @@ class ToolSolderPaste(FlatCAMTool):
 
 
                 diagonal_1 = LineString([min, max])
                 diagonal_1 = LineString([min, max])
                 diagonal_2 = LineString([min_r, max_r])
                 diagonal_2 = LineString([min_r, max_r])
-                round_diag_1 = round(diagonal_1.intersection(p).length, 2)
-                round_diag_2 = round(diagonal_2.intersection(p).length, 2)
+                if self.units == 'MM':
+                    round_diag_1 = round(diagonal_1.intersection(p).length, 1)
+                    round_diag_2 = round(diagonal_2.intersection(p).length, 1)
+                else:
+                    round_diag_1 = round(diagonal_1.intersection(p).length, 2)
+                    round_diag_2 = round(diagonal_2.intersection(p).length, 2)
 
 
                 if round_diag_1 == round_diag_2:
                 if round_diag_1 == round_diag_2:
                     l = distance((xmin, ymin), (xmax, ymin))
                     l = distance((xmin, ymin), (xmax, ymin))
                     h = distance((xmin, ymin), (xmin, ymax))
                     h = distance((xmin, ymin), (xmin, ymax))
+
                     if offset >= l /2 or offset >= h / 2:
                     if offset >= l /2 or offset >= h / 2:
                         return "fail"
                         return "fail"
                     if l > h:
                     if l > h:
@@ -859,7 +894,7 @@ class ToolSolderPaste(FlatCAMTool):
                 geo_obj.tools[tooluid]['offset'] = 'Path'
                 geo_obj.tools[tooluid]['offset'] = 'Path'
                 geo_obj.tools[tooluid]['offset_value'] = 0.0
                 geo_obj.tools[tooluid]['offset_value'] = 0.0
                 geo_obj.tools[tooluid]['type'] = 'SolderPaste'
                 geo_obj.tools[tooluid]['type'] = 'SolderPaste'
-                geo_obj.tools[tooluid]['tool_type'] = 'Dispenser Nozzle'
+                geo_obj.tools[tooluid]['tool_type'] = 'DN'
 
 
                 for g in work_geo:
                 for g in work_geo:
                     if type(g) == MultiPolygon:
                     if type(g) == MultiPolygon:
@@ -914,6 +949,8 @@ class ToolSolderPaste(FlatCAMTool):
         # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
         # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
 
 
     def on_view_gcode(self):
     def on_view_gcode(self):
+        time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
+
         # add the tab if it was closed
         # add the tab if it was closed
         self.app.ui.plot_tab_area.addTab(self.app.ui.cncjob_tab, "Code Editor")
         self.app.ui.plot_tab_area.addTab(self.app.ui.cncjob_tab, "Code Editor")
 
 
@@ -923,15 +960,40 @@ class ToolSolderPaste(FlatCAMTool):
         name = self.cnc_obj_combo.currentText()
         name = self.cnc_obj_combo.currentText()
         obj = self.app.collection.get_by_name(name)
         obj = self.app.collection.get_by_name(name)
 
 
+        try:
+            if obj.special_group != 'solder_paste_tool':
+                self.app.inform.emit("[WARNING_NOTCL]This CNCJob object can't be processed. "
+                                     "NOT a solder_paste_tool CNCJob object.")
+                return
+        except AttributeError:
+            self.app.inform.emit("[WARNING_NOTCL]This CNCJob object can't be processed. "
+                                 "NOT a solder_paste_tool CNCJob object.")
+            return
+
+        gcode = '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \
+                (str(self.app.version), str(self.app.version_date)) + '\n'
+
+        gcode += '(Name: ' + str(name) + ')\n'
+        gcode += '(Type: ' + "G-code from " + str(obj.options['type']) + " for Solder Paste dispenser" + ')\n'
+
+        # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
+        #     gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n'
+
+        gcode += '(Units: ' + self.units.upper() + ')\n' + "\n"
+        gcode += '(Created on ' + time_str + ')\n' + '\n'
+
+        for tool in obj.cnc_tools:
+            gcode += obj.cnc_tools[tool]['gcode']
+
         # then append the text from GCode to the text editor
         # then append the text from GCode to the text editor
         try:
         try:
-            file = StringIO(obj.gcode)
+            lines = StringIO(gcode)
         except:
         except:
             self.app.inform.emit("[ERROR_NOTCL] No Gcode in the object...")
             self.app.inform.emit("[ERROR_NOTCL] No Gcode in the object...")
             return
             return
 
 
         try:
         try:
-            for line in file:
+            for line in lines:
                 proc_line = str(line).strip('\n')
                 proc_line = str(line).strip('\n')
                 self.app.ui.code_editor.append(proc_line)
                 self.app.ui.code_editor.append(proc_line)
         except Exception as e:
         except Exception as e:
@@ -949,6 +1011,11 @@ class ToolSolderPaste(FlatCAMTool):
         name = self.cnc_obj_combo.currentText()
         name = self.cnc_obj_combo.currentText()
         obj = self.app.collection.get_by_name(name)
         obj = self.app.collection.get_by_name(name)
 
 
+        if obj.special_group != 'solder_paste_tool':
+            self.app.inform.emit("[WARNING_NOTCL]This CNCJob object can't be processed. "
+                                 "NOT a solder_paste_tool CNCJob object.")
+            return
+
         _filter_ = "G-Code Files (*.nc);;G-Code Files (*.txt);;G-Code Files (*.tap);;G-Code Files (*.cnc);;" \
         _filter_ = "G-Code Files (*.nc);;G-Code Files (*.txt);;G-Code Files (*.tap);;G-Code Files (*.cnc);;" \
                    "G-Code Files (*.g-code);;All Files (*.*)"
                    "G-Code Files (*.g-code);;All Files (*.*)"
 
 
@@ -978,7 +1045,8 @@ class ToolSolderPaste(FlatCAMTool):
         gcode += '(Units: ' + self.units.upper() + ')\n' + "\n"
         gcode += '(Units: ' + self.units.upper() + ')\n' + "\n"
         gcode += '(Created on ' + time_str + ')\n' + '\n'
         gcode += '(Created on ' + time_str + ')\n' + '\n'
 
 
-        gcode += obj.gcode
+        for tool in obj.cnc_tools:
+            gcode += obj.cnc_tools[tool]['gcode']
         lines = StringIO(gcode)
         lines = StringIO(gcode)
 
 
         ## Write
         ## Write
@@ -1040,6 +1108,7 @@ class ToolSolderPaste(FlatCAMTool):
             job_obj.multitool = True
             job_obj.multitool = True
             job_obj.multigeo = True
             job_obj.multigeo = True
             job_obj.cnc_tools.clear()
             job_obj.cnc_tools.clear()
+            job_obj.special_group = 'solder_paste_tool'
 
 
             job_obj.options['xmin'] = xmin
             job_obj.options['xmin'] = xmin
             job_obj.options['ymin'] = ymin
             job_obj.options['ymin'] = ymin
@@ -1063,13 +1132,14 @@ class ToolSolderPaste(FlatCAMTool):
 
 
                 job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
                 job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
                 job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
                 job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
+                job_obj.tool = int(tooluid_key)
 
 
                 # Propagate options
                 # Propagate options
                 job_obj.options["tooldia"] = tool_dia
                 job_obj.options["tooldia"] = tool_dia
                 job_obj.options['tool_dia'] = tool_dia
                 job_obj.options['tool_dia'] = tool_dia
 
 
                 ### CREATE GCODE ###
                 ### CREATE GCODE ###
-                res = job_obj.generate_gcode_from_solderpaste_geo(**tool_cnc_dict)
+                res = job_obj.generate_gcode_from_solderpaste_geo(**tooluid_value)
 
 
                 if res == 'fail':
                 if res == 'fail':
                     log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed")
                     log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed")
@@ -1089,7 +1159,7 @@ class ToolSolderPaste(FlatCAMTool):
                 app_obj.progress.emit(80)
                 app_obj.progress.emit(80)
 
 
                 job_obj.cnc_tools.update({
                 job_obj.cnc_tools.update({
-                    tooluid_key: copy.deepcopy(tool_cnc_dict)
+                    tooluid_key: deepcopy(tool_cnc_dict)
                 })
                 })
                 tool_cnc_dict.clear()
                 tool_cnc_dict.clear()
 
 

+ 2 - 2
flatcamTools/__init__.py

@@ -6,13 +6,13 @@ from flatcamTools.ToolFilm import Film
 from flatcamTools.ToolMove import ToolMove
 from flatcamTools.ToolMove import ToolMove
 from flatcamTools.ToolDblSided import DblSidedTool
 from flatcamTools.ToolDblSided import DblSidedTool
 
 
-from flatcamTools.ToolCutOut import ToolCutOut
+from flatcamTools.ToolCutOut import CutOut
 from flatcamTools.ToolCalculators import ToolCalculator
 from flatcamTools.ToolCalculators import ToolCalculator
 from flatcamTools.ToolProperties import Properties
 from flatcamTools.ToolProperties import Properties
 from flatcamTools.ToolImage import ToolImage
 from flatcamTools.ToolImage import ToolImage
 from flatcamTools.ToolPaint import ToolPaint
 from flatcamTools.ToolPaint import ToolPaint
 from flatcamTools.ToolNonCopperClear import NonCopperClear
 from flatcamTools.ToolNonCopperClear import NonCopperClear
 from flatcamTools.ToolTransform import ToolTransform
 from flatcamTools.ToolTransform import ToolTransform
-from flatcamTools.ToolSolderPaste import ToolSolderPaste
+from flatcamTools.ToolSolderPaste import SolderPaste
 
 
 from flatcamTools.ToolShell import FCShell
 from flatcamTools.ToolShell import FCShell

+ 76 - 121
postprocessors/Paste_1.py

@@ -1,14 +1,15 @@
 from FlatCAMPostProc import *
 from FlatCAMPostProc import *
 
 
 
 
-class Paste_1(FlatCAMPostProc):
+class Paste_1(FlatCAMPostProc_Tools):
 
 
     coordinate_format = "%.*f"
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
     feedrate_format = '%.*f'
 
 
     def start_code(self, p):
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
         units = ' ' + str(p['units']).lower()
-        coords_xy = p['toolchange_xy']
+        coords_xy = [float(eval(a)) for a in p['xy_toolchange'].split(",")]
+
         gcode = ''
         gcode = ''
 
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
@@ -16,181 +17,135 @@ class Paste_1(FlatCAMPostProc):
         ymin = '%.*f' % (p.coords_decimals, p['options']['ymin'])
         ymin = '%.*f' % (p.coords_decimals, p['options']['ymin'])
         ymax = '%.*f' % (p.coords_decimals, p['options']['ymax'])
         ymax = '%.*f' % (p.coords_decimals, p['options']['ymax'])
 
 
-        if str(p['options']['type']) == 'Geometry':
-            gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
-
-        gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
-
-        if str(p['options']['type']) == 'Geometry':
-            gcode += '(Feedrate_Z: ' + str(p['feedrate_z']) + units + '/min' + ')\n'
+        gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
+        gcode += '(Feedrate_XY: ' + str(p['frxy']) + units + '/min' + ')\n'
+        gcode += '(Feedrate_Z: ' + str(p['frz']) + units + '/min' + ')\n'
+        gcode += '(Feedrate_Z_Dispense: ' + str(p['frz_dispense']) + units + '/min' + ')\n'
 
 
-        gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
-        gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
+        gcode += '(Z_Dispense_Start: ' + str(p['z_start']) + units + ')\n'
+        gcode += '(Z_Dispense: ' + str(p['z_dispense']) + units + ')\n'
+        gcode += '(Z_Dispense_Stop: ' + str(p['z_stop']) + units + ')\n'
+        gcode += '(Z_Travel: ' + str(p['z_travel']) + units + ')\n'
+        gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
 
 
-        if str(p['options']['type']) == 'Geometry':
-            if p['multidepth'] is True:
-                gcode += '(DepthPerCut: ' + str(p['depthpercut']) + units + ' <=>' + \
-                         str(math.ceil(abs(p['z_cut']) / p['depthpercut'])) + ' passes' + ')\n'
+        gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
 
 
-        gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
-        gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n'
-
-        if coords_xy is not None:
-            gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], 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['endz']) + 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 += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' + '\n'
-        else:
-            gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'
+        if 'Paste' in p.pp_solderpaste_name:
+            gcode += '(Postprocessor SolderPaste Dispensing Geometry: ' + str(p.pp_solderpaste_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 += '(Spindle Speed: %s RPM)\n' % str(p['spindlespeed'])
+        gcode += '(Spindle Speed FWD: %s RPM)\n' % str(p['speedfwd'])
+        gcode += '(Spindle Speed REV: %s RPM)\n' % str(p['speedrev'])
+        gcode += '(Dwell FWD: %s RPM)\n' % str(p['dwellfwd'])
+        gcode += '(Dwell REV: %s RPM)\n' % str(p['dwellrev'])
 
 
         gcode += ('G20\n' if p.units.upper() == 'IN' else 'G21\n')
         gcode += ('G20\n' if p.units.upper() == 'IN' else 'G21\n')
         gcode += 'G90\n'
         gcode += 'G90\n'
         gcode += 'G94\n'
         gcode += 'G94\n'
-
         return gcode
         return gcode
 
 
-    def startz_code(self, p):
-        if p.startz is not None:
-            return 'G00 Z' + self.coordinate_format%(p.coords_decimals, p.startz)
-        else:
-            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, float(p['z_travel']))
+
+    def down_z_start_code(self, p):
+        return 'G01 Z' + self.coordinate_format%(p.coords_decimals, float(p['z_start']))
+
+    def lift_z_dispense_code(self, p):
+        return 'G01 Z' + self.coordinate_format%(p.coords_decimals, float(p['z_dispense']))
 
 
-    def down_code(self, p):
-        return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut)
+    def down_z_stop_code(self, p):
+        return 'G01 Z' + self.coordinate_format%(p.coords_decimals, float(p['z_stop']))
 
 
     def toolchange_code(self, p):
     def toolchange_code(self, p):
-        toolchangez = p.toolchangez
-        toolchangexy = p.toolchange_xy
-        f_plunge = p.f_plunge
+        toolchangez = float(p['z_toolchange'])
+        toolchangexy = [float(eval(a)) for a in p['xy_toolchange'].split(",")]
         gcode = ''
         gcode = ''
 
 
         if toolchangexy is not None:
         if toolchangexy is not None:
             toolchangex = toolchangexy[0]
             toolchangex = toolchangexy[0]
             toolchangey = toolchangexy[1]
             toolchangey = toolchangexy[1]
 
 
-        no_drills = 1
-
-        if int(p.tool) == 1 and p.startz is not None:
-            toolchangez = p.startz
-
         if p.units.upper() == 'MM':
         if p.units.upper() == 'MM':
-            toolC_formatted = format(p.toolC, '.2f')
+            toolC_formatted = format(float(p['toolC']), '.2f')
         else:
         else:
-            toolC_formatted = format(p.toolC, '.4f')
-
-        if str(p['options']['type']) == 'Excellon':
-            for i in p['options']['Tools_in_use']:
-                if i[0] == p.tool:
-                    no_drills = i[2]
-
-            if toolchangexy is not None:
-                gcode = """
-M5
-G00 Z{toolchangez}
-G00 X{toolchangex} Y{toolchangey}                
-T{tool}
-M6
-(MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
-M0""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-             toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
-             toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
-             tool=int(p.tool),
-             t_drills=no_drills,
-             toolC=toolC_formatted)
-            else:
-                gcode = """
-M5       
-G00 Z{toolchangez}
-T{tool}
-M6
-(MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
-M0""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
-             tool=int(p.tool),
-             t_drills=no_drills,
-             toolC=toolC_formatted)
-            if f_plunge is True:
-                gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
-            return gcode
+            toolC_formatted = format(float(p['toolC']), '.4f')
 
 
-        else:
-            if toolchangexy is not None:
-                gcode = """
-M5
+        if toolchangexy is not None:
+            gcode = """
 G00 Z{toolchangez}
 G00 Z{toolchangez}
 G00 X{toolchangex} Y{toolchangey}
 G00 X{toolchangex} Y{toolchangey}
 T{tool}
 T{tool}
 M6    
 M6    
-(MSG, Change to Tool Dia = {toolC})
-M0""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
-             toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
-             toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
-             tool=int(p.tool),
-             toolC=toolC_formatted)
-            else:
-                gcode = """
-M5
+(MSG, Change to Tool with Nozzle Dia = {toolC})
+M0
+""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
+           toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
+           toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+           tool=int(int(p.tool)),
+           toolC=toolC_formatted)
+
+        else:
+            gcode = """
 G00 Z{toolchangez}
 G00 Z{toolchangez}
 T{tool}
 T{tool}
 M6    
 M6    
-(MSG, Change to Tool Dia = {toolC})
-M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
-             tool=int(p.tool),
-             toolC=toolC_formatted)
-
-            if f_plunge is True:
-                gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
-            return gcode
+(MSG, Change to Tool with Nozzle Dia = {toolC})
+M0
+""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+           tool=int(int(p.tool)),
+           toolC=toolC_formatted)
 
 
-    def up_to_zero_code(self, p):
-        return 'G01 Z0'
+        return gcode
 
 
     def position_code(self, p):
     def position_code(self, p):
         return ('X' + self.coordinate_format + ' Y' + self.coordinate_format) % \
         return ('X' + self.coordinate_format + ' Y' + self.coordinate_format) % \
                (p.coords_decimals, p.x, p.coords_decimals, p.y)
                (p.coords_decimals, p.x, p.coords_decimals, p.y)
 
 
     def rapid_code(self, p):
     def rapid_code(self, p):
-        return ('G00 ' + self.position_code(p)).format(**p)
+        return ('G00 ' + self.position_code(p)).format(**p) + '\nG00 Z' + \
+               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 = p['toolchange_xy']
-        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
+        coords_xy = [float(eval(a)) for a in p['xy_toolchange'].split(",")]
+        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_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
+    def feedrate_xy_code(self, p):
+        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, float(p['frxy'])))
 
 
     def feedrate_z_code(self, p):
     def feedrate_z_code(self, p):
-        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate_z))
+        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, float(p['frz'])))
+
+    def feedrate_z_dispense_code(self, p):
+        return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, float(p['frz_dispense'])))
 
 
-    def spindle_code(self, p):
+    def spindle_fwd_code(self, p):
         if p.spindlespeed:
         if p.spindlespeed:
-            return 'M03 S' + str(p.spindlespeed)
+            return 'M03 S' + str(float(p['speedfwd']))
         else:
         else:
             return 'M03'
             return 'M03'
 
 
-    def dwell_code(self, p):
-        if p.dwelltime:
-            return 'G4 P' + str(p.dwelltime)
+    def spindle_rev_code(self, p):
+        if p.spindlespeed:
+            return 'M04 S' + str(float(p['speedrev']))
+        else:
+            return 'M04'
 
 
-    def spindle_stop_code(self,p):
+    def spindle_off_code(self,p):
         return 'M05'
         return 'M05'
+
+    def dwell_fwd_code(self, p):
+        if p.dwelltime:
+            return 'G4 P' + str(float(p['dwellfwd']))
+
+    def dwell_rev_code(self, p):
+        if p.dwelltime:
+            return 'G4 P' + str(float(p['dwellrev']))