diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6867be4d..8553b6af 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,8 @@ CHANGELOG for FlatCAM beta
- in Paint, NCC and Cutout Plugins when using a mode that require to be terminated (by mouse RMB or ESC key) the notebook UI element is disabled until this is done
- for Transform and SolderPaste Plugins upgraded the UI
- in SolderPaste Plugin now the paste is dispensed only on the pads/Gerber flashes
+- fixed Find Optimal Plugin: there was an error triggered after finding the minimum distance
+- upgraded the FInd Optimal Plugin UI
1.10.2021
diff --git a/appPlugins/ToolCorners.py b/appPlugins/ToolCorners.py
index 04fe8804..6e2db9a9 100644
--- a/appPlugins/ToolCorners.py
+++ b/appPlugins/ToolCorners.py
@@ -850,7 +850,7 @@ class CornersUI:
self.tools_frame.setLayout(self.tools_box)
# #############################################################################################################
- # Gerber Source Object Frame
+ # Gerber Source Object
# #############################################################################################################
# Gerber object #
@@ -881,9 +881,8 @@ class CornersUI:
par_frame = FCFrame()
self.tools_box.addWidget(par_frame)
- # ## Grid Layout
- par_grid = FCGridLayout(v_spacing=5, h_spacing=3)
- par_frame.setLayout(par_grid)
+ param_grid = FCGridLayout(v_spacing=5, h_spacing=3)
+ par_frame.setLayout(param_grid)
# Type of Marker
self.type_label = FCLabel('%s:' % _("Type"))
@@ -896,8 +895,8 @@ class CornersUI:
{"label": _("Cross"), "value": "c"},
])
- par_grid.addWidget(self.type_label, 2, 0)
- par_grid.addWidget(self.type_radio, 2, 1)
+ param_grid.addWidget(self.type_label, 2, 0)
+ param_grid.addWidget(self.type_radio, 2, 1)
# Thickness #
self.thick_label = FCLabel('%s:' % _("Thickness"))
@@ -910,8 +909,8 @@ class CornersUI:
self.thick_entry.setWrapping(True)
self.thick_entry.setSingleStep(10 ** -self.decimals)
- par_grid.addWidget(self.thick_label, 4, 0)
- par_grid.addWidget(self.thick_entry, 4, 1)
+ param_grid.addWidget(self.thick_label, 4, 0)
+ param_grid.addWidget(self.thick_entry, 4, 1)
# Length #
self.l_label = FCLabel('%s:' % _("Length"))
@@ -923,8 +922,8 @@ class CornersUI:
self.l_entry.set_precision(self.decimals)
self.l_entry.setSingleStep(10 ** -self.decimals)
- par_grid.addWidget(self.l_label, 6, 0)
- par_grid.addWidget(self.l_entry, 6, 1)
+ param_grid.addWidget(self.l_label, 6, 0)
+ param_grid.addWidget(self.l_entry, 6, 1)
# Margin #
self.margin_label = FCLabel('%s:' % _("Margin"))
@@ -936,8 +935,8 @@ class CornersUI:
self.margin_entry.set_precision(self.decimals)
self.margin_entry.setSingleStep(0.1)
- par_grid.addWidget(self.margin_label, 8, 0)
- par_grid.addWidget(self.margin_entry, 8, 1)
+ param_grid.addWidget(self.margin_label, 8, 0)
+ param_grid.addWidget(self.margin_entry, 8, 1)
# #############################################################################################################
# Locations Frame
@@ -1050,7 +1049,7 @@ class CornersUI:
grid_drill.addWidget(self.drill_dia_label, 0, 0)
grid_drill.addWidget(self.drill_dia_entry, 0, 1)
- FCGridLayout.set_common_column_size([grid_sel, par_grid, grid_loc, grid_drill], 0)
+ FCGridLayout.set_common_column_size([grid_sel, param_grid, grid_loc, grid_drill], 0)
# ## Create an Excellon object
self.drill_button = FCButton(_("Create Excellon Object"))
diff --git a/appPlugins/ToolOptimal.py b/appPlugins/ToolOptimal.py
index b7ca8226..7de150a6 100644
--- a/appPlugins/ToolOptimal.py
+++ b/appPlugins/ToolOptimal.py
@@ -9,7 +9,7 @@ from PyQt6 import QtWidgets, QtCore, QtGui
from appTool import AppTool
from appGUI.GUIElements import OptionalHideInputSection, FCTextArea, FCEntry, FCSpinner, FCCheckBox, FCComboBox, \
- FCLabel, FCButton, VerticalScrollArea, FCGridLayout
+ FCLabel, FCButton, VerticalScrollArea, FCGridLayout, FCFrame
from camlib import grace
from shapely.geometry import MultiPolygon
@@ -129,10 +129,57 @@ class ToolOptimal(AppTool):
self.ui.reset_button.clicked.connect(self.set_tool_ui)
+ def disconnect_signals(self):
+ try:
+ self.update_text.disconnect(self.on_update_text)
+ except (TypeError, AttributeError):
+ pass
+
+ try:
+ self.update_sec_distances.disconnect(self.on_update_sec_distances_txt)
+ except (TypeError, AttributeError):
+ pass
+
+ try:
+ self.ui.calculate_button.clicked.disconnect(self.find_minimum_distance)
+ except (TypeError, AttributeError):
+ pass
+
+ try:
+ self.ui.locate_button.clicked.disconnect(self.on_locate_position)
+ except (TypeError, AttributeError):
+ pass
+
+ try:
+ self.ui.locations_textb.cursorPositionChanged.disconnect(self.on_textbox_clicked)
+ except (TypeError, AttributeError):
+ pass
+
+ try:
+ self.ui.locate_sec_button.clicked.disconnect(self.on_locate_sec_position)
+ except (TypeError, AttributeError):
+ pass
+
+ try:
+ self.ui.distances_textb.cursorPositionChanged.disconnect(self.on_distances_textb_clicked)
+ except (TypeError, AttributeError):
+ pass
+
+ try:
+ self.ui.locations_sec_textb.cursorPositionChanged.disconnect(self.on_locations_sec_clicked)
+ except (TypeError, AttributeError):
+ pass
+
+ try:
+ self.ui.reset_button.clicked.disconnect(self.set_tool_ui)
+ except (TypeError, AttributeError):
+ pass
+
def set_tool_ui(self):
self.clear_ui(self.layout)
self.ui = OptimalUI(layout=self.layout, app=self.app)
self.pluginName = self.ui.pluginName
+ self.disconnect_signals()
self.connect_signals_at_init()
self.ui.result_entry.set_value(0.0)
@@ -182,7 +229,7 @@ class ToolOptimal(AppTool):
proc = self.app.proc_container.new('%s...' % _("Working"))
- def job_thread(app_obj):
+ def job_thread(app_obj, plugin_instance):
app_obj.inform.emit(_("Optimal Tool. Started to search for the minimum distance between copper features."))
try:
old_disp_number = 0
@@ -221,28 +268,28 @@ class ToolOptimal(AppTool):
'%s: %s' % (_("Optimal Tool. Finding the distances between each two elements. Iterations"),
str(geo_len)))
- self.min_dict = {}
+ plugin_instance.min_dict = {}
idx = 1
- for geo in total_geo:
- for s_geo in total_geo[idx:]:
- if self.app.abort_flag:
+ for geo in total_geo.geoms:
+ for s_geo in total_geo.geoms[idx:]:
+ if app_obj.abort_flag:
# graceful abort requested by the user
raise grace
# minimize the number of distances by not taking into considerations those that are too small
dist = geo.distance(s_geo)
- dist = float('%.*f' % (self.decimals, dist))
+ dist = app_obj.dec_format(dist, plugin_instance.decimals)
loc_1, loc_2 = nearest_points(geo, s_geo)
proc_loc = (
- (float('%.*f' % (self.decimals, loc_1.x)), float('%.*f' % (self.decimals, loc_1.y))),
- (float('%.*f' % (self.decimals, loc_2.x)), float('%.*f' % (self.decimals, loc_2.y)))
+ (app_obj.dec_format(loc_1.x, self.decimals), app_obj.dec_format(loc_1.y, self.decimals)),
+ (app_obj.dec_format(loc_2.x, self.decimals), app_obj.dec_format(loc_2.y, self.decimals))
)
- if dist in self.min_dict:
- self.min_dict[dist].append(proc_loc)
+ if dist in plugin_instance.min_dict:
+ plugin_instance.min_dict[dist].append(proc_loc)
else:
- self.min_dict[dist] = [proc_loc]
+ plugin_instance.min_dict[dist] = [proc_loc]
pol_nr += 1
disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
@@ -254,29 +301,29 @@ class ToolOptimal(AppTool):
app_obj.inform.emit(_("Optimal Tool. Finding the minimum distance."))
- min_list = list(self.min_dict.keys())
+ min_list = list(plugin_instance.min_dict.keys())
min_dist = min(min_list)
- min_dist -= 10**-self.decimals # make sure that this will work for isolation case
- min_dist_string = '%.*f' % (self.decimals, float(min_dist))
- self.ui.result_entry.set_value(min_dist_string)
+ rep_min_dist = min_dist - 10**-self.decimals # make sure that this will work for isolation case
+ min_dist_string = str(app_obj.dec_format(rep_min_dist, self.decimals))
+ plugin_instance.ui.result_entry.set_value(min_dist_string)
- freq = len(self.min_dict[min_dist])
+ freq = len(plugin_instance.min_dict[min_dist])
freq = '%d' % int(freq)
- self.ui.freq_entry.set_value(freq)
+ plugin_instance.ui.freq_entry.set_value(freq)
- min_locations = self.min_dict.pop(min_dist)
+ min_locations = plugin_instance.min_dict.pop(min_dist)
self.update_text.emit(min_locations)
- self.update_sec_distances.emit(self.min_dict)
+ self.update_sec_distances.emit(plugin_instance.min_dict)
app_obj.inform.emit('[success] %s' % _("Optimal Tool. Finished successfully."))
except Exception as ee:
proc.done()
- log.error(str(ee))
+ app_obj.log.error(str(ee))
return
proc.done()
- self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
+ self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app, self]})
def on_locate_position(self):
# cursor = self.locations_textb.textCursor()
@@ -443,11 +490,16 @@ class OptimalUI:
}
""")
self.layout.addWidget(title_label)
- self.layout.addWidget(FCLabel(""))
+ # self.layout.addWidget(FCLabel(""))
- # ## Grid Layout
- grid0 = FCGridLayout(v_spacing=5, h_spacing=3)
- self.layout.addLayout(grid0)
+ # #############################################################################################################
+ # Gerber Source Object
+ # #############################################################################################################
+ self.obj_combo_label = FCLabel('%s' % _("Source Object"))
+ self.obj_combo_label.setToolTip(
+ "Gerber object for which to find the minimum distance between copper features."
+ )
+ self.layout.addWidget(self.obj_combo_label)
# ## Gerber Object to mirror
self.gerber_object_combo = FCComboBox()
@@ -460,13 +512,25 @@ class OptimalUI:
self.gerber_object_label.setToolTip(
"Gerber object for which to find the minimum distance between copper features."
)
- grid0.addWidget(self.gerber_object_label, 0, 0, 1, 2)
- grid0.addWidget(self.gerber_object_combo, 2, 0, 1, 2)
+ self.layout.addWidget(self.gerber_object_combo)
- separator_line = QtWidgets.QFrame()
- separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
- separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
- grid0.addWidget(separator_line, 4, 0, 1, 2)
+ # separator_line = QtWidgets.QFrame()
+ # separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
+ # separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
+ # grid0.addWidget(separator_line, 4, 0, 1, 2)
+
+ # #############################################################################################################
+ # Parameters Frame
+ # #############################################################################################################
+ self.param_label = FCLabel('%s' % _('Parameters'))
+ self.param_label.setToolTip(_("Parameters used for this tool."))
+ self.layout.addWidget(self.param_label)
+
+ par_frame = FCFrame()
+ self.layout.addWidget(par_frame)
+
+ param_grid = FCGridLayout(v_spacing=5, h_spacing=3)
+ par_frame.setLayout(param_grid)
# Precision = nr of decimals
self.precision_label = FCLabel('%s:' % _("Precision"))
@@ -475,13 +539,21 @@ class OptimalUI:
self.precision_spinner = FCSpinner(callback=self.confirmation_message_int)
self.precision_spinner.set_range(2, 10)
self.precision_spinner.setWrapping(True)
- grid0.addWidget(self.precision_label, 6, 0)
- grid0.addWidget(self.precision_spinner, 6, 1)
+ param_grid.addWidget(self.precision_label, 0, 0)
+ param_grid.addWidget(self.precision_spinner, 0, 1)
- # Results Title
- self.title_res_label = FCLabel('%s:' % _("Minimum distance"))
- self.title_res_label.setToolTip(_("Display minimum distance between copper features."))
- grid0.addWidget(self.title_res_label, 8, 0, 1, 2)
+ # #############################################################################################################
+ # Results Frame
+ # #############################################################################################################
+ res_label = FCLabel('%s' % _("Minimum distance"))
+ res_label.setToolTip(_("Display minimum distance between copper features."))
+ self.layout.addWidget(res_label)
+
+ res_frame = FCFrame()
+ self.layout.addWidget(res_frame)
+
+ res_grid = FCGridLayout(v_spacing=5, h_spacing=3, c_stretch=[0, 1, 0])
+ res_frame.setLayout(res_grid)
# Result value
self.result_label = FCLabel('%s:' % _("Determined"))
@@ -491,25 +563,23 @@ class OptimalUI:
self.units_lbl = FCLabel(self.units.lower())
self.units_lbl.setDisabled(True)
- hlay = QtWidgets.QHBoxLayout()
- hlay.addWidget(self.result_entry)
- hlay.addWidget(self.units_lbl)
-
- grid0.addWidget(self.units_lbl, 10, 0)
- grid0.addLayout(hlay, 10, 1)
+ res_grid.addWidget(self.result_label, 0, 0)
+ res_grid.addWidget(self.result_entry, 0, 1)
+ res_grid.addWidget(self.units_lbl, 0, 2)
# Frequency of minimum encounter
self.freq_label = FCLabel('%s:' % _("Occurring"))
self.freq_label.setToolTip(_("How many times this minimum is found."))
self.freq_entry = FCEntry()
self.freq_entry.setReadOnly(True)
- grid0.addWidget(self.freq_label, 12, 0)
- grid0.addWidget(self.freq_entry, 12, 1)
+
+ res_grid.addWidget(self.freq_label, 2, 0)
+ res_grid.addWidget(self.freq_entry, 2, 1, 1, 2)
# Control if to display the locations of where the minimum was found
self.locations_cb = FCCheckBox(_("Minimum points coordinates"))
self.locations_cb.setToolTip(_("Coordinates for points where minimum distance was found."))
- grid0.addWidget(self.locations_cb, 14, 0, 1, 2)
+ res_grid.addWidget(self.locations_cb, 4, 0, 1, 3)
# Locations where minimum was found
self.locations_textb = FCTextArea()
@@ -524,7 +594,7 @@ class OptimalUI:
"""
self.locations_textb.setStyleSheet(stylesheet)
- grid0.addWidget(self.locations_textb, 16, 0, 1, 2)
+ res_grid.addWidget(self.locations_textb, 6, 0, 1, 3)
# Jump button
self.locate_button = FCButton(_("Jump to selected position"))
@@ -534,24 +604,33 @@ class OptimalUI:
)
self.locate_button.setMinimumWidth(60)
self.locate_button.setDisabled(True)
- grid0.addWidget(self.locate_button, 18, 0, 1, 2)
+ res_grid.addWidget(self.locate_button, 8, 0, 1, 3)
- # Other distances in Gerber
- self.title_second_res_label = FCLabel('%s:' % _("Other distances"))
+ # #############################################################################################################
+ # Other Distances
+ # #############################################################################################################
+ self.title_second_res_label = FCLabel('%s' % _("Other distances"))
self.title_second_res_label.setToolTip(_("Will display other distances in the Gerber file ordered from\n"
"the minimum to the maximum, not including the absolute minimum."))
- grid0.addWidget(self.title_second_res_label, 20, 0, 1, 2)
+ self.layout.addWidget(self.title_second_res_label)
+
+ other_frame = FCFrame()
+ self.layout.addWidget(other_frame)
+
+ other_grid = FCGridLayout(v_spacing=5, h_spacing=3)
+ other_frame.setLayout(other_grid)
# Control if to display the locations of where the minimum was found
self.sec_locations_cb = FCCheckBox(_("Other distances points coordinates"))
self.sec_locations_cb.setToolTip(_("Other distances and the coordinates for points\n"
"where the distance was found."))
- grid0.addWidget(self.sec_locations_cb, 22, 0, 1, 2)
+ other_grid.addWidget(self.sec_locations_cb, 0, 0, 1, 2)
# this way I can hide/show the frame
self.sec_locations_frame = QtWidgets.QFrame()
self.sec_locations_frame.setContentsMargins(0, 0, 0, 0)
- self.layout.addWidget(self.sec_locations_frame)
+ other_grid.addWidget(self.sec_locations_frame, 2, 0, 1, 2)
+
self.distances_box = QtWidgets.QVBoxLayout()
self.distances_box.setContentsMargins(0, 0, 0, 0)
self.sec_locations_frame.setLayout(self.distances_box)
@@ -629,6 +708,8 @@ class OptimalUI:
self.calculate_button.setMinimumWidth(60)
self.layout.addWidget(self.calculate_button)
+ FCGridLayout.set_common_column_size([param_grid, res_grid], 0)
+
self.layout.addStretch(1)
# ## Reset Tool