From dedf8c09dea7b436f6adac6e0f47218a99a7ec18 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 6 Feb 2019 14:03:59 +0200 Subject: [PATCH] - fixed the name self-insert in save dialog file for GCode; added protection in case the save path is None - fixed FlatCAM crash when trying to make drills GCode out of a file that have only slots. - made the shell toggle shortcut key work when focused on Selected Tab; toggle units shortcut also - changed the messages for Units COnversion --- FlatCAMApp.py | 16 ++- FlatCAMGUI.py | 6 + FlatCAMObj.py | 58 ++++++--- README.md | 4 + camlib.py | 329 +++++++++++++++++++++++++++----------------------- 5 files changed, 236 insertions(+), 177 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 5893168d..151e50a5 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1659,7 +1659,10 @@ class App(QtCore.QObject): return self.defaults["global_last_folder"] def get_last_save_folder(self): - return self.defaults["global_last_save_folder"] + loc = self.defaults["global_last_save_folder"] + if loc is None: + loc = self.defaults["global_last_folder"] + return loc def report_usage(self, resource): """ @@ -2070,6 +2073,8 @@ class App(QtCore.QObject): except: self.inform.emit("[ERROR_NOTCL] Failed to write defaults to file.") return + + self.file_saved.emit("preferences", filename) self.inform.emit("[success]Exported Defaults to %s" % filename) def on_preferences_open_folder(self): @@ -2825,6 +2830,9 @@ class App(QtCore.QObject): current.to_form() self.plot_all() + self.inform.emit("[success]Converted units to %s" % self.options["units"]) + # self.ui.units_label.setText("[" + self.options["units"] + "]") + self.set_screen_units(self.options["units"]) else: # Undo toggling self.toggle_units_ignore = True @@ -2833,11 +2841,9 @@ class App(QtCore.QObject): else: self.general_options_form.general_app_group.units_radio.set_value('MM') self.toggle_units_ignore = False + self.inform.emit("[WARNING_NOTCL]Units conversion cancelled.") self.options_read_form() - self.inform.emit("Converted units to %s" % self.options["units"]) - #self.ui.units_label.setText("[" + self.options["units"] + "]") - self.set_screen_units(self.options["units"]) def on_toggle_units_click(self): if self.options["units"] == 'MM': @@ -6044,7 +6050,7 @@ class App(QtCore.QObject): self.defaults["global_last_folder"] = os.path.split(str(filename))[0] def register_save_folder(self, filename): - self.defaults['global_last_save_folder'] = os.path.split(str(filename))[0] + self.defaults["global_last_save_folder"] = os.path.split(str(filename))[0] def set_progress_bar(self, percentage, text=""): self.ui.progress_bar.setValue(int(percentage)) diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 4cd70820..b6a39a53 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -1508,6 +1508,12 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if event.key() == QtCore.Qt.Key_3: self.app.on_select_tab('tool') + if event.key == QtCore.Qt.Key_Q: + self.app.on_toggle_units_click() + + if event.key() == QtCore.Qt.Key_S: + self.app.on_toggle_shell() + # Show shortcut list if event.key() == QtCore.Qt.Key_Ampersand: self.app.on_shortcut_list() diff --git a/FlatCAMObj.py b/FlatCAMObj.py index e8dbdf65..645c4573 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -1564,7 +1564,10 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): return False, "Error: No tools." for tool in tools: - if tooldia > self.tools[tool]["C"]: + # I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse + adj_toolstable_tooldia = float('%.4f' % float(tooldia)) + adj_file_tooldia = float('%.4f' % float(self.tools[tool]["C"])) + if adj_toolstable_tooldia > adj_file_tooldia + 0.0001: self.app.inform.emit("[ERROR_NOTCL] Milling tool for SLOTS is larger than hole size. Cancelled.") return False, "Error: Milling tool is larger than hole." @@ -1590,7 +1593,12 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): # we add a tenth of the minimum value, meaning 0.0000001, which from our point of view is "almost zero" for slot in self.slots: if slot['tool'] in tools: - buffer_value = (float(self.tools[slot['tool']]["C"]) / 2) - float(tooldia / 2) + toolstable_tool = float('%.4f' % float(tooldia)) + file_tool = float('%.4f' % float(self.tools[tool]["C"])) + + # I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse + # for the file_tool (tooldia actually) + buffer_value = float(file_tool / 2) - float(toolstable_tool / 2) + 0.0001 if buffer_value == 0: start = slot['start'] stop = slot['stop'] @@ -1729,14 +1737,16 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): # job_obj.options["tooldia"] = tools_csv = ','.join(tools) - job_obj.generate_from_excellon_by_tool(self, tools_csv, - drillz=self.options['drillz'], - toolchange=self.options["toolchange"], - toolchangez=self.options["toolchangez"], - startz=self.options["startz"], - endz=self.options["endz"], - excellon_optimization_type=self.options["optimization_type"]) - + ret_val = job_obj.generate_from_excellon_by_tool(self, tools_csv, + drillz=self.options['drillz'], + toolchange=self.options["toolchange"], + toolchangez=self.options["toolchangez"], + startz=self.options["startz"], + endz=self.options["endz"], + excellon_optimization_type=self.app.defaults[ + "excellon_optimization_type"]) + if ret_val == 'fail': + return 'fail' app_obj.progress.emit(50) job_obj.gcode_parse() @@ -3125,10 +3135,18 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): segx = segx if segx is not None else float(self.app.defaults['geometry_segx']) segy = segy if segy is not None else float(self.app.defaults['geometry_segy']) - xmin = self.options['xmin'] - ymin = self.options['ymin'] - xmax = self.options['xmax'] - ymax = self.options['ymax'] + try: + xmin = self.options['xmin'] + ymin = self.options['ymin'] + xmax = self.options['xmax'] + ymax = self.options['ymax'] + except Exception as e: + log.debug("FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s\n" % str(e)) + msg = "[ERROR] An internal error has ocurred. See shell.\n" + msg += 'FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s' % str(e) + msg += traceback.format_exc() + self.app.inform.emit(msg) + return # Object initialization function for app.new_object() # RUNNING ON SEPARATE THREAD! @@ -4267,14 +4285,17 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): _filter_ = "G-Code Files (*.nc);;G-Code Files (*.txt);;G-Code Files (*.tap);;G-Code Files (*.cnc);;" \ "G-Code Files (*.g-code);;All Files (*.*)" + dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name) try: - filename = str(QtWidgets.QFileDialog.getSaveFileName( + filename, _ = QtWidgets.QFileDialog.getSaveFileName( caption="Export Machine Code ...", - directory=self.app.get_last_save_folder() + '/' + name, + directory=dir_file_to_save, filter=_filter_ - )[0]) + ) except TypeError: - filename = str(QtWidgets.QFileDialog.getSaveFileName(caption="Export Machine Code ...", filter=_filter_)[0]) + filename, _ = QtWidgets.QFileDialog.getSaveFileName(caption="Export Machine Code ...", filter=_filter_) + + filename = str(filename) if filename == '': self.app.inform.emit("[WARNING_NOTCL]Export Machine Code cancelled ...") @@ -4482,6 +4503,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): elif to_file is False: # Just for adding it to the recent files list. self.app.file_opened.emit("cncjob", filename) + self.app.file_saved.emit("cncjob", filename) self.app.inform.emit("[success] Saved to: " + filename) else: diff --git a/README.md b/README.md index e261f25d..84448839 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ CAD program, and create G-Code for Isolation routing. - fixed bug in multigeometry geometry not having the bounds in self.options and crashing the GCode generation - fixed bug that crashed whole application in case that the GCode editor is activated on a Tool gcode that is defective. - fixed bug in Excellon Slots milling: a value of a dict key was a string instead to be an int. A cast to integer solved it. +- fixed the name self-insert in save dialog file for GCode; added protection in case the save path is None +- fixed FlatCAM crash when trying to make drills GCode out of a file that have only slots. +- made the shell toggle shortcut key work when focused on Selected Tab; toggle units shortcut also +- changed the messages for Units COnversion 5.02.3019 diff --git a/camlib.py b/camlib.py index cd6f5658..b85eb553 100644 --- a/camlib.py +++ b/camlib.py @@ -4549,7 +4549,7 @@ class CNCjob(Geometry): elif drillz == 0: self.app.inform.emit("[WARNING] The Cut Z parameter is zero. " "There will be no cut, skipping %s file" % exobj.options['name']) - return + return 'fail' else: self.z_cut = drillz @@ -4670,139 +4670,188 @@ class CNCjob(Geometry): if current_platform == '64bit': if excellon_optimization_type == 'M': log.debug("Using OR-Tools Metaheuristic Guided Local Search drill path optimization.") - for tool in tools: - self.tool=tool - self.postdata['toolC']=exobj.tools[tool]["C"] + if exobj.drills: + for tool in tools: + self.tool=tool + self.postdata['toolC']=exobj.tools[tool]["C"] - ################################################ - # Create the data. - node_list = [] - locations = create_data_array() - tsp_size = len(locations) - num_routes = 1 # The number of routes, which is 1 in the TSP. - # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route. - depot = 0 - # Create routing model. - if tsp_size > 0: - routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot) - search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters() - search_parameters.local_search_metaheuristic = ( - routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH) + ################################################ + # Create the data. + node_list = [] + locations = create_data_array() + tsp_size = len(locations) + num_routes = 1 # The number of routes, which is 1 in the TSP. + # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route. + depot = 0 + # Create routing model. + if tsp_size > 0: + routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot) + search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters() + search_parameters.local_search_metaheuristic = ( + routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH) - # Set search time limit in milliseconds. - if float(self.app.defaults["excellon_search_time"]) != 0: - search_parameters.time_limit_ms = int( - float(self.app.defaults["excellon_search_time"]) * 1000) + # Set search time limit in milliseconds. + if float(self.app.defaults["excellon_search_time"]) != 0: + search_parameters.time_limit_ms = int( + float(self.app.defaults["excellon_search_time"]) * 1000) + else: + search_parameters.time_limit_ms = 3000 + + # Callback to the distance function. The callback takes two + # arguments (the from and to node indices) and returns the distance between them. + dist_between_locations = CreateDistanceCallback() + dist_callback = dist_between_locations.Distance + routing.SetArcCostEvaluatorOfAllVehicles(dist_callback) + + # Solve, returns a solution if any. + assignment = routing.SolveWithParameters(search_parameters) + + if assignment: + # Solution cost. + log.info("Total distance: " + str(assignment.ObjectiveValue())) + + # Inspect solution. + # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1. + route_number = 0 + node = routing.Start(route_number) + start_node = node + + while not routing.IsEnd(node): + node_list.append(node) + node = assignment.Value(routing.NextVar(node)) + else: + log.warning('No solution found.') else: - search_parameters.time_limit_ms = 3000 + log.warning('Specify an instance greater than 0.') + ################################################ - # Callback to the distance function. The callback takes two - # arguments (the from and to node indices) and returns the distance between them. - dist_between_locations = CreateDistanceCallback() - dist_callback = dist_between_locations.Distance - routing.SetArcCostEvaluatorOfAllVehicles(dist_callback) + # Only if tool has points. + if tool in points: + # Tool change sequence (optional) + if toolchange: + gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy)) + gcode += self.doformat(p.spindle_code) # Spindle start + if self.dwell is True: + gcode += self.doformat(p.dwell_code) # Dwell time + else: + gcode += self.doformat(p.spindle_code) + if self.dwell is True: + gcode += self.doformat(p.dwell_code) # Dwell time - # Solve, returns a solution if any. - assignment = routing.SolveWithParameters(search_parameters) + # Drillling! + for k in node_list: + locx = locations[k][0] + locy = locations[k][1] - if assignment: - # Solution cost. - log.info("Total distance: " + str(assignment.ObjectiveValue())) + gcode += self.doformat(p.rapid_code, x=locx, y=locy) + gcode += self.doformat(p.down_code, x=locx, y=locy) + gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy) + gcode += self.doformat(p.lift_code, x=locx, y=locy) + measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy)) + self.oldx = locx + self.oldy = locy + else: + log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> " + "The loaded Excellon file has no drills ...") + self.app.inform.emit('[ERROR_NOTCL]The loaded Excellon file has no drills ...') + return 'fail' - # Inspect solution. - # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1. - route_number = 0 - node = routing.Start(route_number) - start_node = node - - while not routing.IsEnd(node): - node_list.append(node) - node = assignment.Value(routing.NextVar(node)) - else: - log.warning('No solution found.') - else: - log.warning('Specify an instance greater than 0.') - ################################################ - - # Only if tool has points. - if tool in points: - # Tool change sequence (optional) - if toolchange: - gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy)) - gcode += self.doformat(p.spindle_code) # Spindle start - if self.dwell is True: - gcode += self.doformat(p.dwell_code) # Dwell time - else: - gcode += self.doformat(p.spindle_code) - if self.dwell is True: - gcode += self.doformat(p.dwell_code) # Dwell time - - # Drillling! - for k in node_list: - locx = locations[k][0] - locy = locations[k][1] - - gcode += self.doformat(p.rapid_code, x=locx, y=locy) - gcode += self.doformat(p.down_code, x=locx, y=locy) - gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy) - gcode += self.doformat(p.lift_code, x=locx, y=locy) - measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy)) - self.oldx = locx - self.oldy = locy log.debug("The total travel distance with OR-TOOLS Metaheuristics is: %s" % str(measured_distance)) elif excellon_optimization_type == 'B': log.debug("Using OR-Tools Basic drill path optimization.") - for tool in tools: - self.tool=tool - self.postdata['toolC']=exobj.tools[tool]["C"] + if exobj.drills: + for tool in tools: + self.tool=tool + self.postdata['toolC']=exobj.tools[tool]["C"] - ################################################ - node_list = [] - locations = create_data_array() - tsp_size = len(locations) - num_routes = 1 # The number of routes, which is 1 in the TSP. + ################################################ + node_list = [] + locations = create_data_array() + tsp_size = len(locations) + num_routes = 1 # The number of routes, which is 1 in the TSP. - # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route. - depot = 0 + # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route. + depot = 0 - # Create routing model. - if tsp_size > 0: - routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot) - search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters() + # Create routing model. + if tsp_size > 0: + routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot) + search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters() - # Callback to the distance function. The callback takes two - # arguments (the from and to node indices) and returns the distance between them. - dist_between_locations = CreateDistanceCallback() - dist_callback = dist_between_locations.Distance - routing.SetArcCostEvaluatorOfAllVehicles(dist_callback) + # Callback to the distance function. The callback takes two + # arguments (the from and to node indices) and returns the distance between them. + dist_between_locations = CreateDistanceCallback() + dist_callback = dist_between_locations.Distance + routing.SetArcCostEvaluatorOfAllVehicles(dist_callback) - # Solve, returns a solution if any. - assignment = routing.SolveWithParameters(search_parameters) + # Solve, returns a solution if any. + assignment = routing.SolveWithParameters(search_parameters) - if assignment: - # Solution cost. - log.info("Total distance: " + str(assignment.ObjectiveValue())) + if assignment: + # Solution cost. + log.info("Total distance: " + str(assignment.ObjectiveValue())) - # Inspect solution. - # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1. - route_number = 0 - node = routing.Start(route_number) - start_node = node + # Inspect solution. + # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1. + route_number = 0 + node = routing.Start(route_number) + start_node = node - while not routing.IsEnd(node): - node_list.append(node) - node = assignment.Value(routing.NextVar(node)) + while not routing.IsEnd(node): + node_list.append(node) + node = assignment.Value(routing.NextVar(node)) + else: + log.warning('No solution found.') else: - log.warning('No solution found.') - else: - log.warning('Specify an instance greater than 0.') - ################################################ + log.warning('Specify an instance greater than 0.') + ################################################ + + # Only if tool has points. + if tool in points: + # Tool change sequence (optional) + if toolchange: + gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy)) + gcode += self.doformat(p.spindle_code) # Spindle start) + if self.dwell is True: + gcode += self.doformat(p.dwell_code) # Dwell time + else: + gcode += self.doformat(p.spindle_code) + if self.dwell is True: + gcode += self.doformat(p.dwell_code) # Dwell time + + # Drillling! + for k in node_list: + locx = locations[k][0] + locy = locations[k][1] + gcode += self.doformat(p.rapid_code, x=locx, y=locy) + gcode += self.doformat(p.down_code, x=locx, y=locy) + gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy) + gcode += self.doformat(p.lift_code, x=locx, y=locy) + measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy)) + self.oldx = locx + self.oldy = locy + else: + log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> " + "The loaded Excellon file has no drills ...") + self.app.inform.emit('[ERROR_NOTCL]The loaded Excellon file has no drills ...') + return 'fail' + + log.debug("The total travel distance with OR-TOOLS Basic Algorithm is: %s" % str(measured_distance)) + else: + self.app.inform.emit("[ERROR_NOTCL] Wrong optimization type selected.") + return 'fail' + else: + log.debug("Using Travelling Salesman drill path optimization.") + for tool in tools: + if exobj.drills: + self.tool = tool + self.postdata['toolC'] = exobj.tools[tool]["C"] # Only if tool has points. if tool in points: # Tool change sequence (optional) if toolchange: - gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy)) + gcode += self.doformat(p.toolchange_code, toolchangexy=(self.oldx, self.oldy)) gcode += self.doformat(p.spindle_code) # Spindle start) if self.dwell is True: gcode += self.doformat(p.dwell_code) # Dwell time @@ -4812,52 +4861,23 @@ class CNCjob(Geometry): gcode += self.doformat(p.dwell_code) # Dwell time # Drillling! - for k in node_list: - locx = locations[k][0] - locy = locations[k][1] - gcode += self.doformat(p.rapid_code, x=locx, y=locy) - gcode += self.doformat(p.down_code, x=locx, y=locy) - gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy) - gcode += self.doformat(p.lift_code, x=locx, y=locy) - measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy)) - self.oldx = locx - self.oldy = locy - log.debug("The total travel distance with OR-TOOLS Basic Algorithm is: %s" % str(measured_distance)) - else: - self.app.inform.emit("[ERROR_NOTCL] Wrong optimization type selected.") - return - else: - log.debug("Using Travelling Salesman drill path optimization.") - for tool in tools: - self.tool = tool - self.postdata['toolC'] = exobj.tools[tool]["C"] + altPoints = [] + for point in points[tool]: + altPoints.append((point.coords.xy[0][0], point.coords.xy[1][0])) - # Only if tool has points. - if tool in points: - # Tool change sequence (optional) - if toolchange: - gcode += self.doformat(p.toolchange_code, toolchangexy=(self.oldx, self.oldy)) - gcode += self.doformat(p.spindle_code) # Spindle start) - if self.dwell is True: - gcode += self.doformat(p.dwell_code) # Dwell time + for point in self.optimized_travelling_salesman(altPoints): + gcode += self.doformat(p.rapid_code, x=point[0], y=point[1]) + gcode += self.doformat(p.down_code, x=point[0], y=point[1]) + gcode += self.doformat(p.up_to_zero_code, x=point[0], y=point[1]) + gcode += self.doformat(p.lift_code, x=point[0], y=point[1]) + measured_distance += abs(distance_euclidian(point[0], point[1], self.oldx, self.oldy)) + self.oldx = point[0] + self.oldy = point[1] else: - gcode += self.doformat(p.spindle_code) - if self.dwell is True: - gcode += self.doformat(p.dwell_code) # Dwell time - - # Drillling! - altPoints = [] - for point in points[tool]: - altPoints.append((point.coords.xy[0][0], point.coords.xy[1][0])) - - for point in self.optimized_travelling_salesman(altPoints): - gcode += self.doformat(p.rapid_code, x=point[0], y=point[1]) - gcode += self.doformat(p.down_code, x=point[0], y=point[1]) - gcode += self.doformat(p.up_to_zero_code, x=point[0], y=point[1]) - gcode += self.doformat(p.lift_code, x=point[0], y=point[1]) - measured_distance += abs(distance_euclidian(point[0], point[1], self.oldx, self.oldy)) - self.oldx = point[0] - self.oldy = point[1] + log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> " + "The loaded Excellon file has no drills ...") + self.app.inform.emit('[ERROR_NOTCL]The loaded Excellon file has no drills ...') + return 'fail' log.debug("The total travel distance with Travelling Salesman Algorithm is: %s" % str(measured_distance)) gcode += self.doformat(p.spindle_stop_code) # Spindle stop @@ -4867,6 +4887,7 @@ class CNCjob(Geometry): log.debug("The total travel distance including travel to end position is: %s" % str(measured_distance) + '\n') self.gcode = gcode + return 'OK' def generate_from_multitool_geometry(self, geometry, append=True, tooldia=None, offset=0.0, tolerance=0, z_cut=1.0, z_move=2.0,