From c7b96b7a68a5d74d578d56de0c8b1b75b67a38a9 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 30 Oct 2020 02:01:33 +0200 Subject: [PATCH] - fixed issue with preamble not being inserted when used alone - modified the way that the start GCode is stored such that now the bug in GCode Editor that did not allowed selection of the first tool is now solved --- CHANGELOG.md | 2 + appObjects/FlatCAMCNCJob.py | 63 +- appObjects/FlatCAMGeometry.py | 36 +- camlib.py | 844 ++++++++++++++------------- tclCommands/TclCommandDrillcncjob.py | 1 + 5 files changed, 500 insertions(+), 446 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 252b9ec3..63bc1a6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ CHANGELOG for FlatCAM beta - fixed the usage for Tools Database in Unix-like OS's - done some modest refactoring - fixed the Search and Add feature in Geometry Object UI +- fixed issue with preamble not being inserted when used alone +- modified the way that the start GCode is stored such that now the bug in GCode Editor that did not allowed selection of the first tool is now solved 28.10.2020 diff --git a/appObjects/FlatCAMCNCJob.py b/appObjects/FlatCAMCNCJob.py index 6efa49f6..caeae2bc 100644 --- a/appObjects/FlatCAMCNCJob.py +++ b/appObjects/FlatCAMCNCJob.py @@ -2164,7 +2164,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): if preamble == '': preamble = self.app.defaults["cncjob_prepend"] if postamble == '': - preamble = self.app.defaults["cncjob_append"] + postamble = self.app.defaults["cncjob_append"] try: if self.special_group: @@ -2190,7 +2190,6 @@ class CNCJobObject(FlatCAMObj, CNCjob): gcode = '' if include_header is False: - g = preamble # detect if using multi-tool and make the Gcode summation correctly for each case if self.multitool is True: for tooluid_key in self.cnc_tools: @@ -2201,7 +2200,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): else: gcode += self.gcode - g = g + gcode + postamble + g = preamble + '\n' + gcode + '\n' + postamble else: # search for the GCode beginning which is usually a G20 or G21 # fix so the preamble gets inserted in between the comments header and the actual start of GCODE @@ -2251,39 +2250,51 @@ class CNCJobObject(FlatCAMObj, CNCjob): break if hpgl: - processed_gcode = '' + processed_body_gcode = '' pa_re = re.compile(r"^PA\s*(-?\d+\.\d*),?\s*(-?\d+\.\d*)*;?$") + + # process body gcode for gline in gcode.splitlines(): match = pa_re.search(gline) if match: x_int = int(float(match.group(1))) y_int = int(float(match.group(2))) new_line = 'PA%d,%d;\n' % (x_int, y_int) - processed_gcode += new_line + processed_body_gcode += new_line else: - processed_gcode += gline + '\n' + processed_body_gcode += gline + '\n' - gcode = processed_gcode - g = self.gc_header + '\n' + preamble + '\n' + gcode + postamble + end_gcode + gcode = processed_body_gcode + g = self.gc_header + '\n' + self.gc_start + '\n' + preamble + '\n' + \ + gcode + '\n' + postamble + end_gcode else: - try: - g_idx = gcode.index('G94') - if preamble != '' and postamble != '': - g = self.gc_header + gcode[:g_idx + 3] + '\n' + preamble + '\n' + \ - gcode[(g_idx + 3):] + postamble + end_gcode - elif preamble == '': - g = self.gc_header + gcode[:g_idx + 3] + '\n' + \ - gcode[(g_idx + 3):] + postamble + end_gcode - elif postamble == '': - g = self.gc_header + gcode[:g_idx + 3] + '\n' + preamble + '\n' + \ - gcode[(g_idx + 3):] + end_gcode - else: - g = self.gc_header + gcode[:g_idx + 3] + gcode[(g_idx + 3):] + end_gcode - except ValueError: - self.app.inform.emit('[ERROR_NOTCL] %s' % - _("G-code does not have a G94 code.\n" - "Append Code snippet will not be used..")) - g = self.gc_header + '\n' + gcode + postamble + end_gcode + # try: + # g_idx = gcode.index('G94') + # if preamble != '' and postamble != '': + # g = self.gc_header + gcode[:g_idx + 3] + '\n' + preamble + '\n' + \ + # gcode[(g_idx + 3):] + postamble + end_gcode + # elif preamble == '': + # g = self.gc_header + gcode[:g_idx + 3] + '\n' + \ + # gcode[(g_idx + 3):] + postamble + end_gcode + # elif postamble == '': + # g = self.gc_header + gcode[:g_idx + 3] + '\n' + preamble + '\n' + \ + # gcode[(g_idx + 3):] + end_gcode + # else: + # g = self.gc_header + gcode[:g_idx + 3] + gcode[(g_idx + 3):] + end_gcode + # except ValueError: + # self.app.inform.emit('[ERROR_NOTCL] %s' % + # _("G-code does not have a G94 code.\n" + # "Append Code snippet will not be used..")) + # g = self.gc_header + '\n' + gcode + postamble + end_gcode + if preamble != '' and postamble != '': + g = self.gc_header + self.gc_start + '\n' + preamble + '\n' + gcode + '\n' + \ + postamble + '\n' + end_gcode + if preamble == '': + g = self.gc_header + self.gc_start + '\n' + gcode + '\n' + postamble + '\n' + end_gcode + if postamble == '': + g = self.gc_header + self.gc_start + '\n' + preamble + '\n' + gcode + '\n' + end_gcode + if preamble == '' and postamble == '': + g = self.gc_header + self.gc_start + '\n' + gcode + '\n' + end_gcode # if toolchange custom is used, replace M6 code with the code from the Toolchange Custom Text box # if self.ui.toolchange_cb.get_value() is True: diff --git a/appObjects/FlatCAMGeometry.py b/appObjects/FlatCAMGeometry.py index 39e60550..b210dd29 100644 --- a/appObjects/FlatCAMGeometry.py +++ b/appObjects/FlatCAMGeometry.py @@ -2172,10 +2172,13 @@ class GeometryObject(FlatCAMObj, Geometry): job_obj.options['type'] = 'Geometry' job_obj.options['tool_dia'] = tooldia_val + tool_lst = list(tools_dict.keys()) + is_first = True if tooluid_key == tool_lst[0] else False + # it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially # to a value of 0.0005 which is 20 times less than 0.01 tol = float(self.app.defaults['global_tolerance']) / 20 - res = job_obj.generate_from_geometry_2( + res, start_gcode = job_obj.generate_from_geometry_2( self, tooldia=tooldia_val, offset=tool_offset, tolerance=tol, z_cut=z_cut, z_move=z_move, feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid, @@ -2184,13 +2187,15 @@ class GeometryObject(FlatCAMObj, Geometry): extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz, endxy=endxy, toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy, pp_geometry_name=pp_geometry_name, - tool_no=tool_cnt) + tool_no=tool_cnt, is_first=is_first) if res == 'fail': log.debug("GeometryObject.mtool_gen_cncjob() --> generate_from_geometry2() failed") return 'fail' - else: - dia_cnc_dict['gcode'] = res + + dia_cnc_dict['gcode'] = res + if start_gcode != '': + job_obj.gc_start = start_gcode total_gcode += res @@ -2216,7 +2221,7 @@ class GeometryObject(FlatCAMObj, Geometry): }) dia_cnc_dict.clear() - job_obj.source_file = total_gcode + job_obj.source_file = job_obj.gc_start + total_gcode # Object initialization function for app.app_obj.new_object() # RUNNING ON SEPARATE THREAD! @@ -2513,18 +2518,17 @@ class GeometryObject(FlatCAMObj, Geometry): # it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially # to a value of 0.0005 which is 20 times less than 0.01 tol = float(self.app.defaults['global_tolerance']) / 20 - res = job_obj.generate_from_geometry_2(self, tooldia=tooldia, offset=offset, tolerance=tol, - 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=depthperpass, - toolchange=toolchange, toolchangez=toolchangez, - toolchangexy=toolchangexy, - extracut=extracut, extracut_length=extracut_length, - startz=startz, endz=endz, endxy=endxy, - pp_geometry_name=ppname_g) + res, start_gcode = job_obj.generate_from_geometry_2( + self, tooldia=tooldia, offset=offset, tolerance=tol, 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=depthperpass, toolchange=toolchange, + toolchangez=toolchangez, toolchangexy=toolchangexy, extracut=extracut, extracut_length=extracut_length, + startz=startz, endz=endz, endxy=endxy, pp_geometry_name=ppname_g, is_first=True) - job_obj.source_file = res + if start_gcode != '': + job_obj.gc_start = start_gcode + + job_obj.source_file = start_gcode + res # 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" diff --git a/camlib.py b/camlib.py index 34c4b8a2..0ee202dc 100644 --- a/camlib.py +++ b/camlib.py @@ -3109,6 +3109,7 @@ class CNCjob(Geometry): self.app.inform.emit('[WARNING] %s.' % _("The Cut Z parameter is zero. There will be no cut, aborting")) return 'fail' + # used in Tool Drilling def excellon_tool_gcode_gen(self, tool, points, tools, first_pt, is_first=False, is_last=False, opt_type='T', toolchange=False): """ @@ -3295,7 +3296,7 @@ class CNCjob(Geometry): start_gcode = '' if is_first: start_gcode = self.doformat(p.start_code) - t_gcode += start_gcode + # t_gcode += start_gcode # do the ToolChange event t_gcode += self.doformat(p.z_feedrate_code) @@ -3308,11 +3309,9 @@ class CNCjob(Geometry): if self.dwell is True: t_gcode += self.doformat(p.dwell_code) - current_tooldia = float('%.*f' % (self.decimals, float(tools[tool]["tooldia"]))) + current_tooldia = self.app.dec_format(float(tools[tool]["tooldia"]), self.decimals) self.app.inform.emit( - '%s: %s%s.' % (_("Starting G-Code for tool with diameter"), - str(current_tooldia), - str(self.units)) + '%s: %s%s.' % (_("Starting G-Code for tool with diameter"), str(current_tooldia), str(self.units)) ) # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -3340,6 +3339,7 @@ class CNCjob(Geometry): # graceful abort requested by the user raise grace + # if we use Traveling Salesman Algorithm as an optimization if opt_type == 'T': locx = point[0] locy = point[1] @@ -3447,21 +3447,395 @@ class CNCjob(Geometry): self.app.inform.emit('%s %s' % (_("Finished G-Code generation for tool:"), str(tool))) return t_gcode, (locx, locy), start_gcode - def generate_from_excellon_by_tool(self, exobj, tools="all", order='fwd', use_ui=False): + # used in Geometry (and soon in Tool Milling) + def geometry_tool_gcode_gen(self, tool, tools, first_pt, tolerance, is_first=False, is_last=False, + toolchange=False): + """ + Algorithm to generate GCode from multitool Geometry. + + :param tool: tool number for which to generate GCode + :type tool: int + :param tools: a dictionary holding all the tools and data + :type tools: dict + :param first_pt: a tuple of coordinates for the first point of the current tool + :type first_pt: tuple + :param tolerance: geometry tolerance + :type tolerance: + :param is_first: if the current tool is the first tool (for this we need to add start GCode) + :type is_first: bool + :param is_last: if the current tool is the last tool (for this we need to add the end GCode) + :type is_last: bool + :param toolchange: add toolchange event + :type toolchange: bool + :return: GCode + :rtype: str + """ + + log.debug("geometry_tool_gcode_gen()") + + t_gcode = '' + temp_solid_geometry = [] + + # The Geometry from which we create GCode + geometry = tools[tool]['solid_geometry'] + # ## Flatten the geometry. Only linear elements (no polygons) remain. + flat_geometry = self.flatten(geometry, reset=True, pathonly=True) + log.debug("%d paths" % len(flat_geometry)) + + # ######################################################################################################### + # ######################################################################################################### + # ############# PARAMETERS used in PREPROCESSORS so they need to be updated ############################### + # ######################################################################################################### + # ######################################################################################################### + self.tool = str(tool) + tool_dict = tools[tool]['data'] + # this is the tool diameter, it is used as such to accommodate the preprocessor who need the tool diameter + # given under the name 'toolC' + self.postdata['toolC'] = float(tools[tool]['tooldia']) + self.tooldia = float(tools[tool]['tooldia']) + self.use_ui = True + self.tolerance = tolerance + + # Optimization type. Can be: 'M', 'B', 'T', 'R', 'No' + opt_type = tool_dict['optimization_type'] + opt_time = tool_dict['search_time'] if 'search_time' in tool_dict else 'R' + + if opt_type == 'M': + log.debug("Using OR-Tools Metaheuristic Guided Local Search path optimization.") + elif opt_type == 'B': + log.debug("Using OR-Tools Basic path optimization.") + elif opt_type == 'T': + log.debug("Using Travelling Salesman path optimization.") + elif opt_type == 'R': + log.debug("Using RTree path optimization.") + else: + log.debug("Using no path optimization.") + + # Preprocessor + self.pp_geometry_name = tool_dict['ppname_g'] + self.pp_geometry = self.app.preprocessors[self.pp_geometry_name] + p = self.pp_geometry + + # Offset the Geometry if it is the case + # FIXME need to test if in ["Path", "In", "Out", "Custom"]. For now only 'Custom' is somehow done + offset = tools[tool]['offset_value'] + if offset != 0.0: + for it in flat_geometry: + # if the geometry is a closed shape then create a Polygon out of it + if isinstance(it, LineString): + if it.is_ring: + it = Polygon(it) + temp_solid_geometry.append(it.buffer(offset, join_style=2)) + temp_solid_geometry = self.flatten(temp_solid_geometry, reset=True, pathonly=True) + else: + temp_solid_geometry = flat_geometry + + if self.z_cut is None: + if 'laser' not in self.pp_geometry_name: + self.app.inform.emit( + '[ERROR_NOTCL] %s' % _("Cut_Z parameter is None or zero. Most likely a bad combinations of " + "other parameters.")) + return 'fail' + else: + self.z_cut = 0 + if self.machinist_setting == 0: + if self.z_cut > 0: + self.app.inform.emit('[WARNING] %s' % + _("The Cut Z parameter has positive value. " + "It is the depth value to cut into material.\n" + "The Cut Z parameter needs to have a negative value, assuming it is a typo " + "therefore the app will convert the value to negative." + "Check the resulting CNC code (Gcode etc).")) + self.z_cut = -self.z_cut + elif self.z_cut == 0 and 'laser' not in self.pp_geometry_name: + self.app.inform.emit('[WARNING] %s: %s' % + (_("The Cut Z parameter is zero. There will be no cut, skipping file"), + self.options['name'])) + return 'fail' + + if self.z_move is None: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Travel Z parameter is None or zero.")) + return 'fail' + + if self.z_move < 0: + self.app.inform.emit('[WARNING] %s' % + _("The Travel Z parameter has negative value. " + "It is the height value to travel between cuts.\n" + "The Z Travel parameter needs to have a positive value, assuming it is a typo " + "therefore the app will convert the value to positive." + "Check the resulting CNC code (Gcode etc).")) + self.z_move = -self.z_move + elif self.z_move == 0: + self.app.inform.emit('[WARNING] %s: %s' % + (_("The Z Travel parameter is zero. This is dangerous, skipping file"), + self.options['name'])) + return 'fail' + + # made sure that depth_per_cut is no more then the z_cut + if abs(self.z_cut) < self.z_depthpercut: + self.z_depthpercut = abs(self.z_cut) + + # Depth parameters + self.z_cut = float(tool_dict['cutz']) + self.multidepth = tool_dict['multidepth'] + self.z_depthpercut = float(tool_dict['depthperpass']) + self.z_move = float(tool_dict['travelz']) + self.f_plunge = self.app.defaults["geometry_f_plunge"] + + self.feedrate = float(tool_dict['feedrate']) + self.z_feedrate = float(tool_dict['feedrate_z']) + self.feedrate_rapid = float(tool_dict['feedrate_rapid']) + + self.spindlespeed = float(tool_dict['spindlespeed']) + try: + self.spindledir = tool_dict['spindledir'] + except KeyError: + self.spindledir = self.app.defaults["geometry_spindledir"] + + self.dwell = tool_dict['dwell'] + self.dwelltime = float(tool_dict['dwelltime']) + + self.startz = float(tool_dict['startz']) if tool_dict['startz'] else None + if self.startz == '': + self.startz = None + + self.z_end = float(tool_dict['endz']) + self.xy_end = tool_dict['endxy'] + try: + if self.xy_end == '' or self.xy_end is None: + self.xy_end = None + else: + # either originally it was a string or not, xy_end will be made string + self.xy_end = re.sub('[()\[\]]', '', str(self.xy_end)) if self.xy_end else None + + # and now, xy_end is made into a list of floats in format [x, y] + if self.xy_end: + self.xy_end = [float(eval(a)) for a in self.xy_end.split(",")] + + if self.xy_end and len(self.xy_end) != 2: + self.app.inform.emit('[ERROR]%s' % _("The End X,Y format has to be (x, y).")) + return 'fail' + except Exception as e: + log.debug("camlib.CNCJob.geometry_from_excellon_by_tool() xy_end --> %s" % str(e)) + self.xy_end = [0, 0] + + self.z_toolchange = tool_dict['toolchangez'] + self.xy_toolchange = tool_dict["toolchangexy"] + try: + if self.xy_toolchange == '': + self.xy_toolchange = None + else: + # either originally it was a string or not, xy_toolchange will be made string + self.xy_toolchange = re.sub('[()\[\]]', '', str(self.xy_toolchange)) if self.xy_toolchange else None + + # and now, xy_toolchange is made into a list of floats in format [x, y] + if self.xy_toolchange: + self.xy_toolchange = [float(eval(a)) for a in self.xy_toolchange.split(",")] + + if self.xy_toolchange and len(self.xy_toolchange) != 2: + self.app.inform.emit('[ERROR] %s' % _("The Toolchange X,Y format has to be (x, y).")) + return 'fail' + except Exception as e: + log.debug("camlib.CNCJob.geometry_from_excellon_by_tool() --> %s" % str(e)) + pass + + self.extracut = tool_dict['extracut'] + self.extracut_length = tool_dict['extracut_length'] + + # Probe parameters + # self.z_pdepth = tool_dict["tools_drill_z_pdepth"] + # self.feedrate_probe = tool_dict["tools_drill_feedrate_probe"] + + # ######################################################################################################### + # ############ Create the data. ########################################################################### + # ######################################################################################################### + optimized_path = [] + + geo_storage = {} + for geo in temp_solid_geometry: + if not geo is None: + geo_storage[geo.coords[0]] = geo + locations = list(geo_storage.keys()) + + if opt_type == 'M': + # if there are no locations then go to the next tool + if not locations: + return 'fail' + optimized_locations = self.optimized_ortools_meta(locations=locations, opt_time=opt_time) + optimized_path = [(locations[loc], geo_storage[locations[loc]]) for loc in optimized_locations] + elif opt_type == 'B': + # if there are no locations then go to the next tool + if not locations: + return 'fail' + optimized_locations = self.optimized_ortools_basic(locations=locations) + optimized_path = [(locations[loc], geo_storage[locations[loc]]) for loc in optimized_locations] + elif opt_type == 'T': + # if there are no locations then go to the next tool + if not locations: + return 'fail' + optimized_locations = self.optimized_travelling_salesman(locations) + optimized_path = [(loc, geo_storage[loc]) for loc in optimized_locations] + elif opt_type == 'R': + optimized_path = self.geo_optimized_rtree(temp_solid_geometry) + if optimized_path == 'fail': + return 'fail' + else: + # it's actually not optimized path but here we build a list of (x,y) coordinates + # out of the tool's drills + for geo in temp_solid_geometry: + optimized_path.append(geo.coords[0]) + # ######################################################################################################### + # ######################################################################################################### + + # Only if there are locations to mill + if not optimized_path: + log.debug("camlib.CNCJob.geometry_tool_gcode_gen() -> Optimized path is empty.") + return 'fail' + + if self.app.abort_flag: + # graceful abort requested by the user + raise grace + + # ############################################################################################################# + # ############################################################################################################# + # ################# MILLING !!! ############################################################################## + # ############################################################################################################# + # ############################################################################################################# + log.debug("Starting G-Code...") + + current_tooldia = float('%.*f' % (self.decimals, float(self.tooldia))) + self.app.inform.emit('%s: %s%s.' % (_("Starting G-Code for tool with diameter"), + str(current_tooldia), + str(self.units))) + + # Measurements + total_travel = 0.0 + total_cut = 0.0 + + # Start GCode + start_gcode = '' + if is_first: + start_gcode = self.doformat(p.start_code) + # t_gcode += start_gcode + + # Toolchange code + t_gcode += self.doformat(p.feedrate_code) # sets the feed rate + if toolchange: + t_gcode += self.doformat(p.toolchange_code) + + if 'laser' not in self.pp_geometry_name.lower(): + t_gcode += self.doformat(p.spindle_code) # Spindle start + else: + # for laser this will disable the laser + t_gcode += self.doformat(p.lift_code, x=self.oldx, y=self.oldy) # Move (up) to travel height + + if self.dwell: + t_gcode += self.doformat(p.dwell_code) # Dwell time + else: + t_gcode += self.doformat(p.lift_code, x=0, y=0) # Move (up) to travel height + t_gcode += self.doformat(p.startz_code, x=0, y=0) + + if 'laser' not in self.pp_geometry_name.lower(): + t_gcode += self.doformat(p.spindle_code) # Spindle start + + if self.dwell is True: + t_gcode += self.doformat(p.dwell_code) # Dwell time + t_gcode += self.doformat(p.feedrate_code) # sets the feed rate + + # ## Iterate over geometry paths getting the nearest each time. + path_count = 0 + + # variables to display the percentage of work done + geo_len = len(flat_geometry) + log.warning("Number of paths for which to generate GCode: %s" % str(geo_len)) + old_disp_number = 0 + + current_pt = (0, 0) + for pt, geo in optimized_path: + if self.app.abort_flag: + # graceful abort requested by the user + raise grace + + path_count += 1 + + # 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 = LineString(list(geo.coords)[::-1]) + + # ---------- Single depth/pass -------- + if not self.multidepth: + # calculate the cut distance + total_cut = total_cut + geo.length + + t_gcode += self.create_gcode_single_pass(geo, current_tooldia, self.extracut, + self.extracut_length, self.tolerance, + z_move=self.z_move, old_point=current_pt) + + # --------- Multi-pass --------- + else: + # calculate the cut distance + # due of the number of cuts (multi depth) it has to multiplied by the number of cuts + nr_cuts = 0 + depth = abs(self.z_cut) + while depth > 0: + nr_cuts += 1 + depth -= float(self.z_depthpercut) + + total_cut += (geo.length * nr_cuts) + + gc, geo = self.create_gcode_multi_pass(geo, current_tooldia, self.extracut, + self.extracut_length, self.tolerance, + z_move=self.z_move, postproc=p, old_point=current_pt) + t_gcode += gc + + # calculate the total distance + total_travel = total_travel + abs(distance(pt1=current_pt, pt2=pt)) + current_pt = geo.coords[-1] + + disp_number = int(np.interp(path_count, [0, geo_len], [0, 100])) + if old_disp_number < disp_number <= 100: + self.app.proc_container.update_view_text(' %d%%' % disp_number) + old_disp_number = disp_number + + log.debug("Finished G-Code... %s paths traced." % path_count) + + # add move to end position + total_travel += abs(distance_euclidian(current_pt[0], current_pt[1], 0, 0)) + self.travel_distance += total_travel + total_cut + self.routing_time += total_cut / self.feedrate + + # Finish + if is_last: + t_gcode += self.doformat(p.spindle_stop_code) + t_gcode += self.doformat(p.lift_code, x=current_pt[0], y=current_pt[1]) + t_gcode += self.doformat(p.end_code, x=0, y=0) + self.app.inform.emit( + '%s... %s %s.' % (_("Finished G-Code generation"), str(path_count), _("paths traced")) + ) + + self.gcode = t_gcode + return self.gcode, start_gcode + + # used by the Tcl command Drillcncjob + def generate_from_excellon_by_tool(self, exobj, tools="all", order='fwd', is_first=False, use_ui=False): """ Creates Gcode for this object from an Excellon object for the specified tools. - :param exobj: Excellon object to process - :type exobj: Excellon - :param tools: Comma separated tool names - :type tools: str - :param order: order of tools processing: "fwd", "rev" or "no" - :type order: str - :param use_ui: if True the method will use parameters set in UI - :type use_ui: bool - :return: None - :rtype: None + :param exobj: Excellon object to process + :type exobj: Excellon + :param tools: Comma separated tool names + :type tools: str + :param order: order of tools processing: "fwd", "rev" or "no" + :type order: str + :param is_first: if the tool is the first one should generate the start gcode (not that it matter much + which is the one doing it) + :type is_first: bool + :param use_ui: if True the method will use parameters set in UI + :type use_ui: bool + :return: None + :rtype: None """ # ############################################################################################################# @@ -3651,7 +4025,11 @@ class CNCjob(Geometry): # Initialization # ############################################################################################################# # ############################################################################################################# - gcode = self.doformat(p.start_code) + gcode = '' + start_gcode = '' + if is_first: + start_gcode = self.doformat(p.start_code) + if use_ui is False: gcode += self.doformat(p.z_feedrate_code) @@ -4829,8 +5207,9 @@ class CNCjob(Geometry): self.gcode = gcode self.app.inform.emit(_("Finished G-Code generation...")) - return 'OK' + return gcode, start_gcode + # no longer used def generate_from_multitool_geometry(self, geometry, append=True, tooldia=None, offset=0.0, tolerance=0, z_cut=1.0, z_move=2.0, feedrate=2.0, feedrate_z=2.0, feedrate_rapid=30, spindlespeed=None, spindledir='CW', dwell=False, dwelltime=1.0, @@ -5163,381 +5542,12 @@ class CNCjob(Geometry): ) return self.gcode - def geometry_tool_gcode_gen(self, tool, tools, first_pt, tolerance, is_first=False, is_last=False, - toolchange=False): - """ - Algorithm to generate GCode from multitool Geometry. - - :param tool: tool number for which to generate GCode - :type tool: int - :param tools: a dictionary holding all the tools and data - :type tools: dict - :param first_pt: a tuple of coordinates for the first point of the current tool - :type first_pt: tuple - :param tolerance: geometry tolerance - :type tolerance: - :param is_first: if the current tool is the first tool (for this we need to add start GCode) - :type is_first: bool - :param is_last: if the current tool is the last tool (for this we need to add the end GCode) - :type is_last: bool - :param toolchange: add toolchange event - :type toolchange: bool - :return: GCode - :rtype: str - """ - - log.debug("geometry_tool_gcode_gen()") - - t_gcode = '' - temp_solid_geometry = [] - - # The Geometry from which we create GCode - geometry = tools[tool]['solid_geometry'] - # ## Flatten the geometry. Only linear elements (no polygons) remain. - flat_geometry = self.flatten(geometry, reset=True, pathonly=True) - log.debug("%d paths" % len(flat_geometry)) - - # ######################################################################################################### - # ######################################################################################################### - # ############# PARAMETERS used in PREPROCESSORS so they need to be updated ############################### - # ######################################################################################################### - # ######################################################################################################### - self.tool = str(tool) - tool_dict = tools[tool]['data'] - # this is the tool diameter, it is used as such to accommodate the preprocessor who need the tool diameter - # given under the name 'toolC' - self.postdata['toolC'] = float(tools[tool]['tooldia']) - self.tooldia = float(tools[tool]['tooldia']) - self.use_ui = True - self.tolerance = tolerance - - # Optimization type. Can be: 'M', 'B', 'T', 'R', 'No' - opt_type = tool_dict['optimization_type'] - opt_time = tool_dict['search_time'] if 'search_time' in tool_dict else 'R' - - if opt_type == 'M': - log.debug("Using OR-Tools Metaheuristic Guided Local Search path optimization.") - elif opt_type == 'B': - log.debug("Using OR-Tools Basic path optimization.") - elif opt_type == 'T': - log.debug("Using Travelling Salesman path optimization.") - elif opt_type == 'R': - log.debug("Using RTree path optimization.") - else: - log.debug("Using no path optimization.") - - # Preprocessor - self.pp_geometry_name = tool_dict['ppname_g'] - self.pp_geometry = self.app.preprocessors[self.pp_geometry_name] - p = self.pp_geometry - - # Offset the Geometry if it is the case - # FIXME need to test if in ["Path", "In", "Out", "Custom"]. For now only 'Custom' is somehow done - offset = tools[tool]['offset_value'] - if offset != 0.0: - for it in flat_geometry: - # if the geometry is a closed shape then create a Polygon out of it - if isinstance(it, LineString): - if it.is_ring: - it = Polygon(it) - temp_solid_geometry.append(it.buffer(offset, join_style=2)) - temp_solid_geometry = self.flatten(temp_solid_geometry, reset=True, pathonly=True) - else: - temp_solid_geometry = flat_geometry - - if self.z_cut is None: - if 'laser' not in self.pp_geometry_name: - self.app.inform.emit( - '[ERROR_NOTCL] %s' % _("Cut_Z parameter is None or zero. Most likely a bad combinations of " - "other parameters.")) - return 'fail' - else: - self.z_cut = 0 - if self.machinist_setting == 0: - if self.z_cut > 0: - self.app.inform.emit('[WARNING] %s' % - _("The Cut Z parameter has positive value. " - "It is the depth value to cut into material.\n" - "The Cut Z parameter needs to have a negative value, assuming it is a typo " - "therefore the app will convert the value to negative." - "Check the resulting CNC code (Gcode etc).")) - self.z_cut = -self.z_cut - elif self.z_cut == 0 and 'laser' not in self.pp_geometry_name: - self.app.inform.emit('[WARNING] %s: %s' % - (_("The Cut Z parameter is zero. There will be no cut, skipping file"), - self.options['name'])) - return 'fail' - - if self.z_move is None: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Travel Z parameter is None or zero.")) - return 'fail' - - if self.z_move < 0: - self.app.inform.emit('[WARNING] %s' % - _("The Travel Z parameter has negative value. " - "It is the height value to travel between cuts.\n" - "The Z Travel parameter needs to have a positive value, assuming it is a typo " - "therefore the app will convert the value to positive." - "Check the resulting CNC code (Gcode etc).")) - self.z_move = -self.z_move - elif self.z_move == 0: - self.app.inform.emit('[WARNING] %s: %s' % - (_("The Z Travel parameter is zero. This is dangerous, skipping file"), - self.options['name'])) - return 'fail' - - # made sure that depth_per_cut is no more then the z_cut - if abs(self.z_cut) < self.z_depthpercut: - self.z_depthpercut = abs(self.z_cut) - - # Depth parameters - self.z_cut = float(tool_dict['cutz']) - self.multidepth = tool_dict['multidepth'] - self.z_depthpercut = float(tool_dict['depthperpass']) - self.z_move = float(tool_dict['travelz']) - self.f_plunge = self.app.defaults["geometry_f_plunge"] - - self.feedrate = float(tool_dict['feedrate']) - self.z_feedrate = float(tool_dict['feedrate_z']) - self.feedrate_rapid = float(tool_dict['feedrate_rapid']) - - self.spindlespeed = float(tool_dict['spindlespeed']) - try: - self.spindledir = tool_dict['spindledir'] - except KeyError: - self.spindledir = self.app.defaults["geometry_spindledir"] - - self.dwell = tool_dict['dwell'] - self.dwelltime = float(tool_dict['dwelltime']) - - self.startz = float(tool_dict['startz']) if tool_dict['startz'] else None - if self.startz == '': - self.startz = None - - self.z_end = float(tool_dict['endz']) - self.xy_end = tool_dict['endxy'] - try: - if self.xy_end == '' or self.xy_end is None: - self.xy_end = None - else: - # either originally it was a string or not, xy_end will be made string - self.xy_end = re.sub('[()\[\]]', '', str(self.xy_end)) if self.xy_end else None - - # and now, xy_end is made into a list of floats in format [x, y] - if self.xy_end: - self.xy_end = [float(eval(a)) for a in self.xy_end.split(",")] - - if self.xy_end and len(self.xy_end) != 2: - self.app.inform.emit('[ERROR]%s' % _("The End X,Y format has to be (x, y).")) - return 'fail' - except Exception as e: - log.debug("camlib.CNCJob.geometry_from_excellon_by_tool() xy_end --> %s" % str(e)) - self.xy_end = [0, 0] - - self.z_toolchange = tool_dict['toolchangez'] - self.xy_toolchange = tool_dict["toolchangexy"] - try: - if self.xy_toolchange == '': - self.xy_toolchange = None - else: - # either originally it was a string or not, xy_toolchange will be made string - self.xy_toolchange = re.sub('[()\[\]]', '', str(self.xy_toolchange)) if self.xy_toolchange else None - - # and now, xy_toolchange is made into a list of floats in format [x, y] - if self.xy_toolchange: - self.xy_toolchange = [float(eval(a)) for a in self.xy_toolchange.split(",")] - - if self.xy_toolchange and len(self.xy_toolchange) != 2: - self.app.inform.emit('[ERROR] %s' % _("The Toolchange X,Y format has to be (x, y).")) - return 'fail' - except Exception as e: - log.debug("camlib.CNCJob.geometry_from_excellon_by_tool() --> %s" % str(e)) - pass - - self.extracut = tool_dict['extracut'] - self.extracut_length = tool_dict['extracut_length'] - - # Probe parameters - # self.z_pdepth = tool_dict["tools_drill_z_pdepth"] - # self.feedrate_probe = tool_dict["tools_drill_feedrate_probe"] - - # ######################################################################################################### - # ############ Create the data. ########################################################################### - # ######################################################################################################### - optimized_path = [] - - geo_storage = {} - for geo in temp_solid_geometry: - if not geo is None: - geo_storage[geo.coords[0]] = geo - locations = list(geo_storage.keys()) - - if opt_type == 'M': - # if there are no locations then go to the next tool - if not locations: - return 'fail' - optimized_locations = self.optimized_ortools_meta(locations=locations, opt_time=opt_time) - optimized_path = [(locations[loc], geo_storage[locations[loc]]) for loc in optimized_locations] - elif opt_type == 'B': - # if there are no locations then go to the next tool - if not locations: - return 'fail' - optimized_locations = self.optimized_ortools_basic(locations=locations) - optimized_path = [(locations[loc], geo_storage[locations[loc]]) for loc in optimized_locations] - elif opt_type == 'T': - # if there are no locations then go to the next tool - if not locations: - return 'fail' - optimized_locations = self.optimized_travelling_salesman(locations) - optimized_path = [(loc, geo_storage[loc]) for loc in optimized_locations] - elif opt_type == 'R': - optimized_path = self.geo_optimized_rtree(temp_solid_geometry) - if optimized_path == 'fail': - return 'fail' - else: - # it's actually not optimized path but here we build a list of (x,y) coordinates - # out of the tool's drills - for geo in temp_solid_geometry: - optimized_path.append(geo.coords[0]) - # ######################################################################################################### - # ######################################################################################################### - - # Only if there are locations to mill - if not optimized_path: - log.debug("camlib.CNCJob.geometry_tool_gcode_gen() -> Optimized path is empty.") - return 'fail' - - if self.app.abort_flag: - # graceful abort requested by the user - raise grace - - # ############################################################################################################# - # ############################################################################################################# - # ################# MILLING !!! ############################################################################## - # ############################################################################################################# - # ############################################################################################################# - log.debug("Starting G-Code...") - - current_tooldia = float('%.*f' % (self.decimals, float(self.tooldia))) - self.app.inform.emit('%s: %s%s.' % (_("Starting G-Code for tool with diameter"), - str(current_tooldia), - str(self.units))) - - # Measurements - total_travel = 0.0 - total_cut = 0.0 - - # Start GCode - start_gcode = '' - if is_first: - start_gcode = self.doformat(p.start_code) - t_gcode += start_gcode - - # Toolchange code - t_gcode += self.doformat(p.feedrate_code) # sets the feed rate - if toolchange: - t_gcode += self.doformat(p.toolchange_code) - - if 'laser' not in self.pp_geometry_name.lower(): - t_gcode += self.doformat(p.spindle_code) # Spindle start - else: - # for laser this will disable the laser - t_gcode += self.doformat(p.lift_code, x=self.oldx, y=self.oldy) # Move (up) to travel height - - if self.dwell: - t_gcode += self.doformat(p.dwell_code) # Dwell time - else: - t_gcode += self.doformat(p.lift_code, x=0, y=0) # Move (up) to travel height - t_gcode += self.doformat(p.startz_code, x=0, y=0) - - if 'laser' not in self.pp_geometry_name.lower(): - t_gcode += self.doformat(p.spindle_code) # Spindle start - - if self.dwell is True: - t_gcode += self.doformat(p.dwell_code) # Dwell time - t_gcode += self.doformat(p.feedrate_code) # sets the feed rate - - # ## Iterate over geometry paths getting the nearest each time. - path_count = 0 - - # variables to display the percentage of work done - geo_len = len(flat_geometry) - log.warning("Number of paths for which to generate GCode: %s" % str(geo_len)) - old_disp_number = 0 - - current_pt = (0, 0) - for pt, geo in optimized_path: - if self.app.abort_flag: - # graceful abort requested by the user - raise grace - - path_count += 1 - - # 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 = LineString(list(geo.coords)[::-1]) - - # ---------- Single depth/pass -------- - if not self.multidepth: - # calculate the cut distance - total_cut = total_cut + geo.length - - t_gcode += self.create_gcode_single_pass(geo, current_tooldia, self.extracut, - self.extracut_length, self.tolerance, - z_move=self.z_move, old_point=current_pt) - - # --------- Multi-pass --------- - else: - # calculate the cut distance - # due of the number of cuts (multi depth) it has to multiplied by the number of cuts - nr_cuts = 0 - depth = abs(self.z_cut) - while depth > 0: - nr_cuts += 1 - depth -= float(self.z_depthpercut) - - total_cut += (geo.length * nr_cuts) - - gc, geo = self.create_gcode_multi_pass(geo, current_tooldia, self.extracut, - self.extracut_length, self.tolerance, - z_move=self.z_move, postproc=p, old_point=current_pt) - t_gcode += gc - - # calculate the total distance - total_travel = total_travel + abs(distance(pt1=current_pt, pt2=pt)) - current_pt = geo.coords[-1] - - disp_number = int(np.interp(path_count, [0, geo_len], [0, 100])) - if old_disp_number < disp_number <= 100: - self.app.proc_container.update_view_text(' %d%%' % disp_number) - old_disp_number = disp_number - - log.debug("Finished G-Code... %s paths traced." % path_count) - - # add move to end position - total_travel += abs(distance_euclidian(current_pt[0], current_pt[1], 0, 0)) - self.travel_distance += total_travel + total_cut - self.routing_time += total_cut / self.feedrate - - # Finish - if is_last: - t_gcode += self.doformat(p.spindle_stop_code) - t_gcode += self.doformat(p.lift_code, x=current_pt[0], y=current_pt[1]) - t_gcode += self.doformat(p.end_code, x=0, y=0) - self.app.inform.emit( - '%s... %s %s.' % (_("Finished G-Code generation"), str(path_count), _("paths traced")) - ) - - self.gcode = t_gcode - return self.gcode, start_gcode - def generate_from_geometry_2(self, geometry, append=True, tooldia=None, offset=0.0, tolerance=0, z_cut=None, z_move=None, feedrate=None, feedrate_z=None, feedrate_rapid=None, spindlespeed=None, spindledir='CW', dwell=False, dwelltime=None, multidepth=False, depthpercut=None, toolchange=False, toolchangez=None, toolchangexy="0.0, 0.0", extracut=False, extracut_length=None, startz=None, endz=None, endxy='', pp_geometry_name=None, - tool_no=1): + tool_no=1, is_first=False): """ Second algorithm to generate from Geometry. @@ -5572,6 +5582,7 @@ class CNCjob(Geometry): :param endxy: :param pp_geometry_name: :param tool_no: + :param is_first: if the processed tool is the first one and if we should process the start gcode :return: None """ log.debug("Executing camlib.CNCJob.generate_from_geometry_2()") @@ -5811,7 +5822,11 @@ class CNCjob(Geometry): self.oldx = 0.0 self.oldy = 0.0 - self.gcode = self.doformat(p.start_code) + start_gcode = '' + if is_first: + start_gcode = self.doformat(p.start_code) + + # self.gcode = self.doformat(p.start_code) self.gcode += self.doformat(p.feedrate_code) # sets the feed rate if toolchange is False: @@ -5936,7 +5951,7 @@ class CNCjob(Geometry): '%s... %s %s' % (_("Finished G-Code generation"), str(path_count), _(" paths traced.")) ) - return self.gcode + return self.gcode, start_gcode def generate_gcode_from_solderpaste_geo(self, **kwargs): """ @@ -7551,24 +7566,45 @@ class CNCjob(Geometry): miny = np.Inf maxx = -np.Inf maxy = -np.Inf - for k, v in self.cnc_tools.items(): - minx = np.Inf - miny = np.Inf - maxx = -np.Inf - maxy = -np.Inf - try: - for k in v['solid_geometry']: - minx_, miny_, maxx_, maxy_ = bounds_rec(k) + if self.cnc_tools: + for k, v in self.cnc_tools.items(): + minx = np.Inf + miny = np.Inf + maxx = -np.Inf + maxy = -np.Inf + try: + for k in v['solid_geometry']: + minx_, miny_, maxx_, maxy_ = bounds_rec(k) + minx = min(minx, minx_) + miny = min(miny, miny_) + maxx = max(maxx, maxx_) + maxy = max(maxy, maxy_) + except TypeError: + minx_, miny_, maxx_, maxy_ = bounds_rec(v['solid_geometry']) + minx = min(minx, minx_) + miny = min(miny, miny_) + maxx = max(maxx, maxx_) + maxy = max(maxy, maxy_) + + if self.exc_cnc_tools: + for k, v in self.exc_cnc_tools.items(): + minx = np.Inf + miny = np.Inf + maxx = -np.Inf + maxy = -np.Inf + try: + for k in v['solid_geometry']: + minx_, miny_, maxx_, maxy_ = bounds_rec(k) + minx = min(minx, minx_) + miny = min(miny, miny_) + maxx = max(maxx, maxx_) + maxy = max(maxy, maxy_) + except TypeError: + minx_, miny_, maxx_, maxy_ = bounds_rec(v['solid_geometry']) minx = min(minx, minx_) miny = min(miny, miny_) maxx = max(maxx, maxx_) maxy = max(maxy, maxy_) - except TypeError: - minx_, miny_, maxx_, maxy_ = bounds_rec(v['solid_geometry']) - minx = min(minx, minx_) - miny = min(miny, miny_) - maxx = max(maxx, maxx_) - maxy = max(maxy, maxy_) bounds_coords = minx, miny, maxx, maxy return bounds_coords diff --git a/tclCommands/TclCommandDrillcncjob.py b/tclCommands/TclCommandDrillcncjob.py index 75b16fb0..efd90db4 100644 --- a/tclCommands/TclCommandDrillcncjob.py +++ b/tclCommands/TclCommandDrillcncjob.py @@ -337,6 +337,7 @@ class TclCommandDrillcncjob(TclCommandSignaled): if ret_val == 'fail': return 'fail' + job_obj.gc_start = ret_val[1] for t_item in job_obj.exc_cnc_tools: job_obj.exc_cnc_tools[t_item]['data']['tools_drill_offset'] = \