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 00000000..4e82638a
Binary files /dev/null and b/assets/resources/dark_resources/level32.png differ
diff --git a/assets/resources/level32.png b/assets/resources/level32.png
new file mode 100644
index 00000000..6fdf04c6
Binary files /dev/null and b/assets/resources/level32.png differ
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',