diff --git a/CHANGELOG.md b/CHANGELOG.md index 234bb4ed..1b8f70fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ CHANGELOG for FlatCAM beta ================================================= +3.09.2020 + +- in CNCJob UI Autolevelling: changed the UI a bit +- added a bilinear interpolation calculation class from: https://github.com/pmav99/interpolation +- in CNCJob UI Autolevelling: made sure that the grid can't have less than 2 rows and 2 columns when using the bilinear interpolation or 1 row and 1 column when using the Voronoi polygons + + 2.09.2020 - in CNCJob UI Autolevelling: solved some small errors: when manual adding probe points dragging the mouse with left button pressed created selection rectangles; detection of click inside the solid geometry was failing diff --git a/appCommon/bilinear.py b/appCommon/bilinear.py new file mode 100644 index 00000000..c4845a55 --- /dev/null +++ b/appCommon/bilinear.py @@ -0,0 +1,119 @@ +############################################################################# +# Copyright (c) 2013 by Panagiotis Mavrogiorgos +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name(s) of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AS IS AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +# EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################# +# +# @license: http://opensource.org/licenses/BSD-3-Clause + +from bisect import bisect_left +import logging + +log = logging.getLogger('base') + + +class BilinearInterpolation(object): + """ + Bilinear interpolation with optional extrapolation. + Usage: + table = BilinearInterpolation( + x_index=(1, 2, 3), + y_index=(1, 2, 3), + values=((110, 120, 130), + (210, 220, 230), + (310, 320, 330)), + extrapolate=True) + + assert table(1, 1) == 110 + assert table(2.5, 2.5) == 275 + + """ + def __init__(self, x_index, y_index, values): + # sanity check + x_length = len(x_index) + y_length = len(y_index) + + if x_length < 2 or y_length < 2: + raise ValueError("Table must be at least 2x2.") + if y_length != len(values): + raise ValueError("Table must have equal number of rows to y_index.") + if any(x2 - x1 <= 0 for x1, x2 in zip(x_index, x_index[1:])): + raise ValueError("x_index must be in strictly ascending order!") + if any(y2 - y1 <= 0 for y1, y2 in zip(y_index, y_index[1:])): + raise ValueError("y_index must be in strictly ascending order!") + + self.x_index = x_index + self.y_index = y_index + self.values = values + self.x_length = x_length + self.y_length = y_length + self.extrapolate = True + + #slopes = self.slopes = [] + #for j in range(y_length): + #intervals = zip(x_index, x_index[1:], values[j], values[j][1:]) + #slopes.append([(y2 - y1) / (x2 - x1) for x1, x2, y1, y2 in intervals]) + + def __call__(self, x, y): + # local lookups + x_index, y_index, values = self.x_index, self.y_index, self.values + + i = bisect_left(x_index, x) - 1 + j = bisect_left(y_index, y) - 1 + + if self.extrapolate: + # fix x index + if i == -1: + x_slice = slice(None, 2) + elif i == self.x_length - 1: + x_slice = slice(-2, None) + else: + x_slice = slice(i, i + 2) + + # fix y index + if j == -1: + j = 0 + y_slice = slice(None, 2) + elif j == self.y_length - 1: + j = -2 + y_slice = slice(-2, None) + else: + y_slice = slice(j, j + 2) + else: + if i == -1 or i == self.x_length - 1: + raise ValueError("Extrapolation not allowed!") + if j == -1 or j == self.y_length - 1: + raise ValueError("Extrapolation not allowed!") + + # if the extrapolations is False this will fail + x1, x2 = x_index[x_slice] + y1, y2 = y_index[y_slice] + z11, z12 = values[j][x_slice] + z21, z22 = values[j + 1][x_slice] + + return (z11 * (x2 - x) * (y2 - y) + + z21 * (x - x1) * (y2 - y) + + z12 * (x2 - x) * (y - y1) + + z22 * (x - x1) * (y - y1)) / ((x2 - x1) * (y2 - y1)) diff --git a/appGUI/ObjectUI.py b/appGUI/ObjectUI.py index 4639aa38..289d55b4 100644 --- a/appGUI/ObjectUI.py +++ b/appGUI/ObjectUI.py @@ -1923,11 +1923,13 @@ class CNCObjectUI(ObjectUI): grid0.addWidget(self.al_probe_points_table, 1, 0, 1, 2) - self.voronoi_cb = FCCheckBox(_("Show Voronoi diagram")) - self.voronoi_cb.setToolTip( - _("Display Voronoi diagram if there are probe points in the table.") + 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.voronoi_cb, 3, 0, 1, 2) + grid0.addWidget(self.plot_probing_pts_cb, 3, 0, 1, 2) separator_line = QtWidgets.QFrame() separator_line.setFrameShape(QtWidgets.QFrame.HLine) @@ -2022,6 +2024,7 @@ class CNCObjectUI(ObjectUI): # ## Columns self.al_columns_entry = FCSpinner() + self.al_columns_entry.setMinimum(2) self.al_columns_label = QtWidgets.QLabel('%s:' % _("Columns")) self.al_columns_label.setToolTip( @@ -2032,6 +2035,7 @@ class CNCObjectUI(ObjectUI): # ## Rows self.al_rows_entry = FCSpinner() + self.al_rows_entry.setMinimum(2) self.al_rows_label = QtWidgets.QLabel('%s:' % _("Rows")) self.al_rows_label.setToolTip( @@ -2465,6 +2469,13 @@ class CNCObjectUI(ObjectUI): # 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 a02061be..7db293b1 100644 --- a/appObjects/FlatCAMCNCJob.py +++ b/appObjects/FlatCAMCNCJob.py @@ -466,13 +466,11 @@ class CNCJobObject(FlatCAMObj, CNCjob): 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.voronoi_cb.setDisabled(False) 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.voronoi_cb.setDisabled(True) self.ui.grbl_get_heightmap_button.setDisabled(True) self.ui.grbl_save_height_map_button.setDisabled(True) self.ui.h_gcode_button.setDisabled(True) @@ -573,8 +571,9 @@ class CNCJobObject(FlatCAMObj, CNCjob): # autolevelling signals self.ui.sal_cb.stateChanged.connect(self.on_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.voronoi_cb.stateChanged.connect(self.show_voronoi_diagram) + self.ui.plot_probing_pts_cb.stateChanged.connect(self.show_voronoi_diagram) # 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) @@ -645,6 +644,8 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.ui.al_mode_radio.set_value(self.options['al_mode']) self.on_controller_change() + self.on_method_radio(val=self.options['al_method']) + # def on_cnc_custom_parameters(self, signal_text): # if signal_text == 'Parameters': # return @@ -708,8 +709,8 @@ class CNCJobObject(FlatCAMObj, CNCjob): cols = self.ui.al_columns_entry.get_value() rows = self.ui.al_rows_entry.get_value() - dx = width / (cols - 1) - dy = height / (rows - 1) + dx = 0 if cols == 1 else width / (cols - 1) + dy = 0 if rows == 1 else height / (rows - 1) points = [] new_y = ymin @@ -742,7 +743,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.probing_gcode_text = self.probing_gcode() self.build_al_table_sig.emit() - if self.ui.voronoi_cb.get_value(): + if self.ui.plot_probing_pts_cb.get_value(): self.show_voronoi_diagram(state=True, reset=True) else: # clear probe shapes @@ -780,7 +781,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): self.mouse_events_connected = True self.build_al_table_sig.emit() - if self.ui.voronoi_cb.get_value(): + if self.ui.plot_probing_pts_cb.get_value(): self.show_voronoi_diagram(state=True, reset=True) else: # clear probe shapes @@ -990,7 +991,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): # rebuild the al table self.build_al_table_sig.emit() - if self.ui.voronoi_cb.get_value(): + if self.ui.plot_probing_pts_cb.get_value(): self.show_voronoi_diagram(state=True, reset=True) else: # clear probe shapes @@ -1095,6 +1096,14 @@ class CNCJobObject(FlatCAMObj, CNCjob): 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()