From 28fce8243237a378df21762be96e6f552c013174 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 8 May 2019 20:01:06 +0300 Subject: [PATCH] - Geometry Editor: made the tool to be able to continuously move until the tool is exited either by ESC key or by right mouse button click - Geometry Editor Move Tool: if no shape is selected when triggering this tool, now it is possible to make the selection inside the tool - Gerber editor Move Tool: fixed a bug that repeated the plotting function unnecessarily - Gerber editor Move Tool: if no shape is selected the tool will exit --- README.md | 4 + camlib.py | 2 +- flatcamEditors/FlatCAMGeoEditor.py | 127 ++++++++++++++++++++--------- flatcamEditors/FlatCAMGrbEditor.py | 76 +++++++++++++++-- 4 files changed, 163 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 2264ea6e..e06800b2 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ CAD program, and create G-Code for Isolation routing. - added move action for solid_geometry stored in the gerber_obj.apertures - fixed camlib.Gerber skew, rotate, offset, mirror functions to work for geometry stored in the Gerber apertures - fixed Gerber Editor follow_geometry reconstruction +- Geometry Editor: made the tool to be able to continuously move until the tool is exited either by ESC key or by right mouse button click +- Geometry Editor Move Tool: if no shape is selected when triggering this tool, now it is possible to make the selection inside the tool +- Gerber editor Move Tool: fixed a bug that repeated the plotting function unnecessarily +- Gerber editor Move Tool: if no shape is selected the tool will exit 7.05.2019 diff --git a/camlib.py b/camlib.py index be06a7c5..be9a1665 100644 --- a/camlib.py +++ b/camlib.py @@ -7269,13 +7269,13 @@ class CNCjob(Geometry): self.create_geometry() + def get_bounds(geometry_list): xmin = Inf ymin = Inf xmax = -Inf ymax = -Inf - #print "Getting bounds of:", str(geometry_set) for gs in geometry_list: try: gxmin, gymin, gxmax, gymax = gs.bounds() diff --git a/flatcamEditors/FlatCAMGeoEditor.py b/flatcamEditors/FlatCAMGeoEditor.py index 8da538ac..ec4a86d2 100644 --- a/flatcamEditors/FlatCAMGeoEditor.py +++ b/flatcamEditors/FlatCAMGeoEditor.py @@ -18,6 +18,7 @@ from FlatCAMTool import FlatCAMTool from flatcamGUI.ObjectUI import LengthEntry, RadioSet from shapely.geometry import LineString, LinearRing, MultiLineString +# from shapely.geometry import mapping from shapely.ops import cascaded_union, unary_union import shapely.affinity as affinity @@ -29,6 +30,7 @@ from flatcamGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FC FCTable, FCDoubleSpinner, FCButton, EvalEntry2, FCInputDialog from flatcamParsers.ParseFont import * +# from vispy.io import read_png import gettext import FlatCAMTranslation as fcTranslate @@ -1861,7 +1863,6 @@ class DrawTool(object): def __init__(self, draw_app): self.draw_app = draw_app self.complete = False - self.start_msg = "Click on 1st point..." self.points = [] self.geometry = None # DrawToolShape or None @@ -1939,7 +1940,6 @@ class FCCircle(FCShapeTool): self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_circle_geo.png')) QtGui.QGuiApplication.setOverrideCursor(self.cursor) - self.start_msg = _("Click on Center point ...") self.draw_app.app.inform.emit(_("Click on Center point ...")) self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"] @@ -1991,7 +1991,6 @@ class FCArc(FCShapeTool): self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero_arc.png')) QtGui.QGuiApplication.setOverrideCursor(self.cursor) - self.start_msg = _("Click on Center point ...") self.draw_app.app.inform.emit(_("Click on Center point ...")) # Direction of rotation between point 1 and 2. @@ -2210,12 +2209,13 @@ class FCRectangle(FCShapeTool): self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero.png')) QtGui.QGuiApplication.setOverrideCursor(self.cursor) - self.start_msg = _("Click on 1st corner ...") + self.draw_app.app.inform.emit( _("Click on 1st corner ...")) def click(self, point): self.points.append(point) if len(self.points) == 1: + self.draw_app.app.inform.emit(_("Click on opposite corner to complete ...")) return "Click on opposite corner to complete ..." if len(self.points) == 2: @@ -2262,14 +2262,14 @@ class FCPolygon(FCShapeTool): self.cursor = QtGui.QCursor(QtGui.QPixmap('share/aero.png')) QtGui.QGuiApplication.setOverrideCursor(self.cursor) - self.start_msg = _("Click on 1st point ...") + self.draw_app.app.inform.emit(_("Click on 1st corner ...")) def click(self, point): self.draw_app.in_action = True self.points.append(point) if len(self.points) > 0: - self.draw_app.app.inform.emit(_("Click on next Point or click Right mouse button to complete ...")) + self.draw_app.app.inform.emit(_("Click on next Point or click right mouse button to complete ...")) return "Click on next point or hit ENTER to complete ..." return "" @@ -2445,21 +2445,33 @@ class FCMove(FCShapeTool): FCShapeTool.__init__(self, draw_app) self.name = 'move' - # self.shape_buffer = self.draw_app.shape_buffer - if not self.draw_app.selected: - self.draw_app.app.inform.emit(_("[WARNING_NOTCL] Move cancelled. No shape selected.")) - return + try: + QtGui.QGuiApplication.restoreOverrideCursor() + except: + pass + + self.storage = self.draw_app.storage + self.origin = None self.destination = None - self.start_msg = _("Click on reference point.") + + if len(self.draw_app.get_selected()) == 0: + self.draw_app.app.inform.emit(_("[WARNING_NOTCL] MOVE: No shape selected. Select a shape to move ...")) + else: + self.draw_app.app.inform.emit(_(" MOVE: Click on reference point ...")) def set_origin(self, origin): - self.draw_app.app.inform.emit(_("Click on destination point.")) + self.draw_app.app.inform.emit(_(" Click on destination point ...")) self.origin = origin def click(self, point): if len(self.draw_app.get_selected()) == 0: - return "Nothing to move." + # self.complete = True + # self.draw_app.app.inform.emit(_("[WARNING_NOTCL] Move cancelled. No shape selected.")) + self.select_shapes(point) + self.draw_app.replot() + self.draw_app.app.inform.emit(_(" MOVE: Click on reference point ...")) + return if self.origin is None: self.set_origin(point) @@ -2517,6 +2529,58 @@ class FCMove(FCShapeTool): # return DrawToolUtilityShape([affinity.translate(geom.geo, xoff=dx, yoff=dy) # for geom in self.draw_app.get_selected()]) + def select_shapes(self, pos): + # list where we store the overlapped shapes under our mouse left click position + over_shape_list = [] + + try: + _, closest_shape = self.storage.nearest(pos) + except StopIteration: + return "" + + over_shape_list.append(closest_shape) + + try: + # if there is no shape under our click then deselect all shapes + # it will not work for 3rd method of click selection + if not over_shape_list: + self.draw_app.selected = [] + self.draw_app.draw_shape_idx = -1 + else: + # if there are shapes under our click then advance through the list of them, one at the time in a + # circular way + self.draw_app.draw_shape_idx = (FlatCAMGeoEditor.draw_shape_idx + 1) % len(over_shape_list) + try: + obj_to_add = over_shape_list[int(FlatCAMGeoEditor.draw_shape_idx)] + except IndexError: + return + + key_modifier = QtWidgets.QApplication.keyboardModifiers() + if self.draw_app.app.defaults["global_mselect_key"] == 'Control': + # if CONTROL 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 == Qt.ControlModifier: + if obj_to_add in self.draw_app.selected: + self.draw_app.selected.remove(obj_to_add) + else: + self.draw_app.selected.append(obj_to_add) + else: + self.draw_app.selected = [] + self.draw_app.selected.append(obj_to_add) + else: + if key_modifier == Qt.ShiftModifier: + if obj_to_add in self.draw_app.selected: + self.draw_app.selected.remove(obj_to_add) + else: + self.draw_app.selected.append(obj_to_add) + else: + self.draw_app.selected = [] + self.draw_app.selected.append(obj_to_add) + + except Exception as e: + log.error("[ERROR] Something went bad. %s" % str(e)) + raise + class FCCopy(FCMove): def __init__(self, draw_app): @@ -2550,7 +2614,7 @@ class FCText(FCShapeTool): self.draw_app = draw_app self.app = draw_app.app - self.start_msg = _("Click on the Destination point...") + self.draw_app.app.inform.emit(_("Click on 1st corner ...")) self.origin = (0, 0) self.text_gui = TextInputTool(self.app) @@ -2602,7 +2666,7 @@ class FCBuffer(FCShapeTool): self.draw_app = draw_app self.app = draw_app.app - self.start_msg = _("Create buffer geometry ...") + self.draw_app.app.inform.emit(_("Create buffer geometry ...")) self.origin = (0, 0) self.buff_tool = BufferSelectionTool(self.app, self.draw_app) self.buff_tool.run() @@ -2720,7 +2784,7 @@ class FCPaint(FCShapeTool): self.draw_app = draw_app self.app = draw_app.app - self.start_msg = _("Create Paint geometry ...") + self.draw_app.app.inform.emit(_("Create Paint geometry ...")) self.origin = (0, 0) self.draw_app.paint_tool.run() @@ -2734,7 +2798,7 @@ class FCTransform(FCShapeTool): self.draw_app = draw_app self.app = draw_app.app - self.start_msg = _("Shape transformations ...") + self.draw_app.app.infrom.emit(_("Shape transformations ...")) self.origin = (0, 0) self.draw_app.transform_tool.run() @@ -3159,6 +3223,9 @@ class FlatCAMGeoEditor(QtCore.QObject): :return: None """ + if shape is None: + return + # List of DrawToolShape? if isinstance(shape, list): for subshape in shape: @@ -3280,8 +3347,6 @@ class FlatCAMGeoEditor(QtCore.QObject): self.tools[t]["button"].setChecked(False) self.active_tool = self.tools[tool]["constructor"](self) - if not isinstance(self.active_tool, FCSelect): - self.app.inform.emit(self.active_tool.start_msg) else: self.app.log.debug("%s is NOT checked." % tool) for t in self.tools: @@ -3341,6 +3406,7 @@ class FlatCAMGeoEditor(QtCore.QObject): # Selection with left mouse button if self.active_tool is not None and event.button is 1: + # Dispatch event to active_tool msg = self.active_tool.click(self.snap(self.pos[0], self.pos[1])) @@ -3348,28 +3414,11 @@ class FlatCAMGeoEditor(QtCore.QObject): if isinstance(self.active_tool, FCShapeTool) and self.active_tool.complete: self.on_shape_complete() - # 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.app.defaults["global_mselect_key"] == 'Control': - modifier_to_use = Qt.ControlModifier - else: - modifier_to_use = Qt.ShiftModifier - if isinstance(self.active_tool, FCText): self.select_tool("select") else: self.select_tool(self.active_tool.name) - - # 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.select_tool(self.active_tool.name) - # else: - # self.select_tool("select") - # return - if isinstance(self.active_tool, FCSelect): # self.app.log.debug("Replotting after click.") self.replot() @@ -3616,13 +3665,13 @@ class FlatCAMGeoEditor(QtCore.QObject): self.selected.remove(shape) # TODO: Check performance def on_move(self): + # if not self.selected: + # self.app.inform.emit(_("[WARNING_NOTCL] Move cancelled. No shape selected.")) + # return self.app.ui.geo_move_btn.setChecked(True) self.on_tool_select('move') def on_move_click(self): - if not self.selected: - self.app.inform.emit(_("[WARNING_NOTCL] Move cancelled. No shape selected.")) - return self.on_move() self.active_tool.set_origin(self.snap(self.x, self.y)) diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py index d8e0c74f..e37d043e 100644 --- a/flatcamEditors/FlatCAMGrbEditor.py +++ b/flatcamEditors/FlatCAMGrbEditor.py @@ -2,6 +2,7 @@ from PyQt5 import QtGui, QtCore, QtWidgets from PyQt5.QtCore import Qt, QSettings from shapely.geometry import LineString, LinearRing, MultiLineString +# from shapely.geometry import mapping from shapely.ops import cascaded_union, unary_union import shapely.affinity as affinity @@ -19,6 +20,9 @@ from FlatCAMTool import FlatCAMTool from numpy.linalg import norm as numpy_norm +# from vispy.io import read_png +# import pngcanvas + import gettext import FlatCAMTranslation as fcTranslate @@ -1449,11 +1453,18 @@ class FCApertureMove(FCShapeTool): self.destination = None self.selected_apertures = [] + if len(self.draw_app.get_selected()) == 0: + self.draw_app.app.inform.emit(_("[WARNING_NOTCL] Nothing selected to move ...")) + self.complete = True + self.draw_app.select_tool("select") + return + if self.draw_app.launched_from_shortcuts is True: self.draw_app.launched_from_shortcuts = False self.draw_app.app.inform.emit(_("Click on target location ...")) else: self.draw_app.app.inform.emit(_("Click on reference location ...")) + self.current_storage = None self.geometry = [] @@ -1485,6 +1496,37 @@ class FCApertureMove(FCShapeTool): self.draw_app.select_tool("select") return + # def create_png(self): + # """ + # Create a PNG file out of a list of Shapely polygons + # :return: + # """ + # if len(self.draw_app.get_selected()) == 0: + # return None + # + # geo_list = [geoms.geo for geoms in self.draw_app.get_selected()] + # xmin, ymin, xmax, ymax = get_shapely_list_bounds(geo_list) + # + # iwidth = (xmax - xmin) + # iwidth = int(round(iwidth)) + # iheight = (ymax - ymin) + # iheight = int(round(iheight)) + # c = pngcanvas.PNGCanvas(iwidth, iheight) + # + # pixels = [] + # for geom in self.draw_app.get_selected(): + # m = mapping(geom.geo.exterior) + # pixels += [[coord[0], coord[1]] for coord in m['coordinates']] + # for g in geom.geo.interiors: + # m = mapping(g) + # pixels += [[coord[0], coord[1]] for coord in m['coordinates']] + # c.polyline(pixels) + # pixels = [] + # + # f = open("%s.png" % 'D:\\shapely_image', "wb") + # f.write(c.dump()) + # f.close() + def make(self): # Create new geometry dx = self.destination[0] - self.origin[0] @@ -1499,13 +1541,14 @@ class FCApertureMove(FCShapeTool): self.geometry.append(DrawToolShape(affinity.translate(select_shape.geo, xoff=dx, yoff=dy))) self.current_storage.remove(select_shape) sel_shapes_to_be_deleted.append(select_shape) - self.draw_app.on_grb_shape_complete(self.current_storage) + self.draw_app.on_grb_shape_complete(self.current_storage, noplot=True) self.geometry = [] for shp in sel_shapes_to_be_deleted: self.draw_app.selected.remove(shp) sel_shapes_to_be_deleted = [] + self.draw_app.plot_all() self.draw_app.build_ui() self.draw_app.app.inform.emit(_("[success] Done. Apertures Move completed.")) @@ -1531,8 +1574,9 @@ class FCApertureMove(FCShapeTool): dx = data[0] - self.origin[0] dy = data[1] - self.origin[1] - for geom in self.draw_app.get_selected(): - geo_list.append(affinity.translate(geom.geo, xoff=dx, yoff=dy)) + # for geom in self.draw_app.get_selected(): + # geo_list.append(affinity.translate(geom.geo, xoff=dx, yoff=dy)) + geo_list = [affinity.translate(geom.geo, xoff=dx, yoff=dy) for geom in self.draw_app.get_selected()] return DrawToolUtilityShape(geo_list) @@ -3188,7 +3232,7 @@ class FlatCAMGrbEditor(QtCore.QObject): self.options[key] = self.sender().isChecked() return self.options[key] - def on_grb_shape_complete(self, storage=None, specific_shape=None): + def on_grb_shape_complete(self, storage=None, specific_shape=None, noplot=False): self.app.log.debug("on_shape_complete()") if specific_shape: @@ -3209,8 +3253,9 @@ class FlatCAMGrbEditor(QtCore.QObject): self.delete_utility_geometry() self.tool_shape.clear(update=True) - # Replot and reset tool. - self.plot_all() + if noplot is False: + # Replot and reset tool. + self.plot_all() def add_gerber_shape(self, shape, storage): """ @@ -4860,3 +4905,22 @@ class TransformEditorTool(FlatCAMTool): else: self.app.inform.emit( _("[WARNING_NOTCL] Geometry shape skew Y cancelled...")) + + +def get_shapely_list_bounds(geometry_list): + xmin = Inf + ymin = Inf + xmax = -Inf + ymax = -Inf + + for gs in geometry_list: + try: + gxmin, gymin, gxmax, gymax = gs.bounds + xmin = min([xmin, gxmin]) + ymin = min([ymin, gymin]) + xmax = max([xmax, gxmax]) + ymax = max([ymax, gymax]) + except: + log.warning("DEVELOPMENT: Tried to get bounds of empty geometry.") + + return [xmin, ymin, xmax, ymax] \ No newline at end of file