diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f550735..30424148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ CHANGELOG for FlatCAM beta - in camlib.CNCjob.gcode_parse() fixed an assumption that a certain object is present: tool_data["tools_drill_toolchange"] - fixed an error in Drilling Plugin when selecting only a few tools and not all for drilling - fixed an error when building the UI for a CNCJob object created from drilling an Excellon object with a limited selection of tools +- fixed an KeyError exception in the GCode Editor regarding the key: 'offset' not existing +- updated the Tcl command `drillcncjob` to store the tool gcode and the parsed tool gcode in each tool data structure +- updated the Tcl command `drillcncjob` description and examples 27.01.2022 diff --git a/appEditors/appGCodeEditor.py b/appEditors/appGCodeEditor.py index aaeda0f9..68ae7d80 100644 --- a/appEditors/appGCodeEditor.py +++ b/appEditors/appGCodeEditor.py @@ -288,8 +288,13 @@ class AppGCodeEditor(QtCore.QObject): dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(tooldia))) nr_drills_item = QtWidgets.QTableWidgetItem('%d' % int(t_value['nr_drills'])) nr_slots_item = QtWidgets.QTableWidgetItem('%d' % int(t_value['nr_slots'])) - cutz_item = QtWidgets.QTableWidgetItem('%.*f' % ( - self.decimals, float(t_value['offset']) + float(t_value['data']['tools_drill_cutz']))) + + try: + cutz_item = QtWidgets.QTableWidgetItem('%.*f' % ( + self.decimals, float(t_value['offset']) + float(t_value['data']['tools_drill_cutz']))) + except KeyError: + cutz_item = QtWidgets.QTableWidgetItem('%.*f' % ( + self.decimals, float(t_value['offset_z']) + float(t_value['data']['tools_drill_cutz']))) t_id.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled) dia_item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled) diff --git a/appPlugins/ToolDrilling.py b/appPlugins/ToolDrilling.py index 93a7586f..359f9696 100644 --- a/appPlugins/ToolDrilling.py +++ b/appPlugins/ToolDrilling.py @@ -1836,11 +1836,14 @@ class ToolDrilling(AppTool, Excellon): selected_uid.add(uid) return list(selected_uid) - def create_drill_points(self, selected_tools, selected_sorted_tools): + def create_drill_points(self, selected_tools, selected_sorted_tools, excellon_tools=None): points = {} + if excellon_tools is None: + excellon_tools = self.excellon_tools + # create drill points out of the drills locations - for tool_key, tl_dict in self.excellon_tools.items(): + for tool_key, tl_dict in excellon_tools.items(): if tool_key in selected_tools: if 'drills' in tl_dict and tl_dict['drills']: for drill_pt in tl_dict['drills']: @@ -1858,7 +1861,7 @@ class ToolDrilling(AppTool, Excellon): # convert slots to a sequence of drills and add them to drill points should_add_last_pt = self.ui.last_drill_cb.get_value() - for tool_key, tl_dict in self.excellon_tools.items(): + for tool_key, tl_dict in excellon_tools.items(): convert_slots = tl_dict['data']['tools_drill_drill_slots'] if convert_slots: if tool_key in selected_tools: @@ -1884,11 +1887,13 @@ class ToolDrilling(AppTool, Excellon): return points - def check_intersection(self, points): + def check_intersection(self, points, excellon_tools=None): + if excellon_tools is None: + excellon_tools = self.excellon_tools for tool_key in points: for pt in points[tool_key]: for area in self.app.exc_areas.exclusion_areas_storage: - pt_buf = pt.buffer(self.excellon_tools[tool_key]['tooldia'] / 2.0) + pt_buf = pt.buffer(excellon_tools[tool_key]['tooldia'] / 2.0) if pt_buf.within(area['shape']) or pt_buf.intersects(area['shape']): return True return False @@ -1968,7 +1973,7 @@ class ToolDrilling(AppTool, Excellon): # Create a sorted list of selected sel_tools from the sorted_tools list sel_tools = [i for i, j in sorted_tools for k in selected_tools_ids if i == k] - log.debug("Tools sorted are: %s" % str(sel_tools)) + self.app.log.debug("Tools sorted are: %s" % str(sel_tools)) # ############################################################################################################# # ############################################################################################################# @@ -1982,7 +1987,7 @@ class ToolDrilling(AppTool, Excellon): # check if there are drill points in the exclusion areas (if any areas) if self.app.exc_areas.exclusion_areas_storage and self.check_intersection(points) is True: - self.app.inform.emit("[ERROR_NOTCL] %s" % _("Failed. Drill points inside the exclusion zones.")) + self.app.inform.emit("[ERROR_NOTCL] %s %s" % (_("Failed."), _("Drill points inside the exclusion zones."))) return 'fail' # ############################################################################################################# diff --git a/camlib.py b/camlib.py index be513504..4d43e5b9 100644 --- a/camlib.py +++ b/camlib.py @@ -3013,7 +3013,7 @@ class CNCjob(Geometry): if assignment: # Solution cost. - log.info("OR-tools metaheuristics - Total distance: " + str(assignment.ObjectiveValue())) + self.app.log.info("OR-tools metaheuristics - Total distance: " + str(assignment.ObjectiveValue())) # Inspect solution. # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1. @@ -3064,6 +3064,10 @@ class CNCjob(Geometry): transit_callback_index = routing.RegisterTransitCallback(dist_callback) routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index) + # Setting first solution heuristic. + search_parameters.first_solution_strategy = ( + routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) + # Solve, returns a solution if any. assignment = routing.SolveWithParameters(search_parameters) @@ -3227,10 +3231,11 @@ class CNCjob(Geometry): else: return zcut - # 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): """ + Used in Tool Drilling + Creates Gcode for this object from an Excellon object for the specified tools. @@ -3344,7 +3349,7 @@ class CNCjob(Geometry): self.app.inform.emit('[ERROR] %s' % _("The Toolchange X,Y format has to be (x, y).")) return 'fail' except Exception as e: - log.error("camlib.CNCJob.generate_from_excellon_by_tool() xy_toolchange --> %s" % str(e)) + self.app.log.error("camlib.CNCJob.generate_from_excellon_by_tool() xy_toolchange --> %s" % str(e)) self.xy_toolchange = [0, 0] # End position parameters @@ -3421,7 +3426,7 @@ class CNCjob(Geometry): # Only if there are locations to drill if not optimized_path: - log.debug("CNCJob.excellon_tool_gcode_gen() -> Optimized path is empty.") + self.app.log.error("CNCJob.excellon_tool_gcode_gen() -> Optimized path is empty.") return 'fail' if self.app.abort_flag: @@ -4014,9 +4019,12 @@ class CNCjob(Geometry): 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): """ + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + Used by the Tcl command Drillcncjob + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + Creates Gcode for this object from an Excellon object for the specified tools. @@ -4068,7 +4076,7 @@ class CNCjob(Geometry): "in the format (x, y) \nbut now there is only one value, not two. ")) return 'fail' except Exception as e: - log.error("camlib.CNCJob.generate_from_excellon_by_tool() --> %s" % str(e)) + self.app.log.error("camlib.CNCJob.generate_from_excellon_by_tool() --> %s" % str(e)) pass # XY_end parameter @@ -4084,7 +4092,7 @@ class CNCjob(Geometry): self.pp_excellon = self.app.preprocessors[self.pp_excellon_name] p = self.pp_excellon - log.debug("Creating CNC Job from Excellon...") + self.app.log.debug("Creating CNC Job from Excellon...") # ############################################################################################################# # ############################################################################################################# @@ -4112,7 +4120,7 @@ class CNCjob(Geometry): # Create a sorted list of selected tools from the sorted_tools list tools = [i for i, j in sorted_tools for k in selected_tools if i == k] - log.debug("Tools sorted are: %s" % str(tools)) + self.app.log.debug("Tools sorted are: %s" % str(tools)) # ############################################################################################################# # ############################################################################################################# @@ -4153,13 +4161,17 @@ class CNCjob(Geometry): default_data[k] = deepcopy(v) # it[1] is the tool diameter - self.tools[to_ol] = {} - self.tools[to_ol]['tooldia'] = it[1] - self.tools[to_ol]['nr_drills'] = drill_no - self.tools[to_ol]['nr_slots'] = slot_no - self.tools[to_ol]['offset_z'] = z_off - self.tools[to_ol]['data'] = default_data - self.tools[to_ol]['solid_geometry'] = deepcopy(sol_geo) + self.tools[to_ol] = { + 'tooldia': it[1], + 'nr_drills': drill_no, + 'nr_slots': slot_no, + 'offset_z': z_off, + 'data': default_data, + 'gcode': '', + 'gcode_parsed': [], + 'last_point': (0, 0), + 'solid_geometry': deepcopy(sol_geo) + } self.app.inform.emit(_("Creating a list of points to drill...")) @@ -4181,7 +4193,7 @@ class CNCjob(Geometry): points[tool].append(drill_pt) except KeyError: points[tool] = [drill_pt] - log.debug("Found %d TOOLS with drills." % len(points)) + self.app.log.debug("Found %d TOOLS with drills." % len(points)) # check if there are drill points in the exclusion areas. # If we find any within the exclusion areas return 'fail' @@ -4271,6 +4283,8 @@ class CNCjob(Geometry): if self.toolchange is True: for tool in tools: + tool_gcode = '' + # check if it has drills if not self.exc_tools[tool]['drills']: continue @@ -4287,7 +4301,7 @@ class CNCjob(Geometry): self.z_feedrate = self.exc_tools[tool]['data']['tools_drill_feedrate_z'] self.feedrate = self.exc_tools[tool]['data']['tools_drill_feedrate_z'] self.z_cut = self.exc_tools[tool]['data']['tools_drill_cutz'] - gcode += self.doformat(p.z_feedrate_code) + tool_gcode += self.doformat(p.z_feedrate_code) if self.z_cut > 0: self.app.inform.emit('[WARNING] %s' % @@ -4365,12 +4379,12 @@ class CNCjob(Geometry): # Tool change sequence (optional) if self.toolchange: - gcode += self.doformat(p.toolchange_code, toolchangexy=(self.oldx, self.oldy)) + tool_gcode += self.doformat(p.toolchange_code, toolchangexy=(self.oldx, self.oldy)) # Spindle start - gcode += self.doformat(p.spindle_code) + tool_gcode += self.doformat(p.spindle_code) # Dwell time if self.dwell is True: - gcode += self.doformat(p.dwell_code) + tool_gcode += self.doformat(p.dwell_code) current_tooldia = float('%.*f' % (self.decimals, float(self.exc_tools[tool]["tooldia"]))) @@ -4397,7 +4411,7 @@ class CNCjob(Geometry): geo_len = len(optimized_path) old_disp_number = 0 - log.warning("Number of drills for which to generate GCode: %s" % str(geo_len)) + self.app.log.warning("Number of drills for which to generate GCode: %s" % str(geo_len)) loc_nr = 0 for point in optimized_path: @@ -4422,26 +4436,26 @@ class CNCjob(Geometry): if travel[0] is not None: # move to next point - gcode += self.doformat(p.rapid_code, x=locx, y=locy) + tool_gcode += self.doformat(p.rapid_code, x=locx, y=locy) # raise to safe Z (travel[0]) each time because safe Z may be different self.z_move = travel[0] - gcode += self.doformat(p.lift_code, x=locx, y=locy) + tool_gcode += self.doformat(p.lift_code, x=locx, y=locy) # restore z_move self.z_move = self.exc_tools[tool]['data']['tools_drill_travelz'] else: if prev_z is not None: # move to next point - gcode += self.doformat(p.rapid_code, x=locx, y=locy) + tool_gcode += self.doformat(p.rapid_code, x=locx, y=locy) # we assume that previously the z_move was altered therefore raise to # the travel_z (z_move) self.z_move = self.exc_tools[tool]['data']['tools_drill_travelz'] - gcode += self.doformat(p.lift_code, x=locx, y=locy) + tool_gcode += self.doformat(p.lift_code, x=locx, y=locy) else: # move to next point - gcode += self.doformat(p.rapid_code, x=locx, y=locy) + tool_gcode += self.doformat(p.rapid_code, x=locx, y=locy) # store prev_z prev_z = travel[0] @@ -4457,32 +4471,32 @@ class CNCjob(Geometry): self.z_cut -= self.z_depthpercut if abs(doc) < abs(self.z_cut) < (abs(doc) + self.z_depthpercut): self.z_cut = doc - gcode += self.doformat(p.down_code, x=locx, y=locy) + tool_gcode += self.doformat(p.down_code, x=locx, y=locy) measured_down_distance += abs(self.z_cut) + abs(self.z_move) if self.f_retract is False: - gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy) + tool_gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy) measured_up_to_zero_distance += abs(self.z_cut) measured_lift_distance += abs(self.z_move) else: measured_lift_distance += abs(self.z_cut) + abs(self.z_move) - gcode += self.doformat(p.lift_code, x=locx, y=locy) + tool_gcode += self.doformat(p.lift_code, x=locx, y=locy) else: - gcode += self.doformat(p.down_code, x=locx, y=locy) + tool_gcode += self.doformat(p.down_code, x=locx, y=locy) measured_down_distance += abs(self.z_cut) + abs(self.z_move) if self.f_retract is False: - gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy) + tool_gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy) measured_up_to_zero_distance += abs(self.z_cut) measured_lift_distance += abs(self.z_move) else: measured_lift_distance += abs(self.z_cut) + abs(self.z_move) - gcode += self.doformat(p.lift_code, x=locx, y=locy) + tool_gcode += self.doformat(p.lift_code, x=locx, y=locy) measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy)) self.oldx = locx @@ -4495,6 +4509,9 @@ class CNCjob(Geometry): self.app.proc_container.update_view_text(' %d%%' % disp_number) old_disp_number = disp_number + self.tools[tool]['last_point'] = (locx, locy) + self.tools[tool]['gcode'] = tool_gcode + gcode += tool_gcode else: self.app.inform.emit('[ERROR_NOTCL] %s...' % _('G91 coordinates not implemented')) return 'fail' @@ -4731,6 +4748,7 @@ class CNCjob(Geometry): self.app.inform.emit('[ERROR_NOTCL] %s...' % _('G91 coordinates not implemented')) return 'fail' self.z_cut = deepcopy(old_zcut) + self.tools[one_tool]['gcode'] = gcode if used_excellon_optimization_type == 'M': log.debug("The total travel distance with OR-TOOLS Metaheuristics is: %s" % str(measured_distance)) diff --git a/tclCommands/TclCommandDrillcncjob.py b/tclCommands/TclCommandDrillcncjob.py index 43717caf..e9bdd746 100644 --- a/tclCommands/TclCommandDrillcncjob.py +++ b/tclCommands/TclCommandDrillcncjob.py @@ -59,7 +59,7 @@ class TclCommandDrillcncjob(TclCommandSignaled): ('name', 'Name of the source object.'), ('drilled_dias', 'Comma separated tool diameters of the drills to be drilled (example: 0.6,1.0 or 3.125). ' - 'WARNING: No space allowed'), + 'WARNING: No space allowed. Can also take the value "all" which will drill the holes for all tools.'), ('drillz', 'Drill depth into material (example: -2.0). Negative value.'), ('dpp', 'Progressive drilling into material with a specified step (example: 0.7). Positive value.'), ('travelz', 'Travel distance above material (example: 2.0).'), @@ -89,9 +89,12 @@ class TclCommandDrillcncjob(TclCommandSignaled): ('outname', 'Name of the resulting Geometry object.') ]), 'examples': ['drillcncjob test.TXT -drillz -1.5 -travelz 14 -feedrate_z 222 -feedrate_rapid 456 ' - '-spindlespeed 777 -toolchangez 33 -endz 22 -pp default\n' - 'Usage of -feedrate_rapid matter only when the preprocessor is using it, like -marlin-.', - 'drillcncjob test.DRL -drillz -1.7 -dpp 0.5 -travelz 2 -feedrate_z 800 -endxy 3,3'] + '-spindlespeed 777 -toolchangez 33 -endz 22 -pp Marlin\n' + 'Usage of -feedrate_rapid matter only when the preprocessor is using it, like -Marlin-.\n', + 'drillcncjob test.DRL -drillz -1.7 -dpp 0.5 -travelz 2 -feedrate_z 800 -endxy 3,3\n', + 'drillcncjob test.DRL -drilled_dias "all" -drillz -1.7 -dpp 0.5 -travelz 2 -feedrate_z 800 ' + '-endxy 3,3' + ] } def execute(self, args, unnamed_args): @@ -233,7 +236,7 @@ class TclCommandDrillcncjob(TclCommandSignaled): toolchange = self.app.defaults["tools_drill_toolchange"] toolchangez = float(self.app.defaults["tools_drill_toolchangez"]) - if "toolchangexy" in args and args["tools_drill_toolchangexy"]: + if "toolchangexy" in args and args["toolchangexy"]: xy_toolchange = args["toolchangexy"] else: if self.app.defaults["tools_drill_toolchangexy"]: @@ -269,6 +272,8 @@ class TclCommandDrillcncjob(TclCommandSignaled): # ################# Set parameters ######################################################### # ########################################################################################## job_obj.options['type'] = 'Excellon' + job_obj.multigeo = True + job_obj.multitool = True pp_excellon_name = args["pp"] if "pp" in args and args["pp"] else self.app.defaults["tools_drill_ppname_e"] job_obj.pp_excellon_name = pp_excellon_name @@ -277,9 +282,9 @@ class TclCommandDrillcncjob(TclCommandSignaled): if 'dpp' in args: job_obj.multidepth = True if args['dpp'] is not None: - job_obj.z_depthpercut = float(args['dpp']) + job_obj.z_depthpercut = abs(float(args['dpp'])) else: - job_obj.z_depthpercut = float(obj.options["dpp"]) + job_obj.z_depthpercut = abs(float(obj.options["dpp"])) else: job_obj.multidepth = self.app.defaults["tools_drill_multidepth"] job_obj.z_depthpercut = self.app.defaults["tools_drill_depthperpass"] @@ -334,20 +339,32 @@ class TclCommandDrillcncjob(TclCommandSignaled): job_obj.excellon_optimization_type = opt_type job_obj.spindledir = self.app.defaults["tools_drill_spindledir"] - ret_val = job_obj.generate_from_excellon_by_tool(obj, tools, use_ui=False) - job_obj.source_file = ret_val - + ret_val = job_obj.generate_from_excellon_by_tool(obj, tools, is_first=True, use_ui=False) if ret_val == 'fail': return 'fail' + + job_obj.source_file = ret_val + job_obj.gc_start = ret_val[1] + total_gcode_parsed = [] # from Excellon attribute self.tools for t_item in job_obj.tools: job_obj.tools[t_item]['data']['tools_drill_offset'] = \ float(job_obj.tools[t_item]['offset_z']) + float(drillz) job_obj.tools[t_item]['data']['tools_drill_ppname_e'] = job_obj.options['ppname_e'] - job_obj.gcode_parse() + used_tooldia = obj.tools[t_item]['tooldia'] + job_obj.tools[t_item]['tooldia'] = used_tooldia + tool_gcode = job_obj.tools[t_item]['gcode'] + first_drill_point = job_obj.tools[t_item]['last_point'] + gcode_parsed = job_obj.excellon_tool_gcode_parse(used_tooldia, gcode=tool_gcode, + start_pt=first_drill_point) + total_gcode_parsed += gcode_parsed + job_obj.tools[t_item]['gcode_parsed'] = gcode_parsed + + job_obj.gcode_parsed = total_gcode_parsed + # job_obj.gcode_parse() job_obj.create_geometry() self.app.app_obj.new_object("cncjob", args['outname'], job_init, plot=False)