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

- creating the camlib functions for the ToolSolderPaste gcode generation functions

Marius Stanciu 7 лет назад
Родитель
Сommit
d5768d3b34
4 измененных файлов с 196 добавлено и 178 удалено
  1. 2 1
      FlatCAMApp.py
  2. 2 1
      README.md
  3. 133 2
      camlib.py
  4. 59 174
      flatcamTools/ToolSolderPaste.py

+ 2 - 1
FlatCAMApp.py

@@ -748,7 +748,8 @@ class App(QtCore.QObject):
             "tools_solderpaste_speedfwd": 20,
             "tools_solderpaste_dwellfwd": 1,
             "tools_solderpaste_speedrev": 10,
-            "tools_solderpaste_dwellrev": 1
+            "tools_solderpaste_dwellrev": 1,
+            "tools_solderpaste_pp": ''
         })
 
         ###############################

+ 2 - 1
README.md

@@ -17,7 +17,8 @@ CAD program, and create G-Code for Isolation routing.
 - added the functions for GCode View and GCode Save in Tool SolderPaste
 - some work in the Gcode generation function in Tool SolderPaste
 - 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.
-- ToolSoderPaste 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
 
 20.02.2019
 

+ 133 - 2
camlib.py

@@ -5026,7 +5026,7 @@ class CNCjob(Geometry):
         :param depthpercut: Maximum depth in each pass.
         :param extracut: Adds (or not) an extra cut at the end of each path
             overlapping the first point in path to ensure complete copper removal
-        :return: None
+        :return: GCode - string
         """
 
         log.debug("Generate_from_multitool_geometry()")
@@ -5191,6 +5191,99 @@ 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,
@@ -5443,13 +5536,51 @@ class CNCjob(Geometry):
 
         return self.gcode
 
+    def create_soldepaste_gcode(self, geometry, p):
+        gcode = ''
+        path = self.segment(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
+
+            # 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.feedrate_xy_code)
+
+            # Cutting...
+            for pt in path[1:]:
+                gcode += self.doformat(p.linear_code)  # 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.down_z_stop_code)
+            gcode += self.doformat(p.spindle_off_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.feedrate_z_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_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_off_code)
+            gcode += self.doformat(p.lift_code)
+        return gcode
+
     def create_gcode_single_pass(self, geometry, extracut, tolerance):
         # G-code. Note: self.linear2gcode() and self.point2gcode() will lower and raise the tool every time.
         gcode_single_pass = ''
 
         if type(geometry) == LineString or type(geometry) == LinearRing:
             if extracut is False:
-                gcode_single_pass = self.linear2gcode(geometry, tolerance=tolerance, )
+                gcode_single_pass = self.linear2gcode(geometry, tolerance=tolerance)
             else:
                 if geometry.is_ring:
                     gcode_single_pass = self.linear2gcode_extra(geometry, tolerance=tolerance)

+ 59 - 174
flatcamTools/ToolSolderPaste.py

@@ -109,20 +109,10 @@ class ToolSolderPaste(FlatCAMTool):
             "Generate solder paste dispensing geometry."
         )
 
-        step1_lbl = QtWidgets.QLabel("<b>STEP 1:</b>")
-        step1_lbl.setToolTip(
-            "First step is to select a number of nozzle tools for usage\n"
-            "and then create a solder paste dispensing geometry out of an\n"
-            "Solder Paste Mask Gerber file."
-        )
-
         grid0.addWidget(self.addtool_btn, 0, 0)
         # grid2.addWidget(self.copytool_btn, 0, 1)
         grid0.addWidget(self.deltool_btn, 0, 2)
 
-        grid0.addWidget(step1_lbl, 2, 0)
-        grid0.addWidget(self.soldergeo_btn, 2, 2)
-
         ## Form Layout
         geo_form_layout = QtWidgets.QFormLayout()
         self.layout.addLayout(geo_form_layout)
@@ -259,16 +249,6 @@ class ToolSolderPaste(FlatCAMTool):
             "on PCB pads."
         )
 
-        step2_lbl = QtWidgets.QLabel("<b>STEP 2:</b>")
-        step2_lbl.setToolTip(
-            "Second step is to select a solder paste dispensing geometry,\n"
-            "set the CAM parameters and then generate a CNCJob object which\n"
-            "will pe painted on canvas in blue color."
-        )
-
-        grid1.addWidget(step2_lbl, 0, 0)
-        grid1.addWidget(self.solder_gcode_btn, 0, 2)
-
         ## Form Layout
         cnc_form_layout = QtWidgets.QFormLayout()
         self.gcode_box.addLayout(cnc_form_layout)
@@ -300,6 +280,25 @@ class ToolSolderPaste(FlatCAMTool):
         grid2 = QtWidgets.QGridLayout()
         self.save_gcode_box.addLayout(grid2)
 
+        step1_lbl = QtWidgets.QLabel("<b>STEP 1:</b>")
+        step1_lbl.setToolTip(
+            "First step is to select a number of nozzle tools for usage\n"
+            "and then create a solder paste dispensing geometry out of an\n"
+            "Solder Paste Mask Gerber file."
+        )
+        grid2.addWidget(step1_lbl, 0, 0)
+        grid2.addWidget(self.soldergeo_btn, 0, 2)
+
+        step2_lbl = QtWidgets.QLabel("<b>STEP 2:</b>")
+        step2_lbl.setToolTip(
+            "Second step is to select a solder paste dispensing geometry,\n"
+            "set the CAM parameters and then generate a CNCJob object which\n"
+            "will pe painted on canvas in blue color."
+        )
+
+        grid2.addWidget(step2_lbl, 1, 0)
+        grid2.addWidget(self.solder_gcode_btn, 1, 2)
+
         self.solder_gcode_view_btn = QtWidgets.QPushButton("View GCode")
         self.solder_gcode_view_btn.setToolTip(
             "View the generated GCode for Solder Paste dispensing\n"
@@ -318,9 +317,9 @@ class ToolSolderPaste(FlatCAMTool):
             "a solder paste dispensing geometry, and then view/save it's GCode."
         )
 
-        grid2.addWidget(step3_lbl, 0, 0)
-        grid2.addWidget(self.solder_gcode_view_btn, 0, 2)
-        grid2.addWidget(self.solder_gcode_save_btn, 1, 2)
+        grid2.addWidget(step3_lbl, 2, 0)
+        grid2.addWidget(self.solder_gcode_view_btn, 2, 2)
+        grid2.addWidget(self.solder_gcode_save_btn, 3, 2)
 
         self.layout.addStretch()
 
@@ -853,6 +852,15 @@ class ToolSolderPaste(FlatCAMTool):
                         tooluid = int(uid)
                         break
 
+                geo_obj.tools[tooluid] = {}
+                geo_obj.tools[tooluid]['tooldia'] = tool
+                geo_obj.tools[tooluid]['data'] = self.tools[tooluid]['data']
+                geo_obj.tools[tooluid]['solid_geometry'] = []
+                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'
+
                 for g in work_geo:
                     if type(g) == MultiPolygon:
                         for poly in g:
@@ -860,16 +868,8 @@ class ToolSolderPaste(FlatCAMTool):
                             if geom != 'fail':
                                 try:
                                     geo_obj.tools[tooluid]['solid_geometry'].append(geom)
-                                except KeyError:
-                                    geo_obj.tools[tooluid] = {}
-                                    geo_obj.tools[tooluid]['solid_geometry'] = []
-                                    geo_obj.tools[tooluid]['solid_geometry'].append(geom)
-                                geo_obj.tools[tooluid]['tooldia'] = tool
-                                geo_obj.tools[tooluid]['offset'] = 'Path'
-                                geo_obj.tools[tooluid]['offset_value'] = 0.0
-                                geo_obj.tools[tooluid]['type'] = ' '
-                                geo_obj.tools[tooluid]['tool_type'] = ' '
-                                geo_obj.tools[tooluid]['data'] = {}
+                                except Exception as e:
+                                    log.debug('ToolSoderPaste.on_create_geo() --> %s' % str(e))
                             else:
                                 rest_geo.append(poly)
                     elif type(g) == Polygon:
@@ -877,16 +877,8 @@ class ToolSolderPaste(FlatCAMTool):
                         if geom != 'fail':
                             try:
                                 geo_obj.tools[tooluid]['solid_geometry'].append(geom)
-                            except KeyError:
-                                geo_obj.tools[tooluid] = {}
-                                geo_obj.tools[tooluid]['solid_geometry'] = []
-                                geo_obj.tools[tooluid]['solid_geometry'].append(geom)
-                            geo_obj.tools[tooluid]['tooldia'] = tool
-                            geo_obj.tools[tooluid]['offset'] = 'Path'
-                            geo_obj.tools[tooluid]['offset_value'] = 0.0
-                            geo_obj.tools[tooluid]['type'] = ' '
-                            geo_obj.tools[tooluid]['tool_type'] = ' '
-                            geo_obj.tools[tooluid]['data'] = {}
+                            except Exception as e:
+                                log.debug('ToolSoderPaste.on_create_geo() --> %s' % str(e))
                         else:
                             rest_geo.append(g)
 
@@ -1004,23 +996,11 @@ class ToolSolderPaste(FlatCAMTool):
 
     def on_create_gcode(self, use_thread=True):
         """
-                Creates a multi-tool CNCJob out of this Geometry object.
-                The actual work is done by the target FlatCAMCNCjob object's
-                `generate_from_geometry_2()` method.
-
-                :param z_cut: Cut depth (negative)
-                :param z_move: Hight of the tool when travelling (not cutting)
-                :param feedrate: Feed rate while cutting on X - Y plane
-                :param feedrate_z: Feed rate while cutting on Z plane
-                :param feedrate_rapid: Feed rate while moving with rapids
-                :param tooldia: Tool diameter
-                :param outname: Name of the new object
-                :param spindlespeed: Spindle speed (RPM)
-                :param ppname_g Name of the postprocessor
-                :return: None
-                """
+        Creates a multi-tool CNCJob out of this Geometry object.
+        :return: None
+        """
 
-        name = self.obj_combo.currentText()
+        name = self.geo_obj_combo.currentText()
         obj = self.app.collection.get_by_name(name)
 
         if obj.special_group != 'solder_paste_tool':
@@ -1031,7 +1011,8 @@ class ToolSolderPaste(FlatCAMTool):
         multitool_gcode = ''
 
         # use the name of the first tool selected in self.geo_tools_table which has the diameter passed as tool_dia
-        outname = "%s_%s" % (name, 'cnc_solderpaste')
+        originar_name = obj.options['name'].rpartition('_')[0]
+        outname = "%s_%s" % (originar_name, '_cnc_solderpaste')
 
         try:
             xmin = obj.options['xmin']
@@ -1053,9 +1034,7 @@ class ToolSolderPaste(FlatCAMTool):
             assert isinstance(job_obj, FlatCAMCNCjob), \
                 "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj)
 
-            # count the tools
-            tool_cnt = 0
-            dia_cnc_dict = {}
+            tool_cnc_dict = {}
 
             # this turn on the FlatCAMCNCJob plot for multiple tools
             job_obj.multitool = True
@@ -1067,146 +1046,52 @@ class ToolSolderPaste(FlatCAMTool):
             job_obj.options['xmax'] = xmax
             job_obj.options['ymax'] = ymax
 
-
-            # try:
-            #     job_obj.feedrate_probe = float(self.options["feedrate_probe"])
-            # except ValueError:
-            #     # try to convert comma to decimal point. if it's still not working error message and return
-            #     try:
-            #         job_obj.feedrate_rapid = float(self.options["feedrate_probe"].replace(',', '.'))
-            #     except ValueError:
-            #         self.app.inform.emit(
-            #             '[ERROR_NOTCL]Wrong value format for self.defaults["feedrate_probe"] '
-            #             'or self.options["feedrate_probe"]')
-
-            # make sure that trying to make a CNCJob from an empty file is not creating an app crash
             a = 0
-            for tooluid_key in self.tools:
-                if self.tools[tooluid_key]['solid_geometry'] is None:
+            for tooluid_key in obj.tools:
+                if obj.tools[tooluid_key]['solid_geometry'] is None:
                     a += 1
-            if a == len(self.tools):
+            if a == len(obj.tools):
                 self.app.inform.emit('[ERROR_NOTCL]Cancelled. Empty file, it has no geometry...')
                 return 'fail'
 
-            for tooluid_key in self.tools:
-                tool_cnt += 1
+            for tooluid_key, tooluid_value in obj.tools.items():
                 app_obj.progress.emit(20)
 
                 # find the tool_dia associated with the tooluid_key
-                tool_dia = self.sel_tools[tooluid_key]['tooldia']
-                tool_solid_geometry = self.tools[tooluid_key]['solid_geometry']
-
-                for diadict_key, diadict_value in self.sel_tools[tooluid_key].items():
-                    if diadict_key == 'tooldia':
-                        tooldia_val = float('%.4f' % float(diadict_value))
-                        dia_cnc_dict.update({
-                            diadict_key: tooldia_val
-                        })
-                    if diadict_key == 'offset':
-                        dia_cnc_dict.update({
-                            diadict_key: ''
-                        })
-
-                    if diadict_key == 'type':
-                        dia_cnc_dict.update({
-                            diadict_key: ''
-                        })
-
-                    if diadict_key == 'tool_type':
-                        dia_cnc_dict.update({
-                            diadict_key: ''
-                        })
-
-                    if diadict_key == 'data':
-                        for data_key, data_value in diadict_value.items():
-                            if data_key == "multidepth":
-                                multidepth = data_value
-                            if data_key == "depthperpass":
-                                depthpercut = data_value
-
-                            if data_key == "extracut":
-                                extracut = data_value
-                            if data_key == "startz":
-                                startz = data_value
-                            if data_key == "endz":
-                                endz = data_value
-
-                            if data_key == "toolchangez":
-                                toolchangez = data_value
-                            if data_key == "toolchangexy":
-                                toolchangexy = data_value
-                            if data_key == "toolchange":
-                                toolchange = data_value
-
-                            if data_key == "cutz":
-                                z_cut = data_value
-                            if data_key == "travelz":
-                                z_move = data_value
-
-                            if data_key == "feedrate":
-                                feedrate = data_value
-                            if data_key == "feedrate_z":
-                                feedrate_z = data_value
-                            if data_key == "feedrate_rapid":
-                                feedrate_rapid = data_value
-
-                            if data_key == "ppname_g":
-                                pp_geometry_name = data_value
-
-                            if data_key == "spindlespeed":
-                                spindlespeed = data_value
-                            if data_key == "dwell":
-                                dwell = data_value
-                            if data_key == "dwelltime":
-                                dwelltime = data_value
-
-                        datadict = copy.deepcopy(diadict_value)
-                        dia_cnc_dict.update({
-                            diadict_key: datadict
-                        })
+                tool_dia = tooluid_value['tooldia']
+                tool_cnc_dict = deepcopy(tooluid_value)
 
                 job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"]
                 job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"]
 
                 # Propagate options
-                job_obj.options["tooldia"] = tooldia_val
-                job_obj.options['type'] = 'Geometry'
-                job_obj.options['tool_dia'] = tooldia_val
-
-                app_obj.progress.emit(40)
-
-                res = job_obj.generate_from_multitool_geometry(
-                    tool_solid_geometry, tooldia=tooldia_val, offset=0.0,
-                    tolerance=0.0005, z_cut=z_cut, z_move=z_move,
-                    feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
-                    spindlespeed=spindlespeed, dwell=dwell, dwelltime=dwelltime,
-                    multidepth=multidepth, depthpercut=depthpercut,
-                    extracut=extracut, startz=startz, endz=endz,
-                    toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
-                    pp_geometry_name=pp_geometry_name,
-                    tool_no=tool_cnt)
+                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)
 
                 if res == 'fail':
                     log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed")
                     return 'fail'
                 else:
-                    dia_cnc_dict['gcode'] = res
+                    tool_cnc_dict['gcode'] = res
 
-                dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse()
+                ### PARSE GCODE ###
+                tool_cnc_dict['gcode_parsed'] = job_obj.gcode_parse()
 
                 # TODO this serve for bounding box creation only; should be optimized
-                dia_cnc_dict['solid_geometry'] = cascaded_union([geo['geom'] for geo in dia_cnc_dict['gcode_parsed']])
+                tool_cnc_dict['solid_geometry'] = cascaded_union([geo['geom'] for geo in tool_cnc_dict['gcode_parsed']])
 
                 # tell gcode_parse from which point to start drawing the lines depending on what kind of
                 # object is the source of gcode
                 job_obj.toolchange_xy_type = "geometry"
-
                 app_obj.progress.emit(80)
 
                 job_obj.cnc_tools.update({
-                    tooluid_key: copy.deepcopy(dia_cnc_dict)
+                    tooluid_key: copy.deepcopy(tool_cnc_dict)
                 })
-                dia_cnc_dict.clear()
+                tool_cnc_dict.clear()
 
         if use_thread:
             # To be run in separate thread