From 4ab23749036cdbda6601a23be8648a4255f2f33c Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 21 Feb 2019 01:14:55 +0200 Subject: [PATCH] - added protection against creating CNCJob from an empty Geometry object (with no geometry inside) - changed the shortcut key for YOuTube channel from F2 to key F4 --- FlatCAMGUI.py | 12 +- FlatCAMObj.py | 10 + README.md | 5 + camlib.py | 2 + flatcamTools/ToolSolderPaste.py | 324 +++++++++++++++++++++++++++++--- 5 files changed, 326 insertions(+), 27 deletions(-) diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 64b535d0..1e12ab44 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -325,8 +325,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.menuhelp_manual = self.menuhelp.addAction(QtGui.QIcon('share/globe16.png'), 'Help\tF1') self.menuhelp_home = self.menuhelp.addAction(QtGui.QIcon('share/home16.png'), 'FlatCAM.org') self.menuhelp.addSeparator() - self.menuhelp_videohelp = self.menuhelp.addAction(QtGui.QIcon('share/youtube32.png'), 'YouTube Channel\tF2') self.menuhelp_shortcut_list = self.menuhelp.addAction(QtGui.QIcon('share/shortcuts24.png'), 'Shortcuts List\tF3') + self.menuhelp_videohelp = self.menuhelp.addAction(QtGui.QIcon('share/youtube32.png'), 'YouTube Channel\tF4') self.menuhelp_about = self.menuhelp.addAction(QtGui.QIcon('share/about32.png'), 'About') @@ -1034,7 +1034,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):  Open Online Manual - F2 + F4  Open Online Tutorials @@ -1786,14 +1786,14 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if key == QtCore.Qt.Key_F1 or key == 'F1': webbrowser.open(self.app.manual_url) - # Open Video Help - if key == QtCore.Qt.Key_F2 or key == 'F2': - webbrowser.open(self.app.video_url) - # Show shortcut list if key == QtCore.Qt.Key_F3 or key == 'F3': self.app.on_shortcut_list() + # Open Video Help + if key == QtCore.Qt.Key_F4 or key == 'F4': + webbrowser.open(self.app.video_url) + # Switch to Project Tab if key == QtCore.Qt.Key_1: self.app.on_select_tab('project') diff --git a/FlatCAMObj.py b/FlatCAMObj.py index a2791e10..a8201a95 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -3965,6 +3965,16 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): '[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 + if not self.solid_geometry: + a = 0 + for tooluid_key in self.tools: + if self.tools[tooluid_key]['solid_geometry'] is None: + a += 1 + if a == len(self.tools): + self.app.inform.emit('[ERROR_NOTCL]Cancelled. Empty file, it has no geometry...') + return 'fail' + for tooluid_key in self.sel_tools: tool_cnt += 1 app_obj.progress.emit(20) diff --git a/README.md b/README.md index a712e936..7312b717 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,11 @@ CAD program, and create G-Code for Isolation routing. ================================================= +21.02.2019 + +- added protection against creating CNCJob from an empty Geometry object (with no geometry inside) +- changed the shortcut key for YOuTube channel from F2 to key F4 + 20.02.2019 - finished added a Tool Table for Tool SolderPaste diff --git a/camlib.py b/camlib.py index 4098c83f..59df4410 100644 --- a/camlib.py +++ b/camlib.py @@ -5152,7 +5152,9 @@ class CNCjob(Geometry): log.debug("Starting G-Code...") path_count = 0 current_pt = (0, 0) + pt, geo = storage.nearest(current_pt) + try: while True: path_count += 1 diff --git a/flatcamTools/ToolSolderPaste.py b/flatcamTools/ToolSolderPaste.py index 09e2150a..d4f17235 100644 --- a/flatcamTools/ToolSolderPaste.py +++ b/flatcamTools/ToolSolderPaste.py @@ -323,8 +323,8 @@ class ToolSolderPaste(FlatCAMTool): self.layout.addStretch() - self.gcode_frame.setDisabled(True) - self.save_gcode_frame.setDisabled(True) + # self.gcode_frame.setDisabled(True) + # self.save_gcode_frame.setDisabled(True) self.tools = {} self.tooluid = 0 @@ -686,16 +686,18 @@ class ToolSolderPaste(FlatCAMTool): self.build_ui() def on_geo_select(self): - if self.geo_obj_combo.currentText().rpartition('_')[2] == 'solderpaste': - self.gcode_frame.setDisabled(False) - else: - self.gcode_frame.setDisabled(True) + # if self.geo_obj_combo.currentText().rpartition('_')[2] == 'solderpaste': + # self.gcode_frame.setDisabled(False) + # else: + # self.gcode_frame.setDisabled(True) + pass def on_cncjob_select(self): - if self.cnc_obj_combo.currentText().rpartition('_')[2] == 'solderpaste': - self.save_gcode_frame.setDisabled(False) - else: - self.save_gcode_frame.setDisabled(True) + # if self.cnc_obj_combo.currentText().rpartition('_')[2] == 'solderpaste': + # self.save_gcode_frame.setDisabled(False) + # else: + # self.save_gcode_frame.setDisabled(True) + pass @staticmethod def distance(pt1, pt2): @@ -853,13 +855,28 @@ class ToolSolderPaste(FlatCAMTool): def on_view_gcode(self): name = self.obj_combo.currentText() + obj = self.app.collection.get_by_name(name) - def geo_init(geo_obj, app_obj): - pass + # then append the text from GCode to the text editor + try: + file = StringIO(obj.gcode) + except: + pass - # self.app.new_object("geometry", name + "_cutout", geo_init) - # self.app.inform.emit("[success] Rectangular CutOut operation finished.") - # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab) + try: + for line in file: + print(line) + proc_line = str(line).strip('\n') + self.app.ui.code_editor.append(proc_line) + except Exception as e: + log.debug('ToolSolderPaste.on_view_gcode() -->%s' % str(e)) + self.app.inform.emit('[ERROR]ToolSolderPaste.on_view_gcode() -->%s' % str(e)) + return + + self.app.ui.code_editor.moveCursor(QtGui.QTextCursor.Start) + + self.app.handleTextChanged() + self.app.ui.show() def on_save_gcode(self): name = self.obj_combo.currentText() @@ -871,15 +888,280 @@ class ToolSolderPaste(FlatCAMTool): # self.app.inform.emit("[success] Rectangular CutOut operation finished.") # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab) - def on_create_gcode(self): + 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 + """ + name = self.obj_combo.currentText() + obj = self.app.collection.get_by_name(name) - def geo_init(geo_obj, app_obj): - pass + offset_str = '' + 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') + + try: + xmin = obj.options['xmin'] + ymin = obj.options['ymin'] + xmax = obj.options['xmax'] + ymax = obj.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! + def job_init_multi_geometry(job_obj, app_obj): + assert isinstance(job_obj, FlatCAMCNCjob), \ + "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj) + + # count the tools + tool_cnt = 0 + dia_cnc_dict = {} + current_uid = int(1) + + # this turn on the FlatCAMCNCJob plot for multiple tools + job_obj.multitool = True + job_obj.multigeo = True + job_obj.cnc_tools.clear() + + job_obj.options['xmin'] = xmin + job_obj.options['ymin'] = ymin + job_obj.options['xmax'] = xmax + job_obj.options['ymax'] = ymax + + try: + job_obj.z_pdepth = float(self.options["z_pdepth"]) + except ValueError: + # try to convert comma to decimal point. if it's still not working error message and return + try: + job_obj.z_pdepth = float(self.options["z_pdepth"].replace(',', '.')) + except ValueError: + self.app.inform.emit( + '[ERROR_NOTCL]Wrong value format for self.defaults["z_pdepth"] or self.options["z_pdepth"]') + + 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 + if not self.solid_geometry: + a = 0 + for tooluid_key in self.tools: + if self.tools[tooluid_key]['solid_geometry'] is None: + a += 1 + if a == len(self.tools): + self.app.inform.emit('[ERROR_NOTCL]Cancelled. Empty file, it has no geometry...') + return 'fail' + + for tooluid_key in self.sel_tools: + tool_cnt += 1 + app_obj.progress.emit(20) + + # find the tool_dia associated with the tooluid_key + sel_tool_dia = self.sel_tools[tooluid_key]['tooldia'] + + # search in the self.tools for the sel_tool_dia and when found see what tooluid has + # on the found tooluid in self.tools we also have the solid_geometry that interest us + for k, v in self.tools.items(): + if float('%.4f' % float(v['tooldia'])) == float('%.4f' % float(sel_tool_dia)): + current_uid = int(k) + break + + 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': + o_val = diadict_value.lower() + dia_cnc_dict.update({ + diadict_key: o_val + }) + + if diadict_key == 'type': + t_val = diadict_value + dia_cnc_dict.update({ + diadict_key: t_val + }) + + if diadict_key == 'tool_type': + tt_val = diadict_value + dia_cnc_dict.update({ + diadict_key: tt_val + }) + + 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 + }) + + if dia_cnc_dict['offset'] == 'in': + tool_offset = -dia_cnc_dict['tooldia'] / 2 + offset_str = 'inside' + elif dia_cnc_dict['offset'].lower() == 'out': + tool_offset = dia_cnc_dict['tooldia'] / 2 + offset_str = 'outside' + elif dia_cnc_dict['offset'].lower() == 'path': + offset_str = 'onpath' + tool_offset = 0.0 + else: + offset_str = 'custom' + try: + offset_value = float(self.ui.tool_offset_entry.get_value()) + except ValueError: + # try to convert comma to decimal point. if it's still not working error message and return + try: + offset_value = float(self.ui.tool_offset_entry.get_value().replace(',', '.') + ) + except ValueError: + self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, " + "use a number.") + return + if offset_value: + tool_offset = float(offset_value) + else: + self.app.inform.emit( + "[WARNING] Tool Offset is selected in Tool Table but no value is provided.\n" + "Add a Tool Offset or change the Offset Type." + ) + return + dia_cnc_dict.update({ + 'offset_value': tool_offset + }) + + 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) + + tool_solid_geometry = self.tools[current_uid]['solid_geometry'] + res = job_obj.generate_from_multitool_geometry( + tool_solid_geometry, tooldia=tooldia_val, offset=tool_offset, + 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) + + if res == 'fail': + log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed") + return 'fail' + else: + dia_cnc_dict['gcode'] = res + + dia_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']]) + + # 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) + }) + dia_cnc_dict.clear() + + if use_thread: + # To be run in separate thread + # The idea is that if there is a solid_geometry in the file "root" then most likely thare are no + # separate solid_geometry in the self.tools dictionary + def job_thread(app_obj): + with self.app.proc_container.new("Generating CNC Code"): + if app_obj.new_object("cncjob", outname, job_init_multi_geometry) != 'fail': + app_obj.inform.emit("[success]ToolSolderPaste CNCjob created: %s" % outname) + app_obj.progress.emit(100) + + # Create a promise with the name + self.app.collection.promise(outname) + # Send to worker + self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) + else: + self.app.new_object("cncjob", outname, job_init_multi_geometry) - # self.app.new_object("geometry", name + "_cutout", geo_init) - # self.app.inform.emit("[success] Rectangular CutOut operation finished.") - # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab) def reset_fields(self): self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))