From 61e27920472100beb93cf5df79d2e91f3abae3c4 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 25 Sep 2019 03:26:34 +0300 Subject: [PATCH] - unfortunately the fix for issue where while zooming the mouse cursor shape was not updated braked something in way that Matplotlib work with PyQt5, therefore I removed it --- FlatCAMApp.py | 1 + FlatCAMCommon.py | 23 +++ FlatCAMObj.py | 8 +- ObjectCollection.py | 29 ++-- README.md | 3 + flatcamGUI/PlotCanvasLegacy.py | 262 +++++++++++++++------------------ flatcamGUI/VisPyPatches.py | 2 +- 7 files changed, 169 insertions(+), 159 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 1dbc830c..e2e41879 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -11142,6 +11142,7 @@ class App(QtCore.QObject): self.kp = self.plotcanvas.graph_event_connect('key_press', self.ui.keyPressEvent) self.app_cursor = self.plotcanvas.new_cursor() + if self.ui.grid_snap_btn.isChecked(): self.app_cursor.enabled = True else: diff --git a/FlatCAMCommon.py b/FlatCAMCommon.py index 63f14f81..508884cc 100644 --- a/FlatCAMCommon.py +++ b/FlatCAMCommon.py @@ -46,3 +46,26 @@ class LoudDict(dict): """ self.callback = callback + + +class FCSignal: + """ + Taken from here: https://blog.abstractfactory.io/dynamic-signals-in-pyqt/ + """ + + def __init__(self): + self.__subscribers = [] + + def emit(self, *args, **kwargs): + for subs in self.__subscribers: + subs(*args, **kwargs) + + def connect(self, func): + self.__subscribers.append(func) + + def disconnect(self, func): + try: + self.__subscribers.remove(func) + except ValueError: + print('Warning: function %s not removed ' + 'from signal %s' % (func, self)) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index b5527121..c34f6638 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -5756,9 +5756,8 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): pass self.ui.annotation_cb.stateChanged.connect(self.on_annotation_change) - if self.app.is_legacy is False: - # set if to display text annotations - self.ui.annotation_cb.set_value(self.app.defaults["cncjob_annotation"]) + # set if to display text annotations + self.ui.annotation_cb.set_value(self.app.defaults["cncjob_annotation"]) # Show/Hide Advanced Options if self.app.defaults["global_app_level"] == 'b': @@ -6244,7 +6243,8 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): # self.plot(kind=kind) self.annotation.redraw() else: - self.inform.emit(_("Not available with the current Graphic Engine Legacy(2D).")) + kind = self.ui.cncplot_method_combo.get_value() + self.plot(kind=kind) def convert_units(self, units): log.debug("FlatCAMObj.FlatCAMECNCjob.convert_units()") diff --git a/ObjectCollection.py b/ObjectCollection.py index 82c7b03d..98d6cd61 100644 --- a/ObjectCollection.py +++ b/ObjectCollection.py @@ -605,30 +605,33 @@ class ObjectCollection(QtCore.QAbstractItemModel): def delete_all(self): FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()") + try: + self.app.all_objects_list.clear() - self.app.plotcanvas.redraw() + self.app.geo_editor.clear() - self.app.all_objects_list.clear() + self.app.exc_editor.clear() - self.app.geo_editor.clear() + self.app.dblsidedtool.reset_fields() - self.app.exc_editor.clear() + self.app.panelize_tool.reset_fields() - self.app.dblsidedtool.reset_fields() + self.app.cutout_tool.reset_fields() - self.app.panelize_tool.reset_fields() + self.app.film_tool.reset_fields() - self.app.cutout_tool.reset_fields() + self.beginResetModel() - self.app.film_tool.reset_fields() + self.checked_indexes = [] - self.beginResetModel() + for group in self.root_item.child_items: + group.remove_children() - self.checked_indexes = [] - for group in self.root_item.child_items: - group.remove_children() + self.endResetModel() - self.endResetModel() + self.app.plotcanvas.redraw() + except Exception as e: + log.debug("ObjectCollection.delete_all() --> %s" % str(e)) def get_active(self): """ diff --git a/README.md b/README.md index 943ee0f4..d95bb0aa 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ CAD program, and create G-Code for Isolation routing. - in legacy graphic engine, fixed issue where immediately after changing the mouse cursor snapping the mouse cursor shape was not updated - in legacy graphic engine, fixed issue where while zooming the mouse cursor shape was not updated - in legacy graphic engine, fixed issue where immediately after panning finished the mouse cursor shape was not updated +- unfortunately the fix for issue where while zooming the mouse cursor shape was not updated braked something in way that Matplotlib work with PyQt5, therefore I removed it +- fixed a bug in legacy graphic engine: when doing the self.app.collection.delete_all() in new_project an app crash occurred +- implemented the Annotation change in CNCJob Selected Tab 23.09.2019 diff --git a/flatcamGUI/PlotCanvasLegacy.py b/flatcamGUI/PlotCanvasLegacy.py index a2018692..c8b51c87 100644 --- a/flatcamGUI/PlotCanvasLegacy.py +++ b/flatcamGUI/PlotCanvasLegacy.py @@ -10,14 +10,6 @@ from PyQt5 import QtGui, QtCore, QtWidgets from PyQt5.QtCore import pyqtSignal -# Prevent conflict with Qt5 and above. -from matplotlib import use as mpl_use -mpl_use("Qt5Agg") -from matplotlib.figure import Figure -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg -from matplotlib.backends.backend_agg import FigureCanvasAgg -from matplotlib.widgets import Cursor - # needed for legacy mode # Used for solid polygons in Matplotlib from descartes.patch import PolygonPatch @@ -25,14 +17,21 @@ from descartes.patch import PolygonPatch from shapely.geometry import Polygon, LineString, LinearRing, Point, MultiPolygon, MultiLineString import FlatCAMApp + from copy import deepcopy import logging -import traceback import gettext import FlatCAMTranslation as fcTranslate import builtins +# Prevent conflict with Qt5 and above. +from matplotlib import use as mpl_use +mpl_use("Qt5Agg") +from matplotlib.figure import Figure +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +# from matplotlib.widgets import Cursor + fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext @@ -78,7 +77,7 @@ class CanvasCache(QtCore.QObject): self.axes.set_xticks([]) self.axes.set_yticks([]) - self.canvas = FigureCanvasAgg(self.figure) + self.canvas = FigureCanvas(self.figure) self.cache = None @@ -115,33 +114,6 @@ class CanvasCache(QtCore.QObject): # log.debug("A new object is available. Should plot it!") -class FigureCanvas(FigureCanvasQTAgg): - """ - Reimplemented this so I can emit a signal when the idle drawing is finished and display the mouse shape - """ - - idle_drawing_finished = pyqtSignal() - - def __init__(self, figure): - super().__init__(figure=figure) - - def _draw_idle(self): - if self.height() < 0 or self.width() < 0: - self._draw_pending = False - if not self._draw_pending: - return - try: - self.draw() - except Exception: - # Uncaught exceptions are fatal for PyQt5, so catch them instead. - traceback.print_exc() - finally: - self._draw_pending = False - - # I reimplemented this class only to launch this signal - self.idle_drawing_finished.emit() - - class PlotCanvasLegacy(QtCore.QObject): """ Class handling the plotting area in the application. @@ -151,6 +123,7 @@ class PlotCanvasLegacy(QtCore.QObject): # Request for new bitmap to display. The parameter # is a list with [xmin, xmax, ymin, ymax, zoom(optional)] update_screen_request = QtCore.pyqtSignal(list) + double_click = QtCore.pyqtSignal(object) def __init__(self, container, app): @@ -188,6 +161,7 @@ class PlotCanvasLegacy(QtCore.QObject): # The canvas is the top level container (FigureCanvasQTAgg) self.canvas = FigureCanvas(self.figure) + self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus) self.canvas.setFocus() self.native = self.canvas @@ -203,15 +177,17 @@ class PlotCanvasLegacy(QtCore.QObject): # Update every time the canvas is re-drawn. self.background = self.canvas.copy_from_bbox(self.axes.bbox) + # ################### NOT IMPLEMENTED YET - EXPERIMENTAL ####################### # ## Bitmap Cache - self.cache = CanvasCache(self, self.app) - self.cache_thread = QtCore.QThread() - self.cache.moveToThread(self.cache_thread) - # super(PlotCanvas, self).connect(self.cache_thread, QtCore.SIGNAL("started()"), self.cache.run) - self.cache_thread.started.connect(self.cache.run) - - self.cache_thread.start() - self.cache.new_screen.connect(self.on_new_screen) + # self.cache = CanvasCache(self, self.app) + # self.cache_thread = QtCore.QThread() + # self.cache.moveToThread(self.cache_thread) + # # super(PlotCanvas, self).connect(self.cache_thread, QtCore.SIGNAL("started()"), self.cache.run) + # self.cache_thread.started.connect(self.cache.run) + # + # self.cache_thread.start() + # self.cache.new_screen.connect(self.on_new_screen) + # ############################################################################## # Events self.mp = self.graph_event_connect('button_press_event', self.on_mouse_press) @@ -226,11 +202,11 @@ class PlotCanvasLegacy(QtCore.QObject): # self.graph_event_connect('key_release_event', self.on_key_up) self.odr = self.graph_event_connect('draw_event', self.on_draw) - self.mouse = [0, 0] self.key = None self.pan_axes = [] self.panning = False + self.mouse = [0, 0] # signal is the mouse is dragging self.is_dragging = False @@ -238,9 +214,6 @@ class PlotCanvasLegacy(QtCore.QObject): # signal if there is a doubleclick self.is_dblclk = False - # pay attention, this signal should be connected only after the self.canvas and self.mouse is declared - self.canvas.idle_drawing_finished.connect(lambda: self.draw_cursor(x_pos=self.mouse[0], y_pos=self.mouse[1])) - def graph_event_connect(self, event_name, callback): """ Attach an event handler to the canvas through the Matplotlib interface. @@ -294,6 +267,31 @@ class PlotCanvasLegacy(QtCore.QObject): print(str(e)) return c + def draw_cursor(self, x_pos, y_pos): + """ + Draw a cursor at the mouse grid snapped position + + :param x_pos: mouse x position + :param y_pos: mouse y position + :return: + """ + # there is no point in drawing mouse cursor when panning as it jumps in a confusing way + if self.app.app_cursor.enabled is True and self.panning is False: + try: + x, y = self.app.geo_editor.snap(x_pos, y_pos) + + # Pointer (snapped) + elements = self.axes.plot(x, y, 'k+', ms=40, mew=2, animated=True) + for el in elements: + self.axes.draw_artist(el) + except Exception as e: + # this happen at app initialization since self.app.geo_editor does not exist yet + # I could reshuffle the object instantiating order but what's the point? I could crash something else + # and that's pythonic, too + pass + + self.canvas.blit(self.axes.bbox) + def clear_cursor(self, state): if state is True: @@ -653,31 +651,6 @@ class PlotCanvasLegacy(QtCore.QObject): # self.canvas.blit(self.axes.bbox) - def draw_cursor(self, x_pos, y_pos): - """ - Draw a cursor at the mouse grid snapped position - - :param x_pos: mouse x position - :param y_pos: mouse y position - :return: - """ - # there is no point in drawing mouse cursor when panning as it jumps in a confusing way - if self.app.app_cursor.enabled is True and self.panning is False: - try: - x, y = self.app.geo_editor.snap(x_pos, y_pos) - - # Pointer (snapped) - elements = self.axes.plot(x, y, 'k+', ms=40, mew=2, animated=True) - for el in elements: - self.axes.draw_artist(el) - except Exception as e: - # this happen at app initialization since self.app.geo_editor does not exist yet - # I could reshuffle the object instantiating order but what's the point? I could crash something else - # and that's pythonic, too - pass - - self.canvas.blit(self.axes.bbox) - def translate_coords(self, position): """ This does not do much. It's just for code compatibility @@ -750,66 +723,6 @@ class FakeCursor(QtCore.QObject): pass -class MplCursor(Cursor): - """ - Unfortunately this gets attached to the current axes and if a new axes is added - it will not be showed until that axes is deleted. - Not the kind of behavior needed here so I don't use it anymore. - """ - def __init__(self, axes, color='red', linewidth=1): - - super().__init__(ax=axes, useblit=True, color=color, linewidth=linewidth) - self._enabled = True - - self.axes = axes - self.color = color - self.linewidth = linewidth - - self.x = None - self.y = None - - @property - def enabled(self): - return True if self._enabled else False - - @enabled.setter - def enabled(self, value): - self._enabled = value - self.visible = self._enabled - self.canvas.draw() - - def onmove(self, event): - pass - - def set_data(self, event, pos): - """Internal event handler to draw the cursor when the mouse moves.""" - self.x = pos[0] - self.y = pos[1] - - if self.ignore(event): - return - if not self.canvas.widgetlock.available(self): - return - if event.inaxes != self.ax: - self.linev.set_visible(False) - self.lineh.set_visible(False) - - if self.needclear: - self.canvas.draw() - self.needclear = False - return - self.needclear = True - if not self.visible: - return - self.linev.set_xdata((self.x, self.x)) - - self.lineh.set_ydata((self.y, self.y)) - self.linev.set_visible(self.visible and self.vertOn) - self.lineh.set_visible(self.visible and self.horizOn) - - self._update() - - class ShapeCollectionLegacy: """ This will create the axes for each collection of shapes and will also @@ -930,7 +843,10 @@ class ShapeCollectionLegacy: self.shape_id = 0 self.axes.cla() - self.app.plotcanvas.auto_adjust_axes() + try: + self.app.plotcanvas.auto_adjust_axes() + except Exception as e: + log.debug("ShapeCollectionLegacy.clear() --> %s" % str(e)) if update is True: self.redraw() @@ -1013,12 +929,17 @@ class ShapeCollectionLegacy: self.axes.plot(x, y, linespec, color=linecolor) else: path_num += 1 - if isinstance(local_shapes[element]['shape'], Polygon): - self.axes.annotate(str(path_num), xy=local_shapes[element]['shape'].exterior.coords[0], - xycoords='data', fontsize=20) - else: - self.axes.annotate(str(path_num), xy=local_shapes[element]['shape'].coords[0], - xycoords='data', fontsize=20) + if self.obj.ui.annotation_cb.get_value(): + if isinstance(local_shapes[element]['shape'], Polygon): + self.axes.annotate( + str(path_num), + xy=local_shapes[element]['shape'].exterior.coords[0], + xycoords='data', fontsize=20) + else: + self.axes.annotate( + str(path_num), + xy=local_shapes[element]['shape'].coords[0], + xycoords='data', fontsize=20) patch = PolygonPatch(local_shapes[element]['shape'], facecolor=local_shapes[element]['face_color'], @@ -1108,3 +1029,62 @@ class ShapeCollectionLegacy: if self._visible is False: self.redraw() self._visible = value + +# class MplCursor(Cursor): +# """ +# Unfortunately this gets attached to the current axes and if a new axes is added +# it will not be showed until that axes is deleted. +# Not the kind of behavior needed here so I don't use it anymore. +# """ +# def __init__(self, axes, color='red', linewidth=1): +# +# super().__init__(ax=axes, useblit=True, color=color, linewidth=linewidth) +# self._enabled = True +# +# self.axes = axes +# self.color = color +# self.linewidth = linewidth +# +# self.x = None +# self.y = None +# +# @property +# def enabled(self): +# return True if self._enabled else False +# +# @enabled.setter +# def enabled(self, value): +# self._enabled = value +# self.visible = self._enabled +# self.canvas.draw() +# +# def onmove(self, event): +# pass +# +# def set_data(self, event, pos): +# """Internal event handler to draw the cursor when the mouse moves.""" +# self.x = pos[0] +# self.y = pos[1] +# +# if self.ignore(event): +# return +# if not self.canvas.widgetlock.available(self): +# return +# if event.inaxes != self.ax: +# self.linev.set_visible(False) +# self.lineh.set_visible(False) +# +# if self.needclear: +# self.canvas.draw() +# self.needclear = False +# return +# self.needclear = True +# if not self.visible: +# return +# self.linev.set_xdata((self.x, self.x)) +# +# self.lineh.set_ydata((self.y, self.y)) +# self.linev.set_visible(self.visible and self.vertOn) +# self.lineh.set_visible(self.visible and self.horizOn) +# +# self._update() diff --git a/flatcamGUI/VisPyPatches.py b/flatcamGUI/VisPyPatches.py index 7e2c5d0d..9f13d16a 100644 --- a/flatcamGUI/VisPyPatches.py +++ b/flatcamGUI/VisPyPatches.py @@ -88,7 +88,7 @@ def apply_patches(): def _get_tick_frac_labels(self): """Get the major ticks, minor ticks, and major labels""" minor_num = 4 # number of minor ticks per major division - if (self.axis.scale_type == 'linear'): + if self.axis.scale_type == 'linear': domain = self.axis.domain if domain[1] < domain[0]: flip = True