From fd895e3c21669dbf9175575aec5c0a446ed38ff5 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 16 Aug 2020 23:05:31 +0300 Subject: [PATCH] - in CNCJob UI Autolevelling - updated the UI with controls for probing GCode parameters and added signals and slots for the UI - in CNCJob UI Autolevelling - added a mini gcode sender for the GRBL to be able to send the probing GCode and get the height map (I may make a small and light app for that so it does not need to have FlatCAM on the GCode sender PC) - in CNCJob UI Autolevelling finished the probing GCode generation for MACH/LinuxCNC controllers; this GCode can also be viewed --- CHANGELOG.md | 6 + appEditors/AppTextEditor.py | 2 +- appGUI/ObjectUI.py | 261 +++++++++++++++++++++++++---- appObjects/FlatCAMCNCJob.py | 323 ++++++++++++++++++++++++++++++++++-- 4 files changed, 545 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b370163..b3b1135e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ CHANGELOG for FlatCAM beta ================================================= +16.08.2020 + +- in CNCJob UI Autolevelling - updated the UI with controls for probing GCode parameters and added signals and slots for the UI +- in CNCJob UI Autolevelling - added a mini gcode sender for the GRBL to be able to send the probing GCode and get the height map (I may make a small and light app for that so it does not need to have FlatCAM on the GCode sender PC) +- in CNCJob UI Autolevelling finished the probing GCode generation for MACH/LinuxCNC controllers; this GCode can also be viewed + 14.08.2020 - in CNCJob UI worked on the UI for the Autolevelling diff --git a/appEditors/AppTextEditor.py b/appEditors/AppTextEditor.py index 52e7eb8a..01d2c090 100644 --- a/appEditors/AppTextEditor.py +++ b/appEditors/AppTextEditor.py @@ -365,7 +365,7 @@ class AppTextEditor(QtWidgets.QWidget): def handleCopyAll(self): text = self.code_editor.toPlainText() self.app.clipboard.setText(text) - self.app.inform.emit(_("Code Editor content copied to clipboard ...")) + self.app.inform.emit(_("Content copied to clipboard ...")) # def closeEvent(self, QCloseEvent): # super().closeEvent(QCloseEvent) diff --git a/appGUI/ObjectUI.py b/appGUI/ObjectUI.py index 0d74abb7..1d2f3743 100644 --- a/appGUI/ObjectUI.py +++ b/appGUI/ObjectUI.py @@ -1917,11 +1917,11 @@ class CNCObjectUI(ObjectUI): grid0.setColumnStretch(1, 1) self.al_box.addLayout(grid0) - al_title = FCLabel('%s' % _("Test Points Table")) + al_title = FCLabel('%s' % _("Probe Points Table")) al_title.setToolTip(_("Generate GCode that will obtain the height map")) self.show_al_table = FCCheckBox(_("Show")) - self.show_al_table.setToolTip(_("Toggle the display of the Test Points table.")) + self.show_al_table.setToolTip(_("Toggle the display of the Probe Points table.")) self.show_al_table.setChecked(True) hor_lay = QtWidgets.QHBoxLayout() @@ -1931,16 +1931,16 @@ class CNCObjectUI(ObjectUI): grid0.addLayout(hor_lay, 0, 0, 1, 2) - self.al_testpoints_table = FCTable() - self.al_testpoints_table.setColumnCount(3) - self.al_testpoints_table.setColumnWidth(0, 20) - self.al_testpoints_table.setHorizontalHeaderLabels(['#', _('X-Y Coordinates'), _('Height')]) + self.al_probe_points_table = FCTable() + self.al_probe_points_table.setColumnCount(3) + self.al_probe_points_table.setColumnWidth(0, 20) + self.al_probe_points_table.setHorizontalHeaderLabels(['#', _('X-Y Coordinates'), _('Height')]) - grid0.addWidget(self.al_testpoints_table, 1, 0, 1, 2) + grid0.addWidget(self.al_probe_points_table, 1, 0, 1, 2) self.voronoi_cb = FCCheckBox(_("Show Voronoi diagram")) self.voronoi_cb.setToolTip( - _("Display Voronoi diagram if there are test points in the table.") + _("Display Voronoi diagram if there are probe points in the table.") ) grid0.addWidget(self.voronoi_cb, 3, 0, 1, 2) @@ -1951,8 +1951,8 @@ class CNCObjectUI(ObjectUI): al_mode_lbl = FCLabel('%s:' % _("Mode")) al_mode_lbl.setToolTip(_("Choose a mode for height map generation.\n" - "- Manual: will pick a selection of test points by clicking on canvas\n" - "- Grid: will automatically generate a grid of test points")) + "- Manual: will pick a selection of probe points by clicking on canvas\n" + "- Grid: will automatically generate a grid of probe points")) self.al_mode_radio = RadioSet( [ @@ -1982,7 +1982,7 @@ class CNCObjectUI(ObjectUI): grid0.addWidget(self.al_rows_label, 11, 0) grid0.addWidget(self.al_rows_entry, 11, 1) - self.al_add_button = FCButton(_("Add test points")) + self.al_add_button = FCButton(_("Add Probe Points")) grid0.addWidget(self.al_add_button, 13, 0, 1, 2) separator_line = QtWidgets.QFrame() @@ -1990,27 +1990,232 @@ class CNCObjectUI(ObjectUI): separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) grid0.addWidget(separator_line, 14, 0, 1, 2) - self.al_controller_label = FCLabel('%s:' % _("Controller")) + # ############################################################################################################# + # ############### Probe GCode Generation ###################################################################### + # ############################################################################################################# + + self.probe_gc_label = FCLabel('%s:' % _("Probe GCode Generation")) + self.probe_gc_label.setToolTip( + _("Will create a GCode which will be sent to the controller,\n" + "either through a file or directly, with the intent to get the height map\n" + "that is to modify the original GCode to level the cutting height.") + ) + grid0.addWidget(self.probe_gc_label, 16, 0, 1, 2) + + # Travel Z Probe + self.ptravelz_label = QtWidgets.QLabel('%s:' % _("Probe Z travel")) + self.ptravelz_label.setToolTip( + _("The safe Z for probe travelling between probe points.") + ) + self.ptravelz_entry = FCDoubleSpinner() + self.ptravelz_entry.set_precision(self.decimals) + self.ptravelz_entry.set_range(0.0000, 9999.9999) + + grid0.addWidget(self.ptravelz_label, 18, 0) + grid0.addWidget(self.ptravelz_entry, 18, 1) + + # Probe depth + self.pdepth_label = QtWidgets.QLabel('%s:' % _("Probe Z depth")) + self.pdepth_label.setToolTip( + _("The maximum depth that the probe is allowed\n" + "to probe. Negative value, in current units.") + ) + self.pdepth_entry = FCDoubleSpinner() + self.pdepth_entry.set_precision(self.decimals) + self.pdepth_entry.set_range(-99999.9999, 0.0000) + + grid0.addWidget(self.pdepth_label, 20, 0) + grid0.addWidget(self.pdepth_entry, 20, 1) + + # Probe feedrate + self.feedrate_probe_label = QtWidgets.QLabel('%s:' % _("Feedrate Probe")) + self.feedrate_probe_label.setToolTip( + _("The feedrate used while the probe is probing.") + ) + self.feedrate_probe_entry = FCDoubleSpinner() + self.feedrate_probe_entry.set_precision(self.decimals) + self.feedrate_probe_entry.set_range(0, 99999.9999) + + grid0.addWidget(self.feedrate_probe_label, 22, 0) + grid0.addWidget(self.feedrate_probe_entry, 22, 1) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 23, 0, 1, 2) + + self.al_controller_label = FCLabel('%s:' % _("Controller")) self.al_rows_label.setToolTip( _("The kind of controller for which to generate\n" "height map gcode.") ) self.al_controller_combo = FCComboBox() - self.al_controller_combo.addItems(["MACH", "LinuxCNC"]) - grid0.addWidget(self.al_controller_label, 15, 0) - grid0.addWidget(self.al_controller_combo, 15, 1) + self.al_controller_combo.addItems(["MACH3", "MACH4", "LinuxCNC", "GRBL"]) + grid0.addWidget(self.al_controller_label, 24, 0) + grid0.addWidget(self.al_controller_combo, 24, 1) - self.h_gcode_button = FCButton(_("Generate Height Map GCode")) - grid0.addWidget(self.h_gcode_button, 17, 0, 1, 2) + # ############################################################################################################# + # ########################## GRBL frame ####################################################################### + # ############################################################################################################# + self.grbl_frame = QtWidgets.QFrame() + self.grbl_frame.setContentsMargins(0, 0, 0, 0) + grid0.addWidget(self.grbl_frame, 26, 0, 1, 2) - self.import_heights_button = FCButton(_("Import Height Map")) - grid0.addWidget(self.import_heights_button, 19, 0, 1, 2) + self.grbl_box = QtWidgets.QVBoxLayout() + self.grbl_box.setContentsMargins(0, 0, 0, 0) + self.grbl_frame.setLayout(self.grbl_box) + + grbl_grid = QtWidgets.QGridLayout() + grbl_grid.setColumnStretch(0, 0) + grbl_grid.setColumnStretch(1, 1) + grbl_grid.setColumnStretch(2, 0) + self.grbl_box.addLayout(grbl_grid) + + # GRBL CONNECT + self.grbl_connect_label = FCLabel('%s:' % _("GRBL Connect")) + self.grbl_connect_label.setToolTip( + _("Setup and connect to GRBL controller.") + ) + grbl_grid.addWidget(self.grbl_connect_label, 0, 0, 1, 2) + + # COM list + self.com_list_label = FCLabel('%s:' % _("COM list")) + self.com_list_label.setToolTip( + _("Lists the available serial ports.") + ) + + self.com_list_combo = FCComboBox() + self.com_search_button = FCButton(_("Search")) + self.com_search_button.setToolTip( + _("Search for the available serial ports.") + ) + grbl_grid.addWidget(self.com_list_label, 2, 0) + grbl_grid.addWidget(self.com_list_combo, 2, 1) + grbl_grid.addWidget(self.com_search_button, 2, 2) + + # BAUDRATES list + self.baudrates_list_label = FCLabel('%s:' % _("Baud rates")) + self.baudrates_list_label.setToolTip( + _("Lists the available serial ports.") + ) + + self.baudrates_list_combo = FCComboBox() + cbmodel = QtCore.QStringListModel() + self.baudrates_list_combo.setModel(cbmodel) + self.baudrates_list_combo.addItems( + ['9600', '19200', '38400', '57600', '115200', '230400', '460800', '500000', '576000', '921600', '1000000', + '1152000', '1500000', '2000000']) + self.baudrates_list_combo.setCurrentText('115200') + + self.com_connect_button = FCButton(_("(Dis)Connect")) + self.com_connect_button.setToolTip( + _("Connect to the selected port with the selected baud rate.") + ) + grbl_grid.addWidget(self.baudrates_list_label, 4, 0) + grbl_grid.addWidget(self.baudrates_list_combo, 4, 1) + grbl_grid.addWidget(self.com_connect_button, 4, 2) + + # New baudrate + self.new_bd_label = FCLabel('%s:' % _("New")) + self.new_bd_label.setToolTip( + _("New, custom baudrate.") + ) + + self.new_baudrate_entry = FCSpinner() + self.new_baudrate_entry.set_range(40, 9999999) + + self.add_bd_button = FCButton(_("Add")) + self.add_bd_button.setToolTip( + _("Add the specified custom baudrate to the list.") + ) + grbl_grid.addWidget(self.new_bd_label, 6, 0) + grbl_grid.addWidget(self.new_baudrate_entry, 6, 1) + grbl_grid.addWidget(self.add_bd_button, 6, 2) + + self.del_bd_button = FCButton(_("Delete selected baudrate")) + grbl_grid.addWidget(self.del_bd_button, 8, 0, 1, 3) separator_line = QtWidgets.QFrame() separator_line.setFrameShape(QtWidgets.QFrame.HLine) separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - grid0.addWidget(separator_line, 21, 0, 1, 2) + grbl_grid.addWidget(separator_line, 10, 0, 1, 3) + + # GRBL CONTROL + self.grbl_control_label = FCLabel('%s:' % _("GRBL Control")) + self.grbl_control_label.setToolTip( + _("Send commands to GRBL controller.") + ) + grbl_grid.addWidget(self.grbl_control_label, 12, 0, 1, 3) + + # CUSTOM COMMAND + self.grbl_command_label = FCLabel('%s:' % _("Command")) + self.grbl_command_label.setToolTip( + _("Send a custom command to GRBL.") + ) + + self.grbl_command_entry = FCEntry() + + self.grbl_send_button = FCButton(_("Send")) + self.grbl_send_button.setToolTip( + _("Send a custom command to GRBL.") + ) + grbl_grid.addWidget(self.grbl_command_label, 14, 0) + grbl_grid.addWidget(self.grbl_command_entry, 14, 1) + grbl_grid.addWidget(self.grbl_send_button, 14, 2) + + # ZERO ALL AXES + self.grbl_zero_button = FCButton(_("ZERO all axes")) + self.grbl_zero_button.setToolTip( + _("Zero all CNC axes at current position.") + ) + grbl_grid.addWidget(self.grbl_zero_button, 16, 0, 1, 3) + + # GET HEIGHT MAP + self.grbl_get_heightmap_button = FCButton(_("Get Height Map")) + self.grbl_get_heightmap_button.setToolTip( + _("Will send the probing GCode to the GRBL controller\n" + "and wait for the Z probing data.") + ) + grbl_grid.addWidget(self.grbl_get_heightmap_button, 18, 0, 1, 3) + + self.grbl_frame.hide() + # ############################################################################################################# + + height_lay = QtWidgets.QHBoxLayout() + self.h_gcode_button = FCButton(_("Generate Height Map GCode")) + self.h_gcode_button.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) + + height_lay.addWidget(self.h_gcode_button) + self.view_h_gcode_button = QtWidgets.QToolButton() + self.view_h_gcode_button.setIcon(QtGui.QIcon(self.app.resource_location + '/find32.png')) + # self.view_h_gcode_button.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored) + self.view_h_gcode_button.setToolTip( + _("View the probing GCode.") + ) + # height_lay.addStretch() + height_lay.addWidget(self.view_h_gcode_button) + + grid0.addLayout(height_lay, 28, 0, 1, 2) + + self.import_heights_button = FCButton(_("Import Height Map")) + grid0.addWidget(self.import_heights_button, 30, 0, 1, 2) + + self.h_gcode_button.hide() + self.import_heights_button.hide() + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 31, 0, 1, 2) + + self.al_button = FCButton(_("Apply Autolevel map")) + grid0.addWidget(self.al_button, 32, 0, 1, 2) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 34, 0, 1, 2) # #################### # ## Export G-Code ## @@ -2162,14 +2367,14 @@ class CNCObjectUI(ObjectUI): self.custom_box.addWidget(self.export_gcode_button) self.custom_box.addStretch() - self.al_testpoints_table.setRowCount(0) - self.al_testpoints_table.resizeColumnsToContents() - self.al_testpoints_table.resizeRowsToContents() - v_header = self.al_testpoints_table.verticalHeader() + self.al_probe_points_table.setRowCount(0) + self.al_probe_points_table.resizeColumnsToContents() + self.al_probe_points_table.resizeRowsToContents() + v_header = self.al_probe_points_table.verticalHeader() v_header.hide() - self.al_testpoints_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.al_probe_points_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - h_header = self.al_testpoints_table.horizontalHeader() + h_header = self.al_probe_points_table.horizontalHeader() h_header.setMinimumSectionSize(10) h_header.setDefaultSectionSize(70) h_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) @@ -2177,8 +2382,8 @@ class CNCObjectUI(ObjectUI): h_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) h_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) - self.al_testpoints_table.setMinimumHeight(self.al_testpoints_table.getHeight()) - self.al_testpoints_table.setMaximumHeight(self.al_testpoints_table.getHeight()) + self.al_probe_points_table.setMinimumHeight(self.al_probe_points_table.getHeight()) + self.al_probe_points_table.setMaximumHeight(self.al_probe_points_table.getHeight()) # Signals self.sal_cb.stateChanged.connect(lambda state: self.al_frame.show() if state else self.al_frame.hide()) diff --git a/appObjects/FlatCAMCNCJob.py b/appObjects/FlatCAMCNCJob.py index 29c532cc..85603de0 100644 --- a/appObjects/FlatCAMCNCJob.py +++ b/appObjects/FlatCAMCNCJob.py @@ -30,6 +30,8 @@ except Exception: import os import sys +import serial +import glob import math import gettext @@ -130,6 +132,9 @@ class CNCJobObject(FlatCAMObj, CNCjob): # determine if the GCode was generated out of a Excellon object or a Geometry object self.origin_kind = None + self.coords_decimals = 4 + self.fr_decimals = 2 + # used for parsing the GCode lines to adjust the GCode when the GCode is offseted or scaled gcodex_re_string = r'(?=.*(X[-\+]?\d*\.\d*))' self.g_x_re = re.compile(gcodex_re_string) @@ -152,9 +157,11 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.annotation = self.app.plotcanvas.new_text_group(collection=self.text_col) self.gcode_editor_tab = None + self.gcode_viewer_tab = None self.source_file = '' self.units_found = self.app.defaults['units'] + self.probing_gcode_text = '' # store the current selection shape status to be restored after manual adding test points self.old_selection_state = self.app.defaults['global_selection_shape'] @@ -188,6 +195,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): } ''' self.al_geometry_dict = {} + self.grbl_ser_port = None # Attributes to be included in serialization # Always append to it because it carries contents @@ -403,7 +411,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): tool_idx = 0 n = len(self.al_geometry_dict) - self.ui.al_testpoints_table.setRowCount(n) + self.ui.al_probe_points_table.setRowCount(n) for id_key, value in self.al_geometry_dict.items(): tool_idx += 1 @@ -421,14 +429,14 @@ class CNCJobObject(FlatCAMObj, CNCjob): coords_item.setFlags(QtCore.Qt.ItemIsEnabled) height_item.setFlags(QtCore.Qt.ItemIsEnabled) - self.ui.al_testpoints_table.setItem(row_no, 0, t_id) # Tool name/id - self.ui.al_testpoints_table.setItem(row_no, 1, coords_item) # X-Y coords - self.ui.al_testpoints_table.setItem(row_no, 2, height_item) # Determined Height + self.ui.al_probe_points_table.setItem(row_no, 0, t_id) # Tool name/id + self.ui.al_probe_points_table.setItem(row_no, 1, coords_item) # X-Y coords + self.ui.al_probe_points_table.setItem(row_no, 2, height_item) # Determined Height - self.ui.al_testpoints_table.resizeColumnsToContents() - self.ui.al_testpoints_table.resizeRowsToContents() + self.ui.al_probe_points_table.resizeColumnsToContents() + self.ui.al_probe_points_table.resizeRowsToContents() - h_header = self.ui.al_testpoints_table.horizontalHeader() + h_header = self.ui.al_probe_points_table.horizontalHeader() h_header.setMinimumSectionSize(10) h_header.setDefaultSectionSize(70) h_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) @@ -436,13 +444,19 @@ class CNCJobObject(FlatCAMObj, CNCjob): h_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) h_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) - self.ui.al_testpoints_table.setMinimumHeight(self.ui.al_testpoints_table.getHeight()) - self.ui.al_testpoints_table.setMaximumHeight(self.ui.al_testpoints_table.getHeight()) + self.ui.al_probe_points_table.setMinimumHeight(self.ui.al_probe_points_table.getHeight()) + self.ui.al_probe_points_table.setMaximumHeight(self.ui.al_probe_points_table.getHeight()) - if self.ui.al_testpoints_table.model().rowCount(): + if self.ui.al_probe_points_table.model().rowCount(): self.ui.voronoi_cb.setDisabled(False) + self.ui.grbl_get_heightmap_button.setDisabled(False) + self.ui.h_gcode_button.setDisabled(False) + self.ui.view_h_gcode_button.setDisabled(False) else: self.ui.voronoi_cb.setDisabled(True) + self.ui.grbl_get_heightmap_button.setDisabled(True) + self.ui.h_gcode_button.setDisabled(True) + self.ui.view_h_gcode_button.setDisabled(True) def set_ui(self, ui): FlatCAMObj.set_ui(self, ui) @@ -527,18 +541,27 @@ class CNCJobObject(FlatCAMObj, CNCjob): 'Basic' )) - # self.ui.cnc_frame.hide() + self.ui.sal_cb.hide() else: self.ui.level.setText(_( 'Advanced' )) - # self.ui.cnc_frame.show() + self.ui.sal_cb.show() self.ui.updateplot_button.clicked.connect(self.on_updateplot_button_click) self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_button_click) self.ui.review_gcode_button.clicked.connect(self.on_edit_code_click) self.ui.editor_button.clicked.connect(lambda: self.app.object2editor()) + + # autolevelling signals self.ui.al_mode_radio.activated_custom.connect(self.on_mode_radio) + self.ui.al_controller_combo.currentIndexChanged.connect(self.on_controller_change) + self.ui.com_search_button.clicked.connect(self.on_search_ports) + self.ui.add_bd_button.clicked.connect(self.on_add_baudrate_grbl) + self.ui.del_bd_button.clicked.connect(self.on_delete_baudrate_grbl) + self.ui.com_connect_button.clicked.connect(self.on_connect_grbl) + self.ui.view_h_gcode_button.clicked.connect(self.on_view_probing_gcode) + self.ui.h_gcode_button.clicked.connect(self.on_generate_probing_gcode) # self.ui.tc_variable_combo.currentIndexChanged[str].connect(self.on_cnc_custom_parameters) @@ -550,6 +573,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.source_file = gc.getvalue() self.ui.al_mode_radio.set_value('grid') + self.on_controller_change() # def on_cnc_custom_parameters(self, signal_text): # if signal_text == 'Parameters': @@ -564,7 +588,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.ui.exc_cnc_tools_table.cellWidget(row, 6).clicked.connect(self.on_plot_cb_click_table) self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) - self.ui.al_add_button.clicked.connect(self.on_add_al_testpoints) + self.ui.al_add_button.clicked.connect(self.on_add_al_probepoints) self.ui.show_al_table.stateChanged.connect(self.on_show_al_table) def ui_disconnect(self): @@ -595,14 +619,14 @@ class CNCJobObject(FlatCAMObj, CNCjob): except (TypeError, AttributeError): pass - def on_add_al_testpoints(self): + def on_add_al_probepoints(self): # create the solid_geo solid_geo = [geo['geom'] for geo in self.gcode_parsed if geo['kind'][0] == 'C'] solid_geo = unary_union(solid_geo) # reset al table - self.ui.al_testpoints_table.setRowCount(0) + self.ui.al_probe_points_table.setRowCount(0) # reset the al dict self.al_geometry_dict.clear() @@ -795,11 +819,11 @@ class CNCJobObject(FlatCAMObj, CNCjob): l_x, l_y = self.app.on_jump_to() def on_show_al_table(self, state): - self.ui.al_testpoints_table.show() if state else self.ui.al_testpoints_table.hide() + self.ui.al_probe_points_table.show() if state else self.ui.al_probe_points_table.hide() def on_mode_radio(self, val): # reset al table - self.ui.al_testpoints_table.setRowCount(0) + self.ui.al_probe_points_table.setRowCount(0) # reset the al dict self.al_geometry_dict.clear() @@ -818,6 +842,269 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.ui.al_columns_entry.setDisabled(False) self.ui.al_columns_label.setDisabled(False) + def on_controller_change(self): + if self.ui.al_controller_combo.get_value() == 'GRBL': + self.ui.h_gcode_button.hide() + self.ui.view_h_gcode_button.hide() + + self.ui.import_heights_button.hide() + self.ui.grbl_frame.show() + self.on_search_ports(muted=True) + else: + self.ui.h_gcode_button.show() + self.ui.view_h_gcode_button.show() + + self.ui.import_heights_button.show() + self.ui.grbl_frame.hide() + + def list_serial_ports(self): + """ + Lists serial port names. + From here: https://stackoverflow.com/questions/12090503/listing-available-com-ports-with-python + + :raises EnvironmentError: On unsupported or unknown platforms + :returns: A list of the serial ports available on the system + """ + + if sys.platform.startswith('win'): + ports = ['COM%s' % (i + 1) for i in range(256)] + elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'): + # this excludes your current terminal "/dev/tty" + ports = glob.glob('/dev/tty[A-Za-z]*') + elif sys.platform.startswith('darwin'): + ports = glob.glob('/dev/tty.*') + else: + raise EnvironmentError('Unsupported platform') + + result = [] + s = serial.Serial() + + for port in ports: + s.port = port + + try: + s.open() + s.close() + result.append(port) + except (OSError, serial.SerialException): + # result.append(port + " (in use)") + pass + + return result + + def on_search_ports(self, muted=None): + port_list = self.list_serial_ports() + self.ui.com_list_combo.clear() + self.ui.com_list_combo.addItems(port_list) + if muted is not True: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("COM list updated ...")) + + def on_connect_grbl(self): + port_name = self.ui.com_list_combo.currentText() + if " (" in port_name: + port_name = port_name.rpartition(" (")[0] + + baudrate = int(self.ui.baudrates_list_combo.currentText()) + + try: + self.grbl_ser_port = serial.serial_for_url(port_name, baudrate, + bytesize=serial.EIGHTBITS, + parity=serial.PARITY_NONE, + stopbits=serial.STOPBITS_ONE, + timeout=0.1, + xonxoff=False, + rtscts=False) + + self.app.inform.emit("%s: %s" % (_("Port connected"), port_name)) + self.ui.com_connect_button.setStyleSheet("QPushButton {color: seagreen;}") + + # Toggle DTR to reset the controller loaded with GRBL (Arduino, ESP32, etc) + try: + self.grbl_ser_port.dtr = False + except IOError: + pass + + self.grbl_ser_port.reset_input_buffer() + + try: + self.grbl_ser_port.dtr = True + except IOError: + pass + except serial.SerialException: + self.grbl_ser_port = serial.Serial() + self.grbl_ser_port.port = port_name + self.grbl_ser_port.close() + self.ui.com_connect_button.setStyleSheet("") + self.app.inform.emit("%s: %s" % (_("Port is connected. Disconnecting"), port_name)) + except Exception: + self.app.inform.emit("[ERROR_NOTCL] %s: %s" % (_("Could not connect to port"), port_name)) + + def on_add_baudrate_grbl(self): + new_bd = str(self.ui.new_baudrate_entry.get_value()) + if int(new_bd) >= 40 and new_bd not in self.ui.baudrates_list_combo.model().stringList(): + self.ui.baudrates_list_combo.addItem(new_bd) + self.ui.baudrates_list_combo.setCurrentText(new_bd) + + def on_delete_baudrate_grbl(self): + current_idx = self.ui.baudrates_list_combo.currentIndex() + self.ui.baudrates_list_combo.removeItem(current_idx) + + def probing_gcode(self, coords, pr_travel, probe_fr, pr_depth, controller): + """ + + :param coords: a list of (x, y) tuples of probe points coordinates + :type coords: list + :param pr_travel: the height (z) where the probe travel between probe points + :type pr_travel: float + :param probe_fr: feedrate when probing + :type probe_fr: float + :param pr_depth: how much to lower the probe searching for contact + :type pr_depth: float + :param controller: a string with the name of the GCode sender for which to create the probing GCode. + Can be: 'MACH3', 'MACH4', 'LinuxCNC', 'GRBL' + :type controller: str + :return: Probing GCode + :rtype: str + """ + + p_gcode = '' + header = '' + + # commands + if controller == 'MACH3': + probing_command = 'G31' + probing_var = '#2002' + openfile_command = 'M40' + closefile_command = 'M41' + elif controller == 'MACH4': + probing_command = 'G31' + probing_var = '#5063' + openfile_command = 'M40' + closefile_command = 'M41' + elif controller == 'LinuxCNC': + probing_command = 'G38.2' + probing_var = '#5422' + openfile_command = '(PROBEOPEN a_probing_points_file.txt)' + closefile_command = '(PROBECLOSE)' + else: + log.debug("CNCJobObject.probing_gcode() -> controller not supported") + return + + # ############################################################################################################# + # ########################### GCODE construction ############################################################## + # ############################################################################################################# + + # header + p_gcode += header + '\n\n' + # supplementary message for LinuxCNC + if controller == 'LinuxCNC': + probing_var += "The file with the stored probing points can be found\n" \ + "in the configuration folder for LinuxCNC.\n" \ + "The name of the file is: a_probing_points_file.txt.\n" + # units + p_gcode += 'G21\n' if self.units == 'MM' else 'G20\n' + # reference mode = absolute + p_gcode += 'G90\n' + # open a new file + p_gcode += openfile_command + '\n' + # move to safe height (probe travel Z) + p_gcode += 'G0 Z%s\n' % str(self.app.dec_format(pr_travel, self.coords_decimals)) + + # probing points + for idx, xy_tuple in enumerate(coords, 1): # index starts from 1 + x = xy_tuple[0] + y = xy_tuple[1] + # move to probing point + p_gcode += "G0 X%sY%s\n" % ( + str(self.app.dec_format(x, self.coords_decimals)), + str(self.app.dec_format(y, self.coords_decimals)) + ) + # do the probing + p_gcode += "%s Z%s F%s\n" % ( + probing_command, + str(self.app.dec_format(pr_depth, self.coords_decimals)), + str(self.app.dec_format(probe_fr, self.fr_decimals)), + ) + # store in a global numeric variable the value of the detected probe Z + # I offset the global numeric variable by 500 so it does not conflict with something else + temp_var = int(idx + 500) + p_gcode += "#%d = %s\n" % (temp_var, probing_var) + + # move to safe height (probe travel Z) + p_gcode += 'G0 Z%s\n' % str(self.app.dec_format(pr_travel, self.coords_decimals)) + + # close the file + p_gcode += closefile_command + '\n' + # finish the GCode + p_gcode += 'M2' + + return p_gcode + + def on_generate_probing_gcode(self): + coords = [] + for id_key, value in self.al_geometry_dict.items(): + x = value['point'].x + y = value['point'].y + coords.append( + ( + self.app.dec_format(x, dec=self.app.decimals), + self.app.dec_format(y, dec=self.app.decimals) + ) + ) + + pr_travel = self.ui.ptravelz_entry.get_value() + probe_fr = self.ui.feedrate_probe_entry.get_value() + pr_depth = self.ui.pdepth_entry.get_value() + controller = self.ui.al_controller_combo.get_value() + self.probing_gcode_text = self.probing_gcode(coords, pr_travel, probe_fr, pr_depth, controller) + + def on_view_probing_gcode(self): + self.app.proc_container.view.set_busy(_("Loading...")) + + gco = self.probing_gcode_text + if gco is None or gco == '': + self.app.inform.emit('[WARNING_NOTCL] %s...' % _('There is nothing to view')) + return + + self.gcode_viewer_tab = AppTextEditor(app=self.app, plain_text=True) + + # add the tab if it was closed + self.app.ui.plot_tab_area.addTab(self.gcode_viewer_tab, '%s' % _("Code Viewer")) + self.gcode_viewer_tab.setObjectName('code_viewer_tab') + + # delete the absolute and relative position and messages in the infobar + self.app.ui.position_label.setText("") + self.app.ui.rel_position_label.setText("") + + self.gcode_viewer_tab.code_editor.completer_enable = False + self.gcode_viewer_tab.buttonRun.hide() + + # Switch plot_area to CNCJob tab + self.app.ui.plot_tab_area.setCurrentWidget(self.gcode_viewer_tab) + + self.gcode_viewer_tab.t_frame.hide() + # then append the text from GCode to the text editor + try: + self.gcode_viewer_tab.load_text(gco, move_to_start=True, clear_text=True) + except Exception as e: + log.debug('FlatCAMCNCJob.on_edit_code_click() -->%s' % str(e)) + return + + self.gcode_viewer_tab.t_frame.show() + self.app.proc_container.view.set_idle() + + self.gcode_viewer_tab.buttonSave.hide() + self.gcode_viewer_tab.buttonOpen.hide() + self.gcode_viewer_tab.buttonPrint.hide() + self.gcode_viewer_tab.buttonPreview.hide() + self.gcode_viewer_tab.buttonReplace.hide() + self.gcode_viewer_tab.sel_all_cb.hide() + self.gcode_viewer_tab.entryReplace.hide() + + self.gcode_viewer_tab.code_editor.setReadOnly(True) + + self.app.inform.emit('[success] %s...' % _('Loaded Machine Code into Code Viewer')) + def on_updateplot_button_click(self, *args): """ Callback for the "Updata Plot" button. Reads the form for updates @@ -904,7 +1191,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): def on_edit_code_click(self, *args): """ - Handler activated by a button clicked when editing GCode. + Handler activated by a button clicked when reviewing GCode. :param args: :return: