diff --git a/CHANGELOG.md b/CHANGELOG.md index dd9b88e5..b043eb2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ CHANGELOG for FlatCAM Evo beta - some names are updated - made sure that when a new project is created that the levelling grid shapes are deleted - some code optimization and code refactoring +- Levelling Tool: added parameter (in Preferences too) to control the probe tip diameter which is reflected in the probing location mark diameter +- Levelling Tool: when adding a Grid probing and the avoidance of Excellon is used, now the probing locations will be offset enough so the probing is not done in the Excellon holes 14.11.2023 diff --git a/appGUI/GUIElements.py b/appGUI/GUIElements.py index b051f97f..cd5e7358 100644 --- a/appGUI/GUIElements.py +++ b/appGUI/GUIElements.py @@ -22,6 +22,7 @@ import logging import html import sys import inspect +from typing import Callable import gettext import appTranslation as fcTranslate @@ -1434,11 +1435,12 @@ class FCDoubleSpinner(QtWidgets.QDoubleSpinBox): def __init__(self, suffix=None, alignment=None, parent=None, callback=None, policy=True): """ - :param suffix: a char added to the end of the value in the LineEdit; like a '%' or '$' etc - :param alignment: the value is aligned to left or right - :param parent: - :param callback: called when the entered value is outside limits; the min and max value will be passed to it - :param policy: by default the widget will not compact as much as possible on horizontal + :param suffix: str, a char added to the end of the value in the LineEdit; like a '%' or '$' etc. + :param alignment: str, 'left' or 'right', defines if the value is aligned to left or right + :param parent: QWidget, parent widget + :param callback: Callable, called when the entered value is outside limits; + the min and max value will be passed to it + :param policy: QtGui.QSizePolicy, by default the widget will not compact as much as possible on horizontal """ super(FCDoubleSpinner, self).__init__(parent) self.cursor_pos = None diff --git a/appGUI/preferences/PreferencesUIManager.py b/appGUI/preferences/PreferencesUIManager.py index 30de316f..d684d170 100644 --- a/appGUI/preferences/PreferencesUIManager.py +++ b/appGUI/preferences/PreferencesUIManager.py @@ -400,11 +400,13 @@ class PreferencesUIManager(QtCore.QObject): "tools_mill_milling_connect": self.ui.plugin_pref_form.tools_mill_group.connect_cb, # Autolevelling Tool + "tools_al_avoid_exc_holes_size": self.ui.plugin_eng_pref_form.tools_level_group.avoid_exc_holes_size_entry, "tools_al_mode": self.ui.plugin_eng_pref_form.tools_level_group.al_mode_radio, "tools_al_method": self.ui.plugin_eng_pref_form.tools_level_group.al_method_radio, "tools_al_rows": self.ui.plugin_eng_pref_form.tools_level_group.al_rows_entry, "tools_al_columns": self.ui.plugin_eng_pref_form.tools_level_group.al_columns_entry, - "tools_al_travelz": self.ui.plugin_eng_pref_form.tools_level_group.ptravelz_entry, + "tools_al_travel_z": self.ui.plugin_eng_pref_form.tools_level_group.ptravelz_entry, + "tools_al_probe_tip_dia": self.ui.plugin_eng_pref_form.tools_level_group.probe_tip_dia_entry, "tools_al_probe_depth": self.ui.plugin_eng_pref_form.tools_level_group.pdepth_entry, "tools_al_probe_fr": self.ui.plugin_eng_pref_form.tools_level_group.feedrate_probe_entry, "tools_al_controller": self.ui.plugin_eng_pref_form.tools_level_group.al_controller_combo, diff --git a/appGUI/preferences/tools/ToolsLevelPrefGroupUI.py b/appGUI/preferences/tools/ToolsLevelPrefGroupUI.py index 5002e6c7..0711e4a7 100644 --- a/appGUI/preferences/tools/ToolsLevelPrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsLevelPrefGroupUI.py @@ -49,8 +49,8 @@ class ToolsLevelPrefGroupUI(OptionsGroupUI): {'label': _('Manual'), 'value': 'manual'}, {'label': _('Grid'), 'value': 'grid'} ]) - par_grid.addWidget(al_mode_lbl, 8, 0) - par_grid.addWidget(self.al_mode_radio, 8, 1) + par_grid.addWidget(al_mode_lbl, 0, 0) + par_grid.addWidget(self.al_mode_radio, 0, 1) # AUTOLEVELL METHOD self.al_method_lbl = FCLabel('%s:' % _("Method")) @@ -63,8 +63,21 @@ class ToolsLevelPrefGroupUI(OptionsGroupUI): {'label': _('Voronoi'), 'value': 'v'}, {'label': _('Bilinear'), 'value': 'b'} ]) - par_grid.addWidget(self.al_method_lbl, 9, 0) - par_grid.addWidget(self.al_method_radio, 9, 1) + par_grid.addWidget(self.al_method_lbl, 2, 0) + par_grid.addWidget(self.al_method_radio, 2, 1) + + # Avoid Excellon holes Size + self.avoid_exc_holes_size_label = FCLabel('%s:' % _("Avoid Step")) + self.avoid_exc_holes_size_label.setToolTip( + _("The incremental size to move to the side, to avoid an Excellon hole.") + ) + + self.avoid_exc_holes_size_entry = FCDoubleSpinner() + self.avoid_exc_holes_size_entry.set_precision(self.decimals) + self.avoid_exc_holes_size_entry.set_range(0.0000, 99999.0000) + + par_grid.addWidget(self.avoid_exc_holes_size_label, 4, 0) + par_grid.addWidget(self.avoid_exc_holes_size_entry, 4, 1) # ## Columns self.al_columns_entry = FCSpinner() @@ -73,8 +86,8 @@ class ToolsLevelPrefGroupUI(OptionsGroupUI): self.al_columns_label.setToolTip( _("The number of grid columns.") ) - par_grid.addWidget(self.al_columns_label, 10, 0) - par_grid.addWidget(self.al_columns_entry, 10, 1) + par_grid.addWidget(self.al_columns_label, 6, 0) + par_grid.addWidget(self.al_columns_entry, 6, 1) # ## Rows self.al_rows_entry = FCSpinner() @@ -83,8 +96,20 @@ class ToolsLevelPrefGroupUI(OptionsGroupUI): self.al_rows_label.setToolTip( _("The number of grid rows.") ) - par_grid.addWidget(self.al_rows_label, 12, 0) - par_grid.addWidget(self.al_rows_entry, 12, 1) + par_grid.addWidget(self.al_rows_label, 8, 0) + par_grid.addWidget(self.al_rows_entry, 8, 1) + + # Probe Diameter + self.probe_tip_dia_label = FCLabel('%s:' % _("Probe Tip Dia")) + self.probe_tip_dia_label.setToolTip( + _("The probe tip diameter.") + ) + self.probe_tip_dia_entry = FCDoubleSpinner() + self.probe_tip_dia_entry.set_precision(self.decimals) + self.probe_tip_dia_entry.set_range(0.0000, 10.0000) + + par_grid.addWidget(self.probe_tip_dia_label, 10, 0) + par_grid.addWidget(self.probe_tip_dia_entry, 10, 1) # Travel Z Probe self.ptravelz_label = FCLabel('%s:' % _("Probe Z travel")) diff --git a/appObjects/CNCJobObject.py b/appObjects/CNCJobObject.py index df410c17..02c8e18e 100644 --- a/appObjects/CNCJobObject.py +++ b/appObjects/CNCJobObject.py @@ -87,7 +87,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): # "toolchange_macro": '', # "toolchange_macro_enable": False - "tools_al_travelz": self.app.options["tools_al_travelz"], + "tools_al_travel_z": self.app.options["tools_al_travel_z"], "tools_al_probe_depth": self.app.options["tools_al_probe_depth"], "tools_al_probe_fr": self.app.options["tools_al_probe_fr"], "tools_al_controller": self.app.options["tools_al_controller"], diff --git a/appPlugins/ToolLevelling.py b/appPlugins/ToolLevelling.py index 3431760d..85225d72 100644 --- a/appPlugins/ToolLevelling.py +++ b/appPlugins/ToolLevelling.py @@ -195,6 +195,8 @@ class ToolLevelling(AppTool, CNCjob): self.build_al_table_sig.connect(self.build_al_table) self.ui.level.toggled.connect(self.on_level_changed) + self.ui.avoid_exc_holes_cb.toggled.connect(self.on_avoid_exc_holes) + 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) @@ -273,16 +275,18 @@ class ToolLevelling(AppTool, CNCjob): 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, + "tools_al_probe_tip_dia": self.ui.probe_tip_dia_entry, + "tools_al_travel_z": 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_avoid_exc_holes_size": self.ui.avoid_exc_holes_size_entry, + "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 @@ -341,6 +345,8 @@ class ToolLevelling(AppTool, CNCjob): else: self.ui.al_frame.setDisabled(True) + self.on_avoid_exc_holes(self.app.options["tools_al_avoid_exc_holes"]) + def on_object_changed(self): # load the object @@ -536,7 +542,24 @@ class ToolLevelling(AppTool, CNCjob): else: self.on_add_manual_points() + def check_point_over_excellon(self, pol: Polygon, check: bool) -> MultiPolygon: + if not check: + return MultiPolygon() + + fused_geometries = [ + exc_geo + for obj_in_collection in self.app.collection.get_list() + if obj_in_collection.kind == 'excellon' and obj_in_collection.obj_options['plot'] + for exc_geo in MultiPolygon(obj_in_collection.solid_geometry).geoms + if isinstance(exc_geo, Polygon) and pol.intersects(exc_geo) + ] + return unary_union(fused_geometries) + def on_add_grid_points(self): + check_overlap = self.ui.avoid_exc_holes_cb.get_value() + avoid_step = self.ui.avoid_exc_holes_size_entry.get_value() + radius = self.ui.probe_tip_dia_entry.get_value() / 2 + xmin, ymin, xmax, ymax = self.solid_geo.bounds width = abs(xmax - xmin) @@ -556,9 +579,51 @@ class ToolLevelling(AppTool, CNCjob): self.app.dec_format(new_x, self.app.decimals), self.app.dec_format(new_y, self.app.decimals) ) - # do not add the point if is already added - if formatted_point not in points: - points.append(formatted_point) + + point_buffered = Point(formatted_point).buffer(radius) + if self.check_point_over_excellon(pol=point_buffered, check=check_overlap).is_empty: + # do not add the point if is already added + if formatted_point not in points: + points.append(formatted_point) + new_x += dx + continue + + box_poly: Polygon = box( + new_x - dx if (new_x - dx) > xmin else xmin, + new_y - dy if (new_y - dy) > ymin else ymin, + new_x + dx if (new_x + dx) < xmax else xmax, + new_y + dy if (new_y + dy) < ymax else ymax + ) + + increments = [ + (avoid_step, 0), + (avoid_step * -1, 0), + (0, avoid_step), + (0, avoid_step * -1) + ] + + for increment in increments: + break_for_loop = False + while True: + # check if the point is within the box + formatted_point = ( + formatted_point[0] + increment[0], + formatted_point[1] + increment[1] + ) + point_buffered = Point(formatted_point).buffer(radius) + + if not box_poly.contains(point_buffered): + break + + # check if the point overlaps an excellon hole + if self.check_point_over_excellon(pol=point_buffered, check=check_overlap).is_empty: + # do not add the point if it is already added + if formatted_point not in points: + points.append(formatted_point) + break_for_loop = True + break + if break_for_loop: + break new_x += dx new_y += dy @@ -610,7 +675,7 @@ class ToolLevelling(AppTool, CNCjob): } self.al_voronoi_geo_storage[new_id] = deepcopy(new_dict) - radius = 0.3 if self.units == 'MM' else 0.012 + radius = self.ui.probe_tip_dia_entry.get_value() / 2 fprobe_pt_buff = f_probe_pt.buffer(radius) self.app.inform.emit(_("Click on canvas to add a Probe Point...")) @@ -649,11 +714,11 @@ class ToolLevelling(AppTool, CNCjob): poly_geo = [] al_method = self.ui.al_method_radio.get_value() + radius = self.ui.probe_tip_dia_entry.get_value() / 2 # 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 @@ -671,7 +736,6 @@ class ToolLevelling(AppTool, CNCjob): 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] @@ -909,7 +973,7 @@ class ToolLevelling(AppTool, CNCjob): # rebuild the al table self.build_al_table_sig.emit() - radius = 0.3 if self.units == 'MM' else 0.012 + radius = self.ui.probe_tip_dia_entry.get_value() / 2 probe_pt_buff = probe_pt.buffer(radius) self.plot_probing_geo(geometry=probe_pt_buff, visibility=True, custom_color="#0000FFFA") @@ -1064,7 +1128,7 @@ class ToolLevelling(AppTool, CNCjob): self.ui.al_columns_label.setDisabled(True) self.ui.al_method_lbl.setDisabled(True) self.ui.al_method_radio.setDisabled(True) - self.ui.avoid_exc_holes_cb.setDisabled(False) + # self.ui.avoid_exc_holes_cb.setDisabled(False) else: self.ui.al_rows_entry.setDisabled(False) self.ui.al_rows_label.setDisabled(False) @@ -1073,7 +1137,11 @@ class ToolLevelling(AppTool, CNCjob): self.ui.al_method_lbl.setDisabled(False) self.ui.al_method_radio.setDisabled(False) self.ui.al_method_radio.set_value(self.app.options['tools_al_method']) - self.ui.avoid_exc_holes_cb.setDisabled(True) + # self.ui.avoid_exc_holes_cb.setDisabled(True) + + def on_avoid_exc_holes(self, state): + self.ui.avoid_exc_holes_size_label.show() if state else self.ui.avoid_exc_holes_size_label.hide() + self.ui.avoid_exc_holes_size_entry.show() if state else self.ui.avoid_exc_holes_size_entry.hide() def on_method_radio(self, val): if val == 'b': @@ -1880,7 +1948,7 @@ class LevelUI: self.al_box.addWidget(tt_frame) # Grid Layout - tool_grid = GLay(v_spacing=5, h_spacing=3, c_stretch=[0, 0]) + tool_grid = GLay(v_spacing=5, h_spacing=3, c_stretch=[0, 1]) tt_frame.setLayout(tool_grid) # Probe Points table @@ -1907,6 +1975,19 @@ class LevelUI: ) tool_grid.addWidget(self.avoid_exc_holes_cb, 4, 0, 1, 2) + # Avoid Excellon holes Size + self.avoid_exc_holes_size_label = FCLabel('%s:' % _("Avoid Step")) + self.avoid_exc_holes_size_label.setToolTip( + _("The incremental size to move to the side, to avoid an Excellon hole.") + ) + + self.avoid_exc_holes_size_entry = FCDoubleSpinner() + self.avoid_exc_holes_size_entry.set_precision(self.decimals) + self.avoid_exc_holes_size_entry.set_range(0.0000, 99999.0000) + + tool_grid.addWidget(self.avoid_exc_holes_size_label, 6, 0) + tool_grid.addWidget(self.avoid_exc_holes_size_entry, 6, 1, 1, 1) + # ############################################################################################################# # ############### Probe GCode Generation ###################################################################### # ############################################################################################################# @@ -1925,6 +2006,18 @@ class LevelUI: param_grid = GLay(v_spacing=5, h_spacing=3) tp_frame.setLayout(param_grid) + # Probe Diameter + self.probe_tip_dia_label = FCLabel('%s:' % _("Probe Tip Dia")) + self.probe_tip_dia_label.setToolTip( + _("The probe tip diameter.") + ) + self.probe_tip_dia_entry = FCDoubleSpinner() + self.probe_tip_dia_entry.set_precision(self.decimals) + self.probe_tip_dia_entry.set_range(0.0000, 10.0000) + + param_grid.addWidget(self.probe_tip_dia_label, 0, 0) + param_grid.addWidget(self.probe_tip_dia_entry, 0, 1) + # Travel Z Probe self.ptravelz_label = FCLabel('%s:' % _("Probe Z travel")) self.ptravelz_label.setToolTip( @@ -1934,8 +2027,8 @@ class LevelUI: self.ptravelz_entry.set_precision(self.decimals) self.ptravelz_entry.set_range(0.0000, 10000.0000) - param_grid.addWidget(self.ptravelz_label, 0, 0) - param_grid.addWidget(self.ptravelz_entry, 0, 1) + param_grid.addWidget(self.ptravelz_label, 2, 0) + param_grid.addWidget(self.ptravelz_entry, 2, 1) # Probe depth self.pdepth_label = FCLabel('%s:' % _("Probe Z depth")) @@ -1947,8 +2040,8 @@ class LevelUI: self.pdepth_entry.set_precision(self.decimals) self.pdepth_entry.set_range(-910000.0000, 0.0000) - param_grid.addWidget(self.pdepth_label, 2, 0) - param_grid.addWidget(self.pdepth_entry, 2, 1) + param_grid.addWidget(self.pdepth_label, 4, 0) + param_grid.addWidget(self.pdepth_entry, 4, 1) # Probe feedrate self.feedrate_probe_label = FCLabel('%s:' % _("Probe Feedrate")) @@ -1959,13 +2052,13 @@ class LevelUI: self.feedrate_probe_entry.set_precision(self.decimals) self.feedrate_probe_entry.set_range(0, 910000.0000) - param_grid.addWidget(self.feedrate_probe_label, 4, 0) - param_grid.addWidget(self.feedrate_probe_entry, 4, 1) + param_grid.addWidget(self.feedrate_probe_label, 6, 0) + param_grid.addWidget(self.feedrate_probe_entry, 6, 1) separator_line = QtWidgets.QFrame() separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) - param_grid.addWidget(separator_line, 6, 0, 1, 2) + param_grid.addWidget(separator_line, 8, 0, 1, 2) # AUTOLEVELL MODE al_mode_lbl = FCLabel('%s' % _("Mode"), bold=True) @@ -1978,8 +2071,8 @@ class LevelUI: {'label': _('Manual'), 'value': 'manual'}, {'label': _('Grid'), 'value': 'grid'} ]) - param_grid.addWidget(al_mode_lbl, 8, 0) - param_grid.addWidget(self.al_mode_radio, 8, 1) + param_grid.addWidget(al_mode_lbl, 10, 0) + param_grid.addWidget(self.al_mode_radio, 10, 1) # AUTOLEVELL METHOD self.al_method_lbl = FCLabel('%s:' % _("Method")) @@ -1996,8 +2089,8 @@ class LevelUI: self.al_method_radio.setDisabled(True) self.al_method_radio.set_value('v') - param_grid.addWidget(self.al_method_lbl, 10, 0) - param_grid.addWidget(self.al_method_radio, 10, 1) + param_grid.addWidget(self.al_method_lbl, 12, 0) + param_grid.addWidget(self.al_method_radio, 12, 1) # ## Columns self.al_columns_entry = FCSpinner() @@ -2007,8 +2100,8 @@ class LevelUI: self.al_columns_label.setToolTip( _("The number of grid columns.") ) - param_grid.addWidget(self.al_columns_label, 12, 0) - param_grid.addWidget(self.al_columns_entry, 12, 1) + param_grid.addWidget(self.al_columns_label, 14, 0) + param_grid.addWidget(self.al_columns_entry, 14, 1) # ## Rows self.al_rows_entry = FCSpinner() @@ -2018,8 +2111,8 @@ class LevelUI: self.al_rows_label.setToolTip( _("The number of grid rows.") ) - param_grid.addWidget(self.al_rows_label, 14, 0) - param_grid.addWidget(self.al_rows_entry, 14, 1) + param_grid.addWidget(self.al_rows_label, 16, 0) + param_grid.addWidget(self.al_rows_entry, 16, 1) self.al_add_button = FCButton(_("Add Probe Points")) self.al_box.addWidget(self.al_add_button) @@ -2409,6 +2502,8 @@ class LevelUI: # ############################ FINISHED GUI ################################### # ############################################################################# + GLay.set_common_column_size([tool_grid, param_grid], 0) + self.plot_probing_pts_cb.stateChanged.connect(self.on_plot_points_changed) self.avoid_exc_holes_cb.stateChanged.connect(self.on_avoid_exc_holes_changed) diff --git a/defaults.py b/defaults.py index 4043184e..cb585b2a 100644 --- a/defaults.py +++ b/defaults.py @@ -497,14 +497,16 @@ class AppDefaults: # Autolevelling Plugin "tools_al_plot_points": False, "tools_al_avoid_exc_holes": False, + "tools_al_avoid_exc_holes_size": 0.5, "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_travel_z": 2.0, "tools_al_probe_depth": -1.0, "tools_al_probe_fr": 120, + "tools_al_probe_tip_dia": 0.3, "tools_al_controller": 'MACH3', "tools_al_grbl_jog_step": 5, "tools_al_grbl_jog_fr": 1500,