diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 2f709a89..5b462aca 100644 --- a/FlatCAMApp.py +++ b/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) diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 85a6b439..b9626ab3 100644 --- a/FlatCAMGUI.py +++ b/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() diff --git a/FlatCAMObj.py b/FlatCAMObj.py index ac19f726..699f9eb3 100644 --- a/FlatCAMObj.py +++ b/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: diff --git a/FlatCAMPostProc.py b/FlatCAMPostProc.py index 41fd9f84..cc7a3d52 100644 --- a/FlatCAMPostProc.py +++ b/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')] diff --git a/README.md b/README.md index c1978e9d..14c57144 100644 --- a/README.md +++ b/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 diff --git a/camlib.py b/camlib.py index 028c9833..0686bc0a 100644 --- a/camlib.py +++ b/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: diff --git a/flatcamTools/ToolCutOut.py b/flatcamTools/ToolCutOut.py index d6f901f4..593368cc 100644 --- a/flatcamTools/ToolCutOut.py +++ b/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())) - diff --git a/flatcamTools/ToolNonCopperClear.py b/flatcamTools/ToolNonCopperClear.py index a1783ba7..bf42f8f7 100644 --- a/flatcamTools/ToolNonCopperClear.py +++ b/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 diff --git a/flatcamTools/ToolSolderPaste.py b/flatcamTools/ToolSolderPaste.py index 5a0cebe3..1316897d 100644 --- a/flatcamTools/ToolSolderPaste.py +++ b/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() diff --git a/flatcamTools/__init__.py b/flatcamTools/__init__.py index 8ec21eb7..cd9dd56c 100644 --- a/flatcamTools/__init__.py +++ b/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 diff --git a/postprocessors/Paste_1.py b/postprocessors/Paste_1.py index 66e994b5..f9e7edd0 100644 --- a/postprocessors/Paste_1.py +++ b/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 += '(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: ' + str(p['feedrate']) + units + '/min' + ')\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': - gcode += '(Feedrate_Z: ' + str(p['feedrate_z']) + units + '/min' + ')\n' + gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n' - gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n' - gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n' - - if str(p['options']['type']) == 'Geometry': - if p['multidepth'] is True: - gcode += '(DepthPerCut: ' + str(p['depthpercut']) + units + ' <=>' + \ - str(math.ceil(abs(p['z_cut']) / p['depthpercut'])) + ' passes' + ')\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_code(self, p): - return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut) + 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_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') + toolC_formatted = format(float(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 - - 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) +(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) - if f_plunge is True: - gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move) - return gcode - - 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 spindle_code(self, p): + def feedrate_z_dispense_code(self, p): + return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, float(p['frz_dispense']))) + + 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']))