From 230b8b9f3ab4365643f5ae5ef14a6306cf137d0a Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 2 Jan 2021 22:04:05 +0200 Subject: [PATCH] - moved all the Levelling stuff out of the CNCjob Properties Tab to its own tool, Levelling Tool. The functionality itself is not finished yet. --- CHANGELOG.md | 1 + appGUI/MainGUI.py | 8 + appGUI/ObjectUI.py | 578 +---- appObjects/FlatCAMCNCJob.py | 1366 +---------- appTools/ToolLevelling.py | 2264 +++++++++++++++++++ appTools/ToolMilling.py | 7 +- appTools/__init__.py | 2 +- app_Main.py | 6 + assets/resources/dark_resources/level32.png | Bin 0 -> 577 bytes assets/resources/level32.png | Bin 0 -> 561 bytes defaults.py | 27 +- 11 files changed, 2310 insertions(+), 1949 deletions(-) create mode 100644 appTools/ToolLevelling.py create mode 100644 assets/resources/dark_resources/level32.png create mode 100644 assets/resources/level32.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 782c6eda..25598f82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ CHANGELOG for FlatCAM beta - removed the 'machinist setting' and allow all over the app the usages of both negative and positive values (where it is the case) - applied the changes from Andre Spahlinger from PR's #332 and #334 +- moved all the Levelling stuff out of the CNCjob Properties Tab to its own tool, Levelling Tool. The functionality itself is not finished yet. 31.12.2020 diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index 072e8722..1642ed5a 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -1067,6 +1067,10 @@ class MainGUI(QtWidgets.QMainWindow): QtGui.QIcon(self.app.resource_location + '/extract_drill32.png'), _("Drilling Tool")) self.mill_btn = self.toolbartools.addAction( QtGui.QIcon(self.app.resource_location + '/milling_tool32.png'), _("Milling Tool")) + self.level_btn = self.toolbartools.addAction( + QtGui.QIcon(self.app.resource_location + '/level32.png'), _("Levelling Tool")) + self.level_btn.setDisabled(True) + self.level_btn.setToolTip("DISABLED. Work in progress!") self.toolbartools.addSeparator() @@ -2282,6 +2286,10 @@ class MainGUI(QtWidgets.QMainWindow): QtGui.QIcon(self.app.resource_location + '/extract_drill32.png'), _("Drilling Tool")) self.mill_btn = self.toolbartools.addAction( QtGui.QIcon(self.app.resource_location + '/milling_tool32.png'), _("Milling Tool")) + self.level_btn = self.toolbartools.addAction( + QtGui.QIcon(self.app.resource_location + '/level32.png'), _("Levelling Tool")) + self.level_btn.setDisabled(True) + self.level_btn.setToolTip("DISABLED. Work in progress!") self.toolbartools.addSeparator() diff --git a/appGUI/ObjectUI.py b/appGUI/ObjectUI.py index 20a73b3a..944c492d 100644 --- a/appGUI/ObjectUI.py +++ b/appGUI/ObjectUI.py @@ -2160,555 +2160,6 @@ class CNCObjectUI(ObjectUI): ) self.custom_box.addWidget(self.snippets_cb) - # Autolevelling - self.sal_btn = FCButton('%s' % _("Autolevelling"), checkable=True) - # self.sal_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/properties32.png')) - self.sal_btn.setToolTip( - _("Enable the autolevelling feature.") - ) - self.sal_btn.setStyleSheet(""" - QPushButton - { - font-weight: bold; - } - """) - self.custom_box.addWidget(self.sal_btn) - - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - self.custom_box.addWidget(separator_line) - - self.al_frame = QtWidgets.QFrame() - self.al_frame.setContentsMargins(0, 0, 0, 0) - self.custom_box.addWidget(self.al_frame) - - self.al_box = QtWidgets.QVBoxLayout() - self.al_box.setContentsMargins(0, 0, 0, 0) - self.al_frame.setLayout(self.al_box) - - grid0 = QtWidgets.QGridLayout() - grid0.setColumnStretch(0, 0) - grid0.setColumnStretch(1, 1) - self.al_box.addLayout(grid0) - - 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 Probe Points table.")) - self.show_al_table.setChecked(True) - - hor_lay = QtWidgets.QHBoxLayout() - hor_lay.addWidget(al_title) - hor_lay.addStretch() - hor_lay.addWidget(self.show_al_table, alignment=QtCore.Qt.AlignRight) - - grid0.addLayout(hor_lay, 0, 0, 1, 2) - - 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_probe_points_table, 1, 0, 1, 2) - - self.plot_probing_pts_cb = FCCheckBox(_("Plot probing points")) - self.plot_probing_pts_cb.setToolTip( - _("Plot the probing points in the table.\n" - "If a Voronoi method is used then\n" - "the Voronoi areas are also plotted.") - ) - grid0.addWidget(self.plot_probing_pts_cb, 3, 0, 1, 2) - - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - grid0.addWidget(separator_line, 5, 0, 1, 2) - - # ############################################################################################################# - # ############### 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, 7, 0, 1, 2) - - # Travel Z Probe - self.ptravelz_label = FCLabel('%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, 10000.0000) - - grid0.addWidget(self.ptravelz_label, 9, 0) - grid0.addWidget(self.ptravelz_entry, 9, 1) - - # Probe depth - self.pdepth_label = FCLabel('%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(-910000.0000, 0.0000) - - grid0.addWidget(self.pdepth_label, 11, 0) - grid0.addWidget(self.pdepth_entry, 11, 1) - - # Probe feedrate - self.feedrate_probe_label = FCLabel('%s:' % _("Probe Feedrate")) - 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, 910000.0000) - - grid0.addWidget(self.feedrate_probe_label, 13, 0) - grid0.addWidget(self.feedrate_probe_entry, 13, 1) - - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - grid0.addWidget(separator_line, 15, 0, 1, 2) - - # AUTOLEVELL MODE - al_mode_lbl = FCLabel('%s:' % _("Mode")) - al_mode_lbl.setToolTip(_("Choose a mode for height map generation.\n" - "- 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( - [ - {'label': _('Manual'), 'value': 'manual'}, - {'label': _('Grid'), 'value': 'grid'} - ]) - grid0.addWidget(al_mode_lbl, 16, 0) - grid0.addWidget(self.al_mode_radio, 16, 1) - - # AUTOLEVELL METHOD - self.al_method_lbl = FCLabel('%s:' % _("Method")) - self.al_method_lbl.setToolTip(_("Choose a method for approximation of heights from autolevelling data.\n" - "- Voronoi: will generate a Voronoi diagram\n" - "- Bilinear: will use bilinear interpolation. Usable only for grid mode.")) - - self.al_method_radio = RadioSet( - [ - {'label': _('Voronoi'), 'value': 'v'}, - {'label': _('Bilinear'), 'value': 'b'} - ]) - self.al_method_lbl.setDisabled(True) - self.al_method_radio.setDisabled(True) - self.al_method_radio.set_value('v') - - grid0.addWidget(self.al_method_lbl, 17, 0) - grid0.addWidget(self.al_method_radio, 17, 1) - - # ## Columns - self.al_columns_entry = FCSpinner() - self.al_columns_entry.setMinimum(2) - - self.al_columns_label = FCLabel('%s:' % _("Columns")) - self.al_columns_label.setToolTip( - _("The number of grid columns.") - ) - grid0.addWidget(self.al_columns_label, 19, 0) - grid0.addWidget(self.al_columns_entry, 19, 1) - - # ## Rows - self.al_rows_entry = FCSpinner() - self.al_rows_entry.setMinimum(2) - - self.al_rows_label = FCLabel('%s:' % _("Rows")) - self.al_rows_label.setToolTip( - _("The number of grid rows.") - ) - grid0.addWidget(self.al_rows_label, 21, 0) - grid0.addWidget(self.al_rows_entry, 21, 1) - - self.al_add_button = FCButton(_("Add Probe Points")) - grid0.addWidget(self.al_add_button, 23, 0, 1, 2) - - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - grid0.addWidget(separator_line, 25, 0, 1, 2) - - self.al_controller_label = FCLabel('%s:' % _("Controller")) - self.al_controller_label.setToolTip( - _("The kind of controller for which to generate\n" - "height map gcode.") - ) - - self.al_controller_combo = FCComboBox() - self.al_controller_combo.addItems(["MACH3", "MACH4", "LinuxCNC", "GRBL"]) - grid0.addWidget(self.al_controller_label, 27, 0) - grid0.addWidget(self.al_controller_combo, 27, 1) - - # ############################################################################################################# - # ########################## GRBL frame ####################################################################### - # ############################################################################################################# - self.grbl_frame = QtWidgets.QFrame() - self.grbl_frame.setContentsMargins(0, 0, 0, 0) - grid0.addWidget(self.grbl_frame, 29, 0, 1, 2) - - self.grbl_box = QtWidgets.QVBoxLayout() - self.grbl_box.setContentsMargins(0, 0, 0, 0) - self.grbl_frame.setLayout(self.grbl_box) - - # ############################################################################################################# - # ########################## GRBL TOOLBAR ##################################################################### - # ############################################################################################################# - self.al_toolbar = FCDetachableTab(protect=True, parent=self) - self.al_toolbar.setTabsClosable(False) - self.al_toolbar.useOldIndex(True) - self.al_toolbar.set_detachable(val=False) - self.grbl_box.addWidget(self.al_toolbar) - - # GRBL Connect TAB - self.gr_conn_tab = QtWidgets.QWidget() - self.gr_conn_tab.setObjectName("connect_tab") - self.gr_conn_tab_layout = QtWidgets.QVBoxLayout(self.gr_conn_tab) - self.gr_conn_tab_layout.setContentsMargins(2, 2, 2, 2) - # self.gr_conn_scroll_area = VerticalScrollArea() - # self.gr_conn_tab_layout.addWidget(self.gr_conn_scroll_area) - self.al_toolbar.addTab(self.gr_conn_tab, _("Connect")) - - # GRBL Control TAB - self.gr_ctrl_tab = QtWidgets.QWidget() - self.gr_ctrl_tab.setObjectName("connect_tab") - self.gr_ctrl_tab_layout = QtWidgets.QVBoxLayout(self.gr_ctrl_tab) - self.gr_ctrl_tab_layout.setContentsMargins(2, 2, 2, 2) - - # self.gr_ctrl_scroll_area = VerticalScrollArea() - # self.gr_ctrl_tab_layout.addWidget(self.gr_ctrl_scroll_area) - self.al_toolbar.addTab(self.gr_ctrl_tab, _("Control")) - - # GRBL Sender TAB - self.gr_send_tab = QtWidgets.QWidget() - self.gr_send_tab.setObjectName("connect_tab") - self.gr_send_tab_layout = QtWidgets.QVBoxLayout(self.gr_send_tab) - self.gr_send_tab_layout.setContentsMargins(2, 2, 2, 2) - - # self.gr_send_scroll_area = VerticalScrollArea() - # self.gr_send_tab_layout.addWidget(self.gr_send_scroll_area) - self.al_toolbar.addTab(self.gr_send_tab, _("Sender")) - - for idx in range(self.al_toolbar.count()): - if self.al_toolbar.tabText(idx) == _("Connect"): - self.al_toolbar.tabBar.setTabTextColor(idx, QtGui.QColor('red')) - if self.al_toolbar.tabText(idx) == _("Control"): - self.al_toolbar.tabBar.setTabEnabled(idx, False) - if self.al_toolbar.tabText(idx) == _("Sender"): - self.al_toolbar.tabBar.setTabEnabled(idx, False) - # ############################################################################################################# - - # ############################################################################################################# - # GRBL CONNECT - # ############################################################################################################# - grbl_conn_grid = QtWidgets.QGridLayout() - grbl_conn_grid.setColumnStretch(0, 0) - grbl_conn_grid.setColumnStretch(1, 1) - grbl_conn_grid.setColumnStretch(2, 0) - self.gr_conn_tab_layout.addLayout(grbl_conn_grid) - - # 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_conn_grid.addWidget(self.com_list_label, 2, 0) - grbl_conn_grid.addWidget(self.com_list_combo, 2, 1) - grbl_conn_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') - - grbl_conn_grid.addWidget(self.baudrates_list_label, 4, 0) - grbl_conn_grid.addWidget(self.baudrates_list_combo, 4, 1) - - # 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_conn_grid.addWidget(self.new_bd_label, 6, 0) - grbl_conn_grid.addWidget(self.new_baudrate_entry, 6, 1) - grbl_conn_grid.addWidget(self.add_bd_button, 6, 2) - - self.del_bd_button = FCButton(_("Delete selected baudrate")) - grbl_conn_grid.addWidget(self.del_bd_button, 8, 0, 1, 3) - - ctrl_hlay = QtWidgets.QHBoxLayout() - self.controller_reset_button = FCButton(_("Reset")) - self.controller_reset_button.setToolTip( - _("Software reset of the controller.") - ) - self.controller_reset_button.setDisabled(True) - ctrl_hlay.addWidget(self.controller_reset_button) - - self.com_connect_button = FCButton() - self.com_connect_button.setText(_("Disconnected")) - self.com_connect_button.setToolTip( - _("Connect to the selected port with the selected baud rate.") - ) - self.com_connect_button.setStyleSheet("QPushButton {background-color: red;}") - ctrl_hlay.addWidget(self.com_connect_button) - - grbl_conn_grid.addWidget(FCLabel(""), 9, 0, 1, 3) - grbl_conn_grid.setRowStretch(9, 1) - grbl_conn_grid.addLayout(ctrl_hlay, 10, 0, 1, 3) - - # ############################################################################################################# - # GRBL CONTROL - # ############################################################################################################# - grbl_ctrl_grid = QtWidgets.QGridLayout() - grbl_ctrl_grid.setColumnStretch(0, 0) - grbl_ctrl_grid.setColumnStretch(1, 1) - grbl_ctrl_grid.setColumnStretch(2, 0) - self.gr_ctrl_tab_layout.addLayout(grbl_ctrl_grid) - - grbl_ctrl2_grid = QtWidgets.QGridLayout() - grbl_ctrl2_grid.setColumnStretch(0, 0) - grbl_ctrl2_grid.setColumnStretch(1, 1) - self.gr_ctrl_tab_layout.addLayout(grbl_ctrl2_grid) - - self.gr_ctrl_tab_layout.addStretch(1) - - jog_title_label = FCLabel(_("Jog")) - jog_title_label.setStyleSheet(""" - FCLabel - { - font-weight: bold; - } - """) - - zero_title_label = FCLabel(_("Zero Axes")) - zero_title_label.setStyleSheet(""" - FCLabel - { - font-weight: bold; - } - """) - - grbl_ctrl_grid.addWidget(jog_title_label, 0, 0) - grbl_ctrl_grid.addWidget(zero_title_label, 0, 2) - - self.jog_wdg = FCJog(self.app) - self.jog_wdg.setStyleSheet(""" - FCJog - { - border: 1px solid lightgray; - border-radius: 5px; - } - """) - - self.zero_axs_wdg = FCZeroAxes(self.app) - self.zero_axs_wdg.setStyleSheet(""" - FCZeroAxes - { - border: 1px solid lightgray; - border-radius: 5px - } - """) - grbl_ctrl_grid.addWidget(self.jog_wdg, 2, 0) - grbl_ctrl_grid.addWidget(self.zero_axs_wdg, 2, 2) - - self.pause_resume_button = RotatedToolButton() - self.pause_resume_button.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.pause_resume_button.setText(_("Pause/Resume")) - self.pause_resume_button.setCheckable(True) - self.pause_resume_button.setStyleSheet(""" - RotatedToolButton:checked - { - background-color: red; - color: white; - border: none; - } - """) - - pause_frame = QtWidgets.QFrame() - pause_frame.setContentsMargins(0, 0, 0, 0) - pause_frame.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Expanding) - pause_hlay = QtWidgets.QHBoxLayout() - pause_hlay.setContentsMargins(0, 0, 0, 0) - - pause_hlay.addWidget(self.pause_resume_button) - pause_frame.setLayout(pause_hlay) - grbl_ctrl_grid.addWidget(pause_frame, 2, 1) - - # JOG Step - self.jog_step_label = FCLabel('%s:' % _("Step")) - self.jog_step_label.setToolTip( - _("Each jog action will move the axes with this value.") - ) - - self.jog_step_entry = FCSliderWithDoubleSpinner() - self.jog_step_entry.set_precision(self.decimals) - self.jog_step_entry.setSingleStep(0.1) - self.jog_step_entry.set_range(0, 500) - - grbl_ctrl2_grid.addWidget(self.jog_step_label, 0, 0) - grbl_ctrl2_grid.addWidget(self.jog_step_entry, 0, 1) - - # JOG Feedrate - self.jog_fr_label = FCLabel('%s:' % _("Feedrate")) - self.jog_fr_label.setToolTip( - _("Feedrate when jogging.") - ) - - self.jog_fr_entry = FCSliderWithDoubleSpinner() - self.jog_fr_entry.set_precision(self.decimals) - self.jog_fr_entry.setSingleStep(10) - self.jog_fr_entry.set_range(0, 10000) - - grbl_ctrl2_grid.addWidget(self.jog_fr_label, 1, 0) - grbl_ctrl2_grid.addWidget(self.jog_fr_entry, 1, 1) - - # ############################################################################################################# - # GRBL SENDER - # ############################################################################################################# - grbl_send_grid = QtWidgets.QGridLayout() - grbl_send_grid.setColumnStretch(0, 1) - grbl_send_grid.setColumnStretch(1, 0) - self.gr_send_tab_layout.addLayout(grbl_send_grid) - - # Send CUSTOM COMMAND - self.grbl_command_label = FCLabel('%s:' % _("Send Command")) - self.grbl_command_label.setToolTip( - _("Send a custom command to GRBL.") - ) - grbl_send_grid.addWidget(self.grbl_command_label, 2, 0, 1, 2) - - self.grbl_command_entry = FCEntry() - self.grbl_command_entry.setPlaceholderText(_("Type GRBL command ...")) - - self.grbl_send_button = QtWidgets.QToolButton() - self.grbl_send_button.setText(_("Send")) - self.grbl_send_button.setToolTip( - _("Send a custom command to GRBL.") - ) - grbl_send_grid.addWidget(self.grbl_command_entry, 4, 0) - grbl_send_grid.addWidget(self.grbl_send_button, 4, 1) - - # Get Parameter - self.grbl_get_param_label = FCLabel('%s:' % _("Get Config parameter")) - self.grbl_get_param_label.setToolTip( - _("A GRBL configuration parameter.") - ) - grbl_send_grid.addWidget(self.grbl_get_param_label, 6, 0, 1, 2) - - self.grbl_parameter_entry = FCEntry() - self.grbl_parameter_entry.setPlaceholderText(_("Type GRBL parameter ...")) - - self.grbl_get_param_button = QtWidgets.QToolButton() - self.grbl_get_param_button.setText(_("Get")) - self.grbl_get_param_button.setToolTip( - _("Get the value of a specified GRBL parameter.") - ) - grbl_send_grid.addWidget(self.grbl_parameter_entry, 8, 0) - grbl_send_grid.addWidget(self.grbl_get_param_button, 8, 1) - - grbl_send_grid.setRowStretch(9, 1) - - # GET Report - self.grbl_report_button = FCButton(_("Get Report")) - self.grbl_report_button.setToolTip( - _("Print in shell the GRBL report.") - ) - grbl_send_grid.addWidget(self.grbl_report_button, 10, 0, 1, 2) - - hm_lay = QtWidgets.QHBoxLayout() - # GET HEIGHT MAP - self.grbl_get_heightmap_button = FCButton(_("Apply AutoLevelling")) - self.grbl_get_heightmap_button.setToolTip( - _("Will send the probing GCode to the GRBL controller,\n" - "wait for the Z probing data and then apply this data\n" - "over the original GCode therefore doing autolevelling.") - ) - hm_lay.addWidget(self.grbl_get_heightmap_button, stretch=1) - - self.grbl_save_height_map_button = QtWidgets.QToolButton() - self.grbl_save_height_map_button.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as.png')) - self.grbl_save_height_map_button.setToolTip( - _("Will save the GRBL height map.") - ) - hm_lay.addWidget(self.grbl_save_height_map_button, stretch=0, alignment=Qt.AlignRight) - - grbl_send_grid.addLayout(hm_lay, 12, 0, 1, 2) - - self.grbl_frame.hide() - # ############################################################################################################# - - height_lay = QtWidgets.QHBoxLayout() - self.h_gcode_button = FCButton(_("Save Probing GCode")) - self.h_gcode_button.setToolTip( - _("Will save the probing 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 + '/edit_file32.png')) - # self.view_h_gcode_button.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored) - self.view_h_gcode_button.setToolTip( - _("View/Edit the probing GCode.") - ) - # height_lay.addStretch() - height_lay.addWidget(self.view_h_gcode_button) - - grid0.addLayout(height_lay, 31, 0, 1, 2) - - self.import_heights_button = FCButton(_("Import Height Map")) - self.import_heights_button.setToolTip( - _("Import the file that has the Z heights\n" - "obtained through probing and then apply this data\n" - "over the original GCode therefore\n" - "doing autolevelling.") - ) - grid0.addWidget(self.import_heights_button, 33, 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, 35, 0, 1, 2) - # ############################################################################################################# # ## Export G-Code ## # ############################################################################################################# @@ -2737,35 +2188,8 @@ class CNCObjectUI(ObjectUI): self.review_gcode_button.setIcon(QtGui.QIcon(self.app.resource_location + '/find32.png')) g_export_lay.addWidget(self.review_gcode_button) - self.custom_box.addStretch() + self.custom_box.addStretch(1) - 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_probe_points_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - - h_header = self.al_probe_points_table.horizontalHeader() - h_header.setMinimumSectionSize(10) - h_header.setDefaultSectionSize(70) - h_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) - h_header.resizeSection(0, 20) - h_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) - h_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) - - self.al_probe_points_table.setMinimumHeight(self.al_probe_points_table.getHeight()) - self.al_probe_points_table.setMaximumHeight(self.al_probe_points_table.getHeight()) - - # Set initial UI - self.al_frame.hide() - self.al_rows_entry.setDisabled(True) - self.al_rows_label.setDisabled(True) - self.al_columns_entry.setDisabled(True) - self.al_columns_label.setDisabled(True) - self.al_method_lbl.setDisabled(True) - self.al_method_radio.setDisabled(True) - self.al_method_radio.set_value('v') # self.on_mode_radio(val='grid') diff --git a/appObjects/FlatCAMCNCJob.py b/appObjects/FlatCAMCNCJob.py index b5981775..c11e803b 100644 --- a/appObjects/FlatCAMCNCJob.py +++ b/appObjects/FlatCAMCNCJob.py @@ -16,28 +16,11 @@ from datetime import datetime from appEditors.AppTextEditor import AppTextEditor from appObjects.FlatCAMObj import * -from matplotlib.backend_bases import KeyEvent as mpl_key_event - from camlib import CNCjob -from shapely.ops import unary_union -from shapely.geometry import Point, MultiPoint, box -import shapely.affinity as affinity -try: - from shapely.ops import voronoi_diagram - VORONOI_ENABLED = True - # from appCommon.Common import voronoi_diagram -except Exception: - VORONOI_ENABLED = False - import os import sys -import time -import serial -import glob import math -import numpy as np -import random import gettext import appTranslation as fcTranslate @@ -83,6 +66,16 @@ class CNCJobObject(FlatCAMObj, CNCjob): "type": 'Geometry', # "toolchange_macro": '', # "toolchange_macro_enable": False + "tools_al_travelz": self.app.defaults["tools_al_travelz"], + "tools_al_probe_depth": self.app.defaults["tools_al_probe_depth"], + "tools_al_probe_fr": self.app.defaults["tools_al_probe_fr"], + "tools_al_controller": self.app.defaults["tools_al_controller"], + "tools_al_method": self.app.defaults["tools_al_method"], + "tools_al_mode": self.app.defaults["tools_al_mode"], + "tools_al_rows": self.app.defaults["tools_al_rows"], + "tools_al_columns": self.app.defaults["tools_al_columns"], + "tools_al_grbl_jog_step": self.app.defaults["tools_al_grbl_jog_step"], + "tools_al_grbl_jog_fr": self.app.defaults["tools_al_grbl_jog_fr"], }) ''' @@ -170,58 +163,16 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.source_file = '' self.units_found = self.app.defaults['units'] - self.probing_gcode_text = '' - self.grbl_probe_result = '' # store the current selection shape status to be restored after manual adding test points self.old_selection_state = self.app.defaults['global_selection_shape'] - # if mouse is dragging set the object True - self.mouse_is_dragging = False - - # if mouse events are bound to local methods - self.mouse_events_connected = False - - # event handlers references - self.kp = None - self.mm = None - self.mr = None - self.prepend_snippet = '' self.append_snippet = '' self.gc_header = self.gcode_header() self.gc_start = '' self.gc_end = '' - ''' - dictionary of dictionaries to store the information's for the autolevelling - format when using Voronoi diagram: - { - id: { - 'point': Shapely Point - 'geo': Shapely Polygon from Voronoi diagram, - 'height': float - } - } - ''' - self.al_voronoi_geo_storage = {} - - ''' - list of (x, y, x) tuples to store the information's for the autolevelling - format when using bilinear interpolation: - [(x0, y0, z0), (x1, y1, z1), ...] - ''' - self.al_bilinear_geo_storage = [] - - self.solid_geo = None - self.grbl_ser_port = None - - # Shapes container for the Voronoi cells in Autolevelling - if self.app.is_legacy is False: - self.probing_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1) - else: - self.probing_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name=name + "_probing_shapes") - # Attributes to be included in serialization # Always append to it because it carries contents # from predecessors. @@ -233,11 +184,6 @@ class CNCJobObject(FlatCAMObj, CNCjob): def build_ui(self): self.ui_disconnect() - # FIXME: until Shapely 1.8 comes this is disabled - self.ui.sal_btn.setChecked(False) - self.ui.sal_btn.setDisabled(True) - self.ui.sal_btn.setToolTip("DISABLED. Work in progress!") - FlatCAMObj.build_ui(self) self.units = self.app.defaults['units'].upper() @@ -252,9 +198,6 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.ui.exc_cnc_tools_table.show() self.build_excellon_cnc_tools() - if self.ui.sal_btn.isChecked(): - self.build_al_table() - self.ui_connect() def build_cnc_tools_table(self): @@ -442,57 +385,6 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.ui.exc_cnc_tools_table.setMinimumHeight(self.ui.exc_cnc_tools_table.getHeight()) self.ui.exc_cnc_tools_table.setMaximumHeight(self.ui.exc_cnc_tools_table.getHeight()) - def build_al_table(self): - tool_idx = 0 - - n = len(self.al_voronoi_geo_storage) - self.ui.al_probe_points_table.setRowCount(n) - - for id_key, value in self.al_voronoi_geo_storage.items(): - tool_idx += 1 - row_no = tool_idx - 1 - - t_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx)) - x = value['point'].x - y = value['point'].y - xy_coords = self.app.dec_format(x, dec=self.app.decimals), self.app.dec_format(y, dec=self.app.decimals) - coords_item = QtWidgets.QTableWidgetItem(str(xy_coords)) - height = self.app.dec_format(value['height'], dec=self.app.decimals) - height_item = QtWidgets.QTableWidgetItem(str(height)) - - t_id.setFlags(QtCore.Qt.ItemIsEnabled) - coords_item.setFlags(QtCore.Qt.ItemIsEnabled) - height_item.setFlags(QtCore.Qt.ItemIsEnabled) - - 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_probe_points_table.resizeColumnsToContents() - self.ui.al_probe_points_table.resizeRowsToContents() - - h_header = self.ui.al_probe_points_table.horizontalHeader() - h_header.setMinimumSectionSize(10) - h_header.setDefaultSectionSize(70) - h_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) - h_header.resizeSection(0, 20) - h_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) - h_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) - - 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_probe_points_table.model().rowCount(): - self.ui.grbl_get_heightmap_button.setDisabled(False) - self.ui.grbl_save_height_map_button.setDisabled(False) - self.ui.h_gcode_button.setDisabled(False) - self.ui.view_h_gcode_button.setDisabled(False) - else: - self.ui.grbl_get_heightmap_button.setDisabled(True) - self.ui.grbl_save_height_map_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) @@ -515,16 +407,6 @@ class CNCJobObject(FlatCAMObj, CNCjob): # "prepend": self.ui.prepend_text, # "toolchange_macro": self.ui.toolchange_text, # "toolchange_macro_enable": self.ui.toolchange_cb, - "al_travelz": self.ui.ptravelz_entry, - "al_probe_depth": self.ui.pdepth_entry, - "al_probe_fr": self.ui.feedrate_probe_entry, - "al_controller": self.ui.al_controller_combo, - "al_method": self.ui.al_method_radio, - "al_mode": self.ui.al_mode_radio, - "al_rows": self.ui.al_rows_entry, - "al_columns": self.ui.al_columns_entry, - "al_grbl_jog_step": self.ui.jog_step_entry, - "al_grbl_jog_fr": self.ui.jog_fr_entry, }) # Fill form fields only on object create @@ -603,69 +485,9 @@ class CNCJobObject(FlatCAMObj, CNCjob): # Include CNC Job Snippets changed self.ui.snippets_cb.toggled.connect(self.on_update_source_file) - # autolevelling signals - self.ui.sal_btn.toggled.connect(self.on_toggle_autolevelling) - self.ui.al_mode_radio.activated_custom.connect(self.on_mode_radio) - self.ui.al_method_radio.activated_custom.connect(self.on_method_radio) - self.ui.al_controller_combo.currentIndexChanged.connect(self.on_controller_change) - self.ui.plot_probing_pts_cb.stateChanged.connect(self.show_probing_geo) - # GRBL - self.ui.com_search_button.clicked.connect(self.on_grbl_search_ports) - self.ui.add_bd_button.clicked.connect(self.on_grbl_add_baudrate) - self.ui.del_bd_button.clicked.connect(self.on_grbl_delete_baudrate_grbl) - self.ui.controller_reset_button.clicked.connect(self.on_grbl_reset) - self.ui.com_connect_button.clicked.connect(self.on_grbl_connect) - self.ui.grbl_send_button.clicked.connect(self.on_grbl_send_command) - self.ui.grbl_command_entry.returnPressed.connect(self.on_grbl_send_command) - - # Jog - self.ui.jog_wdg.jog_up_button.clicked.connect(lambda: self.on_grbl_jog(direction='yplus')) - self.ui.jog_wdg.jog_down_button.clicked.connect(lambda: self.on_grbl_jog(direction='yminus')) - self.ui.jog_wdg.jog_right_button.clicked.connect(lambda: self.on_grbl_jog(direction='xplus')) - self.ui.jog_wdg.jog_left_button.clicked.connect(lambda: self.on_grbl_jog(direction='xminus')) - self.ui.jog_wdg.jog_z_up_button.clicked.connect(lambda: self.on_grbl_jog(direction='zplus')) - self.ui.jog_wdg.jog_z_down_button.clicked.connect(lambda: self.on_grbl_jog(direction='zminus')) - self.ui.jog_wdg.jog_origin_button.clicked.connect(lambda: self.on_grbl_jog(direction='origin')) - - # Zero - self.ui.zero_axs_wdg.grbl_zerox_button.clicked.connect(lambda: self.on_grbl_zero(axis='x')) - self.ui.zero_axs_wdg.grbl_zeroy_button.clicked.connect(lambda: self.on_grbl_zero(axis='y')) - self.ui.zero_axs_wdg.grbl_zeroz_button.clicked.connect(lambda: self.on_grbl_zero(axis='z')) - self.ui.zero_axs_wdg.grbl_zero_all_button.clicked.connect(lambda: self.on_grbl_zero(axis='all')) - self.ui.zero_axs_wdg.grbl_homing_button.clicked.connect(self.on_grbl_homing) - - # Sender - self.ui.grbl_report_button.clicked.connect(lambda: self.send_grbl_command(command='?')) - self.ui.grbl_get_param_button.clicked.connect( - lambda: self.on_grbl_get_parameter(param=self.ui.grbl_parameter_entry.get_value())) - self.ui.view_h_gcode_button.clicked.connect(self.on_edit_probing_gcode) - self.ui.h_gcode_button.clicked.connect(self.on_save_probing_gcode) - self.ui.import_heights_button.clicked.connect(self.on_import_height_map) - self.ui.pause_resume_button.clicked.connect(self.on_grbl_pause_resume) - self.ui.grbl_get_heightmap_button.clicked.connect(self.on_grbl_autolevel) - self.ui.grbl_save_height_map_button.clicked.connect(self.on_grbl_heightmap_save) - - self.build_al_table_sig.connect(self.build_al_table) # ###################################### END Signal connections ############################################### # ############################################################################################################# - # Show/Hide Advanced Options - if self.app.defaults["global_app_level"] == 'b': - self.ui.level.setText('%s' % _("Beginner")) - - self.ui.sal_btn.hide() - self.ui.sal_btn.setChecked(False) - else: - self.ui.level.setText('%s' % _("Advanced")) - - if 'Roland' in self.pp_excellon_name or 'Roland' in self.pp_geometry_name or 'hpgl' in \ - self.pp_geometry_name: - self.ui.sal_btn.hide() - self.ui.sal_btn.setChecked(False) - else: - self.ui.sal_btn.show() - self.ui.sal_btn.setChecked(self.app.defaults["cncjob_al_status"]) - self.append_snippet = self.app.defaults['cncjob_append'] self.prepend_snippet = self.app.defaults['cncjob_prepend'] @@ -688,12 +510,6 @@ class CNCJobObject(FlatCAMObj, CNCjob): # gc is text self.source_file = gc - self.ui.al_mode_radio.set_value(self.options['al_mode']) - self.on_controller_change() - - self.on_mode_radio(val=self.options['al_mode']) - self.on_method_radio(val=self.options['al_method']) - def ui_connect(self): for row in range(self.ui.cnc_tools_table.rowCount()): self.ui.cnc_tools_table.cellWidget(row, 6).clicked.connect(self.on_plot_cb_click_table) @@ -701,9 +517,6 @@ 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_probepoints) - self.ui.show_al_table.stateChanged.connect(self.on_show_al_table) - def ui_disconnect(self): for row in range(self.ui.cnc_tools_table.rowCount()): try: @@ -722,16 +535,6 @@ class CNCJobObject(FlatCAMObj, CNCjob): except (TypeError, AttributeError): pass - try: - self.ui.al_add_button.clicked.disconnect() - except (TypeError, AttributeError): - pass - - try: - self.ui.show_al_table.stateChanged.disconnect() - except (TypeError, AttributeError): - pass - def on_properties(self, state): if state: self.ui.properties_frame.show() @@ -750,1155 +553,6 @@ class CNCJobObject(FlatCAMObj, CNCjob): for col in range(self.treeWidget.columnCount()): self.ui.treeWidget.resizeColumnToContents(col) - def on_add_al_probepoints(self): - # create the solid_geo - - self.solid_geo = unary_union([geo['geom'] for geo in self.gcode_parsed if geo['kind'][0] == 'C']) - - # reset al table - self.ui.al_probe_points_table.setRowCount(0) - - # reset the al dict - self.al_voronoi_geo_storage.clear() - - xmin, ymin, xmax, ymax = self.solid_geo.bounds - - if self.ui.al_mode_radio.get_value() == 'grid': - width = abs(xmax - xmin) - height = abs(ymax - ymin) - cols = self.ui.al_columns_entry.get_value() - rows = self.ui.al_rows_entry.get_value() - - dx = 0 if cols == 1 else width / (cols - 1) - dy = 0 if rows == 1 else height / (rows - 1) - - points = [] - new_y = ymin - for x in range(rows): - new_x = xmin - for y in range(cols): - formatted_point = ( - self.app.dec_format(new_x, self.app.decimals), - self.app.dec_format(new_y, self.app.decimals) - ) - points.append(formatted_point) - new_x += dx - new_y += dy - - pt_id = 0 - vor_pts_list = [] - bl_pts_list = [] - for point in points: - pt_id += 1 - pt = Point(point) - vor_pts_list.append(pt) - bl_pts_list.append((point[0], point[1], 0.0)) - new_dict = { - 'point': pt, - 'geo': None, - 'height': 0.0 - } - self.al_voronoi_geo_storage[pt_id] = deepcopy(new_dict) - - al_method = self.ui.al_method_radio.get_value() - if al_method == 'v': - if VORONOI_ENABLED is True: - self.generate_voronoi_geometry(pts=vor_pts_list) - # generate Probing GCode - self.probing_gcode_text = self.probing_gcode(storage=self.al_voronoi_geo_storage) - else: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Voronoi function can not be loaded.\n" - "Shapely >= 1.8 is required")) - else: - self.generate_bilinear_geometry(pts=bl_pts_list) - # generate Probing GCode - self.probing_gcode_text = self.probing_gcode(storage=self.al_bilinear_geo_storage) - - self.build_al_table_sig.emit() - if self.ui.plot_probing_pts_cb.get_value(): - self.show_probing_geo(state=True, reset=True) - else: - # clear probe shapes - self.plot_probing_geo(None, False) - - else: - f_probe_pt = Point([xmin, xmin]) - int_keys = [int(k) for k in self.al_voronoi_geo_storage.keys()] - new_id = max(int_keys) + 1 if int_keys else 1 - new_dict = { - 'point': f_probe_pt, - 'geo': None, - 'height': 0.0 - } - self.al_voronoi_geo_storage[new_id] = deepcopy(new_dict) - - radius = 0.3 if self.units == 'MM' else 0.012 - fprobe_pt_buff = f_probe_pt.buffer(radius) - - self.app.inform.emit(_("Click on canvas to add a Probe Point...")) - self.app.defaults['global_selection_shape'] = False - - if self.app.is_legacy is False: - self.app.plotcanvas.graph_event_disconnect('key_press', self.app.ui.keyPressEvent) - self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot) - self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot) - else: - self.app.plotcanvas.graph_event_disconnect(self.app.kp) - self.app.plotcanvas.graph_event_disconnect(self.app.mp) - self.app.plotcanvas.graph_event_disconnect(self.app.mr) - - self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press) - self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release) - - self.mouse_events_connected = True - - self.build_al_table_sig.emit() - if self.ui.plot_probing_pts_cb.get_value(): - self.show_probing_geo(state=True, reset=True) - else: - # clear probe shapes - self.plot_probing_geo(None, False) - - self.plot_probing_geo(geometry=fprobe_pt_buff, visibility=True, custom_color="#0000FFFA") - - def show_probing_geo(self, state, reset=False): - - if reset: - self.probing_shapes.clear(update=True) - - points_geo = [] - poly_geo = [] - - al_method = self.ui.al_method_radio.get_value() - # voronoi diagram - if al_method == 'v': - # create the geometry - radius = 0.1 if self.units == 'MM' else 0.004 - for pt in self.al_voronoi_geo_storage: - if not self.al_voronoi_geo_storage[pt]['geo']: - continue - - p_geo = self.al_voronoi_geo_storage[pt]['point'].buffer(radius) - s_geo = self.al_voronoi_geo_storage[pt]['geo'].buffer(0.0000001) - - points_geo.append(p_geo) - poly_geo.append(s_geo) - - if not points_geo and not poly_geo: - return - - self.plot_probing_geo(geometry=points_geo, visibility=state, custom_color='#000000FF') - self.plot_probing_geo(geometry=poly_geo, visibility=state) - # bilinear interpolation - elif al_method == 'b': - radius = 0.1 if self.units == 'MM' else 0.004 - for pt in self.al_bilinear_geo_storage: - - x_pt = pt[0] - y_pt = pt[1] - p_geo = Point([x_pt, y_pt]).buffer(radius) - - if p_geo.is_valid: - points_geo.append(p_geo) - - if not points_geo: - return - - self.plot_probing_geo(geometry=points_geo, visibility=state, custom_color='#000000FF') - - def plot_probing_geo(self, geometry, visibility, custom_color=None): - if visibility: - if self.app.is_legacy is False: - def random_color(): - r_color = np.random.rand(4) - r_color[3] = 0.5 - return r_color - else: - def random_color(): - while True: - r_color = np.random.rand(4) - r_color[3] = 0.5 - - new_color = '#' - for idx in range(len(r_color)): - new_color += '%x' % int(r_color[idx] * 255) - # do it until a valid color is generated - # a valid color has the # symbol, another 6 chars for the color and the last 2 chars for alpha - # for a total of 9 chars - if len(new_color) == 9: - break - return new_color - - try: - # if self.app.is_legacy is False: - # color = "#0000FFFE" - # else: - # color = "#0000FFFE" - # for sh in points_geo: - # self.add_probing_shape(shape=sh, color=color, face_color=color, visible=True) - - edge_color = "#000000FF" - try: - for sh in geometry: - if custom_color is None: - self.add_probing_shape(shape=sh, color=edge_color, face_color=random_color(), visible=True) - else: - self.add_probing_shape(shape=sh, color=custom_color, face_color=custom_color, visible=True) - except TypeError: - if custom_color is None: - self.add_probing_shape( - shape=geometry, color=edge_color, face_color=random_color(), visible=True) - else: - self.add_probing_shape( - shape=geometry, color=custom_color, face_color=custom_color, visible=True) - - self.probing_shapes.redraw() - except (ObjectDeleted, AttributeError): - self.probing_shapes.clear(update=True) - except Exception as e: - self.app.log.error("CNCJobObject.plot_probing_geo() --> %s" % str(e)) - else: - self.probing_shapes.clear(update=True) - - def add_probing_shape(self, **kwargs): - if self.deleted: - raise ObjectDeleted() - else: - key = self.probing_shapes.add(tolerance=self.drawing_tolerance, layer=0, **kwargs) - return key - - def generate_voronoi_geometry(self, pts): - env = self.solid_geo.envelope - fact = 1 if self.units == 'MM' else 0.039 - env = env.buffer(fact) - - new_pts = deepcopy(pts) - try: - pts_union = MultiPoint(pts) - voronoi_union = voronoi_diagram(geom=pts_union, envelope=env) - except Exception as e: - self.app.log.error("CNCJobObject.generate_voronoi_geometry() --> %s" % str(e)) - for pt_index in range(len(pts)): - new_pts[pt_index] = affinity.translate( - new_pts[pt_index], random.random() * 1e-09, random.random() * 1e-09) - - pts_union = MultiPoint(new_pts) - try: - voronoi_union = voronoi_diagram(geom=pts_union, envelope=env) - except Exception: - return - - new_voronoi = [] - for p in voronoi_union: - new_voronoi.append(p.intersection(env)) - - for pt_key in list(self.al_voronoi_geo_storage.keys()): - for poly in new_voronoi: - if self.al_voronoi_geo_storage[pt_key]['point'].within(poly): - self.al_voronoi_geo_storage[pt_key]['geo'] = poly - - def generate_bilinear_geometry(self, pts): - self.al_bilinear_geo_storage = pts - - # To be called after clicking on the plot. - def on_mouse_click_release(self, event): - - if self.app.is_legacy is False: - event_pos = event.pos - # event_is_dragging = event.is_dragging - right_button = 2 - else: - event_pos = (event.xdata, event.ydata) - # event_is_dragging = self.app.plotcanvas.is_dragging - right_button = 3 - - try: - x = float(event_pos[0]) - y = float(event_pos[1]) - except TypeError: - return - event_pos = (x, y) - - # do paint single only for left mouse clicks - if event.button == 1: - pos = self.app.plotcanvas.translate_coords(event_pos) - - # use the snapped position as reference - snapped_pos = self.app.geo_editor.snap(pos[0], pos[1]) - - probe_pt = Point(snapped_pos) - - xxmin, yymin, xxmax, yymax = self.solid_geo.bounds - box_geo = box(xxmin, yymin, xxmax, yymax) - if not probe_pt.within(box_geo): - self.app.inform.emit(_("Point is not within the object area. Choose another point.")) - return - - int_keys = [int(k) for k in self.al_voronoi_geo_storage.keys()] - new_id = max(int_keys) + 1 if int_keys else 1 - new_dict = { - 'point': probe_pt, - 'geo': None, - 'height': 0.0 - } - self.al_voronoi_geo_storage[new_id] = deepcopy(new_dict) - - # rebuild the al table - self.build_al_table_sig.emit() - - radius = 0.3 if self.units == 'MM' else 0.012 - probe_pt_buff = probe_pt.buffer(radius) - - self.plot_probing_geo(geometry=probe_pt_buff, visibility=True, custom_color="#0000FFFA") - - self.app.inform.emit(_("Added a Probe Point... Click again to add another or right click to finish ...")) - - # if RMB then we exit - elif event.button == right_button and self.mouse_is_dragging is False: - if self.app.is_legacy is False: - self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press) - self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release) - else: - self.app.plotcanvas.graph_event_disconnect(self.kp) - self.app.plotcanvas.graph_event_disconnect(self.mr) - - self.app.kp = self.app.plotcanvas.graph_event_connect('key_press', self.app.ui.keyPressEvent) - self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot) - self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release', - self.app.on_mouse_click_release_over_plot) - - # signal that the mouse events are disconnected from local methods - self.mouse_events_connected = False - - # restore selection - self.app.defaults['global_selection_shape'] = self.old_selection_state - - self.app.inform.emit(_("Finished adding Probe Points...")) - - al_method = self.ui.al_method_radio.get_value() - if al_method == 'v': - if VORONOI_ENABLED is True: - pts_list = [] - for k in self.al_voronoi_geo_storage: - pts_list.append(self.al_voronoi_geo_storage[k]['point']) - self.generate_voronoi_geometry(pts=pts_list) - - self.probing_gcode_text = self.probing_gcode(self.al_voronoi_geo_storage) - else: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Voronoi function can not be loaded.\n" - "Shapely >= 1.8 is required")) - - # rebuild the al table - self.build_al_table_sig.emit() - - if self.ui.plot_probing_pts_cb.get_value(): - self.show_probing_geo(state=True, reset=True) - else: - # clear probe shapes - self.plot_probing_geo(None, False) - - def on_key_press(self, event): - # events out of the self.app.collection view (it's about Project Tab) are of type int - if type(event) is int: - key = event - # events from the GUI are of type QKeyEvent - elif type(event) == QtGui.QKeyEvent: - key = event.key() - elif isinstance(event, mpl_key_event): # MatPlotLib key events are trickier to interpret than the rest - key = event.key - key = QtGui.QKeySequence(key) - - # check for modifiers - key_string = key.toString().lower() - if '+' in key_string: - mod, __, key_text = key_string.rpartition('+') - if mod.lower() == 'ctrl': - # modifiers = QtCore.Qt.ControlModifier - pass - elif mod.lower() == 'alt': - # modifiers = QtCore.Qt.AltModifier - pass - elif mod.lower() == 'shift': - # modifiers = QtCore.Qt.ShiftModifier - pass - else: - # modifiers = QtCore.Qt.NoModifier - pass - key = QtGui.QKeySequence(key_text) - # events from Vispy are of type KeyEvent - else: - key = event.key - - # Escape = Deselect All - if key == QtCore.Qt.Key_Escape or key == 'Escape': - if self.mouse_events_connected is True: - self.mouse_events_connected = False - if self.app.is_legacy is False: - self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press) - self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release) - else: - self.app.plotcanvas.graph_event_disconnect(self.kp) - self.app.plotcanvas.graph_event_disconnect(self.mr) - - self.app.kp = self.app.plotcanvas.graph_event_connect('key_press', self.app.ui.keyPressEvent) - self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot) - self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release', - self.app.on_mouse_click_release_over_plot) - - if self.ui.big_cursor_cb.get_value(): - # restore cursor - self.app.on_cursor_type(val=self.old_cursor_type) - # restore selection - self.app.defaults['global_selection_shape'] = self.old_selection_state - - # Grid toggle - if key == QtCore.Qt.Key_G or key == 'G': - self.app.ui.grid_snap_btn.trigger() - - # Jump to coords - if key == QtCore.Qt.Key_J or key == 'J': - self.app.on_jump_to() - - def on_toggle_autolevelling(self, state): - self.ui.al_frame.show() if state else self.ui.al_frame.hide() - self.app.defaults["cncjob_al_status"] = True if state else False - - def autolevell_gcode(self): - pass - - def autolevell_gcode_line(self, gcode_line): - al_method = self.ui.al_method_radio.get_value() - - coords = () - - if al_method == 'v': - self.autolevell_voronoi(gcode_line, coords) - elif al_method == 'b': - self.autolevell_bilinear(gcode_line, coords) - - def autolevell_bilinear(self, gcode_line, coords): - pass - - def autolevell_voronoi(self, gcode_line, coords): - pass - - def on_show_al_table(self, state): - 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_probe_points_table.setRowCount(0) - - # reset the al dict - self.al_voronoi_geo_storage.clear() - - # reset Voronoi Shapes - self.probing_shapes.clear(update=True) - - # build AL table - self.build_al_table() - - if val == "manual": - self.ui.al_rows_entry.setDisabled(True) - self.ui.al_rows_label.setDisabled(True) - self.ui.al_columns_entry.setDisabled(True) - self.ui.al_columns_label.setDisabled(True) - self.ui.al_method_lbl.setDisabled(True) - self.ui.al_method_radio.setDisabled(True) - self.ui.al_method_radio.set_value('v') - else: - self.ui.al_rows_entry.setDisabled(False) - self.ui.al_rows_label.setDisabled(False) - self.ui.al_columns_entry.setDisabled(False) - self.ui.al_columns_label.setDisabled(False) - self.ui.al_method_lbl.setDisabled(False) - self.ui.al_method_radio.setDisabled(False) - self.ui.al_method_radio.set_value(self.app.defaults['cncjob_al_method']) - - def on_method_radio(self, val): - if val == 'b': - self.ui.al_columns_entry.setMinimum(2) - self.ui.al_rows_entry.setMinimum(2) - else: - self.ui.al_columns_entry.setMinimum(1) - self.ui.al_rows_entry.setMinimum(1) - - 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_grbl_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() - - # if the is empty then there is a chance that we've added probe points but the GRBL controller was selected - # therefore no Probing GCode was genrated (it is different for GRBL on how it gets it's Probing GCode - if not self.probing_gcode_text or self.probing_gcode_text == '': - # generate Probing GCode - al_method = self.ui.al_method_radio.get_value() - storage = self.al_voronoi_geo_storage if al_method == 'v' else self.al_bilinear_geo_storage - self.probing_gcode_text = self.probing_gcode(storage=storage) - - @staticmethod - def on_grbl_list_serial_ports(): - """ - 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_grbl_search_ports(self, muted=None): - port_list = self.on_grbl_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_grbl_connect(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) - - # 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 - - answer = self.on_grbl_wake() - answer = ['ok'] # FIXME: hack for development without a GRBL controller connected - for line in answer: - if 'ok' in line.lower(): - self.ui.com_connect_button.setStyleSheet("QPushButton {background-color: seagreen;}") - self.ui.com_connect_button.setText(_("Connected")) - self.ui.controller_reset_button.setDisabled(False) - - for idx in range(self.ui.al_toolbar.count()): - if self.ui.al_toolbar.tabText(idx) == _("Connect"): - self.ui.al_toolbar.tabBar.setTabTextColor(idx, QtGui.QColor('seagreen')) - if self.ui.al_toolbar.tabText(idx) == _("Control"): - self.ui.al_toolbar.tabBar.setTabEnabled(idx, True) - if self.ui.al_toolbar.tabText(idx) == _("Sender"): - self.ui.al_toolbar.tabBar.setTabEnabled(idx, True) - - self.app.inform.emit("%s: %s" % (_("Port connected"), port_name)) - return - - self.grbl_ser_port.close() - self.app.inform.emit("[ERROR_NOTCL] %s: %s" % (_("Could not connect to GRBL on port"), port_name)) - - 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("QPushButton {background-color: red;}") - self.ui.com_connect_button.setText(_("Disconnected")) - self.ui.controller_reset_button.setDisabled(True) - - for idx in range(self.ui.al_toolbar.count()): - if self.ui.al_toolbar.tabText(idx) == _("Connect"): - self.ui.al_toolbar.tabBar.setTabTextColor(idx, QtGui.QColor('red')) - if self.ui.al_toolbar.tabText(idx) == _("Control"): - self.ui.al_toolbar.tabBar.setTabEnabled(idx, False) - if self.ui.al_toolbar.tabText(idx) == _("Sender"): - self.ui.al_toolbar.tabBar.setTabEnabled(idx, False) - 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_grbl_add_baudrate(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_grbl_delete_baudrate_grbl(self): - current_idx = self.ui.baudrates_list_combo.currentIndex() - self.ui.baudrates_list_combo.removeItem(current_idx) - - def on_grbl_wake(self): - # Wake up grbl - self.grbl_ser_port.write("\r\n\r\n".encode('utf-8')) - # Wait for GRBL controller to initialize - time.sleep(1) - - grbl_out = deepcopy(self.grbl_ser_port.readlines()) - self.grbl_ser_port.reset_input_buffer() - - return grbl_out - - def on_grbl_send_command(self): - cmd = self.ui.grbl_command_entry.get_value() - - # show the Shell Dock - self.app.ui.shell_dock.show() - - def worker_task(): - with self.app.proc_container.new('%s...' % _("Sending")): - self.send_grbl_command(command=cmd) - - self.app.worker_task.emit({'fcn': worker_task, 'params': []}) - - def send_grbl_command(self, command, echo=True): - """ - - :param command: GCode command - :type command: str - :param echo: if to send a '\n' char after - :type echo: bool - :return: the text returned by the GRBL controller after each command - :rtype: str - """ - cmd = command.strip() - if echo: - self.app.inform_shell[str, bool].emit(cmd, False) - - # Send Gcode command to GRBL - snd = cmd + '\n' - self.grbl_ser_port.write(snd.encode('utf-8')) - grbl_out = self.grbl_ser_port.readlines() - if not grbl_out: - self.app.inform_shell[str, bool].emit('\t\t\t: No answer\n', False) - - result = '' - for line in grbl_out: - if echo: - try: - self.app.inform_shell.emit('\t\t\t: ' + line.decode('utf-8').strip().upper()) - except Exception as e: - self.app.log.error("CNCJobObject.send_grbl_command() --> %s" % str(e)) - if 'ok' in line: - result = grbl_out - - return result - - def send_grbl_block(self, command, echo=True): - stripped_cmd = command.strip() - - for grbl_line in stripped_cmd.split('\n'): - if echo: - self.app.inform_shell[str, bool].emit(grbl_line, False) - - # Send Gcode block to GRBL - snd = grbl_line + '\n' - self.grbl_ser_port.write(snd.encode('utf-8')) - grbl_out = self.grbl_ser_port.readlines() - - for line in grbl_out: - if echo: - try: - self.app.inform_shell.emit(' : ' + line.decode('utf-8').strip().upper()) - except Exception as e: - self.app.log.error("CNCJobObject.send_grbl_block() --> %s" % str(e)) - - def on_grbl_get_parameter(self, param): - if '$' in param: - param = param.replace('$', '') - - snd = '$$\n' - self.grbl_ser_port.write(snd.encode('utf-8')) - grbl_out = self.grbl_ser_port.readlines() - for line in grbl_out: - decoded_line = line.decode('utf-8') - par = '$%s' % str(param) - if par in decoded_line: - result = float(decoded_line.rpartition('=')[2]) - self.app.shell_message("GRBL Parameter: %s = %s" % (str(param), str(result)), show=True) - return result - - def on_grbl_jog(self, direction=None): - if direction is None: - return - cmd = '' - - step = self.ui.jog_step_entry.get_value(), - feedrate = self.ui.jog_fr_entry.get_value() - travelz = float(self.app.defaults["cncjob_al_grbl_travelz"]) - - if direction == 'xplus': - cmd = "$J=G91 %s X%s F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(step), str(feedrate)) - if direction == 'xminus': - cmd = "$J=G91 %s X-%s F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(step), str(feedrate)) - if direction == 'yplus': - cmd = "$J=G91 %s Y%s F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(step), str(feedrate)) - if direction == 'yminus': - cmd = "$J=G91 %s Y-%s F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(step), str(feedrate)) - - if direction == 'zplus': - cmd = "$J=G91 %s Z%s F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(step), str(feedrate)) - if direction == 'zminus': - cmd = "$J=G91 %s Z-%s F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(step), str(feedrate)) - - if direction == 'origin': - cmd = "$J=G90 %s Z%s F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(travelz), str(feedrate)) - self.send_grbl_command(command=cmd, echo=False) - cmd = "$J=G90 %s X0.0 Y0.0 F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(feedrate)) - self.send_grbl_command(command=cmd, echo=False) - return - - self.send_grbl_command(command=cmd, echo=False) - - def on_grbl_zero(self, axis): - current_mode = self.on_grbl_get_parameter('10') - if current_mode is None: - return - - cmd = '$10=0' - self.send_grbl_command(command=cmd, echo=False) - - if axis == 'x': - cmd = 'G10 L2 P1 X0' - elif axis == 'y': - cmd = 'G10 L2 P1 Y0' - elif axis == 'z': - cmd = 'G10 L2 P1 Z0' - else: - # all - cmd = 'G10 L2 P1 X0 Y0 Z0' - self.send_grbl_command(command=cmd, echo=False) - - # restore previous mode - cmd = '$10=%d' % int(current_mode) - self.send_grbl_command(command=cmd, echo=False) - - def on_grbl_homing(self): - cmd = '$H' - self.app.inform.emit("%s" % _("GRBL is doing a home cycle.")) - self.on_grbl_wake() - self.send_grbl_command(command=cmd) - - def on_grbl_reset(self): - cmd = '\x18' - self.app.inform.emit("%s" % _("GRBL software reset was sent.")) - self.on_grbl_wake() - self.send_grbl_command(command=cmd) - - def on_grbl_pause_resume(self, checked): - if checked is False: - cmd = '~' - self.send_grbl_command(command=cmd) - self.app.inform.emit("%s" % _("GRBL resumed.")) - else: - cmd = '!' - self.send_grbl_command(command=cmd) - self.app.inform.emit("%s" % _("GRBL paused.")) - - def probing_gcode(self, storage): - """ - :param storage: either a dict of dicts (voronoi) or a list of tuples (bilinear) - :return: Probing GCode - :rtype: str - """ - - p_gcode = '' - header = '' - time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) - - coords = [] - al_method = self.ui.al_method_radio.get_value() - if al_method == 'v': - for id_key, value in storage.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) - ) - ) - else: - for pt in storage: - x = pt[0] - y = pt[1] - 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() - - header += '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \ - (str(self.app.version), str(self.app.version_date)) + '\n' - - header += '(This is a autolevelling probing GCode.)\n' \ - '(Make sure that before you start the job you first do a zero for all axis.)\n\n' - - header += '(Name: ' + str(self.options['name']) + ')\n' - header += '(Type: ' + "Autolevelling Probing GCode " + ')\n' - - header += '(Units: ' + self.units.upper() + ')\n' - header += '(Created on ' + time_str + ')\n' - - # 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)' - elif controller == 'GRBL': - # do nothing here because the Probing GCode for GRBL is obtained differently - return - else: - self.app.log.debug("CNCJobObject.probing_gcode() -> controller not supported") - return - - # ############################################################################################################# - # ########################### GCODE construction ############################################################## - # ############################################################################################################# - - # header - p_gcode += header + '\n' - # supplementary message for LinuxCNC - if controller == 'LinuxCNC': - p_gcode += "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_save_probing_gcode(self): - lines = StringIO(self.probing_gcode_text) - - _filter_ = self.app.defaults['cncjob_save_filters'] - name = "probing_gcode" - try: - dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name) - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export Code ..."), - directory=dir_file_to_save, - ext_filter=_filter_ - ) - except TypeError: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export Code ..."), - ext_filter=_filter_) - - if filename == '': - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export cancelled ...")) - return - else: - try: - force_windows_line_endings = self.app.defaults['cncjob_line_ending'] - if force_windows_line_endings and sys.platform != 'win32': - with open(filename, 'w', newline='\r\n') as f: - for line in lines: - f.write(line) - else: - with open(filename, 'w') as f: - for line in lines: - f.write(line) - except FileNotFoundError: - self.app.inform.emit('[WARNING_NOTCL] %s' % _("No such file or directory")) - return - except PermissionError: - self.app.inform.emit( - '[WARNING] %s' % _("Permission denied, saving not possible.\n" - "Most likely another app is holding the file open and not accessible.") - ) - return 'fail' - - def on_edit_probing_gcode(self): - self.app.proc_container.view.set_busy('%s...' % _("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: - self.app.log.error('FlatCAMCNCJob.on_edit_probing_gcode() -->%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.button_update_code.show() - - # self.gcode_viewer_tab.code_editor.setReadOnly(True) - - self.gcode_viewer_tab.button_update_code.clicked.connect(self.on_update_probing_gcode) - - self.app.inform.emit('[success] %s...' % _('Loaded Machine Code into Code Viewer')) - - def on_update_probing_gcode(self): - self.probing_gcode_text = self.gcode_viewer_tab.code_editor.toPlainText() - - def on_import_height_map(self): - """ - Import the height map file into the app - :return: - :rtype: - """ - - _filter_ = "Text File .txt (*.txt);;All Files (*.*)" - try: - filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import Height Map"), - directory=self.app.get_last_folder(), - filter=_filter_) - except TypeError: - filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import Height Map"), - filter=_filter_) - - filename = str(filename) - - if filename == '': - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - self.app.worker_task.emit({'fcn': self.import_height_map, 'params': [filename]}) - - def import_height_map(self, filename): - """ - - :param filename: - :type filename: - :return: - :rtype: - """ - - try: - if filename: - with open(filename, 'r') as f: - stream = f.readlines() - else: - return - except IOError: - self.app.log.error("Failed to open height map file: %s" % filename) - self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open height map file"), filename)) - return - - idx = 0 - if stream is not None and stream != '': - for line in stream: - if line != '': - idx += 1 - line = line.replace(' ', ',').replace('\n', '').split(',') - if idx not in self.al_voronoi_geo_storage: - self.al_voronoi_geo_storage[idx] = {} - self.al_voronoi_geo_storage[idx]['height'] = float(line[2]) - if 'point' not in self.al_voronoi_geo_storage[idx]: - x = float(line[0]) - y = float(line[1]) - self.al_voronoi_geo_storage[idx]['point'] = Point((x, y)) - - self.build_al_table_sig.emit() - - def on_grbl_autolevel(self): - # show the Shell Dock - self.app.ui.shell_dock.show() - - def worker_task(): - with self.app.proc_container.new('%s...' % _("Sending")): - self.grbl_probe_result = '' - pr_travelz = str(self.ui.ptravelz_entry.get_value()) - probe_fr = str(self.ui.feedrate_probe_entry.get_value()) - pr_depth = str(self.ui.pdepth_entry.get_value()) - - cmd = 'G21\n' - self.send_grbl_command(command=cmd) - cmd = 'G90\n' - self.send_grbl_command(command=cmd) - - for pt_key in self.al_voronoi_geo_storage: - x = str(self.al_voronoi_geo_storage[pt_key]['point'].x) - y = str(self.al_voronoi_geo_storage[pt_key]['point'].y) - - cmd = 'G0 Z%s\n' % pr_travelz - self.send_grbl_command(command=cmd) - cmd = 'G0 X%s Y%s\n' % (x, y) - self.send_grbl_command(command=cmd) - cmd = 'G38.2 Z%s F%s' % (pr_depth, probe_fr) - output = self.send_grbl_command(command=cmd) - - self.grbl_probe_result += output + '\n' - - cmd = 'M2\n' - self.send_grbl_command(command=cmd) - self.app.inform.emit('%s' % _("Finished probing. Doing the autolevelling.")) - - # apply autolevel here - self.on_grbl_apply_autolevel() - - self.app.inform.emit('%s' % _("Sending probing GCode to the GRBL controller.")) - self.app.worker_task.emit({'fcn': worker_task, 'params': []}) - - def on_grbl_heightmap_save(self): - if self.grbl_probe_result != '': - _filter_ = "Text File .txt (*.txt);;All Files (*.*)" - name = "probing_gcode" - try: - dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name) - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export Code ..."), - directory=dir_file_to_save, - ext_filter=_filter_ - ) - except TypeError: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export Code ..."), - ext_filter=_filter_) - - if filename == '': - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export cancelled ...")) - return - else: - try: - force_windows_line_endings = self.app.defaults['cncjob_line_ending'] - if force_windows_line_endings and sys.platform != 'win32': - with open(filename, 'w', newline='\r\n') as f: - for line in self.grbl_probe_result: - f.write(line) - else: - with open(filename, 'w') as f: - for line in self.grbl_probe_result: - f.write(line) - except FileNotFoundError: - self.app.inform.emit('[WARNING_NOTCL] %s' % _("No such file or directory")) - return - except PermissionError: - self.app.inform.emit( - '[WARNING] %s' % _("Permission denied, saving not possible.\n" - "Most likely another app is holding the file open and not accessible.") - ) - return 'fail' - else: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Empty GRBL heightmap.")) - - def on_grbl_apply_autolevel(self): - # TODO here we call the autolevell method - self.app.inform.emit('%s' % _("Finished autolevelling.")) - def on_updateplot_button_click(self, *args): """ Callback for the "Updata Plot" button. Reads the form for updates diff --git a/appTools/ToolLevelling.py b/appTools/ToolLevelling.py new file mode 100644 index 00000000..ddefb261 --- /dev/null +++ b/appTools/ToolLevelling.py @@ -0,0 +1,2264 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# File by: Marius Adrian Stanciu (c) # +# Date: 11/12/2020 # +# License: MIT Licence # +# ########################################################## + +from PyQt5 import QtWidgets, QtCore, QtGui +from PyQt5.QtCore import Qt + +from appObjects.FlatCAMObj import ObjectDeleted +from appTool import AppTool +from appGUI.VisPyVisuals import * +from appGUI.PlotCanvasLegacy import ShapeCollectionLegacy +from appGUI.GUIElements import RadioSet, FCButton, FCComboBox, FCLabel, FCFileSaveDialog, FCCheckBox, FCTable, \ + FCDoubleSpinner, FCSpinner, FCDetachableTab, FCZeroAxes, FCJog, FCSliderWithDoubleSpinner, RotatedToolButton, \ + FCEntry +from appEditors.AppTextEditor import AppTextEditor + +from camlib import CNCjob + +from copy import deepcopy +import time +import serial +import glob +import random +import sys +from io import StringIO +from datetime import datetime + +import numpy as np + +from shapely.ops import unary_union +from shapely.geometry import Point, MultiPoint, box +import shapely.affinity as affinity + +from matplotlib.backend_bases import KeyEvent as mpl_key_event + +try: + from shapely.ops import voronoi_diagram + VORONOI_ENABLED = True + # from appCommon.Common import voronoi_diagram +except Exception: + VORONOI_ENABLED = False + +import logging +import gettext +import appTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + +log = logging.getLogger('base') + + +class ToolLevelling(AppTool, CNCjob): + build_al_table_sig = QtCore.pyqtSignal() + + def __init__(self, app): + self.app = app + self.decimals = self.app.decimals + + AppTool.__init__(self, app) + CNCjob.__init__(self, steps_per_circle=self.app.defaults["cncjob_steps_per_circle"]) + + # ############################################################################# + # ######################### Tool GUI ########################################## + # ############################################################################# + self.ui = LevelUI(layout=self.layout, app=self.app) + self.toolName = self.ui.toolName + + # updated in the self.set_tool_ui() + self.form_fields = {} + + self.first_click = False + self.cursor_pos = None + self.mouse_is_dragging = False + + # if mouse is dragging set the object True + self.mouse_is_dragging = False + + # if mouse events are bound to local methods + self.mouse_events_connected = False + + # event handlers references + self.kp = None + self.mm = None + self.mr = None + + self.probing_gcode_text = '' + self.grbl_probe_result = '' + + ''' + dictionary of dictionaries to store the information's for the autolevelling + format when using Voronoi diagram: + { + id: { + 'point': Shapely Point + 'geo': Shapely Polygon from Voronoi diagram, + 'height': float + } + } + ''' + self.al_voronoi_geo_storage = {} + + ''' + list of (x, y, x) tuples to store the information's for the autolevelling + format when using bilinear interpolation: + [(x0, y0, z0), (x1, y1, z1), ...] + ''' + self.al_bilinear_geo_storage = [] + + self.solid_geo = None + self.grbl_ser_port = None + + self.probing_shapes = None + + self.gcode_viewer_tab = None + + # ############################################################################################################# + # ####################################### Signals ########################################################### + # ############################################################################################################# + self.connect_signals_at_init() + + def install(self, icon=None, separator=None, **kwargs): + AppTool.install(self, icon, separator, shortcut='', **kwargs) + + def run(self, toggle=True): + self.app.defaults.report_usage("ToolFollow()") + log.debug("ToolFOllow().run() was launched ...") + + if toggle: + # if the splitter is hidden, display it + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + # if the Tool Tab is hidden display it, else hide it but only if the objectName is the same + found_idx = None + for idx in range(self.app.ui.notebook.count()): + if self.app.ui.notebook.widget(idx).objectName() == "tool_tab": + found_idx = idx + break + # show the Tab + if not found_idx: + self.app.ui.notebook.addTab(self.app.ui.tool_tab, _("Tool")) + # focus on Tool Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab) + + try: + if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName and found_idx: + # if the Tool Tab is not focused, focus on it + if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab: + # focus on Tool Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab) + else: + # else remove the Tool Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab) + self.app.ui.notebook.removeTab(2) + + # if there are no objects loaded in the app then hide the Notebook widget + if not self.app.collection.get_list(): + self.app.ui.splitter.setSizes([0, 1]) + except AttributeError: + pass + else: + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + AppTool.run(self) + self.set_tool_ui() + + self.app.ui.notebook.setTabText(2, _("Levelling Tool")) + + def connect_signals_at_init(self): + self.build_al_table_sig.connect(self.build_al_table) + self.ui.level.toggled.connect(self.on_level_changed) + + self.ui.al_mode_radio.activated_custom.connect(self.on_mode_radio) + self.ui.al_method_radio.activated_custom.connect(self.on_method_radio) + self.ui.al_controller_combo.currentIndexChanged.connect(self.on_controller_change) + self.ui.plot_probing_pts_cb.stateChanged.connect(self.show_probing_geo) + # GRBL + self.ui.com_search_button.clicked.connect(self.on_grbl_search_ports) + self.ui.add_bd_button.clicked.connect(self.on_grbl_add_baudrate) + self.ui.del_bd_button.clicked.connect(self.on_grbl_delete_baudrate_grbl) + self.ui.controller_reset_button.clicked.connect(self.on_grbl_reset) + self.ui.com_connect_button.clicked.connect(self.on_grbl_connect) + self.ui.grbl_send_button.clicked.connect(self.on_grbl_send_command) + self.ui.grbl_command_entry.returnPressed.connect(self.on_grbl_send_command) + + # Jog + self.ui.jog_wdg.jog_up_button.clicked.connect(lambda: self.on_grbl_jog(direction='yplus')) + self.ui.jog_wdg.jog_down_button.clicked.connect(lambda: self.on_grbl_jog(direction='yminus')) + self.ui.jog_wdg.jog_right_button.clicked.connect(lambda: self.on_grbl_jog(direction='xplus')) + self.ui.jog_wdg.jog_left_button.clicked.connect(lambda: self.on_grbl_jog(direction='xminus')) + self.ui.jog_wdg.jog_z_up_button.clicked.connect(lambda: self.on_grbl_jog(direction='zplus')) + self.ui.jog_wdg.jog_z_down_button.clicked.connect(lambda: self.on_grbl_jog(direction='zminus')) + self.ui.jog_wdg.jog_origin_button.clicked.connect(lambda: self.on_grbl_jog(direction='origin')) + + # Zero + self.ui.zero_axs_wdg.grbl_zerox_button.clicked.connect(lambda: self.on_grbl_zero(axis='x')) + self.ui.zero_axs_wdg.grbl_zeroy_button.clicked.connect(lambda: self.on_grbl_zero(axis='y')) + self.ui.zero_axs_wdg.grbl_zeroz_button.clicked.connect(lambda: self.on_grbl_zero(axis='z')) + self.ui.zero_axs_wdg.grbl_zero_all_button.clicked.connect(lambda: self.on_grbl_zero(axis='all')) + self.ui.zero_axs_wdg.grbl_homing_button.clicked.connect(self.on_grbl_homing) + + # Sender + self.ui.grbl_report_button.clicked.connect(lambda: self.send_grbl_command(command='?')) + self.ui.grbl_get_param_button.clicked.connect( + lambda: self.on_grbl_get_parameter(param=self.ui.grbl_parameter_entry.get_value())) + self.ui.view_h_gcode_button.clicked.connect(self.on_edit_probing_gcode) + self.ui.h_gcode_button.clicked.connect(self.on_save_probing_gcode) + self.ui.import_heights_button.clicked.connect(self.on_import_height_map) + self.ui.pause_resume_button.clicked.connect(self.on_grbl_pause_resume) + self.ui.grbl_get_heightmap_button.clicked.connect(self.on_grbl_autolevel) + self.ui.grbl_save_height_map_button.clicked.connect(self.on_grbl_heightmap_save) + + # When object selection on canvas change + self.app.proj_selection_changed.connect(self.on_object_selection_changed) + + # Reset Tool + self.ui.reset_button.clicked.connect(self.set_tool_ui) + # Cleanup on Graceful exit (CTRL+ALT+X combo key) + self.app.cleanup.connect(self.set_tool_ui) + + def set_tool_ui(self): + self.units = self.app.defaults['units'].upper() + + # try to select in the Gerber combobox the active object + try: + selected_obj = self.app.collection.get_active() + if selected_obj.kind == 'cncjob': + current_name = selected_obj.options['name'] + self.ui.object_combo.set_value(current_name) + except Exception: + pass + + loaded_obj = self.app.collection.get_by_name(self.ui.object_combo.get_value()) + if loaded_obj: + name = loaded_obj.options['name'] + else: + name = '' + + # Shapes container for the Voronoi cells in Autolevelling + if self.app.is_legacy is False: + self.probing_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1) + else: + self.probing_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name=name + "_probing_shapes") + + self.form_fields.update({ + "tools_al_travelz": self.ui.ptravelz_entry, + "tools_al_probe_depth": self.ui.pdepth_entry, + "tools_al_probe_fr": self.ui.feedrate_probe_entry, + "tools_al_controller": self.ui.al_controller_combo, + "tools_al_method": self.ui.al_method_radio, + "tools_al_mode": self.ui.al_mode_radio, + "tools_al_rows": self.ui.al_rows_entry, + "tools_al_columns": self.ui.al_columns_entry, + "tools_al_grbl_jog_step": self.ui.jog_step_entry, + "tools_al_grbl_jog_fr": self.ui.jog_fr_entry, + }) + + # Fill Form fields + self.to_form() + + self.ui.al_probe_points_table.setRowCount(0) + self.ui.al_probe_points_table.resizeColumnsToContents() + self.ui.al_probe_points_table.resizeRowsToContents() + v_header = self.ui.al_probe_points_table.verticalHeader() + v_header.hide() + self.ui.al_probe_points_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + h_header = self.ui.al_probe_points_table.horizontalHeader() + h_header.setMinimumSectionSize(10) + h_header.setDefaultSectionSize(70) + h_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) + h_header.resizeSection(0, 20) + h_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) + h_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) + + 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()) + + # Set initial UI + self.ui.al_rows_entry.setDisabled(True) + self.ui.al_rows_label.setDisabled(True) + self.ui.al_columns_entry.setDisabled(True) + self.ui.al_columns_label.setDisabled(True) + self.ui.al_method_lbl.setDisabled(True) + self.ui.al_method_radio.setDisabled(True) + self.ui.al_method_radio.set_value('v') + + target_obj = self.app.collection.get_by_name(self.ui.object_combo.get_value()) + if target_obj: + self.ui.al_frame.setDisabled(False) + self.ui.al_mode_radio.set_value(target_obj.options['tools_al_mode']) + self.on_controller_change() + + self.on_mode_radio(val=target_obj.options['tools_al_mode']) + self.on_method_radio(val=target_obj.options['tools_al_method']) + else: + self.ui.al_frame.setDisabled(True) + + # Show/Hide Advanced Options + app_mode = self.app.defaults["global_app_level"] + self.change_level(app_mode) + + try: + self.ui.object_combo.currentIndexChanged.disconnect() + except (AttributeError, TypeError): + pass + self.ui.object_combo.currentIndexChanged.connect(self.on_object_changed) + + def on_object_changed(self): + + # load the object + obj_name = self.ui.object_combo.currentText() + + # Get source object. + try: + target_obj = self.app.collection.get_by_name(obj_name) + except Exception: + self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name))) + return + + if target_obj is None: + self.ui.al_frame.setDisabled(True) + else: + self.ui.al_frame.setDisabled(False) + + # Shapes container for the Voronoi cells in Autolevelling + if self.app.is_legacy is False: + self.probing_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1) + else: + self.probing_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name=obj_name + "_probing_shapes") + + def on_object_selection_changed(self, current, previous): + try: + sel_obj = current.indexes()[0].internalPointer().obj + name = sel_obj.options['name'] + kind = sel_obj.kind + + if kind == 'cncjob': + self.ui.object_combo.set_value(name) + except IndexError: + pass + + def change_level(self, level): + """ + + :param level: application level: either 'b' or 'a' + :type level: str + :return: + """ + + if level == 'a': + self.ui.level.setChecked(True) + else: + self.ui.level.setChecked(False) + self.on_level_changed(self.ui.level.isChecked()) + + def on_level_changed(self, checked): + + target_obj = self.app.collection.get_by_name(self.ui.object_combo.get_value()) + + # if 'Roland' in target_obj.pp_excellon_name or 'Roland' in target_obj.pp_geometry_name or 'hpgl' in \ + # target_obj.pp_geometry_name: + # # TODO DO NOT AUTOLEVELL + # pass + + if not checked: + self.ui.level.setText('%s' % _('Beginner')) + self.ui.level.setStyleSheet(""" + QToolButton + { + color: green; + } + """) + + # Context Menu section + # self.ui.al_probe_points_table.removeContextMenu() + else: + self.ui.level.setText('%s' % _('Advanced')) + self.ui.level.setStyleSheet(""" + QToolButton + { + color: red; + } + """) + + # Context Menu section + # self.ui.al_probe_points_table.setupContextMenu() + + def build_tool_ui(self): + self.ui_disconnect() + + self.build_al_table() + + self.ui_connect() + + def build_al_table(self): + tool_idx = 0 + + n = len(self.al_voronoi_geo_storage) + self.ui.al_probe_points_table.setRowCount(n) + + for id_key, value in self.al_voronoi_geo_storage.items(): + tool_idx += 1 + row_no = tool_idx - 1 + + t_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx)) + x = value['point'].x + y = value['point'].y + xy_coords = self.app.dec_format(x, dec=self.app.decimals), self.app.dec_format(y, dec=self.app.decimals) + coords_item = QtWidgets.QTableWidgetItem(str(xy_coords)) + height = self.app.dec_format(value['height'], dec=self.app.decimals) + height_item = QtWidgets.QTableWidgetItem(str(height)) + + t_id.setFlags(QtCore.Qt.ItemIsEnabled) + coords_item.setFlags(QtCore.Qt.ItemIsEnabled) + height_item.setFlags(QtCore.Qt.ItemIsEnabled) + + 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_probe_points_table.resizeColumnsToContents() + self.ui.al_probe_points_table.resizeRowsToContents() + + h_header = self.ui.al_probe_points_table.horizontalHeader() + h_header.setMinimumSectionSize(10) + h_header.setDefaultSectionSize(70) + h_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) + h_header.resizeSection(0, 20) + h_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) + h_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) + + 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_probe_points_table.model().rowCount(): + self.ui.grbl_get_heightmap_button.setDisabled(False) + self.ui.grbl_save_height_map_button.setDisabled(False) + self.ui.h_gcode_button.setDisabled(False) + self.ui.view_h_gcode_button.setDisabled(False) + else: + self.ui.grbl_get_heightmap_button.setDisabled(True) + self.ui.grbl_save_height_map_button.setDisabled(True) + self.ui.h_gcode_button.setDisabled(True) + self.ui.view_h_gcode_button.setDisabled(True) + + def to_form(self, storage=None): + if storage is None: + storage = self.app.options + + for k in self.form_fields: + for option in storage: + if option.startswith('tools_al_'): + if k == option: + try: + self.form_fields[k].set_value(storage[option]) + except Exception: + # it may fail for form fields found in the tools tables if there are no rows + pass + + def on_add_al_probepoints(self): + # create the solid_geo + + self.solid_geo = unary_union([geo['geom'] for geo in self.gcode_parsed if geo['kind'][0] == 'C']) + + # reset al table + self.ui.al_probe_points_table.setRowCount(0) + + # reset the al dict + self.al_voronoi_geo_storage.clear() + + xmin, ymin, xmax, ymax = self.solid_geo.bounds + + if self.ui.al_mode_radio.get_value() == 'grid': + width = abs(xmax - xmin) + height = abs(ymax - ymin) + cols = self.ui.al_columns_entry.get_value() + rows = self.ui.al_rows_entry.get_value() + + dx = 0 if cols == 1 else width / (cols - 1) + dy = 0 if rows == 1 else height / (rows - 1) + + points = [] + new_y = ymin + for x in range(rows): + new_x = xmin + for y in range(cols): + formatted_point = ( + self.app.dec_format(new_x, self.app.decimals), + self.app.dec_format(new_y, self.app.decimals) + ) + points.append(formatted_point) + new_x += dx + new_y += dy + + pt_id = 0 + vor_pts_list = [] + bl_pts_list = [] + for point in points: + pt_id += 1 + pt = Point(point) + vor_pts_list.append(pt) + bl_pts_list.append((point[0], point[1], 0.0)) + new_dict = { + 'point': pt, + 'geo': None, + 'height': 0.0 + } + self.al_voronoi_geo_storage[pt_id] = deepcopy(new_dict) + + al_method = self.ui.al_method_radio.get_value() + if al_method == 'v': + if VORONOI_ENABLED is True: + self.generate_voronoi_geometry(pts=vor_pts_list) + # generate Probing GCode + self.probing_gcode_text = self.probing_gcode(storage=self.al_voronoi_geo_storage) + else: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Voronoi function can not be loaded.\n" + "Shapely >= 1.8 is required")) + else: + self.generate_bilinear_geometry(pts=bl_pts_list) + # generate Probing GCode + self.probing_gcode_text = self.probing_gcode(storage=self.al_bilinear_geo_storage) + + self.build_al_table_sig.emit() + if self.ui.plot_probing_pts_cb.get_value(): + self.show_probing_geo(state=True, reset=True) + else: + # clear probe shapes + self.plot_probing_geo(None, False) + + else: + f_probe_pt = Point([xmin, xmin]) + int_keys = [int(k) for k in self.al_voronoi_geo_storage.keys()] + new_id = max(int_keys) + 1 if int_keys else 1 + new_dict = { + 'point': f_probe_pt, + 'geo': None, + 'height': 0.0 + } + self.al_voronoi_geo_storage[new_id] = deepcopy(new_dict) + + radius = 0.3 if self.units == 'MM' else 0.012 + fprobe_pt_buff = f_probe_pt.buffer(radius) + + self.app.inform.emit(_("Click on canvas to add a Probe Point...")) + self.app.defaults['global_selection_shape'] = False + + if self.app.is_legacy is False: + self.app.plotcanvas.graph_event_disconnect('key_press', self.app.ui.keyPressEvent) + self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot) + self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot) + else: + self.app.plotcanvas.graph_event_disconnect(self.app.kp) + self.app.plotcanvas.graph_event_disconnect(self.app.mp) + self.app.plotcanvas.graph_event_disconnect(self.app.mr) + + self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press) + self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release) + + self.mouse_events_connected = True + + self.build_al_table_sig.emit() + if self.ui.plot_probing_pts_cb.get_value(): + self.show_probing_geo(state=True, reset=True) + else: + # clear probe shapes + self.plot_probing_geo(None, False) + + self.plot_probing_geo(geometry=fprobe_pt_buff, visibility=True, custom_color="#0000FFFA") + + def show_probing_geo(self, state, reset=False): + + if reset: + self.probing_shapes.clear(update=True) + + points_geo = [] + poly_geo = [] + + al_method = self.ui.al_method_radio.get_value() + # voronoi diagram + if al_method == 'v': + # create the geometry + radius = 0.1 if self.units == 'MM' else 0.004 + for pt in self.al_voronoi_geo_storage: + if not self.al_voronoi_geo_storage[pt]['geo']: + continue + + p_geo = self.al_voronoi_geo_storage[pt]['point'].buffer(radius) + s_geo = self.al_voronoi_geo_storage[pt]['geo'].buffer(0.0000001) + + points_geo.append(p_geo) + poly_geo.append(s_geo) + + if not points_geo and not poly_geo: + return + + self.plot_probing_geo(geometry=points_geo, visibility=state, custom_color='#000000FF') + self.plot_probing_geo(geometry=poly_geo, visibility=state) + # bilinear interpolation + elif al_method == 'b': + radius = 0.1 if self.units == 'MM' else 0.004 + for pt in self.al_bilinear_geo_storage: + + x_pt = pt[0] + y_pt = pt[1] + p_geo = Point([x_pt, y_pt]).buffer(radius) + + if p_geo.is_valid: + points_geo.append(p_geo) + + if not points_geo: + return + + self.plot_probing_geo(geometry=points_geo, visibility=state, custom_color='#000000FF') + + def plot_probing_geo(self, geometry, visibility, custom_color=None): + if visibility: + if self.app.is_legacy is False: + def random_color(): + r_color = np.random.rand(4) + r_color[3] = 0.5 + return r_color + else: + def random_color(): + while True: + r_color = np.random.rand(4) + r_color[3] = 0.5 + + new_color = '#' + for idx in range(len(r_color)): + new_color += '%x' % int(r_color[idx] * 255) + # do it until a valid color is generated + # a valid color has the # symbol, another 6 chars for the color and the last 2 chars for alpha + # for a total of 9 chars + if len(new_color) == 9: + break + return new_color + + try: + # if self.app.is_legacy is False: + # color = "#0000FFFE" + # else: + # color = "#0000FFFE" + # for sh in points_geo: + # self.add_probing_shape(shape=sh, color=color, face_color=color, visible=True) + + edge_color = "#000000FF" + try: + for sh in geometry: + if custom_color is None: + self.add_probing_shape(shape=sh, color=edge_color, face_color=random_color(), visible=True) + else: + self.add_probing_shape(shape=sh, color=custom_color, face_color=custom_color, visible=True) + except TypeError: + if custom_color is None: + self.add_probing_shape( + shape=geometry, color=edge_color, face_color=random_color(), visible=True) + else: + self.add_probing_shape( + shape=geometry, color=custom_color, face_color=custom_color, visible=True) + + self.probing_shapes.redraw() + except (ObjectDeleted, AttributeError): + self.probing_shapes.clear(update=True) + except Exception as e: + self.app.log.error("CNCJobObject.plot_probing_geo() --> %s" % str(e)) + else: + self.probing_shapes.clear(update=True) + + def add_probing_shape(self, **kwargs): + if self.deleted: + raise ObjectDeleted() + else: + key = self.probing_shapes.add(tolerance=self.drawing_tolerance, layer=0, **kwargs) + return key + + def generate_voronoi_geometry(self, pts): + env = self.solid_geo.envelope + fact = 1 if self.units == 'MM' else 0.039 + env = env.buffer(fact) + + new_pts = deepcopy(pts) + try: + pts_union = MultiPoint(pts) + voronoi_union = voronoi_diagram(geom=pts_union, envelope=env) + except Exception as e: + self.app.log.error("CNCJobObject.generate_voronoi_geometry() --> %s" % str(e)) + for pt_index in range(len(pts)): + new_pts[pt_index] = affinity.translate( + new_pts[pt_index], random.random() * 1e-09, random.random() * 1e-09) + + pts_union = MultiPoint(new_pts) + try: + voronoi_union = voronoi_diagram(geom=pts_union, envelope=env) + except Exception: + return + + new_voronoi = [] + for p in voronoi_union: + new_voronoi.append(p.intersection(env)) + + for pt_key in list(self.al_voronoi_geo_storage.keys()): + for poly in new_voronoi: + if self.al_voronoi_geo_storage[pt_key]['point'].within(poly): + self.al_voronoi_geo_storage[pt_key]['geo'] = poly + + def generate_bilinear_geometry(self, pts): + self.al_bilinear_geo_storage = pts + + # To be called after clicking on the plot. + def on_mouse_click_release(self, event): + + if self.app.is_legacy is False: + event_pos = event.pos + # event_is_dragging = event.is_dragging + right_button = 2 + else: + event_pos = (event.xdata, event.ydata) + # event_is_dragging = self.app.plotcanvas.is_dragging + right_button = 3 + + try: + x = float(event_pos[0]) + y = float(event_pos[1]) + except TypeError: + return + event_pos = (x, y) + + # do paint single only for left mouse clicks + if event.button == 1: + pos = self.app.plotcanvas.translate_coords(event_pos) + + # use the snapped position as reference + snapped_pos = self.app.geo_editor.snap(pos[0], pos[1]) + + probe_pt = Point(snapped_pos) + + xxmin, yymin, xxmax, yymax = self.solid_geo.bounds + box_geo = box(xxmin, yymin, xxmax, yymax) + if not probe_pt.within(box_geo): + self.app.inform.emit(_("Point is not within the object area. Choose another point.")) + return + + int_keys = [int(k) for k in self.al_voronoi_geo_storage.keys()] + new_id = max(int_keys) + 1 if int_keys else 1 + new_dict = { + 'point': probe_pt, + 'geo': None, + 'height': 0.0 + } + self.al_voronoi_geo_storage[new_id] = deepcopy(new_dict) + + # rebuild the al table + self.build_al_table_sig.emit() + + radius = 0.3 if self.units == 'MM' else 0.012 + probe_pt_buff = probe_pt.buffer(radius) + + self.plot_probing_geo(geometry=probe_pt_buff, visibility=True, custom_color="#0000FFFA") + + self.app.inform.emit(_("Added a Probe Point... Click again to add another or right click to finish ...")) + + # if RMB then we exit + elif event.button == right_button and self.mouse_is_dragging is False: + if self.app.is_legacy is False: + self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press) + self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release) + else: + self.app.plotcanvas.graph_event_disconnect(self.kp) + self.app.plotcanvas.graph_event_disconnect(self.mr) + + self.app.kp = self.app.plotcanvas.graph_event_connect('key_press', self.app.ui.keyPressEvent) + self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot) + self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release', + self.app.on_mouse_click_release_over_plot) + + # signal that the mouse events are disconnected from local methods + self.mouse_events_connected = False + + # restore selection + self.app.defaults['global_selection_shape'] = self.old_selection_state + + self.app.inform.emit(_("Finished adding Probe Points...")) + + al_method = self.ui.al_method_radio.get_value() + if al_method == 'v': + if VORONOI_ENABLED is True: + pts_list = [] + for k in self.al_voronoi_geo_storage: + pts_list.append(self.al_voronoi_geo_storage[k]['point']) + self.generate_voronoi_geometry(pts=pts_list) + + self.probing_gcode_text = self.probing_gcode(self.al_voronoi_geo_storage) + else: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Voronoi function can not be loaded.\n" + "Shapely >= 1.8 is required")) + + # rebuild the al table + self.build_al_table_sig.emit() + + if self.ui.plot_probing_pts_cb.get_value(): + self.show_probing_geo(state=True, reset=True) + else: + # clear probe shapes + self.plot_probing_geo(None, False) + + def on_key_press(self, event): + # events out of the self.app.collection view (it's about Project Tab) are of type int + if type(event) is int: + key = event + # events from the GUI are of type QKeyEvent + elif type(event) == QtGui.QKeyEvent: + key = event.key() + elif isinstance(event, mpl_key_event): # MatPlotLib key events are trickier to interpret than the rest + key = event.key + key = QtGui.QKeySequence(key) + + # check for modifiers + key_string = key.toString().lower() + if '+' in key_string: + mod, __, key_text = key_string.rpartition('+') + if mod.lower() == 'ctrl': + # modifiers = QtCore.Qt.ControlModifier + pass + elif mod.lower() == 'alt': + # modifiers = QtCore.Qt.AltModifier + pass + elif mod.lower() == 'shift': + # modifiers = QtCore.Qt.ShiftModifier + pass + else: + # modifiers = QtCore.Qt.NoModifier + pass + key = QtGui.QKeySequence(key_text) + # events from Vispy are of type KeyEvent + else: + key = event.key + + # Escape = Deselect All + if key == QtCore.Qt.Key_Escape or key == 'Escape': + if self.mouse_events_connected is True: + self.mouse_events_connected = False + if self.app.is_legacy is False: + self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press) + self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release) + else: + self.app.plotcanvas.graph_event_disconnect(self.kp) + self.app.plotcanvas.graph_event_disconnect(self.mr) + + self.app.kp = self.app.plotcanvas.graph_event_connect('key_press', self.app.ui.keyPressEvent) + self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot) + self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release', + self.app.on_mouse_click_release_over_plot) + + # Grid toggle + if key == QtCore.Qt.Key_G or key == 'G': + self.app.ui.grid_snap_btn.trigger() + + # Jump to coords + if key == QtCore.Qt.Key_J or key == 'J': + self.app.on_jump_to() + + def autolevell_gcode(self): + pass + + def autolevell_gcode_line(self, gcode_line): + al_method = self.ui.al_method_radio.get_value() + + coords = () + + if al_method == 'v': + self.autolevell_voronoi(gcode_line, coords) + elif al_method == 'b': + self.autolevell_bilinear(gcode_line, coords) + + def autolevell_bilinear(self, gcode_line, coords): + pass + + def autolevell_voronoi(self, gcode_line, coords): + pass + + def on_show_al_table(self, state): + 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_probe_points_table.setRowCount(0) + + # reset the al dict + self.al_voronoi_geo_storage.clear() + + # reset Voronoi Shapes + self.probing_shapes.clear(update=True) + + # build AL table + self.build_al_table() + + if val == "manual": + self.ui.al_rows_entry.setDisabled(True) + self.ui.al_rows_label.setDisabled(True) + self.ui.al_columns_entry.setDisabled(True) + self.ui.al_columns_label.setDisabled(True) + self.ui.al_method_lbl.setDisabled(True) + self.ui.al_method_radio.setDisabled(True) + self.ui.al_method_radio.set_value('v') + else: + self.ui.al_rows_entry.setDisabled(False) + self.ui.al_rows_label.setDisabled(False) + self.ui.al_columns_entry.setDisabled(False) + self.ui.al_columns_label.setDisabled(False) + self.ui.al_method_lbl.setDisabled(False) + self.ui.al_method_radio.setDisabled(False) + self.ui.al_method_radio.set_value(self.app.defaults['cncjob_al_method']) + + def on_method_radio(self, val): + if val == 'b': + self.ui.al_columns_entry.setMinimum(2) + self.ui.al_rows_entry.setMinimum(2) + else: + self.ui.al_columns_entry.setMinimum(1) + self.ui.al_rows_entry.setMinimum(1) + + 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_grbl_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() + + # if the is empty then there is a chance that we've added probe points but the GRBL controller was selected + # therefore no Probing GCode was genrated (it is different for GRBL on how it gets it's Probing GCode + if not self.probing_gcode_text or self.probing_gcode_text == '': + # generate Probing GCode + al_method = self.ui.al_method_radio.get_value() + storage = self.al_voronoi_geo_storage if al_method == 'v' else self.al_bilinear_geo_storage + self.probing_gcode_text = self.probing_gcode(storage=storage) + + @staticmethod + def on_grbl_list_serial_ports(): + """ + 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_grbl_search_ports(self, muted=None): + port_list = self.on_grbl_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_grbl_connect(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) + + # 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 + + answer = self.on_grbl_wake() + answer = ['ok'] # FIXME: hack for development without a GRBL controller connected + for line in answer: + if 'ok' in line.lower(): + self.ui.com_connect_button.setStyleSheet("QPushButton {background-color: seagreen;}") + self.ui.com_connect_button.setText(_("Connected")) + self.ui.controller_reset_button.setDisabled(False) + + for idx in range(self.ui.al_toolbar.count()): + if self.ui.al_toolbar.tabText(idx) == _("Connect"): + self.ui.al_toolbar.tabBar.setTabTextColor(idx, QtGui.QColor('seagreen')) + if self.ui.al_toolbar.tabText(idx) == _("Control"): + self.ui.al_toolbar.tabBar.setTabEnabled(idx, True) + if self.ui.al_toolbar.tabText(idx) == _("Sender"): + self.ui.al_toolbar.tabBar.setTabEnabled(idx, True) + + self.app.inform.emit("%s: %s" % (_("Port connected"), port_name)) + return + + self.grbl_ser_port.close() + self.app.inform.emit("[ERROR_NOTCL] %s: %s" % (_("Could not connect to GRBL on port"), port_name)) + + 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("QPushButton {background-color: red;}") + self.ui.com_connect_button.setText(_("Disconnected")) + self.ui.controller_reset_button.setDisabled(True) + + for idx in range(self.ui.al_toolbar.count()): + if self.ui.al_toolbar.tabText(idx) == _("Connect"): + self.ui.al_toolbar.tabBar.setTabTextColor(idx, QtGui.QColor('red')) + if self.ui.al_toolbar.tabText(idx) == _("Control"): + self.ui.al_toolbar.tabBar.setTabEnabled(idx, False) + if self.ui.al_toolbar.tabText(idx) == _("Sender"): + self.ui.al_toolbar.tabBar.setTabEnabled(idx, False) + 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_grbl_add_baudrate(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_grbl_delete_baudrate_grbl(self): + current_idx = self.ui.baudrates_list_combo.currentIndex() + self.ui.baudrates_list_combo.removeItem(current_idx) + + def on_grbl_wake(self): + # Wake up grbl + self.grbl_ser_port.write("\r\n\r\n".encode('utf-8')) + # Wait for GRBL controller to initialize + time.sleep(1) + + grbl_out = deepcopy(self.grbl_ser_port.readlines()) + self.grbl_ser_port.reset_input_buffer() + + return grbl_out + + def on_grbl_send_command(self): + cmd = self.ui.grbl_command_entry.get_value() + + # show the Shell Dock + self.app.ui.shell_dock.show() + + def worker_task(): + with self.app.proc_container.new('%s...' % _("Sending")): + self.send_grbl_command(command=cmd) + + self.app.worker_task.emit({'fcn': worker_task, 'params': []}) + + def send_grbl_command(self, command, echo=True): + """ + + :param command: GCode command + :type command: str + :param echo: if to send a '\n' char after + :type echo: bool + :return: the text returned by the GRBL controller after each command + :rtype: str + """ + cmd = command.strip() + if echo: + self.app.inform_shell[str, bool].emit(cmd, False) + + # Send Gcode command to GRBL + snd = cmd + '\n' + self.grbl_ser_port.write(snd.encode('utf-8')) + grbl_out = self.grbl_ser_port.readlines() + if not grbl_out: + self.app.inform_shell[str, bool].emit('\t\t\t: No answer\n', False) + + result = '' + for line in grbl_out: + if echo: + try: + self.app.inform_shell.emit('\t\t\t: ' + line.decode('utf-8').strip().upper()) + except Exception as e: + self.app.log.error("CNCJobObject.send_grbl_command() --> %s" % str(e)) + if 'ok' in line: + result = grbl_out + + return result + + def send_grbl_block(self, command, echo=True): + stripped_cmd = command.strip() + + for grbl_line in stripped_cmd.split('\n'): + if echo: + self.app.inform_shell[str, bool].emit(grbl_line, False) + + # Send Gcode block to GRBL + snd = grbl_line + '\n' + self.grbl_ser_port.write(snd.encode('utf-8')) + grbl_out = self.grbl_ser_port.readlines() + + for line in grbl_out: + if echo: + try: + self.app.inform_shell.emit(' : ' + line.decode('utf-8').strip().upper()) + except Exception as e: + self.app.log.error("CNCJobObject.send_grbl_block() --> %s" % str(e)) + + def on_grbl_get_parameter(self, param): + if '$' in param: + param = param.replace('$', '') + + snd = '$$\n' + self.grbl_ser_port.write(snd.encode('utf-8')) + grbl_out = self.grbl_ser_port.readlines() + for line in grbl_out: + decoded_line = line.decode('utf-8') + par = '$%s' % str(param) + if par in decoded_line: + result = float(decoded_line.rpartition('=')[2]) + self.app.shell_message("GRBL Parameter: %s = %s" % (str(param), str(result)), show=True) + return result + + def on_grbl_jog(self, direction=None): + if direction is None: + return + cmd = '' + + step = self.ui.jog_step_entry.get_value(), + feedrate = self.ui.jog_fr_entry.get_value() + travelz = float(self.app.defaults["cncjob_al_grbl_travelz"]) + + if direction == 'xplus': + cmd = "$J=G91 %s X%s F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(step), str(feedrate)) + if direction == 'xminus': + cmd = "$J=G91 %s X-%s F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(step), str(feedrate)) + if direction == 'yplus': + cmd = "$J=G91 %s Y%s F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(step), str(feedrate)) + if direction == 'yminus': + cmd = "$J=G91 %s Y-%s F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(step), str(feedrate)) + + if direction == 'zplus': + cmd = "$J=G91 %s Z%s F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(step), str(feedrate)) + if direction == 'zminus': + cmd = "$J=G91 %s Z-%s F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(step), str(feedrate)) + + if direction == 'origin': + cmd = "$J=G90 %s Z%s F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(travelz), str(feedrate)) + self.send_grbl_command(command=cmd, echo=False) + cmd = "$J=G90 %s X0.0 Y0.0 F%s" % ({'IN': 'G20', 'MM': 'G21'}[self.units], str(feedrate)) + self.send_grbl_command(command=cmd, echo=False) + return + + self.send_grbl_command(command=cmd, echo=False) + + def on_grbl_zero(self, axis): + current_mode = self.on_grbl_get_parameter('10') + if current_mode is None: + return + + cmd = '$10=0' + self.send_grbl_command(command=cmd, echo=False) + + if axis == 'x': + cmd = 'G10 L2 P1 X0' + elif axis == 'y': + cmd = 'G10 L2 P1 Y0' + elif axis == 'z': + cmd = 'G10 L2 P1 Z0' + else: + # all + cmd = 'G10 L2 P1 X0 Y0 Z0' + self.send_grbl_command(command=cmd, echo=False) + + # restore previous mode + cmd = '$10=%d' % int(current_mode) + self.send_grbl_command(command=cmd, echo=False) + + def on_grbl_homing(self): + cmd = '$H' + self.app.inform.emit("%s" % _("GRBL is doing a home cycle.")) + self.on_grbl_wake() + self.send_grbl_command(command=cmd) + + def on_grbl_reset(self): + cmd = '\x18' + self.app.inform.emit("%s" % _("GRBL software reset was sent.")) + self.on_grbl_wake() + self.send_grbl_command(command=cmd) + + def on_grbl_pause_resume(self, checked): + if checked is False: + cmd = '~' + self.send_grbl_command(command=cmd) + self.app.inform.emit("%s" % _("GRBL resumed.")) + else: + cmd = '!' + self.send_grbl_command(command=cmd) + self.app.inform.emit("%s" % _("GRBL paused.")) + + def probing_gcode(self, storage): + """ + :param storage: either a dict of dicts (voronoi) or a list of tuples (bilinear) + :return: Probing GCode + :rtype: str + """ + + target_obj = self.app.collection.get_by_name(self.ui.object_combo.get_value()) + + p_gcode = '' + header = '' + time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) + + coords = [] + al_method = self.ui.al_method_radio.get_value() + if al_method == 'v': + for id_key, value in storage.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) + ) + ) + else: + for pt in storage: + x = pt[0] + y = pt[1] + 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() + + header += '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \ + (str(self.app.version), str(self.app.version_date)) + '\n' + + header += '(This is a autolevelling probing GCode.)\n' \ + '(Make sure that before you start the job you first do a zero for all axis.)\n\n' + + header += '(Name: ' + str(target_obj.options['name']) + ')\n' + header += '(Type: ' + "Autolevelling Probing GCode " + ')\n' + + header += '(Units: ' + self.units.upper() + ')\n' + header += '(Created on ' + time_str + ')\n' + + # 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)' + elif controller == 'GRBL': + # do nothing here because the Probing GCode for GRBL is obtained differently + return + else: + self.app.log.debug("CNCJobObject.probing_gcode() -> controller not supported") + return + + # ############################################################################################################# + # ########################### GCODE construction ############################################################## + # ############################################################################################################# + + # header + p_gcode += header + '\n' + # supplementary message for LinuxCNC + if controller == 'LinuxCNC': + p_gcode += "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, target_obj.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, target_obj.coords_decimals)), + str(self.app.dec_format(y, target_obj.coords_decimals)) + ) + # do the probing + p_gcode += "%s Z%s F%s\n" % ( + probing_command, + str(self.app.dec_format(pr_depth, target_obj.coords_decimals)), + str(self.app.dec_format(probe_fr, target_obj.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, target_obj.coords_decimals)) + + # close the file + p_gcode += closefile_command + '\n' + # finish the GCode + p_gcode += 'M2' + + return p_gcode + + def on_save_probing_gcode(self): + lines = StringIO(self.probing_gcode_text) + + _filter_ = self.app.defaults['cncjob_save_filters'] + name = "probing_gcode" + try: + dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name) + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export Code ..."), + directory=dir_file_to_save, + ext_filter=_filter_ + ) + except TypeError: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export Code ..."), + ext_filter=_filter_) + + if filename == '': + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export cancelled ...")) + return + else: + try: + force_windows_line_endings = self.app.defaults['cncjob_line_ending'] + if force_windows_line_endings and sys.platform != 'win32': + with open(filename, 'w', newline='\r\n') as f: + for line in lines: + f.write(line) + else: + with open(filename, 'w') as f: + for line in lines: + f.write(line) + except FileNotFoundError: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("No such file or directory")) + return + except PermissionError: + self.app.inform.emit( + '[WARNING] %s' % _("Permission denied, saving not possible.\n" + "Most likely another app is holding the file open and not accessible.") + ) + return 'fail' + + def on_edit_probing_gcode(self): + self.app.proc_container.view.set_busy('%s...' % _("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: + self.app.log.error('FlatCAMCNCJob.on_edit_probing_gcode() -->%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.button_update_code.show() + + # self.gcode_viewer_tab.code_editor.setReadOnly(True) + + self.gcode_viewer_tab.button_update_code.clicked.connect(self.on_update_probing_gcode) + + self.app.inform.emit('[success] %s...' % _('Loaded Machine Code into Code Viewer')) + + def on_update_probing_gcode(self): + self.probing_gcode_text = self.gcode_viewer_tab.code_editor.toPlainText() + + def on_import_height_map(self): + """ + Import the height map file into the app + :return: + :rtype: + """ + + _filter_ = "Text File .txt (*.txt);;All Files (*.*)" + try: + filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import Height Map"), + directory=self.app.get_last_folder(), + filter=_filter_) + except TypeError: + filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import Height Map"), + filter=_filter_) + + filename = str(filename) + + if filename == '': + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + else: + self.app.worker_task.emit({'fcn': self.import_height_map, 'params': [filename]}) + + def import_height_map(self, filename): + """ + + :param filename: + :type filename: + :return: + :rtype: + """ + + try: + if filename: + with open(filename, 'r') as f: + stream = f.readlines() + else: + return + except IOError: + self.app.log.error("Failed to open height map file: %s" % filename) + self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open height map file"), filename)) + return + + idx = 0 + if stream is not None and stream != '': + for line in stream: + if line != '': + idx += 1 + line = line.replace(' ', ',').replace('\n', '').split(',') + if idx not in self.al_voronoi_geo_storage: + self.al_voronoi_geo_storage[idx] = {} + self.al_voronoi_geo_storage[idx]['height'] = float(line[2]) + if 'point' not in self.al_voronoi_geo_storage[idx]: + x = float(line[0]) + y = float(line[1]) + self.al_voronoi_geo_storage[idx]['point'] = Point((x, y)) + + self.build_al_table_sig.emit() + + def on_grbl_autolevel(self): + # show the Shell Dock + self.app.ui.shell_dock.show() + + def worker_task(): + with self.app.proc_container.new('%s...' % _("Sending")): + self.grbl_probe_result = '' + pr_travelz = str(self.ui.ptravelz_entry.get_value()) + probe_fr = str(self.ui.feedrate_probe_entry.get_value()) + pr_depth = str(self.ui.pdepth_entry.get_value()) + + cmd = 'G21\n' + self.send_grbl_command(command=cmd) + cmd = 'G90\n' + self.send_grbl_command(command=cmd) + + for pt_key in self.al_voronoi_geo_storage: + x = str(self.al_voronoi_geo_storage[pt_key]['point'].x) + y = str(self.al_voronoi_geo_storage[pt_key]['point'].y) + + cmd = 'G0 Z%s\n' % pr_travelz + self.send_grbl_command(command=cmd) + cmd = 'G0 X%s Y%s\n' % (x, y) + self.send_grbl_command(command=cmd) + cmd = 'G38.2 Z%s F%s' % (pr_depth, probe_fr) + output = self.send_grbl_command(command=cmd) + + self.grbl_probe_result += output + '\n' + + cmd = 'M2\n' + self.send_grbl_command(command=cmd) + self.app.inform.emit('%s' % _("Finished probing. Doing the autolevelling.")) + + # apply autolevel here + self.on_grbl_apply_autolevel() + + self.app.inform.emit('%s' % _("Sending probing GCode to the GRBL controller.")) + self.app.worker_task.emit({'fcn': worker_task, 'params': []}) + + def on_grbl_heightmap_save(self): + if self.grbl_probe_result != '': + _filter_ = "Text File .txt (*.txt);;All Files (*.*)" + name = "probing_gcode" + try: + dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name) + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export Code ..."), + directory=dir_file_to_save, + ext_filter=_filter_ + ) + except TypeError: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export Code ..."), + ext_filter=_filter_) + + if filename == '': + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export cancelled ...")) + return + else: + try: + force_windows_line_endings = self.app.defaults['cncjob_line_ending'] + if force_windows_line_endings and sys.platform != 'win32': + with open(filename, 'w', newline='\r\n') as f: + for line in self.grbl_probe_result: + f.write(line) + else: + with open(filename, 'w') as f: + for line in self.grbl_probe_result: + f.write(line) + except FileNotFoundError: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("No such file or directory")) + return + except PermissionError: + self.app.inform.emit( + '[WARNING] %s' % _("Permission denied, saving not possible.\n" + "Most likely another app is holding the file open and not accessible.") + ) + return 'fail' + else: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Empty GRBL heightmap.")) + + def on_grbl_apply_autolevel(self): + # TODO here we call the autolevell method + self.app.inform.emit('%s' % _("Finished autolevelling.")) + + def ui_connect(self): + 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): + try: + self.ui.al_add_button.clicked.disconnect() + except (TypeError, AttributeError): + pass + + try: + self.ui.show_al_table.stateChanged.disconnect() + except (TypeError, AttributeError): + pass + + def reset_fields(self): + self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + + +class LevelUI: + toolName = _("Levelling Tool") + + def __init__(self, layout, app): + self.app = app + self.decimals = self.app.decimals + self.layout = layout + + self.tools_frame = QtWidgets.QFrame() + self.tools_frame.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.tools_frame) + self.tools_box = QtWidgets.QVBoxLayout() + self.tools_box.setContentsMargins(0, 0, 0, 0) + self.tools_frame.setLayout(self.tools_box) + + self.title_box = QtWidgets.QHBoxLayout() + self.tools_box.addLayout(self.title_box) + + # ## Title + title_label = FCLabel("%s" % self.toolName) + title_label.setStyleSheet(""" + QLabel + { + font-size: 16px; + font-weight: bold; + } + """) + title_label.setToolTip( + _("Generate CNC Code with auto-levelled paths.") + ) + + self.title_box.addWidget(title_label) + + # App Level label + self.level = QtWidgets.QToolButton() + self.level.setToolTip( + _( + "BASIC is suitable for a beginner. Many parameters\n" + "are hidden from the user in this mode.\n" + "ADVANCED mode will make available all parameters.\n\n" + "To change the application LEVEL, go to:\n" + "Edit -> Preferences -> General and check:\n" + "'APP. LEVEL' radio button." + ) + ) + # self.level.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + self.level.setCheckable(True) + self.title_box.addWidget(self.level) + + self.obj_combo_label = FCLabel('%s:' % _("CNCjob")) + self.obj_combo_label.setToolTip( + _("Source object.") + ) + + self.tools_box.addWidget(self.obj_combo_label) + + # ############################################################################################################# + # ################################ The object to be Autolevelled ############################################## + # ############################################################################################################# + self.object_combo = FCComboBox() + self.object_combo.setModel(self.app.collection) + self.object_combo.setRootModelIndex(self.app.collection.index(3, 0, QtCore.QModelIndex())) + # self.object_combo.setCurrentIndex(1) + self.object_combo.is_last = True + + self.tools_box.addWidget(self.object_combo) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.tools_box.addWidget(separator_line) + + # Autolevelling + self.al_frame = QtWidgets.QFrame() + self.al_frame.setContentsMargins(0, 0, 0, 0) + self.tools_box.addWidget(self.al_frame) + + self.al_box = QtWidgets.QVBoxLayout() + self.al_box.setContentsMargins(0, 0, 0, 0) + self.al_frame.setLayout(self.al_box) + + grid0 = QtWidgets.QGridLayout() + grid0.setColumnStretch(0, 0) + grid0.setColumnStretch(1, 1) + self.al_box.addLayout(grid0) + + 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 Probe Points table.")) + self.show_al_table.setChecked(True) + + hor_lay = QtWidgets.QHBoxLayout() + hor_lay.addWidget(al_title) + hor_lay.addStretch() + hor_lay.addWidget(self.show_al_table, alignment=QtCore.Qt.AlignRight) + + grid0.addLayout(hor_lay, 0, 0, 1, 2) + + 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_probe_points_table, 1, 0, 1, 2) + + self.plot_probing_pts_cb = FCCheckBox(_("Plot probing points")) + self.plot_probing_pts_cb.setToolTip( + _("Plot the probing points in the table.\n" + "If a Voronoi method is used then\n" + "the Voronoi areas are also plotted.") + ) + grid0.addWidget(self.plot_probing_pts_cb, 3, 0, 1, 2) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 5, 0, 1, 2) + + # ############################################################################################################# + # ############### Probe GCode Generation ###################################################################### + # ############################################################################################################# + + self.probe_gc_label = FCLabel('%s:' % _("Parameters")) + 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, 7, 0, 1, 2) + + # Travel Z Probe + self.ptravelz_label = FCLabel('%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, 10000.0000) + + grid0.addWidget(self.ptravelz_label, 9, 0) + grid0.addWidget(self.ptravelz_entry, 9, 1) + + # Probe depth + self.pdepth_label = FCLabel('%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(-910000.0000, 0.0000) + + grid0.addWidget(self.pdepth_label, 11, 0) + grid0.addWidget(self.pdepth_entry, 11, 1) + + # Probe feedrate + self.feedrate_probe_label = FCLabel('%s:' % _("Probe Feedrate")) + 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, 910000.0000) + + grid0.addWidget(self.feedrate_probe_label, 13, 0) + grid0.addWidget(self.feedrate_probe_entry, 13, 1) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 15, 0, 1, 2) + + # AUTOLEVELL MODE + al_mode_lbl = FCLabel('%s:' % _("Mode")) + al_mode_lbl.setToolTip(_("Choose a mode for height map generation.\n" + "- 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( + [ + {'label': _('Manual'), 'value': 'manual'}, + {'label': _('Grid'), 'value': 'grid'} + ]) + grid0.addWidget(al_mode_lbl, 16, 0) + grid0.addWidget(self.al_mode_radio, 16, 1) + + # AUTOLEVELL METHOD + self.al_method_lbl = FCLabel('%s:' % _("Method")) + self.al_method_lbl.setToolTip(_("Choose a method for approximation of heights from autolevelling data.\n" + "- Voronoi: will generate a Voronoi diagram\n" + "- Bilinear: will use bilinear interpolation. Usable only for grid mode.")) + + self.al_method_radio = RadioSet( + [ + {'label': _('Voronoi'), 'value': 'v'}, + {'label': _('Bilinear'), 'value': 'b'} + ]) + self.al_method_lbl.setDisabled(True) + self.al_method_radio.setDisabled(True) + self.al_method_radio.set_value('v') + + grid0.addWidget(self.al_method_lbl, 17, 0) + grid0.addWidget(self.al_method_radio, 17, 1) + + # ## Columns + self.al_columns_entry = FCSpinner() + self.al_columns_entry.setMinimum(2) + + self.al_columns_label = FCLabel('%s:' % _("Columns")) + self.al_columns_label.setToolTip( + _("The number of grid columns.") + ) + grid0.addWidget(self.al_columns_label, 19, 0) + grid0.addWidget(self.al_columns_entry, 19, 1) + + # ## Rows + self.al_rows_entry = FCSpinner() + self.al_rows_entry.setMinimum(2) + + self.al_rows_label = FCLabel('%s:' % _("Rows")) + self.al_rows_label.setToolTip( + _("The number of grid rows.") + ) + grid0.addWidget(self.al_rows_label, 21, 0) + grid0.addWidget(self.al_rows_entry, 21, 1) + + self.al_add_button = FCButton(_("Add Probe Points")) + grid0.addWidget(self.al_add_button, 23, 0, 1, 2) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 25, 0, 1, 2) + + self.al_controller_label = FCLabel('%s:' % _("Controller")) + self.al_controller_label.setToolTip( + _("The kind of controller for which to generate\n" + "height map gcode.") + ) + + self.al_controller_combo = FCComboBox() + self.al_controller_combo.addItems(["MACH3", "MACH4", "LinuxCNC", "GRBL"]) + grid0.addWidget(self.al_controller_label, 27, 0) + grid0.addWidget(self.al_controller_combo, 27, 1) + + # ############################################################################################################# + # ########################## GRBL frame ####################################################################### + # ############################################################################################################# + self.grbl_frame = QtWidgets.QFrame() + self.grbl_frame.setContentsMargins(0, 0, 0, 0) + grid0.addWidget(self.grbl_frame, 29, 0, 1, 2) + + self.grbl_box = QtWidgets.QVBoxLayout() + self.grbl_box.setContentsMargins(0, 0, 0, 0) + self.grbl_frame.setLayout(self.grbl_box) + + # ############################################################################################################# + # ########################## GRBL TOOLBAR ##################################################################### + # ############################################################################################################# + self.al_toolbar = FCDetachableTab(protect=True) + self.al_toolbar.setTabsClosable(False) + self.al_toolbar.useOldIndex(True) + self.al_toolbar.set_detachable(val=False) + self.grbl_box.addWidget(self.al_toolbar) + + # GRBL Connect TAB + self.gr_conn_tab = QtWidgets.QWidget() + self.gr_conn_tab.setObjectName("connect_tab") + self.gr_conn_tab_layout = QtWidgets.QVBoxLayout(self.gr_conn_tab) + self.gr_conn_tab_layout.setContentsMargins(2, 2, 2, 2) + # self.gr_conn_scroll_area = VerticalScrollArea() + # self.gr_conn_tab_layout.addWidget(self.gr_conn_scroll_area) + self.al_toolbar.addTab(self.gr_conn_tab, _("Connect")) + + # GRBL Control TAB + self.gr_ctrl_tab = QtWidgets.QWidget() + self.gr_ctrl_tab.setObjectName("connect_tab") + self.gr_ctrl_tab_layout = QtWidgets.QVBoxLayout(self.gr_ctrl_tab) + self.gr_ctrl_tab_layout.setContentsMargins(2, 2, 2, 2) + + # self.gr_ctrl_scroll_area = VerticalScrollArea() + # self.gr_ctrl_tab_layout.addWidget(self.gr_ctrl_scroll_area) + self.al_toolbar.addTab(self.gr_ctrl_tab, _("Control")) + + # GRBL Sender TAB + self.gr_send_tab = QtWidgets.QWidget() + self.gr_send_tab.setObjectName("connect_tab") + self.gr_send_tab_layout = QtWidgets.QVBoxLayout(self.gr_send_tab) + self.gr_send_tab_layout.setContentsMargins(2, 2, 2, 2) + + # self.gr_send_scroll_area = VerticalScrollArea() + # self.gr_send_tab_layout.addWidget(self.gr_send_scroll_area) + self.al_toolbar.addTab(self.gr_send_tab, _("Sender")) + + for idx in range(self.al_toolbar.count()): + if self.al_toolbar.tabText(idx) == _("Connect"): + self.al_toolbar.tabBar.setTabTextColor(idx, QtGui.QColor('red')) + if self.al_toolbar.tabText(idx) == _("Control"): + self.al_toolbar.tabBar.setTabEnabled(idx, False) + if self.al_toolbar.tabText(idx) == _("Sender"): + self.al_toolbar.tabBar.setTabEnabled(idx, False) + # ############################################################################################################# + + # ############################################################################################################# + # GRBL CONNECT + # ############################################################################################################# + grbl_conn_grid = QtWidgets.QGridLayout() + grbl_conn_grid.setColumnStretch(0, 0) + grbl_conn_grid.setColumnStretch(1, 1) + grbl_conn_grid.setColumnStretch(2, 0) + self.gr_conn_tab_layout.addLayout(grbl_conn_grid) + + # 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_conn_grid.addWidget(self.com_list_label, 2, 0) + grbl_conn_grid.addWidget(self.com_list_combo, 2, 1) + grbl_conn_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') + + grbl_conn_grid.addWidget(self.baudrates_list_label, 4, 0) + grbl_conn_grid.addWidget(self.baudrates_list_combo, 4, 1) + + # 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_conn_grid.addWidget(self.new_bd_label, 6, 0) + grbl_conn_grid.addWidget(self.new_baudrate_entry, 6, 1) + grbl_conn_grid.addWidget(self.add_bd_button, 6, 2) + + self.del_bd_button = FCButton(_("Delete selected baudrate")) + grbl_conn_grid.addWidget(self.del_bd_button, 8, 0, 1, 3) + + ctrl_hlay = QtWidgets.QHBoxLayout() + self.controller_reset_button = FCButton(_("Reset")) + self.controller_reset_button.setToolTip( + _("Software reset of the controller.") + ) + self.controller_reset_button.setDisabled(True) + ctrl_hlay.addWidget(self.controller_reset_button) + + self.com_connect_button = FCButton() + self.com_connect_button.setText(_("Disconnected")) + self.com_connect_button.setToolTip( + _("Connect to the selected port with the selected baud rate.") + ) + self.com_connect_button.setStyleSheet("QPushButton {background-color: red;}") + ctrl_hlay.addWidget(self.com_connect_button) + + grbl_conn_grid.addWidget(FCLabel(""), 9, 0, 1, 3) + grbl_conn_grid.setRowStretch(9, 1) + grbl_conn_grid.addLayout(ctrl_hlay, 10, 0, 1, 3) + + # ############################################################################################################# + # GRBL CONTROL + # ############################################################################################################# + grbl_ctrl_grid = QtWidgets.QGridLayout() + grbl_ctrl_grid.setColumnStretch(0, 0) + grbl_ctrl_grid.setColumnStretch(1, 1) + grbl_ctrl_grid.setColumnStretch(2, 0) + self.gr_ctrl_tab_layout.addLayout(grbl_ctrl_grid) + + grbl_ctrl2_grid = QtWidgets.QGridLayout() + grbl_ctrl2_grid.setColumnStretch(0, 0) + grbl_ctrl2_grid.setColumnStretch(1, 1) + self.gr_ctrl_tab_layout.addLayout(grbl_ctrl2_grid) + + self.gr_ctrl_tab_layout.addStretch(1) + + jog_title_label = FCLabel(_("Jog")) + jog_title_label.setStyleSheet(""" + FCLabel + { + font-weight: bold; + } + """) + + zero_title_label = FCLabel(_("Zero Axes")) + zero_title_label.setStyleSheet(""" + FCLabel + { + font-weight: bold; + } + """) + + grbl_ctrl_grid.addWidget(jog_title_label, 0, 0) + grbl_ctrl_grid.addWidget(zero_title_label, 0, 2) + + self.jog_wdg = FCJog(self.app) + self.jog_wdg.setStyleSheet(""" + FCJog + { + border: 1px solid lightgray; + border-radius: 5px; + } + """) + + self.zero_axs_wdg = FCZeroAxes(self.app) + self.zero_axs_wdg.setStyleSheet(""" + FCZeroAxes + { + border: 1px solid lightgray; + border-radius: 5px + } + """) + grbl_ctrl_grid.addWidget(self.jog_wdg, 2, 0) + grbl_ctrl_grid.addWidget(self.zero_axs_wdg, 2, 2) + + self.pause_resume_button = RotatedToolButton() + self.pause_resume_button.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.pause_resume_button.setText(_("Pause/Resume")) + self.pause_resume_button.setCheckable(True) + self.pause_resume_button.setStyleSheet(""" + RotatedToolButton:checked + { + background-color: red; + color: white; + border: none; + } + """) + + pause_frame = QtWidgets.QFrame() + pause_frame.setContentsMargins(0, 0, 0, 0) + pause_frame.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Expanding) + pause_hlay = QtWidgets.QHBoxLayout() + pause_hlay.setContentsMargins(0, 0, 0, 0) + + pause_hlay.addWidget(self.pause_resume_button) + pause_frame.setLayout(pause_hlay) + grbl_ctrl_grid.addWidget(pause_frame, 2, 1) + + # JOG Step + self.jog_step_label = FCLabel('%s:' % _("Step")) + self.jog_step_label.setToolTip( + _("Each jog action will move the axes with this value.") + ) + + self.jog_step_entry = FCSliderWithDoubleSpinner() + self.jog_step_entry.set_precision(self.decimals) + self.jog_step_entry.setSingleStep(0.1) + self.jog_step_entry.set_range(0, 500) + + grbl_ctrl2_grid.addWidget(self.jog_step_label, 0, 0) + grbl_ctrl2_grid.addWidget(self.jog_step_entry, 0, 1) + + # JOG Feedrate + self.jog_fr_label = FCLabel('%s:' % _("Feedrate")) + self.jog_fr_label.setToolTip( + _("Feedrate when jogging.") + ) + + self.jog_fr_entry = FCSliderWithDoubleSpinner() + self.jog_fr_entry.set_precision(self.decimals) + self.jog_fr_entry.setSingleStep(10) + self.jog_fr_entry.set_range(0, 10000) + + grbl_ctrl2_grid.addWidget(self.jog_fr_label, 1, 0) + grbl_ctrl2_grid.addWidget(self.jog_fr_entry, 1, 1) + + # ############################################################################################################# + # GRBL SENDER + # ############################################################################################################# + grbl_send_grid = QtWidgets.QGridLayout() + grbl_send_grid.setColumnStretch(0, 1) + grbl_send_grid.setColumnStretch(1, 0) + self.gr_send_tab_layout.addLayout(grbl_send_grid) + + # Send CUSTOM COMMAND + self.grbl_command_label = FCLabel('%s:' % _("Send Command")) + self.grbl_command_label.setToolTip( + _("Send a custom command to GRBL.") + ) + grbl_send_grid.addWidget(self.grbl_command_label, 2, 0, 1, 2) + + self.grbl_command_entry = FCEntry() + self.grbl_command_entry.setPlaceholderText(_("Type GRBL command ...")) + + self.grbl_send_button = QtWidgets.QToolButton() + self.grbl_send_button.setText(_("Send")) + self.grbl_send_button.setToolTip( + _("Send a custom command to GRBL.") + ) + grbl_send_grid.addWidget(self.grbl_command_entry, 4, 0) + grbl_send_grid.addWidget(self.grbl_send_button, 4, 1) + + # Get Parameter + self.grbl_get_param_label = FCLabel('%s:' % _("Get Config parameter")) + self.grbl_get_param_label.setToolTip( + _("A GRBL configuration parameter.") + ) + grbl_send_grid.addWidget(self.grbl_get_param_label, 6, 0, 1, 2) + + self.grbl_parameter_entry = FCEntry() + self.grbl_parameter_entry.setPlaceholderText(_("Type GRBL parameter ...")) + + self.grbl_get_param_button = QtWidgets.QToolButton() + self.grbl_get_param_button.setText(_("Get")) + self.grbl_get_param_button.setToolTip( + _("Get the value of a specified GRBL parameter.") + ) + grbl_send_grid.addWidget(self.grbl_parameter_entry, 8, 0) + grbl_send_grid.addWidget(self.grbl_get_param_button, 8, 1) + + grbl_send_grid.setRowStretch(9, 1) + + # GET Report + self.grbl_report_button = FCButton(_("Get Report")) + self.grbl_report_button.setToolTip( + _("Print in shell the GRBL report.") + ) + grbl_send_grid.addWidget(self.grbl_report_button, 10, 0, 1, 2) + + hm_lay = QtWidgets.QHBoxLayout() + # GET HEIGHT MAP + self.grbl_get_heightmap_button = FCButton(_("Apply AutoLevelling")) + self.grbl_get_heightmap_button.setToolTip( + _("Will send the probing GCode to the GRBL controller,\n" + "wait for the Z probing data and then apply this data\n" + "over the original GCode therefore doing autolevelling.") + ) + hm_lay.addWidget(self.grbl_get_heightmap_button, stretch=1) + + self.grbl_save_height_map_button = QtWidgets.QToolButton() + self.grbl_save_height_map_button.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as.png')) + self.grbl_save_height_map_button.setToolTip( + _("Will save the GRBL height map.") + ) + hm_lay.addWidget(self.grbl_save_height_map_button, stretch=0, alignment=Qt.AlignRight) + + grbl_send_grid.addLayout(hm_lay, 12, 0, 1, 2) + + self.grbl_frame.hide() + # ############################################################################################################# + + height_lay = QtWidgets.QHBoxLayout() + self.h_gcode_button = FCButton(_("Save Probing GCode")) + self.h_gcode_button.setToolTip( + _("Will save the probing 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 + '/edit_file32.png')) + # self.view_h_gcode_button.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored) + self.view_h_gcode_button.setToolTip( + _("View/Edit the probing GCode.") + ) + # height_lay.addStretch() + height_lay.addWidget(self.view_h_gcode_button) + + grid0.addLayout(height_lay, 31, 0, 1, 2) + + self.import_heights_button = FCButton(_("Import Height Map")) + self.import_heights_button.setToolTip( + _("Import the file that has the Z heights\n" + "obtained through probing and then apply this data\n" + "over the original GCode therefore\n" + "doing autolevelling.") + ) + grid0.addWidget(self.import_heights_button, 33, 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, 35, 0, 1, 2) + + self.tools_box.addStretch(1) + + # ## Reset Tool + self.reset_button = FCButton(_("Reset Tool")) + self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png')) + self.reset_button.setToolTip( + _("Will reset the tool parameters.") + ) + self.reset_button.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + self.tools_box.addWidget(self.reset_button) + # ############################ FINISHED GUI ################################### + # ############################################################################# + + def confirmation_message(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"), + self.decimals, + minval, + self.decimals, + maxval), False) + else: + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) + + def confirmation_message_int(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' % + (_("Edited value is out of range"), minval, maxval), False) + else: + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) diff --git a/appTools/ToolMilling.py b/appTools/ToolMilling.py index 12903363..391c17b3 100644 --- a/appTools/ToolMilling.py +++ b/appTools/ToolMilling.py @@ -1150,8 +1150,11 @@ class ToolMilling(AppTool, Excellon): def on_object_selection_changed(self, current, previous): try: - name = current.indexes()[0].internalPointer().obj.options['name'] - self.ui.object_combo.set_value(name) + sel_obj = current.indexes()[0].internalPointer().obj + name = sel_obj.options['name'] + kind = sel_obj.kind + if kind in ['geometry', 'excellon']: + self.ui.object_combo.set_value(name) except IndexError: pass diff --git a/appTools/__init__.py b/appTools/__init__.py index aab619fa..35cc1c1e 100644 --- a/appTools/__init__.py +++ b/appTools/__init__.py @@ -1,4 +1,3 @@ - from appTools.ToolCalculators import ToolCalculator from appTools.ToolCalibration import ToolCalibration @@ -22,6 +21,7 @@ from appTools.ToolIsolation import ToolIsolation from appTools.ToolFollow import ToolFollow from appTools.ToolDrilling import ToolDrilling from appTools.ToolMilling import ToolMilling +from appTools.ToolLevelling import ToolLevelling from appTools.ToolOptimal import ToolOptimal diff --git a/app_Main.py b/app_Main.py index 6a2457d8..cb39f99f 100644 --- a/app_Main.py +++ b/app_Main.py @@ -1202,6 +1202,7 @@ class App(QtCore.QObject): self.follow_tool = None self.drilling_tool = None self.milling_tool = None + self.levelling_tool = None self.optimal_tool = None self.transform_tool = None @@ -1920,6 +1921,10 @@ class App(QtCore.QObject): self.milling_tool.install(icon=QtGui.QIcon(self.resource_location + '/milling_tool32.png'), pos=self.ui.menutool, before=self.sub_tool.menuAction, separator=True) + self.levelling_tool = ToolLevelling(self) + self.levelling_tool.install(icon=QtGui.QIcon(self.resource_location + '/level32.png'), + pos=self.ui.menuoptions_experimental, separator=True) + self.copper_thieving_tool = ToolCopperThieving(self) self.copper_thieving_tool.install(icon=QtGui.QIcon(self.resource_location + '/copperfill32.png'), pos=self.ui.menutool) @@ -2192,6 +2197,7 @@ class App(QtCore.QObject): self.ui.drill_btn.triggered.connect(lambda: self.drilling_tool.run(toggle=True)) self.ui.mill_btn.triggered.connect(lambda: self.milling_tool.run(toggle=True)) + self.ui.level_btn.triggered.connect(lambda: self.levelling_tool.run(toggle=True)) self.ui.isolation_btn.triggered.connect(lambda: self.isolation_tool.run(toggle=True)) self.ui.follow_btn.triggered.connect(lambda: self.follow_tool.run(toggle=True)) diff --git a/assets/resources/dark_resources/level32.png b/assets/resources/dark_resources/level32.png new file mode 100644 index 0000000000000000000000000000000000000000..4e82638a81f1e9ddadf3f512b962143a4031ebfb GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-GzZ+Rj;xUkjGiz z5n0T@z;^_M8K-LVNdpDhOFVsD*`G2p3Go?)96Gg&fq{|V)5S5w<9Kp{#HR*9r@flL z<=MoebT4`CVe}DrRWH$Obhx_hJI{N`DS~c-%n=XPXfImF0|e{&4GShGF$gbBY%p+m z{Pom!u_+P<4kvtxpH#vukXYb$Wtx$GfDD_}`m%l7mLFjdb6fad#Z{q7B1Ynafk8#> zr?uLtdO!aEzkh*`dGdAhxZeSfTIK)C8`Ug1(x9F6?ny->>(t-#Kk~{qzj|%lVrsNb z=;a}U2lkI=&*b^VlP-Br_)7jK`yR%-4gY)AFE?0bbVk%OS!IS?oP64YmJI?M5B#X& zN){`;wXwZXb+PH;oQWmJ{y$+;5g5-u U&wghk1ysb~>FVdQ&MBb@09WP3P5=M^ literal 0 HcmV?d00001 diff --git a/assets/resources/level32.png b/assets/resources/level32.png new file mode 100644 index 0000000000000000000000000000000000000000..6fdf04c6ce41f90697bd9119f9f66467e1a6e6fb GIT binary patch literal 561 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-GzZ+Rj;xUkjGiz z5n0T@z;^_M8K-LVNdpDhOFVsD*`G2p3Gs=3JW+oUDD>ac#WBR=_|+-)U56Y*+V@W^ zIi*y#tt&w=z@)V|Lf7n?;w2Lba^WY9E?l_H~n_t%Xyu8cA;;gmC!sbt2;(x|-ijfKu4-CUroq4pzs7B|- z<*Db#1pO6skS-gZ0Q3-Px^<`}Bc9 zp<3b^QIe8al4_M)lnSI6j0}tnbq#>XD8$gh%Fx8h$WYtBz{nC}Q!>*k zacj6ZA?_|vgCxj?;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1STJYD@<);T3K F0RX}5$D#lL literal 0 HcmV?d00001 diff --git a/defaults.py b/defaults.py index badf8395..cfe971bf 100644 --- a/defaults.py +++ b/defaults.py @@ -386,19 +386,6 @@ class FlatCAMDefaults: # CNC Job Advanced Options "cncjob_annotation_fontsize": 9, "cncjob_annotation_fontcolor": '#990000', - # Autolevelling - "cncjob_al_status": False, - "cncjob_al_mode": 'grid', - "cncjob_al_method": 'v', - "cncjob_al_rows": 4, - "cncjob_al_columns": 4, - "cncjob_al_travelz": 2.0, - "cncjob_al_probe_depth": -1.0, - "cncjob_al_probe_fr": 120, - "cncjob_al_controller": 'MACH3', - "cncjob_al_grbl_jog_step": 5, - "cncjob_al_grbl_jog_fr": 1500, - "cncjob_al_grbl_travelz": 15.0, # CNC Job (GCode) Editor "cncjob_prepend": "", @@ -468,6 +455,20 @@ class FlatCAMDefaults: # Milling Tool "tools_mill_tool_type": 'C1', + # Autolevelling Tool + "tools_al_status": False, + "tools_al_mode": 'grid', + "tools_al_method": 'v', + "tools_al_rows": 4, + "tools_al_columns": 4, + "tools_al_travelz": 2.0, + "tools_al_probe_depth": -1.0, + "tools_al_probe_fr": 120, + "tools_al_controller": 'MACH3', + "tools_al_grbl_jog_step": 5, + "tools_al_grbl_jog_fr": 1500, + "tools_al_grbl_travelz": 15.0, + # NCC Tool "tools_ncc_tools": "1.0, 0.5", "tools_ncc_order": 'rev',