Преглед изворни кода

- finished work in ToolSolderPaste

Marius Stanciu пре 7 година
родитељ
комит
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_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_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_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_dwellfwd": self.tools_defaults_form.tools_solderpaste_group.dwellfwd_entry,
             "tools_solderpaste_speedrev": self.tools_defaults_form.tools_solderpaste_group.speedrev_entry,
@@ -499,16 +502,13 @@ class App(QtCore.QObject):
 
         }
 
-
         #############################
         #### LOAD POSTPROCESSORS ####
         #############################
 
-
         self.postprocessors = load_postprocessors(self)
 
         for name in list(self.postprocessors.keys()):
-
             # 'Paste' postprocessors are to be used only in the Solder Paste Dispensing Tool
             if name.partition('_')[0] == 'Paste':
                 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_stop": 0.005,
             "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_frz": 3.0,
+            "tools_solderpaste_frz_dispense": 1.0,
             "tools_solderpaste_speedfwd": 20,
             "tools_solderpaste_dwellfwd": 1,
             "tools_solderpaste_speedrev": 10,
             "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.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.move_tool = ToolMove(self)
         self.move_tool.install(icon=QtGui.QIcon('share/move16.png'), pos=self.ui.menuedit,
                                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,
                                  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_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
         self.frxy_entry = FCEntry()
         self.frxy_label = QtWidgets.QLabel("Feedrate X-Y:")
         self.frxy_label.setToolTip(
             "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
         self.frz_entry = FCEntry()
@@ -5240,8 +5259,18 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
             "Feedrate (speed) while moving vertically\n"
             "(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
         self.speedfwd_entry = FCEntry()
@@ -5250,8 +5279,8 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
             "The dispenser speed while pushing solder paste\n"
             "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
         self.dwellfwd_entry = FCEntry()
@@ -5259,8 +5288,8 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         self.dwellfwd_label.setToolTip(
             "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
         self.speedrev_entry = FCEntry()
@@ -5269,8 +5298,8 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
             "The dispenser speed while retracting solder paste\n"
             "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
         self.dwellrev_entry = FCEntry()
@@ -5279,8 +5308,8 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
             "Pause after solder paste dispenser retracted,\n"
             "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
         pp_label = QtWidgets.QLabel('PostProcessors:')
@@ -5289,8 +5318,8 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
         )
 
         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()
 

+ 23 - 5
FlatCAMObj.py

@@ -4695,6 +4695,9 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
        '''
         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
         # (like the one in the TCL Command), False
@@ -4944,11 +4947,22 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         preamble = str(self.ui.prepend_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.inform.emit("[success] Machine Code file saved to: %s" % filename)
 
     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
         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
         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)
         self.app.ui.code_editor.clear()
 
@@ -5076,6 +5086,14 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         roland = 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
         try:
             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
         return newclass
 
+
 class FlatCAMPostProc(object, metaclass=ABCPostProcRegister):
     @abstractmethod
     def start_code(self, p):
@@ -65,6 +66,77 @@ class FlatCAMPostProc(object, metaclass=ABCPostProcRegister):
     def spindle_stop_code(self,p):
         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):
     postprocessors_path_search = [os.path.join(app.data_path,'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.
 - ToolSolderPaste tools (nozzles) now have each it's own settings
 - creating the camlib functions for the ToolSolderPaste gcode generation functions
+- finished work in ToolSolderPaste
 
 20.02.2019
 

+ 135 - 106
camlib.py

@@ -4503,6 +4503,9 @@ class CNCjob(Geometry):
         self.pp_excellon_name = 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
         self.f_plunge = None
 
@@ -4527,6 +4530,8 @@ class CNCjob(Geometry):
         self.oldx = None
         self.oldy = None
 
+        self.tool = 0.0
+
         # Attributes to be included in serialization
         # Always append to it because it carries contents
         # from Geometry.
@@ -5191,99 +5196,6 @@ class CNCjob(Geometry):
 
         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,
                                  tooldia=None, offset=0.0, tolerance=0,
                                  z_cut=1.0, z_move=2.0,
@@ -5536,41 +5448,152 @@ class CNCjob(Geometry):
 
         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):
         gcode = ''
-        path = self.segment(geometry.coords)
+        path = geometry.coords
 
         if type(geometry) == LineString or type(geometry) == LinearRing:
             # 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
             gcode += self.doformat(p.feedrate_z_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)
 
             # Cutting...
             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.
             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.spindle_off_code)
+            gcode += self.doformat(p.dwell_rev_code)
+            gcode += self.doformat(p.feedrate_z_code)
             gcode += self.doformat(p.lift_code)
         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.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_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.down_z_stop_code)
+            gcode += self.doformat(p.dwell_rev_code)
+            gcode += self.doformat(p.feedrate_z_code)
             gcode += self.doformat(p.lift_code)
         return gcode
 
@@ -5685,7 +5708,8 @@ class CNCjob(Geometry):
                 else:
                     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)
             if match_lsr:
                 command['X'] = float(match_lsr.group(1).replace(" ", ""))
@@ -5699,7 +5723,12 @@ class CNCjob(Geometry):
                     command['Z'] = 1
                 else:
                     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:
             match = re.search(r'^\s*([A-Z])\s*([\+\-\.\d\s]+)', gline)
             while match:

+ 1 - 7
flatcamTools/ToolCutOut.py

@@ -1,14 +1,9 @@
 from FlatCAMTool import FlatCAMTool
-from copy import copy,deepcopy
 from ObjectCollection 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"
 
@@ -472,4 +467,3 @@ class ToolCutOut(FlatCAMTool):
 
     def reset_fields(self):
         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 copy import copy,deepcopy
-# from GUIElements import IntEntry, RadioSet, FCEntry
-# from FlatCAMObj import FlatCAMGeometry, FlatCAMExcellon, FlatCAMGerber
 from ObjectCollection import *
 import time
 

+ 80 - 10
flatcamTools/ToolSolderPaste.py

@@ -9,7 +9,7 @@ from FlatCAMCommon import LoudDict
 from FlatCAMObj import FlatCAMGeometry, FlatCAMExcellon, FlatCAMGerber
 
 
-class ToolSolderPaste(FlatCAMTool):
+class SolderPaste(FlatCAMTool):
 
     toolName = "Solder Paste Tool"
 
@@ -177,6 +177,23 @@ class ToolSolderPaste(FlatCAMTool):
         )
         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
         self.frxy_entry = FCEntry()
         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)
 
+        # 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
         self.speedfwd_entry = FCEntry()
         self.speedfwd_label = QtWidgets.QLabel("Spindle Speed FWD:")
@@ -332,6 +358,8 @@ class ToolSolderPaste(FlatCAMTool):
         self.options = LoudDict()
         self.form_fields = {}
 
+        self.units = ''
+
         ## Signals
         self.addtool_btn.clicked.connect(self.on_tool_add)
         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_stop": self.z_stop_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_frz":  self.frz_entry,
+            "tools_solderpaste_frz_dispense": self.frz_dispense_entry,
             "tools_solderpaste_speedfwd": self.speedfwd_entry,
             "tools_solderpaste_dwellfwd": self.dwellfwd_entry,
             "tools_solderpaste_speedrev": self.speedrev_entry,
@@ -707,7 +738,6 @@ class ToolSolderPaste(FlatCAMTool):
         self.ui_disconnect()
 
         deleted_tools_list = []
-
         if all:
             self.tools.clear()
             self.build_ui()
@@ -810,12 +840,17 @@ class ToolSolderPaste(FlatCAMTool):
 
                 diagonal_1 = LineString([min, max])
                 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:
                     l = distance((xmin, ymin), (xmax, ymin))
                     h = distance((xmin, ymin), (xmin, ymax))
+
                     if offset >= l /2 or offset >= h / 2:
                         return "fail"
                     if l > h:
@@ -859,7 +894,7 @@ class ToolSolderPaste(FlatCAMTool):
                 geo_obj.tools[tooluid]['offset'] = 'Path'
                 geo_obj.tools[tooluid]['offset_value'] = 0.0
                 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:
                     if type(g) == MultiPolygon:
@@ -914,6 +949,8 @@ class ToolSolderPaste(FlatCAMTool):
         # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
 
     def on_view_gcode(self):
+        time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
+
         # add the tab if it was closed
         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()
         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
         try:
-            file = StringIO(obj.gcode)
+            lines = StringIO(gcode)
         except:
             self.app.inform.emit("[ERROR_NOTCL] No Gcode in the object...")
             return
 
         try:
-            for line in file:
+            for line in lines:
                 proc_line = str(line).strip('\n')
                 self.app.ui.code_editor.append(proc_line)
         except Exception as e:
@@ -949,6 +1011,11 @@ class ToolSolderPaste(FlatCAMTool):
         name = self.cnc_obj_combo.currentText()
         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);;" \
                    "G-Code Files (*.g-code);;All Files (*.*)"
 
@@ -978,7 +1045,8 @@ class ToolSolderPaste(FlatCAMTool):
         gcode += '(Units: ' + self.units.upper() + ')\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)
 
         ## Write
@@ -1040,6 +1108,7 @@ class ToolSolderPaste(FlatCAMTool):
             job_obj.multitool = True
             job_obj.multigeo = True
             job_obj.cnc_tools.clear()
+            job_obj.special_group = 'solder_paste_tool'
 
             job_obj.options['xmin'] = xmin
             job_obj.options['ymin'] = ymin
@@ -1063,13 +1132,14 @@ class ToolSolderPaste(FlatCAMTool):
 
                 job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
                 job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
+                job_obj.tool = int(tooluid_key)
 
                 # Propagate options
                 job_obj.options["tooldia"] = tool_dia
                 job_obj.options['tool_dia'] = tool_dia
 
                 ### 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':
                     log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed")
@@ -1089,7 +1159,7 @@ class ToolSolderPaste(FlatCAMTool):
                 app_obj.progress.emit(80)
 
                 job_obj.cnc_tools.update({
-                    tooluid_key: copy.deepcopy(tool_cnc_dict)
+                    tooluid_key: deepcopy(tool_cnc_dict)
                 })
                 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.ToolDblSided import DblSidedTool
 
-from flatcamTools.ToolCutOut import ToolCutOut
+from flatcamTools.ToolCutOut import CutOut
 from flatcamTools.ToolCalculators import ToolCalculator
 from flatcamTools.ToolProperties import Properties
 from flatcamTools.ToolImage import ToolImage
 from flatcamTools.ToolPaint import ToolPaint
 from flatcamTools.ToolNonCopperClear import NonCopperClear
 from flatcamTools.ToolTransform import ToolTransform
-from flatcamTools.ToolSolderPaste import ToolSolderPaste
+from flatcamTools.ToolSolderPaste import SolderPaste
 
 from flatcamTools.ToolShell import FCShell

+ 76 - 121
postprocessors/Paste_1.py

@@ -1,14 +1,15 @@
 from FlatCAMPostProc import *
 
 
-class Paste_1(FlatCAMPostProc):
+class Paste_1(FlatCAMPostProc_Tools):
 
     coordinate_format = "%.*f"
     feedrate_format = '%.*f'
 
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
-        coords_xy = p['toolchange_xy']
+        coords_xy = [float(eval(a)) for a in p['xy_toolchange'].split(",")]
+
         gcode = ''
 
         xmin = '%.*f' % (p.coords_decimals, p['options']['xmin'])
@@ -16,181 +17,135 @@ class Paste_1(FlatCAMPostProc):
         ymin = '%.*f' % (p.coords_decimals, p['options']['ymin'])
         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 += '(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 += 'G90\n'
         gcode += 'G94\n'
-
         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):
-        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):
-        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 = ''
 
         if toolchangexy is not None:
             toolchangex = toolchangexy[0]
             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':
-            toolC_formatted = format(p.toolC, '.2f')
+            toolC_formatted = format(float(p['toolC']), '.2f')
         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 X{toolchangex} Y{toolchangey}
 T{tool}
 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}
 T{tool}
 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):
         return ('X' + self.coordinate_format + ' Y' + self.coordinate_format) % \
                (p.coords_decimals, p.x, p.coords_decimals, p.y)
 
     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):
         return ('G01 ' + self.position_code(p)).format(**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:
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         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):
-        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:
-            return 'M03 S' + str(p.spindlespeed)
+            return 'M03 S' + str(float(p['speedfwd']))
         else:
             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'
+
+    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']))