From 1332601624055d80f6e823ebe35840e86e686cf6 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 14 Apr 2019 15:16:37 +0300 Subject: [PATCH] - moved the key handler out of the Measurement tool to flatcamGUI.FlatCAMGui.keyPressEvent() - Gerber Editor: started to add new function of poligonize which should make a filled polygon out of a shape --- README.md | 2 + camlib.py | 61 +++++++++++++++++++++++- flatcamEditors/FlatCAMExcEditor.py | 6 +-- flatcamEditors/FlatCAMGeoEditor.py | 6 +-- flatcamEditors/FlatCAMGrbEditor.py | 75 ++++++++++++++++++++++++++++-- flatcamGUI/FlatCAMGUI.py | 20 ++++++++ flatcamTools/ToolMeasurement.py | 68 ++++++++++----------------- 7 files changed, 185 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index d16f0015..e70c1538 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ CAD program, and create G-Code for Isolation routing. - Editors: activated an old function that was no longer active: each tool can have it's own set of shortcut keys, the Editor general shortcut keys that are letters are overridden - Gerber and Geometry editors, when using the Backspace keys for certain tools, they will backtrack one point but now the utility geometry is immediately updated - In Geometry Editor I fixed bug in Arc modes. Arc mode shortcut key is now key 'M' and arc direction change shortcut key is 'D' +- moved the key handler out of the Measurement tool to flatcamGUI.FlatCAMGui.keyPressEvent() +- Gerber Editor: started to add new function of poligonize which should make a filled polygon out of a shape 13.04.2019 diff --git a/camlib.py b/camlib.py index 20f88c89..ec598154 100644 --- a/camlib.py +++ b/camlib.py @@ -26,10 +26,11 @@ from rtree import index as rtindex from lxml import etree as ET # See: http://toblerity.org/shapely/manual.html + from shapely.geometry import Polygon, LineString, Point, LinearRing, MultiLineString from shapely.geometry import MultiPoint, MultiPolygon from shapely.geometry import box as shply_box -from shapely.ops import cascaded_union, unary_union +from shapely.ops import cascaded_union, unary_union, polygonize import shapely.affinity as affinity from shapely.wkt import loads as sloads from shapely.wkt import dumps as sdumps @@ -45,6 +46,7 @@ import ezdxf # TODO: Commented for FlatCAM packaging with cx_freeze # from scipy.spatial import KDTree, Delaunay +from scipy.spatial import Delaunay from flatcamParsers.ParseSVG import * from flatcamParsers.ParseDXF import * @@ -7348,6 +7350,63 @@ def parse_gerber_number(strnumber, int_digits, frac_digits, zeros): return ret_val +def alpha_shape(points, alpha): + """ + Compute the alpha shape (concave hull) of a set of points. + + @param points: Iterable container of points. + @param alpha: alpha value to influence the gooeyness of the border. Smaller + numbers don't fall inward as much as larger numbers. Too large, + and you lose everything! + """ + if len(points) < 4: + # When you have a triangle, there is no sense in computing an alpha + # shape. + return MultiPoint(list(points)).convex_hull + + def add_edge(edges, edge_points, coords, i, j): + """Add a line between the i-th and j-th points, if not in the list already""" + if (i, j) in edges or (j, i) in edges: + # already added + return + edges.add( (i, j) ) + edge_points.append(coords[ [i, j] ]) + + coords = np.array([point.coords[0] for point in points]) + + tri = Delaunay(coords) + edges = set() + edge_points = [] + # loop over triangles: + # ia, ib, ic = indices of corner points of the triangle + for ia, ib, ic in tri.vertices: + pa = coords[ia] + pb = coords[ib] + pc = coords[ic] + + # Lengths of sides of triangle + a = math.sqrt((pa[0]-pb[0])**2 + (pa[1]-pb[1])**2) + b = math.sqrt((pb[0]-pc[0])**2 + (pb[1]-pc[1])**2) + c = math.sqrt((pc[0]-pa[0])**2 + (pc[1]-pa[1])**2) + + # Semiperimeter of triangle + s = (a + b + c)/2.0 + + # Area of triangle by Heron's formula + area = math.sqrt(s*(s-a)*(s-b)*(s-c)) + circum_r = a*b*c/(4.0*area) + + # Here's the radius filter. + #print circum_r + if circum_r < 1.0/alpha: + add_edge(edges, edge_points, coords, ia, ib) + add_edge(edges, edge_points, coords, ib, ic) + add_edge(edges, edge_points, coords, ic, ia) + + m = MultiLineString(edge_points) + triangles = list(polygonize(m)) + return cascaded_union(triangles), edge_points + # def voronoi(P): # """ # Returns a list of all edges of the voronoi diagram for the given input points. diff --git a/flatcamEditors/FlatCAMExcEditor.py b/flatcamEditors/FlatCAMExcEditor.py index 5dc5c731..2f02b7c6 100644 --- a/flatcamEditors/FlatCAMExcEditor.py +++ b/flatcamEditors/FlatCAMExcEditor.py @@ -1683,12 +1683,12 @@ class FlatCAMExcEditor(QtCore.QObject): self.canvas.vis_connect('mouse_press', self.on_canvas_click) self.canvas.vis_connect('mouse_move', self.on_canvas_move) - self.canvas.vis_connect('mouse_release', self.on_canvas_click_release) + self.canvas.vis_connect('mouse_release', self.on_exc_click_release) def disconnect_canvas_event_handlers(self): self.canvas.vis_disconnect('mouse_press', self.on_canvas_click) self.canvas.vis_disconnect('mouse_move', self.on_canvas_move) - self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release) + self.canvas.vis_disconnect('mouse_release', self.on_exc_click_release) # we restore the key and mouse control to FlatCAMApp method self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot) @@ -2136,7 +2136,7 @@ class FlatCAMExcEditor(QtCore.QObject): else: self.storage.insert(shape) # TODO: Check performance - def on_canvas_click_release(self, event): + def on_exc_click_release(self, event): pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos) self.modifiers = QtWidgets.QApplication.keyboardModifiers() diff --git a/flatcamEditors/FlatCAMGeoEditor.py b/flatcamEditors/FlatCAMGeoEditor.py index 46ffc961..add13229 100644 --- a/flatcamEditors/FlatCAMGeoEditor.py +++ b/flatcamEditors/FlatCAMGeoEditor.py @@ -2973,13 +2973,13 @@ class FlatCAMGeoEditor(QtCore.QObject): self.canvas.vis_connect('mouse_press', self.on_canvas_click) self.canvas.vis_connect('mouse_move', self.on_canvas_move) - self.canvas.vis_connect('mouse_release', self.on_canvas_click_release) + self.canvas.vis_connect('mouse_release', self.on_geo_click_release) def disconnect_canvas_event_handlers(self): self.canvas.vis_disconnect('mouse_press', self.on_canvas_click) self.canvas.vis_disconnect('mouse_move', self.on_canvas_move) - self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release) + self.canvas.vis_disconnect('mouse_release', self.on_geo_click_release) # we restore the key and mouse control to FlatCAMApp method self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot) @@ -3310,7 +3310,7 @@ class FlatCAMGeoEditor(QtCore.QObject): # Update cursor self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20) - def on_canvas_click_release(self, event): + def on_geo_click_release(self, event): pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos) if self.app.grid_status(): diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py index 58d78d3a..16d7b5e2 100644 --- a/flatcamEditors/FlatCAMGrbEditor.py +++ b/flatcamEditors/FlatCAMGrbEditor.py @@ -476,6 +476,73 @@ class FCPadArray(FCShapeTool): self.draw_app.plot_all() +class FCPoligonize(FCShapeTool): + """ + Resulting type: Polygon + """ + + def __init__(self, draw_app): + DrawTool.__init__(self, draw_app) + self.name = 'poligonize' + self.draw_app = draw_app + + self.start_msg = _("Select shape(s) and then click ...") + self.draw_app.in_action = True + self.make() + + def click(self, point): + # self.draw_app.in_action = True + # if self.draw_app.selected: + # self.make() + # else: + # self.draw_app.app.inform.emit(_("[WARNING_NOTCL] No shapes are selected. Select shapes and try again ...")) + + return "" + + def make(self): + geo = [] + + for shape in self.draw_app.selected: + current_storage = self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['solid_geometry'] + print(self.draw_app.active_tool) + aha = [] + if shape.geo: + shape_points = list(shape.geo.exterior.coords) + for pt in shape_points: + aha.append(Point(pt)) + concave_hull, bla_bla = alpha_shape(points=aha, alpha=0.5) + geo.append(concave_hull) + print(geo) + self.geometry = DrawToolShape(geo) + self.draw_app.on_grb_shape_complete(current_storage) + + self.draw_app.in_action = False + self.complete = True + self.draw_app.app.inform.emit(_("[success] Done. Poligonize completed.")) + + self.draw_app.build_ui() + # MS: always return to the Select Tool if modifier key is not pressed + # else return to the current tool + + key_modifier = QtWidgets.QApplication.keyboardModifiers() + if self.draw_app.app.defaults["global_mselect_key"] == 'Control': + modifier_to_use = Qt.ControlModifier + else: + modifier_to_use = Qt.ShiftModifier + # if modifier key is pressed then we add to the selected list the current shape but if it's already + # in the selected list, we removed it. Therefore first click selects, second deselects. + if key_modifier == modifier_to_use: + self.draw_app.select_tool(self.draw_app.active_tool.name) + else: + self.draw_app.select_tool("select") + return + + def clean_up(self): + self.draw_app.selected = [] + self.draw_app.apertures_table.clearSelection() + self.draw_app.plot_all() + + class FCRegion(FCShapeTool): """ Resulting type: Polygon @@ -1259,6 +1326,8 @@ class FlatCAMGrbEditor(QtCore.QObject): "constructor": FCTrack}, "region": {"button": self.app.ui.grb_add_region_btn, "constructor": FCRegion}, + "poligonize": {"button": self.app.ui.grb_convert_poly_btn, + "constructor": FCPoligonize}, "buffer": {"button": self.app.ui.aperture_buffer_btn, "constructor": FCBuffer}, "scale": {"button": self.app.ui.aperture_scale_btn, @@ -1918,12 +1987,12 @@ class FlatCAMGrbEditor(QtCore.QObject): self.canvas.vis_connect('mouse_press', self.on_canvas_click) self.canvas.vis_connect('mouse_move', self.on_canvas_move) - self.canvas.vis_connect('mouse_release', self.on_canvas_click_release) + self.canvas.vis_connect('mouse_release', self.on_grb_click_release) def disconnect_canvas_event_handlers(self): self.canvas.vis_disconnect('mouse_press', self.on_canvas_click) self.canvas.vis_disconnect('mouse_move', self.on_canvas_move) - self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release) + self.canvas.vis_disconnect('mouse_release', self.on_grb_click_release) # we restore the key and mouse control to FlatCAMApp method self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot) @@ -2338,7 +2407,7 @@ class FlatCAMGrbEditor(QtCore.QObject): else: self.app.log.debug("No active tool to respond to click!") - def on_canvas_click_release(self, event): + def on_grb_click_release(self, event): pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos) self.modifiers = QtWidgets.QApplication.keyboardModifiers() diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py index 8e4c038d..583aa31a 100644 --- a/flatcamGUI/FlatCAMGUI.py +++ b/flatcamGUI/FlatCAMGUI.py @@ -690,6 +690,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.add_pad_ar_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/padarray32.png'), _('Add Pad Array')) self.grb_add_track_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/track32.png'), _("Add Track")) self.grb_add_region_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/polygon32.png'), _("Add Region")) + self.grb_convert_poly_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/polygon32.png'), _("Poligonize")) + self.grb_edit_toolbar.addSeparator() self.aperture_buffer_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/buffer16-2.png'), _('Buffer')) @@ -2911,6 +2913,24 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if key == QtCore.Qt.Key_F3 or key == 'F3': self.app.on_shortcut_list() return + elif self.app.call_source == 'measurement': + if modifiers == QtCore.Qt.ControlModifier: + pass + elif modifiers == QtCore.Qt.AltModifier: + pass + elif modifiers == QtCore.Qt.ShiftModifier: + pass + elif modifiers == QtCore.Qt.NoModifier: + if key == QtCore.Qt.Key_Escape or key == 'Escape': + # abort the measurement action + self.app.measurement_tool.on_measure(activate=False) + self.app.measurement_tool.deactivate_measure_tool() + self.app.inform.emit(_("Measurement Tool exit...")) + return + + if key == QtCore.Qt.Key_G or key == 'G': + self.app.ui.grid_snap_btn.trigger() + return def dragEnterEvent(self, event): if event.mimeData().hasUrls: diff --git a/flatcamTools/ToolMeasurement.py b/flatcamTools/ToolMeasurement.py index 6cf0fded..12f68bdd 100644 --- a/flatcamTools/ToolMeasurement.py +++ b/flatcamTools/ToolMeasurement.py @@ -112,6 +112,8 @@ class Measurement(FlatCAMTool): # self.setVisible(False) self.active = 0 + self.original_call_source = 'app' + # VisPy visuals self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene, layers=1) @@ -125,7 +127,7 @@ class Measurement(FlatCAMTool): self.app.ui.notebook.setTabText(2, _("Meas. Tool")) - # if the splitter is hidden, display it, else hide it but only if the current widget is the same + # if the splitter is hidden, display it if self.app.ui.splitter.sizes()[0] == 0: self.app.ui.splitter.setSizes([1, 1]) @@ -155,29 +157,22 @@ class Measurement(FlatCAMTool): self.distance_y_entry.set_value('0') self.total_distance_entry.set_value('0') - def activate(self): + def activate_measure_tool(self): # we disconnect the mouse/key handlers from wherever the measurement tool was called - self.canvas.vis_disconnect('key_press') self.canvas.vis_disconnect('mouse_move') self.canvas.vis_disconnect('mouse_press') self.canvas.vis_disconnect('mouse_release') - self.canvas.vis_disconnect('key_release') # we can safely connect the app mouse events to the measurement tool self.canvas.vis_connect('mouse_move', self.on_mouse_move_meas) - self.canvas.vis_connect('mouse_release', self.on_mouse_click) - self.canvas.vis_connect('key_release', self.on_key_release_meas) + self.canvas.vis_connect('mouse_release', self.on_mouse_click_release) self.set_tool_ui() - def deactivate(self): + def deactivate_measure_tool(self): # disconnect the mouse/key events from functions of measurement tool self.canvas.vis_disconnect('mouse_move', self.on_mouse_move_meas) - self.canvas.vis_disconnect('mouse_release', self.on_mouse_click) - self.canvas.vis_disconnect('key_release', self.on_key_release_meas) - - # reconnect the mouse/key events to the functions from where the tool was called - self.canvas.vis_connect('key_press', self.app.ui.keyPressEvent) + self.canvas.vis_disconnect('mouse_release', self.on_mouse_click_release) if self.app.call_source == 'app': self.canvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot) @@ -186,57 +181,44 @@ class Measurement(FlatCAMTool): elif self.app.call_source == 'geo_editor': self.canvas.vis_connect('mouse_move', self.app.geo_editor.on_canvas_move) self.canvas.vis_connect('mouse_press', self.app.geo_editor.on_canvas_click) - # self.canvas.vis_connect('key_press', self.app.geo_editor.on_canvas_key) - self.canvas.vis_connect('mouse_release', self.app.geo_editor.on_canvas_click_release) + self.canvas.vis_connect('mouse_release', self.app.geo_editor.on_geo_click_release) elif self.app.call_source == 'exc_editor': self.canvas.vis_connect('mouse_move', self.app.exc_editor.on_canvas_move) self.canvas.vis_connect('mouse_press', self.app.exc_editor.on_canvas_click) - # self.canvas.vis_connect('key_press', self.app.exc_editor.on_canvas_key) - self.canvas.vis_connect('mouse_release', self.app.exc_editor.on_canvas_click_release) + self.canvas.vis_connect('mouse_release', self.app.exc_editor.on_exc_click_release) elif self.app.call_source == 'grb_editor': self.canvas.vis_connect('mouse_move', self.app.grb_editor.on_canvas_move) self.canvas.vis_connect('mouse_press', self.app.grb_editor.on_canvas_click) - # self.canvas.vis_connect('key_press', self.app.grb_editor.on_canvas_key) - self.canvas.vis_connect('mouse_release', self.app.grb_editor.on_canvas_click_release) + self.canvas.vis_connect('mouse_release', self.app.grb_editor.on_grb_click_release) self.app.ui.notebook.setTabText(2, _("Tools")) self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab) def on_measure(self, signal=None, activate=None): log.debug("Measurement.on_measure()") - if activate is False or activate is None: - # DISABLE the Measuring TOOL - self.deactivate() + if activate is True: + # ENABLE the Measuring TOOL + self.clicked_meas = 0 + self.original_call_source = copy(self.app.call_source) + self.app.call_source = 'measurement' + self.app.inform.emit(_("MEASURING: Click on the Start point ...")) + self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower() + + self.activate_measure_tool() + log.debug("Measurement Tool --> tool initialized") + else: + # DISABLE the Measuring TOOL + self.deactivate_measure_tool() + self.app.call_source = copy(self.original_call_source) self.app.command_active = None # delete the measuring line self.delete_shape() log.debug("Measurement Tool --> exit tool") - elif activate is True: - # ENABLE the Measuring TOOL - self.clicked_meas = 0 - self.app.inform.emit(_("MEASURING: Click on the Start point ...")) - self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower() - - self.activate() - log.debug("Measurement Tool --> tool initialized") - - def on_key_release_meas(self, event): - if event.key == 'escape': - # abort the measurement action - self.on_measure(activate=False) - self.app.inform.emit(_("Measurement Tool exit...")) - return - - if event.key == 'G': - # toggle grid status - self.app.ui.grid_snap_btn.trigger() - return - - def on_mouse_click(self, event): + def on_mouse_click_release(self, event): # mouse click releases will be accepted only if the left button is clicked # this is necessary because right mouse click or middle mouse click # are used for panning on the canvas