diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fe73a41..e027c2d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ CHANGELOG for FlatCAM beta ================================================= +23.08.2020 + +- in CNCJob UI Autolevelling - autolevelling is made to be not available for cnc code generated with Roland or HPGL preprocessors +- in CNCJob UI Autolevelling - added a save dialog for the probing GCode +- added a new GUI element, a DoubleSlider +- in CNCJob UI Autolevelling - GRBL controller - Control: trying to add DoubleSlider + DoubleSpinner combo controls + 21.08.2020 - in CNCJob UI Autolevelling - GRBL controller - Control: added a Origin button; changed the UI to have rounded rectangles diff --git a/appGUI/GUIElements.py b/appGUI/GUIElements.py index e8da1143..a1f75abe 100644 --- a/appGUI/GUIElements.py +++ b/appGUI/GUIElements.py @@ -12,7 +12,7 @@ # ########################################################## from PyQt5 import QtGui, QtCore, QtWidgets -from PyQt5.QtCore import Qt, pyqtSlot, QSettings +from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal, QSettings from PyQt5.QtWidgets import QTextEdit, QCompleter, QAction from PyQt5.QtGui import QKeySequence, QTextCursor @@ -923,6 +923,116 @@ class FCSpinner(QtWidgets.QSpinBox): # return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height()) +class FCDoubleSlider(QtWidgets.QSlider): + # frome here: https://stackoverflow.com/questions/42820380/use-float-for-qslider + + # create our our signal that we can connect to if necessary + doubleValueChanged = pyqtSignal(float) + + def __init__(self, decimals=3, orientation='horizontal', *args, **kargs): + if orientation == 'horizontal': + super(FCDoubleSlider, self).__init__(QtCore.Qt.Horizontal, *args, **kargs) + else: + super(FCDoubleSlider, self).__init__(QtCore.Qt.Vertical, *args, **kargs) + + self._multi = 10 ** decimals + + self.valueChanged.connect(self.emitDoubleValueChanged) + + def emitDoubleValueChanged(self): + value = float(super(FCDoubleSlider, self).value()) / self._multi + self.doubleValueChanged.emit(value) + + def value(self): + return float(super(FCDoubleSlider, self).value()) / self._multi + + def setMinimum(self, value): + return super(FCDoubleSlider, self).setMinimum(value * self._multi) + + def setMaximum(self, value): + return super(FCDoubleSlider, self).setMaximum(value * self._multi) + + def setSingleStep(self, value): + return super(FCDoubleSlider, self).setSingleStep(value * self._multi) + + def singleStep(self): + return float(super(FCDoubleSlider, self).singleStep()) / self._multi + + def set_value(self, value): + super(FCDoubleSlider, self).setValue(int(value * self._multi)) + + def set_range(self, min, max): + self.blockSignals(True) + self.setRange(min, max) + self.blockSignals(False) + + +class FCSliderWithDoubleSpinner(QtWidgets.QFrame): + + def __init__(self, min=0, max=9999.9999, step=1, precision=4, orientation='horizontal', **kwargs): + super().__init__(**kwargs) + + self.slider = FCDoubleSlider(orientation=orientation) + self.slider.setMinimum(min) + self.slider.setMaximum(max) + self.slider.setSingleStep(step) + self.slider.set_range(min, max) + + self.spinner = FCDoubleSpinner() + self.spinner.set_range(min, max) + self.spinner.set_precision(precision) + + self.spinner.set_step(step) + self.spinner.setMinimumWidth(70) + + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + self.spinner.setSizePolicy(sizePolicy) + + self.layout = QtWidgets.QHBoxLayout() + self.layout.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) + self.layout.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.slider) + self.layout.addWidget(self.spinner) + self.setLayout(self.layout) + + self.slider.doubleValueChanged.connect(self._on_slider) + self.spinner.valueChanged.connect(self._on_spinner) + + self.valueChanged = self.spinner.valueChanged + + def set_precision(self, prec): + self.spinner.set_precision(prec) + + def setSingleStep(self, step): + self.spinner.set_step(step) + + def set_range(self, min, max): + self.spinner.set_range(min, max) + self.slider.set_range(min, max) + + def set_minimum(self, min): + self.slider.setMinimum(min) + self.spinner.setMinimum(min) + + def set_maximum(self, max): + self.slider.setMaximum(max) + self.spinner.setMaximum(max) + + def get_value(self) -> float: + return self.spinner.get_value() + + def set_value(self, value: float): + self.spinner.set_value(value) + + def _on_spinner(self): + spinner_value = self.spinner.value() + self.slider.set_value(spinner_value) + + def _on_slider(self): + slider_value = self.slider.value() + self.spinner.set_value(slider_value) + + class FCDoubleSpinner(QtWidgets.QDoubleSpinBox): returnPressed = QtCore.pyqtSignal() @@ -1056,6 +1166,11 @@ class FCDoubleSpinner(QtWidgets.QDoubleSpinBox): self.setRange(min_val, max_val) + def set_step(self, p_int): + self.blockSignals(True) + self.setSingleStep(p_int) + self.blockSignals(False) + # def sizeHint(self): # default_hint_size = super(FCDoubleSpinner, self).sizeHint() # return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height()) diff --git a/appGUI/ObjectUI.py b/appGUI/ObjectUI.py index 13a17855..85a2255f 100644 --- a/appGUI/ObjectUI.py +++ b/appGUI/ObjectUI.py @@ -2278,7 +2278,7 @@ class CNCObjectUI(ObjectUI): _("Each jog action will move the axes with this value.") ) - self.jog_step_entry = FCDoubleSpinner() + 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, 99999.9999) @@ -2292,7 +2292,7 @@ class CNCObjectUI(ObjectUI): _("Feedrate when jogging.") ) - self.jog_fr_entry = FCDoubleSpinner() + 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, 99999.9999) diff --git a/appObjects/FlatCAMCNCJob.py b/appObjects/FlatCAMCNCJob.py index 37dde131..27c35e2b 100644 --- a/appObjects/FlatCAMCNCJob.py +++ b/appObjects/FlatCAMCNCJob.py @@ -201,6 +201,8 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.solid_geo = None self.grbl_ser_port = None + self.pressed_button = None + # Attributes to be included in serialization # Always append to it because it carries contents # from predecessors. @@ -568,27 +570,13 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.ui.grbl_command_entry.returnPressed.connect(self.on_send_grbl_command) # Jog - self.ui.jog_wdg.jog_up_button.clicked.connect( - lambda: self.on_jog(direction='yplus', step=self.ui.jog_step_entry.get_value(), - feedrate=self.ui.jog_fr_entry.get_value())) - self.ui.jog_wdg.jog_down_button.clicked.connect( - lambda: self.on_jog(direction='yminus', step=self.ui.jog_step_entry.get_value(), - feedrate=self.ui.jog_fr_entry.get_value())) - self.ui.jog_wdg.jog_right_button.clicked.connect( - lambda: self.on_jog(direction='xplus', step=self.ui.jog_step_entry.get_value(), - feedrate=self.ui.jog_fr_entry.get_value())) - self.ui.jog_wdg.jog_left_button.clicked.connect( - lambda: self.on_jog(direction='xminus', step=self.ui.jog_step_entry.get_value(), - feedrate=self.ui.jog_fr_entry.get_value())) - self.ui.jog_wdg.jog_z_up_button.clicked.connect( - lambda: self.on_jog(direction='zplus', step=self.ui.jog_step_entry.get_value(), - feedrate=self.ui.jog_fr_entry.get_value())) - self.ui.jog_wdg.jog_z_down_button.clicked.connect( - lambda: self.on_jog(direction='zminus', step=self.ui.jog_step_entry.get_value(), - feedrate=self.ui.jog_fr_entry.get_value())) - self.ui.jog_wdg.jog_origin_button.clicked.connect( - lambda: self.on_jog(direction='origin', travelz=float(self.app.defaults["cncjob_al_grbl_travelz"]), - feedrate=self.ui.jog_fr_entry.get_value())) + self.ui.jog_wdg.jog_up_button.clicked.connect(lambda: self.on_jog(direction='yplus')) + self.ui.jog_wdg.jog_down_button.clicked.connect(lambda: self.on_jog(direction='yminus')) + self.ui.jog_wdg.jog_right_button.clicked.connect(lambda: self.on_jog(direction='xplus')) + self.ui.jog_wdg.jog_left_button.clicked.connect(lambda: self.on_jog(direction='xminus')) + self.ui.jog_wdg.jog_z_up_button.clicked.connect(lambda: self.on_jog(direction='zplus')) + self.ui.jog_wdg.jog_z_down_button.clicked.connect(lambda: self.on_jog(direction='zminus')) + self.ui.jog_wdg.jog_origin_button.clicked.connect(lambda: self.on_jog(direction='origin')) # Zero self.ui.zero_axs_wdg.grbl_zerox_button.clicked.connect(lambda: self.on_grbl_zero(axis='x')) @@ -624,8 +612,12 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.ui.level.setText(_( 'Advanced' )) - self.ui.sal_cb.show() - self.ui.sal_cb.set_value(self.app.defaults["cncjob_al_status"]) + if 'Roland' in self.pp_excellon_name or 'Roland' in self.pp_geometry_name or 'hpgl' in \ + self.pp_geometry_name: + pass + else: + self.ui.sal_cb.show() + self.ui.sal_cb.set_value(self.app.defaults["cncjob_al_status"]) preamble = self.append_snippet postamble = self.prepend_snippet @@ -1154,11 +1146,15 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.app.shell_message("GRBL Parameter: %s = %s" % (str(param), str(result)), show=True) return result - def on_jog(self, direction=None, step=5.0, feedrate=1000.0, travelz=15.0): + def on_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': @@ -1349,6 +1345,44 @@ class CNCJobObject(FlatCAMObj, CNCjob): controller = self.ui.al_controller_combo.get_value() self.probing_gcode_text = self.probing_gcode(coords, pr_travel, probe_fr, pr_depth, controller) + 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_view_probing_gcode(self): self.app.proc_container.view.set_busy(_("Loading...")) @@ -1415,7 +1449,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): filename = str(filename) if filename == '': - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) else: self.app.worker_task.emit({'fcn': self.import_height_map, 'params': [filename]})