Merged in marius_stanciu/flatcam_beta/Beta (pull request #231)

Beta
This commit is contained in:
Marius Stanciu
2019-09-25 00:37:59 +00:00
7 changed files with 170 additions and 160 deletions

View File

@@ -124,7 +124,7 @@ class App(QtCore.QObject):
# ################## Version and VERSION DATE ############################## # ################## Version and VERSION DATE ##############################
# ########################################################################## # ##########################################################################
version = 8.97 version = 8.97
version_date = "2019/09/22" version_date = "2019/09/27"
beta = True beta = True
engine = '3D' engine = '3D'
@@ -11142,6 +11142,7 @@ class App(QtCore.QObject):
self.kp = self.plotcanvas.graph_event_connect('key_press', self.ui.keyPressEvent) self.kp = self.plotcanvas.graph_event_connect('key_press', self.ui.keyPressEvent)
self.app_cursor = self.plotcanvas.new_cursor() self.app_cursor = self.plotcanvas.new_cursor()
if self.ui.grid_snap_btn.isChecked(): if self.ui.grid_snap_btn.isChecked():
self.app_cursor.enabled = True self.app_cursor.enabled = True
else: else:

View File

@@ -46,3 +46,26 @@ class LoudDict(dict):
""" """
self.callback = callback 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))

View File

@@ -5756,9 +5756,8 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
pass pass
self.ui.annotation_cb.stateChanged.connect(self.on_annotation_change) self.ui.annotation_cb.stateChanged.connect(self.on_annotation_change)
if self.app.is_legacy is False: # set if to display text annotations
# set if to display text annotations self.ui.annotation_cb.set_value(self.app.defaults["cncjob_annotation"])
self.ui.annotation_cb.set_value(self.app.defaults["cncjob_annotation"])
# Show/Hide Advanced Options # Show/Hide Advanced Options
if self.app.defaults["global_app_level"] == 'b': if self.app.defaults["global_app_level"] == 'b':
@@ -6244,7 +6243,8 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
# self.plot(kind=kind) # self.plot(kind=kind)
self.annotation.redraw() self.annotation.redraw()
else: 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): def convert_units(self, units):
log.debug("FlatCAMObj.FlatCAMECNCjob.convert_units()") log.debug("FlatCAMObj.FlatCAMECNCjob.convert_units()")

View File

@@ -605,30 +605,33 @@ class ObjectCollection(QtCore.QAbstractItemModel):
def delete_all(self): def delete_all(self):
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()") 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 = [] self.endResetModel()
for group in self.root_item.child_items:
group.remove_children()
self.endResetModel() self.app.plotcanvas.redraw()
except Exception as e:
log.debug("ObjectCollection.delete_all() --> %s" % str(e))
def get_active(self): def get_active(self):
""" """

View File

@@ -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 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 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 - 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 23.09.2019

View File

@@ -10,14 +10,6 @@
from PyQt5 import QtGui, QtCore, QtWidgets from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtCore import pyqtSignal 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 # needed for legacy mode
# Used for solid polygons in Matplotlib # Used for solid polygons in Matplotlib
from descartes.patch import PolygonPatch from descartes.patch import PolygonPatch
@@ -25,14 +17,21 @@ from descartes.patch import PolygonPatch
from shapely.geometry import Polygon, LineString, LinearRing, Point, MultiPolygon, MultiLineString from shapely.geometry import Polygon, LineString, LinearRing, Point, MultiPolygon, MultiLineString
import FlatCAMApp import FlatCAMApp
from copy import deepcopy from copy import deepcopy
import logging import logging
import traceback
import gettext import gettext
import FlatCAMTranslation as fcTranslate import FlatCAMTranslation as fcTranslate
import builtins 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') fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__: if '_' not in builtins.__dict__:
_ = gettext.gettext _ = gettext.gettext
@@ -78,7 +77,7 @@ class CanvasCache(QtCore.QObject):
self.axes.set_xticks([]) self.axes.set_xticks([])
self.axes.set_yticks([]) self.axes.set_yticks([])
self.canvas = FigureCanvasAgg(self.figure) self.canvas = FigureCanvas(self.figure)
self.cache = None self.cache = None
@@ -115,33 +114,6 @@ class CanvasCache(QtCore.QObject):
# log.debug("A new object is available. Should plot it!") # 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 PlotCanvasLegacy(QtCore.QObject):
""" """
Class handling the plotting area in the application. Class handling the plotting area in the application.
@@ -151,6 +123,7 @@ class PlotCanvasLegacy(QtCore.QObject):
# Request for new bitmap to display. The parameter # Request for new bitmap to display. The parameter
# is a list with [xmin, xmax, ymin, ymax, zoom(optional)] # is a list with [xmin, xmax, ymin, ymax, zoom(optional)]
update_screen_request = QtCore.pyqtSignal(list) update_screen_request = QtCore.pyqtSignal(list)
double_click = QtCore.pyqtSignal(object) double_click = QtCore.pyqtSignal(object)
def __init__(self, container, app): def __init__(self, container, app):
@@ -188,6 +161,7 @@ class PlotCanvasLegacy(QtCore.QObject):
# The canvas is the top level container (FigureCanvasQTAgg) # The canvas is the top level container (FigureCanvasQTAgg)
self.canvas = FigureCanvas(self.figure) self.canvas = FigureCanvas(self.figure)
self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus) self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
self.canvas.setFocus() self.canvas.setFocus()
self.native = self.canvas self.native = self.canvas
@@ -203,15 +177,17 @@ class PlotCanvasLegacy(QtCore.QObject):
# Update every time the canvas is re-drawn. # Update every time the canvas is re-drawn.
self.background = self.canvas.copy_from_bbox(self.axes.bbox) self.background = self.canvas.copy_from_bbox(self.axes.bbox)
# ################### NOT IMPLEMENTED YET - EXPERIMENTAL #######################
# ## Bitmap Cache # ## Bitmap Cache
self.cache = CanvasCache(self, self.app) # self.cache = CanvasCache(self, self.app)
self.cache_thread = QtCore.QThread() # self.cache_thread = QtCore.QThread()
self.cache.moveToThread(self.cache_thread) # self.cache.moveToThread(self.cache_thread)
# super(PlotCanvas, self).connect(self.cache_thread, QtCore.SIGNAL("started()"), self.cache.run) # # super(PlotCanvas, self).connect(self.cache_thread, QtCore.SIGNAL("started()"), self.cache.run)
self.cache_thread.started.connect(self.cache.run) # self.cache_thread.started.connect(self.cache.run)
#
self.cache_thread.start() # self.cache_thread.start()
self.cache.new_screen.connect(self.on_new_screen) # self.cache.new_screen.connect(self.on_new_screen)
# ##############################################################################
# Events # Events
self.mp = self.graph_event_connect('button_press_event', self.on_mouse_press) 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.graph_event_connect('key_release_event', self.on_key_up)
self.odr = self.graph_event_connect('draw_event', self.on_draw) self.odr = self.graph_event_connect('draw_event', self.on_draw)
self.mouse = [0, 0]
self.key = None self.key = None
self.pan_axes = [] self.pan_axes = []
self.panning = False self.panning = False
self.mouse = [0, 0]
# signal is the mouse is dragging # signal is the mouse is dragging
self.is_dragging = False self.is_dragging = False
@@ -238,9 +214,6 @@ class PlotCanvasLegacy(QtCore.QObject):
# signal if there is a doubleclick # signal if there is a doubleclick
self.is_dblclk = False 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): def graph_event_connect(self, event_name, callback):
""" """
Attach an event handler to the canvas through the Matplotlib interface. Attach an event handler to the canvas through the Matplotlib interface.
@@ -294,6 +267,31 @@ class PlotCanvasLegacy(QtCore.QObject):
print(str(e)) print(str(e))
return c 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): def clear_cursor(self, state):
if state is True: if state is True:
@@ -653,31 +651,6 @@ class PlotCanvasLegacy(QtCore.QObject):
# self.canvas.blit(self.axes.bbox) # 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): def translate_coords(self, position):
""" """
This does not do much. It's just for code compatibility This does not do much. It's just for code compatibility
@@ -750,66 +723,6 @@ class FakeCursor(QtCore.QObject):
pass 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: class ShapeCollectionLegacy:
""" """
This will create the axes for each collection of shapes and will also This will create the axes for each collection of shapes and will also
@@ -930,7 +843,10 @@ class ShapeCollectionLegacy:
self.shape_id = 0 self.shape_id = 0
self.axes.cla() 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: if update is True:
self.redraw() self.redraw()
@@ -1013,12 +929,17 @@ class ShapeCollectionLegacy:
self.axes.plot(x, y, linespec, color=linecolor) self.axes.plot(x, y, linespec, color=linecolor)
else: else:
path_num += 1 path_num += 1
if isinstance(local_shapes[element]['shape'], Polygon): if self.obj.ui.annotation_cb.get_value():
self.axes.annotate(str(path_num), xy=local_shapes[element]['shape'].exterior.coords[0], if isinstance(local_shapes[element]['shape'], Polygon):
xycoords='data', fontsize=20) self.axes.annotate(
else: str(path_num),
self.axes.annotate(str(path_num), xy=local_shapes[element]['shape'].coords[0], xy=local_shapes[element]['shape'].exterior.coords[0],
xycoords='data', fontsize=20) 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'], patch = PolygonPatch(local_shapes[element]['shape'],
facecolor=local_shapes[element]['face_color'], facecolor=local_shapes[element]['face_color'],
@@ -1108,3 +1029,62 @@ class ShapeCollectionLegacy:
if self._visible is False: if self._visible is False:
self.redraw() self.redraw()
self._visible = value 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()

View File

@@ -88,7 +88,7 @@ def apply_patches():
def _get_tick_frac_labels(self): def _get_tick_frac_labels(self):
"""Get the major ticks, minor ticks, and major labels""" """Get the major ticks, minor ticks, and major labels"""
minor_num = 4 # number of minor ticks per major division 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 domain = self.axis.domain
if domain[1] < domain[0]: if domain[1] < domain[0]:
flip = True flip = True