diff --git a/CHANGELOG.md b/CHANGELOG.md
index 510fec3b..882c08bd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,12 @@ CHANGELOG for FlatCAM beta
=================================================
+2.12.2020
+
+- Subtraction Tool - minor changes
+- in Gerber Editor - added some parameters to the UI: selected polygon coordinates and vertexes number and also added polygon simplification
+- in Gerber Editor - fixed update of Aperture Table rows selection on multiple shapes selection
+
1.12.2020
- in Milling Tool fixed the UI change as a result of preprocessor change
diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py
index 215a3c38..7cb84b9b 100644
--- a/appEditors/AppGerberEditor.py
+++ b/appEditors/AppGerberEditor.py
@@ -19,7 +19,7 @@ import logging
from camlib import distance, arc, three_point_circle
from appGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, FCSpinner, RadioSet, EvalEntry2, \
- FCInputDoubleSpinner, FCButton, OptionalInputSection, FCCheckBox, NumericalEvalTupleEntry, FCLabel
+ FCInputDoubleSpinner, FCButton, OptionalInputSection, FCCheckBox, NumericalEvalTupleEntry, FCLabel, FCTextEdit
from appTool import AppTool
import numpy as np
@@ -2561,7 +2561,7 @@ class SelectEditorGrb(QtCore.QObject, DrawTool):
self.draw_app.bend_mode = 1
# here store the selected apertures
- self.sel_aperture = []
+ self.sel_aperture = set()
# multiprocessing results
self.results = []
@@ -2666,6 +2666,8 @@ class SelectEditorGrb(QtCore.QObject, DrawTool):
# add the object to the selected shapes
editor_obj.selected.append(shape_stored)
+ self.draw_app.update_ui_sig.emit()
+
editor_obj.plot_object.emit(None)
self.draw_app.app.worker_task.emit({'fcn': job_thread, 'params': [self.draw_app]})
@@ -2691,23 +2693,20 @@ class SelectEditorGrb(QtCore.QObject, DrawTool):
except Exception as e:
log.debug("AppGerberEditor.SelectEditorGrb.click_release() --> %s" % str(e))
- brake_flag = False
for shape_s in self.draw_app.selected:
for storage in self.draw_app.storage_dict:
if shape_s in self.draw_app.storage_dict[storage]['geometry']:
- self.sel_aperture.append(storage)
- brake_flag = True
- break
- if brake_flag is True:
- break
+ self.sel_aperture.add(storage)
# actual row selection is done here
+ self.draw_app.ui.apertures_table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
for aper in self.sel_aperture:
for row in range(self.draw_app.ui.apertures_table.rowCount()):
if str(aper) == self.draw_app.ui.apertures_table.item(row, 1).text():
- if not self.draw_app.ui.apertures_table.item(row, 0).isSelected():
+ if row not in set(idx.row() for idx in self.draw_app.ui.apertures_table.selectedIndexes()):
self.draw_app.ui.apertures_table.selectRow(row)
self.draw_app.last_aperture_selected = aper
+ self.draw_app.ui.apertures_table.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
# reconnect signal when clicking in the table
self.draw_app.ui.apertures_table.cellPressed.connect(self.draw_app.on_row_selected)
@@ -2742,7 +2741,8 @@ class AppGerberEditor(QtCore.QObject):
draw_shape_idx = -1
# plot_finished = QtCore.pyqtSignal()
mp_finished = QtCore.pyqtSignal(list)
-
+ build_ui_sig = QtCore.pyqtSignal()
+ update_ui_sig = QtCore.pyqtSignal()
plot_object = QtCore.pyqtSignal(object)
def __init__(self, app):
@@ -2896,6 +2896,8 @@ class AppGerberEditor(QtCore.QObject):
# #############################################################################################################
self.app.pool_recreated.connect(self.pool_recreated)
self.mp_finished.connect(self.on_multiprocessing_finished)
+ self.build_ui_sig.connect(self.build_ui)
+ self.update_ui_sig.connect(self.update_ui)
# connect the toolbar signals
self.connect_grb_toolbar_signals()
@@ -2939,6 +2941,8 @@ class AppGerberEditor(QtCore.QObject):
self.ui.array_type_radio.activated_custom.connect(self.on_array_type_radio)
self.ui.pad_axis_radio.activated_custom.connect(self.on_linear_angle_radio)
+ self.ui.simplification_btn.clicked.connect(self.on_simplification_click)
+
self.ui.exit_editor_button.clicked.connect(lambda: self.app.editor2object())
self.conversion_factor = 1
@@ -3035,6 +3039,11 @@ class AppGerberEditor(QtCore.QObject):
self.ui.pad_direction_radio.set_value(self.app.defaults["gerber_editor_circ_dir"])
self.ui.pad_angle_entry.set_value(float(self.app.defaults["gerber_editor_circ_angle"]))
+ self.ui.geo_coords_entry.setText('')
+ self.ui.geo_vertex_entry.set_value(0.0)
+ self.ui.geo_tol_entry.set_value(0.01)
+ self.ui.geo_zoom.set_value(False)
+
def build_ui(self, first_run=None):
try:
@@ -3527,6 +3536,120 @@ class AppGerberEditor(QtCore.QObject):
self.ui.apertures_table.itemChanged.connect(self.on_tool_edit)
# self.ui.apertures_table.cellPressed.connect(self.on_row_selected)
+ def on_simplification_click(self):
+ self.app.log.debug("AppGrbEditor.on_simplification_click()")
+
+ selected_shapes = []
+ selected_shapes_geos = []
+ tol = self.ui.geo_tol_entry.get_value()
+
+ def task_job():
+ with self.app.proc_container.new('%s...' % _("Simplify")):
+ for obj_shape in self.selected:
+ try:
+ selected_shapes.append(obj_shape)
+
+ new_geo = {
+ 'apid': '',
+ 'geo': {}
+ }
+
+ # find the aperture where the shape is stored
+ current_apid = None
+ for apid in self.storage_dict:
+ if obj_shape in self.storage_dict[apid]['geometry']:
+ current_apid = apid
+ break
+
+ if current_apid is None:
+ current_apid = self.last_aperture_selected
+
+ new_geo['apid'] = deepcopy(current_apid)
+ print(current_apid)
+
+ if 'solid' in obj_shape.geo:
+ new_geo['geo']['solid'] = obj_shape.geo['solid'].simplify(tolerance=tol)
+ if 'follow' in obj_shape.geo:
+ new_geo['geo']['follow'] = obj_shape.geo['follow'].simplify(tolerance=tol)
+ if 'clear' in obj_shape.geo:
+ new_geo['geo']['clear'] = obj_shape.geo['clear'].simplify(tolerance=tol)
+
+ selected_shapes_geos.append(deepcopy(new_geo))
+ except ValueError:
+ pass
+
+ for shape in selected_shapes:
+ self.delete_shape(geo_el=shape)
+
+ for geo in selected_shapes_geos:
+ stora = self.storage_dict[geo['apid']]['geometry']
+ geo_el = geo['geo']
+ self.add_gerber_shape(DrawToolShape(geo_el), storage=stora)
+
+ self.plot_all()
+
+ self.app.worker_task.emit({'fcn': task_job, 'params': []})
+
+ def update_ui(self):
+ if not self.selected:
+ self.ui.geo_coords_entry.setText('')
+ self.ui.geo_vertex_entry.set_value(0)
+ return
+
+ last_sel_geo = self.selected[-1].geo
+ last_sel_geo_solid = last_sel_geo['solid']
+
+ if self.ui.geo_zoom.get_value():
+ xmin, ymin, xmax, ymax = last_sel_geo_solid.bounds
+ if xmin == xmax and ymin != ymax:
+ xmin = ymin
+ xmax = ymax
+ elif xmin != xmax and ymin == ymax:
+ ymin = xmin
+ ymax = xmax
+
+ if self.app.is_legacy is False:
+ rect = Rect(xmin, ymin, xmax, ymax)
+ rect.left, rect.right = xmin, xmax
+ rect.bottom, rect.top = ymin, ymax
+ # Lock updates in other threads
+ self.shapes.lock_updates()
+
+ # adjust the view camera to be slightly bigger than the bounds so the shape collection can be
+ # seen clearly otherwise the shape collection boundary will have no border
+ dx = rect.right - rect.left
+ dy = rect.top - rect.bottom
+ x_factor = dx * 0.02
+ y_factor = dy * 0.02
+
+ rect.left -= x_factor
+ rect.bottom -= y_factor
+ rect.right += x_factor
+ rect.top += y_factor
+
+ self.app.plotcanvas.view.camera.rect = rect
+ self.shapes.unlock_updates()
+ else:
+ width = xmax - xmin
+ height = ymax - ymin
+ xmin -= 0.05 * width
+ xmax += 0.05 * width
+ ymin -= 0.05 * height
+ ymax += 0.05 * height
+ self.app.plotcanvas.adjust_axes(xmin, ymin, xmax, ymax)
+
+ if last_sel_geo_solid.geom_type == 'Polygon':
+ coords = list(last_sel_geo_solid.exterior.coords)
+ vertex_nr = len(coords)
+ elif last_sel_geo_solid.geom_type in ['LinearRing', 'LineString']:
+ coords = list(last_sel_geo_solid.coords)
+ vertex_nr = len(coords)
+ else:
+ return
+
+ self.ui.geo_coords_entry.setText(str(coords))
+ self.ui.geo_vertex_entry.set_value(vertex_nr)
+
def on_name_activate(self):
self.edited_obj_name = self.ui.name_entry.get_value()
@@ -3652,7 +3775,7 @@ class AppGerberEditor(QtCore.QObject):
# don't ask why but if there is nothing connected I've seen issues
self.mp = self.canvas.graph_event_connect('mouse_press', self.on_canvas_click)
self.mm = self.canvas.graph_event_connect('mouse_move', self.on_canvas_move)
- self.mr = self.canvas.graph_event_connect('mouse_release', self.on_grb_click_release)
+ self.mr = self.canvas.graph_event_connect('mouse_release', self.on_canvas_click_release)
if self.app.is_legacy is False:
self.canvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
@@ -3704,7 +3827,7 @@ class AppGerberEditor(QtCore.QObject):
if self.app.is_legacy is False:
self.canvas.graph_event_disconnect('mouse_press', self.on_canvas_click)
self.canvas.graph_event_disconnect('mouse_move', self.on_canvas_move)
- self.canvas.graph_event_disconnect('mouse_release', self.on_grb_click_release)
+ self.canvas.graph_event_disconnect('mouse_release', self.on_canvas_click_release)
else:
self.canvas.graph_event_disconnect(self.mp)
self.canvas.graph_event_disconnect(self.mm)
@@ -4057,7 +4180,7 @@ class AppGerberEditor(QtCore.QObject):
else:
storage_dict[k] = aperture_dict[k]
except Exception as e:
- self.app.log.debug("AppGerberEditor.edit_fcgerber().job_thread() --> %s" % str(e))
+ log.debug("AppGerberEditor.edit_fcgerber().job_thread() --> %s" % str(e))
return [aperture_id, storage_dict]
@@ -4439,7 +4562,7 @@ class AppGerberEditor(QtCore.QObject):
else:
self.app.log.debug("No active tool to respond to click!")
- def on_grb_click_release(self, event):
+ def on_canvas_click_release(self, event):
self.modifiers = QtWidgets.QApplication.keyboardModifiers()
if self.app.is_legacy is False:
event_pos = event.pos
@@ -4558,11 +4681,14 @@ class AppGerberEditor(QtCore.QObject):
self.selected.append(obj)
sel_aperture.add(storage)
+ # #############################################################################################################
+ # ########## select the aperture code of the selected geometry, in the tool table ###########################
+ # #############################################################################################################
try:
self.ui.apertures_table.cellPressed.disconnect()
except Exception as e:
self.app.log.debug("AppGerberEditor.draw_selection_Area_handler() --> %s" % str(e))
- # select the aperture code of the selected geometry, in the tool table
+
self.ui.apertures_table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
for aper in sel_aperture:
for row_to_sel in range(self.ui.apertures_table.rowCount()):
@@ -5369,6 +5495,101 @@ class AppGerberEditorUI:
hlay_ad.addWidget(self.addaperture_btn)
hlay_ad.addWidget(self.delaperture_btn)
+ # #############################################################################################################
+ # ############################################ Shape Properties ###############################################
+ # #############################################################################################################
+ self.shape_frame = QtWidgets.QFrame()
+ self.shape_frame.setContentsMargins(0, 0, 0, 0)
+ self.custom_box.addWidget(self.shape_frame)
+
+ self.shape_grid = QtWidgets.QGridLayout()
+ self.shape_grid.setColumnStretch(0, 0)
+ self.shape_grid.setColumnStretch(1, 1)
+ self.shape_grid.setContentsMargins(0, 0, 0, 0)
+ self.shape_frame.setLayout(self.shape_grid)
+
+ # Zoom Selection
+ self.geo_zoom = FCCheckBox(_("Zoom on selection"))
+ self.shape_grid.addWidget(self.geo_zoom, 0, 0, 1, 3)
+
+ separator_line = QtWidgets.QFrame()
+ separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+ separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.shape_grid.addWidget(separator_line, 2, 0, 1, 3)
+
+ # Parameters Title
+ param_title = FCLabel('%s' % _("Parameters"))
+ param_title.setToolTip(
+ _("Geometry parameters.")
+ )
+ self.shape_grid.addWidget(param_title, 4, 0, 1, 3)
+
+ # Coordinates
+ coords_lbl = FCLabel('%s:' % _("Coordinates"))
+ coords_lbl.setToolTip(
+ _("The coordinates of the selected geometry element.")
+ )
+ self.shape_grid.addWidget(coords_lbl, 6, 0, 1, 3)
+
+ self.geo_coords_entry = FCTextEdit()
+ self.geo_coords_entry.setPlaceholderText(
+ _("The coordinates of the selected geometry element.")
+ )
+ self.shape_grid.addWidget(self.geo_coords_entry, 8, 0, 1, 3)
+
+ # Vertex Points Number
+ vertex_lbl = FCLabel('%s:' % _("Vertex Points"))
+ vertex_lbl.setToolTip(
+ _("The number of vertex points in the selected geometry element.")
+ )
+ self.geo_vertex_entry = FCEntry(decimals=self.decimals)
+ self.geo_vertex_entry.setReadOnly(True)
+
+ self.shape_grid.addWidget(vertex_lbl, 10, 0)
+ self.shape_grid.addWidget(self.geo_vertex_entry, 10, 1, 1, 2)
+
+ separator_line = QtWidgets.QFrame()
+ separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+ separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.shape_grid.addWidget(separator_line, 12, 0, 1, 3)
+
+ # Simplification Title
+ simplif_lbl = FCLabel('%s:' % _("Simplification"))
+ simplif_lbl.setToolTip(
+ _("Simplify a geometry by reducing its vertex points number.")
+ )
+ self.shape_grid.addWidget(simplif_lbl, 14, 0, 1, 3)
+
+ # Simplification Tolerance
+ simplification_tol_lbl = FCLabel('%s:' % _("Tolerance"))
+ simplification_tol_lbl.setToolTip(
+ _("All points in the simplified object will be\n"
+ "within the tolerance distance of the original geometry.")
+ )
+ self.geo_tol_entry = FCDoubleSpinner()
+ self.geo_tol_entry.set_precision(self.decimals)
+ self.geo_tol_entry.setSingleStep(10 ** -self.decimals)
+ self.geo_tol_entry.set_range(0.0000, 10000.0000)
+ self.geo_tol_entry.set_value(10 ** -self.decimals)
+
+ self.shape_grid.addWidget(simplification_tol_lbl, 16, 0)
+ self.shape_grid.addWidget(self.geo_tol_entry, 16, 1, 1, 2)
+
+ # Simplification button
+ self.simplification_btn = FCButton(_("Simplify"))
+ self.simplification_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/simplify32.png'))
+ self.simplification_btn.setToolTip(
+ _("Simplify a geometry element by reducing its vertex points number.")
+ )
+ self.simplification_btn.setStyleSheet("""
+ QPushButton
+ {
+ font-weight: bold;
+ }
+ """)
+
+ self.shape_grid.addWidget(self.simplification_btn, 18, 0, 1, 3)
+
# #############################################################################################################
# ############################################ BUFFER TOOL ####################################################
# #############################################################################################################
diff --git a/appTools/ToolSub.py b/appTools/ToolSub.py
index a95edab9..ff2e58b6 100644
--- a/appTools/ToolSub.py
+++ b/appTools/ToolSub.py
@@ -274,7 +274,7 @@ class ToolSub(AppTool):
:type target_geo: list
:param sub_geometry: the apertures dict that holds all the geometry that is subtracted
:type sub_geometry: dict
- :return: (apid, unaffected_geometry lsit, affected_geometry list)
+ :return: (apid, unaffected_geometry list, affected_geometry list)
:rtype: tuple
"""
@@ -286,28 +286,30 @@ class ToolSub(AppTool):
destination_geo_obj = {}
if "solid" in target_geo_obj:
diff = []
+ target_solid = target_geo_obj["solid"]
for sub_solid_geo in sub_geometry["solid"]:
- if target_geo_obj["solid"].intersects(sub_solid_geo):
- new_geo = target_geo_obj["solid"].difference(sub_solid_geo)
+ if target_solid.intersects(sub_solid_geo) or target_solid.contains(sub_solid_geo):
+ new_geo = target_solid.difference(sub_solid_geo)
if not new_geo.is_empty:
diff.append(new_geo)
solid_is_modified = True
if solid_is_modified:
- target_geo_obj["solid"] = unary_union(diff)
- destination_geo_obj["solid"] = deepcopy(target_geo_obj["solid"])
+ target_solid = unary_union(diff)
+ destination_geo_obj["solid"] = deepcopy(target_solid)
clear_is_modified = False
if "clear" in target_geo_obj:
clear_diff = []
+ target_clear = target_geo_obj["clear"]
for sub_clear_geo in sub_geometry["clear"]:
- if target_geo_obj["clear"].intersects(sub_clear_geo):
- new_geo = target_geo_obj["clear"].difference(sub_clear_geo)
+ if target_clear.intersects(sub_clear_geo) or target_clear.contains(sub_clear_geo):
+ new_geo = target_clear.difference(sub_clear_geo)
if not new_geo.is_empty:
clear_diff.append(new_geo)
clear_is_modified = True
if clear_is_modified:
- target_geo_obj["clear"] = unary_union(clear_diff)
- destination_geo_obj["clear"] = deepcopy(target_geo_obj["clear"])
+ target_clear = unary_union(clear_diff)
+ destination_geo_obj["clear"] = deepcopy(target_clear)
if solid_is_modified or clear_is_modified:
affected_geo.append(deepcopy(destination_geo_obj))