# ########################################################## ## # FlatCAM: 2D Post-processing for Manufacturing # # http://caram.cl/software/flatcam # # Author: Juan Pablo Caram (c) # # Date: 2/5/2014 # # MIT Licence # # ########################################################## ## from PyQt5 import QtCore import logging from flatcamGUI.VisPyCanvas import VisPyCanvas, time from flatcamGUI.VisPyVisuals import ShapeGroup, ShapeCollection, TextCollection, TextGroup, Cursor from vispy.scene.visuals import InfiniteLine, Line import numpy as np from vispy.geometry import Rect log = logging.getLogger('base') class PlotCanvas(QtCore.QObject, VisPyCanvas): """ Class handling the plotting area in the application. """ def __init__(self, container, fcapp): """ The constructor configures the VisPy figure that will contain all plots, creates the base axes and connects events to the plotting area. :param container: The parent container in which to draw plots. :rtype: PlotCanvas """ super(PlotCanvas, self).__init__() # VisPyCanvas.__init__(self) # VisPyCanvas does not allow new attributes. Override. self.unfreeze() self.fcapp = fcapp # Parent container self.container = container # workspace lines; I didn't use the rectangle because I didn't want to add another VisPy Node, # which might decrease performance self.b_line, self.r_line, self.t_line, self.l_line = None, None, None, None # self.create_native() self.native.setParent(self.fcapp.ui) # self.container.addWidget(self.native) # ## AXIS # ## self.v_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 1.0), vertical=True, parent=self.view.scene) self.h_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 1.0), vertical=False, parent=self.view.scene) # draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area # all CNC have a limited workspace self.draw_workspace() # if self.app.defaults['global_workspace'] is True: # if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM': # self.wkspace_t = Line(pos=) self.shape_collections = [] self.shape_collection = self.new_shape_collection() self.fcapp.pool_recreated.connect(self.on_pool_recreated) self.text_collection = self.new_text_collection() # TODO: Should be setting to show/hide CNC job annotations (global or per object) self.text_collection.enabled = True # Keep VisPy canvas happy by letting it be "frozen" again. self.freeze() # draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area # all CNC have a limited workspace def draw_workspace(self): a = np.empty((0, 0)) a4p_in = np.array([(0, 0), (8.3, 0), (8.3, 11.7), (0, 11.7)]) a4l_in = np.array([(0, 0), (11.7, 0), (11.7, 8.3), (0, 8.3)]) a3p_in = np.array([(0, 0), (11.7, 0), (11.7, 16.5), (0, 16.5)]) a3l_in = np.array([(0, 0), (16.5, 0), (16.5, 11.7), (0, 11.7)]) a4p_mm = np.array([(0, 0), (210, 0), (210, 297), (0, 297)]) a4l_mm = np.array([(0, 0), (297, 0), (297,210), (0, 210)]) a3p_mm = np.array([(0, 0), (297, 0), (297, 420), (0, 420)]) a3l_mm = np.array([(0, 0), (420, 0), (420, 297), (0, 297)]) if self.fcapp.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM': if self.fcapp.defaults['global_workspaceT'] == 'A4P': a = a4p_mm elif self.fcapp.defaults['global_workspaceT'] == 'A4L': a = a4l_mm elif self.fcapp.defaults['global_workspaceT'] == 'A3P': a = a3p_mm elif self.fcapp.defaults['global_workspaceT'] == 'A3L': a = a3l_mm else: if self.fcapp.defaults['global_workspaceT'] == 'A4P': a = a4p_in elif self.fcapp.defaults['global_workspaceT'] == 'A4L': a = a4l_in elif self.fcapp.defaults['global_workspaceT'] == 'A3P': a = a3p_in elif self.fcapp.defaults['global_workspaceT'] == 'A3L': a = a3l_in self.delete_workspace() self.b_line = Line(pos=a[0:2], color=(0.70, 0.3, 0.3, 1.0), antialias= True, method='agg', parent=self.view.scene) self.r_line = Line(pos=a[1:3], color=(0.70, 0.3, 0.3, 1.0), antialias= True, method='agg', parent=self.view.scene) self.t_line = Line(pos=a[2:4], color=(0.70, 0.3, 0.3, 1.0), antialias= True, method='agg', parent=self.view.scene) self.l_line = Line(pos=np.array((a[0], a[3])), color=(0.70, 0.3, 0.3, 1.0), antialias= True, method='agg', parent=self.view.scene) if self.fcapp.defaults['global_workspace'] is False: self.delete_workspace() # delete the workspace lines from the plot by removing the parent def delete_workspace(self): try: self.b_line.parent = None self.r_line.parent = None self.t_line.parent = None self.l_line.parent = None except Exception as e: pass # redraw the workspace lines on the plot by readding them to the parent view.scene def restore_workspace(self): try: self.b_line.parent = self.view.scene self.r_line.parent = self.view.scene self.t_line.parent = self.view.scene self.l_line.parent = self.view.scene except Exception as e: pass def graph_event_connect(self, event_name, callback): return getattr(self.events, event_name).connect(callback) def graph_event_disconnect(self, event_name, callback=None): if callback is None: getattr(self.events, event_name).disconnect() else: getattr(self.events, event_name).disconnect(callback) def zoom(self, factor, center=None): """ Zooms the plot by factor around a given center point. Takes care of re-drawing. :param factor: Number by which to scale the plot. :type factor: float :param center: Coordinates [x, y] of the point around which to scale the plot. :type center: list :return: None """ self.view.camera.zoom(factor, center) def new_shape_group(self, shape_collection=None): if shape_collection: return ShapeGroup(shape_collection) return ShapeGroup(self.shape_collection) def new_shape_collection(self, **kwargs): # sc = ShapeCollection(parent=self.view.scene, pool=self.app.pool, **kwargs) # self.shape_collections.append(sc) # return sc return ShapeCollection(parent=self.view.scene, pool=self.fcapp.pool, **kwargs) def new_cursor(self): c = Cursor(pos=np.empty((0, 2)), parent=self.view.scene) c.antialias = 0 return c def new_text_group(self, collection=None): if collection: return TextGroup(collection) else: return TextGroup(self.text_collection) def new_text_collection(self, **kwargs): return TextCollection(parent=self.view.scene, **kwargs) def fit_view(self, rect=None): # Lock updates in other threads self.shape_collection.lock_updates() if not rect: rect = Rect(-1, -1, 20, 20) try: rect.left, rect.right = self.shape_collection.bounds(axis=0) rect.bottom, rect.top = self.shape_collection.bounds(axis=1) except TypeError: pass # adjust the view camera to be slightly bigger than the bounds so the shape colleaction can be seen clearly # otherwise the shape collection boundary will have no border rect.left *= 0.96 rect.bottom *= 0.96 rect.right *= 1.01 rect.top *= 1.01 self.view.camera.rect = rect self.shape_collection.unlock_updates() def fit_center(self, loc, rect=None): # Lock updates in other threads self.shape_collection.lock_updates() if not rect: try: rect = Rect(loc[0]-20, loc[1]-20, 40, 40) except TypeError: pass self.view.camera.rect = rect self.shape_collection.unlock_updates() def clear(self): pass def redraw(self): self.shape_collection.redraw([]) self.text_collection.redraw() def on_pool_recreated(self, pool): self.shape_collection.pool = pool