diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 7f0b435d..9db0a174 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -24,6 +24,7 @@ from FlatCAMCommon import LoudDict from FlatCAMTool import * from FlatCAMShell import FCShell +from FlatCAMDraw import FlatCAMDraw ######################################## @@ -48,7 +49,7 @@ class App(QtCore.QObject): version_date = "2014/10" ## URL for update checks and statistics - version_url = "http://flatcam.org/FlatCAM/apptalk/version" + version_url = "http://flatcam.org/version" ## App URL app_url = "http://flatcam.org" @@ -74,6 +75,9 @@ class App(QtCore.QObject): """ App.log.info("FlatCAM Starting...") + self.path = os.path.dirname(sys.argv[0]) + #App.log.debug("Running in " + os.path.realpath(__file__)) + App.log.debug("Running in " + self.path) QtCore.QObject.__init__(self) @@ -307,6 +311,9 @@ class App(QtCore.QObject): self.ui.menufilesaveprojectas.triggered.connect(self.on_file_saveprojectas) self.ui.menufilesaveprojectcopy.triggered.connect(lambda: self.on_file_saveprojectas(make_copy=True)) self.ui.menufilesavedefaults.triggered.connect(self.on_file_savedefaults) + self.ui.menueditnew.triggered.connect(lambda: self.new_object('geometry', 'New Geometry', lambda x, y: None)) + self.ui.menueditedit.triggered.connect(self.edit_geometry) + self.ui.menueditok.triggered.connect(self.editor2geometry) self.ui.menueditdelete.triggered.connect(self.on_delete) self.ui.menuoptions_transfer_a2o.triggered.connect(self.on_options_app2object) self.ui.menuoptions_transfer_a2p.triggered.connect(self.on_options_app2project) @@ -327,6 +334,9 @@ class App(QtCore.QObject): self.ui.zoom_out_btn.triggered.connect(lambda: self.plotcanvas.zoom(1/1.5)) self.ui.clear_plot_btn.triggered.connect(self.plotcanvas.clear) self.ui.replot_btn.triggered.connect(self.on_toolbar_replot) + self.ui.newgeo_btn.triggered.connect(lambda: self.new_object('geometry', 'New Geometry', lambda x, y: None)) + self.ui.editgeo_btn.triggered.connect(self.edit_geometry) + self.ui.updategeo_btn.triggered.connect(self.editor2geometry) self.ui.delete_btn.triggered.connect(self.on_delete) self.ui.shell_btn.triggered.connect(lambda: self.shell.show()) # Object list @@ -351,6 +361,8 @@ class App(QtCore.QObject): self.measeurement_tool = Measurement(self) self.measeurement_tool.install() + self.draw = FlatCAMDraw(self, disabled=True) + ############# ### Shell ### ############# @@ -411,6 +423,34 @@ class App(QtCore.QObject): # Send to worker self.worker_task.emit({'fcn': worker_task, 'params': [self]}) + def edit_geometry(self): + """ + Send the current geometry object (if any) into the editor. + + :return: None + """ + if not isinstance(self.collection.get_active(), FlatCAMGeometry): + self.info("Select a Geometry Object to edit.") + return + + self.draw.edit_fcgeometry(self.collection.get_active()) + + def editor2geometry(self): + """ + Transfers the geometry in the editor to the current geometry object. + + :return: + """ + geo = self.collection.get_active() + if not isinstance(geo, FlatCAMGeometry): + self.info("Select a Geometry Object to update.") + return + + self.draw.update_fcgeometry(geo) + self.draw.clear() + self.draw.drawing_toolbar.setDisabled(True) + geo.plot() + def report_usage(self, resource): """ Increments usage counter for the given resource @@ -491,7 +531,7 @@ class App(QtCore.QObject): :return: None """ try: - f = open("defaults.json") + f = open(self.path + "/defaults.json") options = f.read() f.close() except IOError: @@ -630,7 +670,9 @@ class App(QtCore.QObject): try: self.options_form_fields[option].set_value(self.options[option]) except KeyError: - self.log.error("options_write_form(): No field for: %s" % option) + # Changed from error to debug. This allows to have data stored + # which is not user-editable. + self.log.debug("options_write_form(): No field for: %s" % option) def on_about(self): """ @@ -702,11 +744,13 @@ class App(QtCore.QObject): # Read options from file try: - f = open("defaults.json") + f = open(self.path + "/defaults.json") options = f.read() f.close() except: + e = sys.exc_info()[0] App.log.error("Could not load defaults file.") + App.log.error(str(e)) self.inform.emit("ERROR: Could not load defaults file.") return @@ -1262,8 +1306,11 @@ class App(QtCore.QObject): if str(filename) == "": self.inform.emit("Open cancelled.") else: - self.worker_task.emit({'fcn': self.open_project, - 'params': [filename]}) + # self.worker_task.emit({'fcn': self.open_project, + # 'params': [filename]}) + # The above was failing because open_project() is not + # thread safe. The new_project() + self.open_project(filename) def on_file_saveproject(self): """ @@ -1501,6 +1548,7 @@ class App(QtCore.QObject): """ App.log.debug("Opening project: " + filename) + ## Open and parse try: f = open(filename, 'r') except IOError: @@ -1518,15 +1566,16 @@ class App(QtCore.QObject): self.file_opened.emit("project", filename) - # Clear the current project + ## Clear the current project + ## NOT THREAD SAFE ## self.on_file_new() - # Project options + ##Project options self.options.update(d['options']) self.project_filename = filename self.ui.units_label.setText("[" + self.options["units"] + "]") - # Re create objects + ## Re create objects App.log.debug("Re-creating objects...") for obj in d['objs']: def obj_init(obj_inst, app_inst): diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py new file mode 100644 index 00000000..7edf280f --- /dev/null +++ b/FlatCAMDraw.py @@ -0,0 +1,553 @@ +from PyQt4 import QtGui, QtCore, Qt +import FlatCAMApp + +from shapely.geometry import Polygon, LineString, Point, LinearRing +from shapely.geometry import MultiPoint, MultiPolygon +from shapely.geometry import box as shply_box +from shapely.ops import cascaded_union, unary_union +import shapely.affinity as affinity +from shapely.wkt import loads as sloads +from shapely.wkt import dumps as sdumps +from shapely.geometry.base import BaseGeometry + +from numpy import arctan2, Inf, array, sqrt, pi, ceil, sin, cos + + +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 + + def click(self, point): + return "" + + def utility_geometry(self, data=None): + return None + + +class FCShapeTool(DrawTool): + def __init__(self, draw_app): + DrawTool.__init__(self, draw_app) + + def make(self): + pass + + +class FCCircle(FCShapeTool): + def __init__(self, draw_app): + DrawTool.__init__(self, draw_app) + self.start_msg = "Click on CENTER ..." + + def click(self, point): + self.points.append(point) + + if len(self.points) == 1: + return "Click on perimeter to complete ..." + + if len(self.points) == 2: + self.make() + return "Done." + + return "" + + def utility_geometry(self, data=None): + if len(self.points) == 1: + p1 = self.points[0] + p2 = data + radius = sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2) + return Point(p1).buffer(radius) + + return None + + def make(self): + p1 = self.points[0] + p2 = self.points[1] + radius = sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2) + self.geometry = Point(p1).buffer(radius) + self.complete = True + + +class FCRectangle(FCShapeTool): + def __init__(self, draw_app): + DrawTool.__init__(self, draw_app) + self.start_msg = "Click on 1st corner ..." + + def click(self, point): + self.points.append(point) + + if len(self.points) == 1: + return "Click on opposite corner to complete ..." + + if len(self.points) == 2: + self.make() + return "Done." + + return "" + + def utility_geometry(self, data=None): + if len(self.points) == 1: + p1 = self.points[0] + p2 = data + return LinearRing([p1, (p2[0], p1[1]), p2, (p1[0], p2[1])]) + + return None + + def make(self): + p1 = self.points[0] + p2 = self.points[1] + #self.geometry = LinearRing([p1, (p2[0], p1[1]), p2, (p1[0], p2[1])]) + self.geometry = Polygon([p1, (p2[0], p1[1]), p2, (p1[0], p2[1])]) + self.complete = True + + +class FCPolygon(FCShapeTool): + def __init__(self, draw_app): + DrawTool.__init__(self, draw_app) + self.start_msg = "Click on 1st point ..." + + def click(self, point): + self.points.append(point) + + if len(self.points) > 0: + return "Click on next point or hit SPACE to complete ..." + + return "" + + def utility_geometry(self, data=None): + if len(self.points) == 1: + temp_points = [x for x in self.points] + temp_points.append(data) + return LineString(temp_points) + + if len(self.points) > 1: + temp_points = [x for x in self.points] + temp_points.append(data) + return LinearRing(temp_points) + + return None + + def make(self): + # self.geometry = LinearRing(self.points) + self.geometry = Polygon(self.points) + self.complete = True + + +class FCPath(FCPolygon): + def make(self): + self.geometry = LineString(self.points) + self.complete = True + + def utility_geometry(self, data=None): + if len(self.points) > 1: + temp_points = [x for x in self.points] + temp_points.append(data) + return LineString(temp_points) + + return None + + +class FCSelect(DrawTool): + def __init__(self, draw_app): + DrawTool.__init__(self, draw_app) + self.shape_buffer = self.draw_app.shape_buffer + self.start_msg = "Click on geometry to select" + + def click(self, point): + min_distance = Inf + closest_shape = None + + for shape in self.shape_buffer: + if self.draw_app.key != 'control': + shape["selected"] = False + + distance = Point(point).distance(shape["geometry"]) + if distance < min_distance: + closest_shape = shape + min_distance = distance + + if closest_shape is not None: + closest_shape["selected"] = True + return "Shape selected." + + return "Nothing selected." + + +class FlatCAMDraw: + def __init__(self, app, disabled=False): + assert isinstance(app, FlatCAMApp.App) + self.app = app + self.canvas = app.plotcanvas + self.axes = self.canvas.new_axes("draw") + + ### Drawing Toolbar ### + self.drawing_toolbar = QtGui.QToolBar() + self.drawing_toolbar.setDisabled(disabled) + self.app.ui.addToolBar(self.drawing_toolbar) + self.select_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/pointer32.png'), 'Select') + self.add_circle_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/circle32.png'), 'Add Circle') + self.add_rectangle_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/rectangle32.png'), 'Add Rectangle') + self.add_polygon_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/polygon32.png'), 'Add Polygon') + self.add_path_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/path32.png'), 'Add Path') + self.union_btn = self.drawing_toolbar.addAction(QtGui.QIcon('share/union32.png'), 'Polygon Union') + + ### Event handlers ### + ## Canvas events + self.canvas.mpl_connect('button_press_event', self.on_canvas_click) + self.canvas.mpl_connect('motion_notify_event', self.on_canvas_move) + self.canvas.mpl_connect('key_press_event', self.on_canvas_key) + self.canvas.mpl_connect('key_release_event', self.on_canvas_key_release) + + self.union_btn.triggered.connect(self.union) + + ## Toolbar events and properties + self.tools = { + "select": {"button": self.select_btn, + "constructor": FCSelect}, + "circle": {"button": self.add_circle_btn, + "constructor": FCCircle}, + "rectangle": {"button": self.add_rectangle_btn, + "constructor": FCRectangle}, + "polygon": {"button": self.add_polygon_btn, + "constructor": FCPolygon}, + "path": {"button": self.add_path_btn, + "constructor": FCPath} + } + + # Data + self.active_tool = None + self.shape_buffer = [] + + self.move_timer = QtCore.QTimer() + self.move_timer.setSingleShot(True) + + self.key = None # Currently pressed key + + def make_callback(tool): + def f(): + self.on_tool_select(tool) + return f + + for tool in self.tools: + self.tools[tool]["button"].triggered.connect(make_callback(tool)) # Events + self.tools[tool]["button"].setCheckable(True) # Checkable + + def clear(self): + self.active_tool = None + self.shape_buffer = [] + self.replot() + + def on_tool_select(self, tool): + """ + + :rtype : None + """ + self.app.log.debug("on_tool_select('%s')" % tool) + + # This is to make the group behave as radio group + if tool in self.tools: + if self.tools[tool]["button"].isChecked(): + self.app.log.debug("%s is checked.") + for t in self.tools: + if t != tool: + self.tools[t]["button"].setChecked(False) + + self.active_tool = self.tools[tool]["constructor"](self) + self.app.info(self.active_tool.start_msg) + else: + self.app.log.debug("%s is NOT checked.") + for t in self.tools: + self.tools[t]["button"].setChecked(False) + self.active_tool = None + + def on_canvas_click(self, event): + """ + event.x .y have canvas coordinates + event.xdaya .ydata have plot coordinates + + :param event: + :return: + """ + if self.active_tool is not None: + # Dispatch event to active_tool + msg = self.active_tool.click((event.xdata, event.ydata)) + self.app.info(msg) + + # If it is a shape generating tool + if isinstance(self.active_tool, FCShapeTool) and self.active_tool.complete: + self.on_shape_complete() + return + + if isinstance(self.active_tool, FCSelect): + self.app.log.debug("Replotting after click.") + self.replot() + + def on_canvas_move(self, event): + """ + event.x .y have canvas coordinates + event.xdaya .ydata have plot coordinates + + :param event: + :return: + """ + self.on_canvas_move_effective(event) + return + + self.move_timer.stop() + + if self.active_tool is None: + return + + # Make a function to avoid late evaluation + def make_callback(): + def f(): + self.on_canvas_move_effective(event) + return f + callback = make_callback() + + self.move_timer.timeout.connect(callback) + self.move_timer.start(500) # Stops if aready running + + def on_canvas_move_effective(self, event): + """ + Is called after timeout on timer set in on_canvas_move. + + For details on animating on MPL see: + http://wiki.scipy.org/Cookbook/Matplotlib/Animations + + event.x .y have canvas coordinates + event.xdaya .ydata have plot coordinates + + :param event: + :return: + """ + + try: + x = float(event.xdata) + y = float(event.ydata) + except TypeError: + return + + if self.active_tool is None: + return + + geo = self.active_tool.utility_geometry(data=(x, y)) + + if geo is not None: + + # Remove any previous utility shape + for shape in self.shape_buffer: + if shape['utility']: + self.shape_buffer.remove(shape) + + # Add the new utility shape + self.shape_buffer.append({ + 'geometry': geo, + 'selected': False, + 'utility': True + }) + + # Efficient plotting for fast animation + elements = self.plot_shape(geometry=geo, linespec="b--", animated=True) + self.canvas.canvas.restore_region(self.canvas.background) + for el in elements: + self.axes.draw_artist(el) + self.canvas.canvas.blit(self.axes.bbox) + + #self.replot() + + def on_canvas_key(self, event): + """ + event.key has the key. + + :param event: + :return: + """ + self.key = event.key + + ### Finish the current action. Use with tools that do not + ### complete automatically, like a polygon or path. + if event.key == ' ': + if isinstance(self.active_tool, FCShapeTool): + self.active_tool.click((event.xdata, event.ydata)) + self.active_tool.make() + if self.active_tool.complete: + self.on_shape_complete() + return + + ### Abort the current action + if event.key == 'escape': + # TODO: ...? + self.on_tool_select("select") + self.app.info("Cancelled.") + for_deletion = [shape for shape in self.shape_buffer if shape['utility']] + for shape in for_deletion: + self.shape_buffer.remove(shape) + + self.replot() + return + + ### Delete selected object + if event.key == '-': + self.delete_selected() + self.replot() + + def on_canvas_key_release(self, event): + self.key = None + + def delete_selected(self): + for_deletion = [shape for shape in self.shape_buffer if shape["selected"]] + + for shape in for_deletion: + self.shape_buffer.remove(shape) + self.app.info("Shape deleted.") + + def plot_shape(self, geometry=None, linespec='b-', linewidth=1, animated=False): + self.app.log.debug("plot_shape()") + plot_elements = [] + + if geometry is None: + geometry = self.active_tool.geometry + try: + _ = iter(geometry) + iterable_geometry = geometry + except TypeError: + iterable_geometry = [geometry] + + for geo in iterable_geometry: + + if type(geo) == Polygon: + x, y = geo.exterior.coords.xy + element, = self.axes.plot(x, y, linespec, linewidth=linewidth, animated=animated) + plot_elements.append(element) + for ints in geo.interiors: + x, y = ints.coords.xy + element, = self.axes.plot(x, y, linespec, linewidth=linewidth, animated=animated) + plot_elements.append(element) + continue + + if type(geo) == LineString or type(geo) == LinearRing: + x, y = geo.coords.xy + element, = self.axes.plot(x, y, linespec, linewidth=linewidth, animated=animated) + plot_elements.append(element) + continue + + if type(geo) == MultiPolygon: + for poly in geo: + x, y = poly.exterior.coords.xy + element, = self.axes.plot(x, y, linespec, linewidth=linewidth, animated=animated) + plot_elements.append(element) + for ints in poly.interiors: + x, y = ints.coords.xy + element, = self.axes.plot(x, y, linespec, linewidth=linewidth, animated=animated) + plot_elements.append(element) + continue + + return plot_elements + # self.canvas.auto_adjust_axes() + + def plot_all(self): + self.app.log.debug("plot_all()") + self.axes.cla() + for shape in self.shape_buffer: + if shape['utility']: + self.plot_shape(geometry=shape['geometry'], linespec='k--', linewidth=1) + continue + + if shape['selected']: + self.plot_shape(geometry=shape['geometry'], linespec='k-', linewidth=2) + continue + + self.plot_shape(geometry=shape['geometry']) + + self.canvas.auto_adjust_axes() + + def on_shape_complete(self): + self.app.log.debug("on_shape_complete()") + + # For some reason plotting just the last created figure does not + # work. The figure is not shown. Calling replot does the trick + # which generates a new axes object. + #self.plot_shape() + #self.canvas.auto_adjust_axes() + + self.shape_buffer.append({'geometry': self.active_tool.geometry, + 'selected': False, + 'utility': False}) + + # Remove any utility shapes + for shape in self.shape_buffer: + if shape['utility']: + self.shape_buffer.remove(shape) + + self.replot() + self.active_tool = type(self.active_tool)(self) + + def replot(self): + #self.canvas.clear() + self.axes = self.canvas.new_axes("draw") + self.plot_all() + + def edit_fcgeometry(self, fcgeometry): + try: + _ = iter(fcgeometry.solid_geometry) + geometry = fcgeometry.solid_geometry + except TypeError: + geometry = [fcgeometry.solid_geometry] + + # Delete contents of editor. + self.shape_buffer = [] + + # Link shapes into editor. + for shape in geometry: + self.shape_buffer.append({'geometry': shape, + 'selected': False, + 'utility': False}) + + self.replot() + self.drawing_toolbar.setDisabled(False) + + def update_fcgeometry(self, fcgeometry): + """ + Transfers the drawing tool shape buffer to the selected geometry + object. The geometry already in the object are removed. + + :param fcgeometry: FlatCAMGeometry + :return: None + """ + fcgeometry.solid_geometry = [] + for shape in self.shape_buffer: + fcgeometry.solid_geometry.append(shape['geometry']) + + def union(self): + """ + Makes union of selected polygons. Original polygons + are deleted. + + :return: None. + """ + targets = [shape for shape in self.shape_buffer if shape['selected']] + + results = cascaded_union([t['geometry'] for t in targets]) + + for shape in targets: + self.shape_buffer.remove(shape) + + try: + for geo in results: + + self.shape_buffer.append({ + 'geometry': geo, + 'selected': True, + 'utility': False + }) + except TypeError: + self.shape_buffer.append({ + 'geometry': results, + 'selected': True, + 'utility': False + }) + + self.replot() \ No newline at end of file diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 2f3380e0..292e3e98 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -67,6 +67,10 @@ class FlatCAMGUI(QtGui.QMainWindow): ### Edit ### self.menuedit = self.menu.addMenu('&Edit') + self.menueditnew = self.menuedit.addAction(QtGui.QIcon('share/new_geo16.png'), 'New Geometry') + self.menueditedit = self.menuedit.addAction(QtGui.QIcon('share/edit16.png'), 'Edit Geometry') + self.menueditok = self.menuedit.addAction(QtGui.QIcon('share/edit_ok16.png'), 'Update Geometry') + #self.menueditcancel = self.menuedit.addAction(QtGui.QIcon('share/cancel_edit16.png'), "Cancel Edit") self.menueditdelete = self.menuedit.addAction(QtGui.QIcon('share/trash16.png'), 'Delete') ### Options ### @@ -107,6 +111,10 @@ class FlatCAMGUI(QtGui.QMainWindow): self.zoom_in_btn = self.toolbar.addAction(QtGui.QIcon('share/zoom_in32.png'), "&Zoom In") self.clear_plot_btn = self.toolbar.addAction(QtGui.QIcon('share/clear_plot32.png'), "&Clear Plot") self.replot_btn = self.toolbar.addAction(QtGui.QIcon('share/replot32.png'), "&Replot") + self.newgeo_btn = self.toolbar.addAction(QtGui.QIcon('share/new_geo32.png'), "New Blank Geometry") + self.editgeo_btn = self.toolbar.addAction(QtGui.QIcon('share/edit32.png'), "Edit Geometry") + self.updategeo_btn = self.toolbar.addAction(QtGui.QIcon('share/edit_ok32.png'), "Update Geometry") + #self.canceledit_btn = self.toolbar.addAction(QtGui.QIcon('share/cancel_edit32.png'), "Cancel Edit") self.delete_btn = self.toolbar.addAction(QtGui.QIcon('share/delete32.png'), "&Delete") self.shell_btn = self.toolbar.addAction(QtGui.QIcon('share/shell32.png'), "&Command Line") diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 533a28cc..dff9c6a8 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -4,6 +4,7 @@ import FlatCAMApp import inspect # TODO: For debugging only. from camlib import * from FlatCAMCommon import LoudDict +from FlatCAMDraw import FlatCAMDraw ######################################## @@ -880,6 +881,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): # from predecessors. self.ser_attrs += ['options', 'kind'] + def build_ui(self): + FlatCAMObj.build_ui(self) + + + def set_ui(self, ui): FlatCAMObj.set_ui(self, ui) diff --git a/FlatCAMWorker.py b/FlatCAMWorker.py index 8ea95dd6..b0b6c720 100644 --- a/FlatCAMWorker.py +++ b/FlatCAMWorker.py @@ -1,5 +1,4 @@ from PyQt4 import QtCore -#import Queue import FlatCAMApp @@ -16,10 +15,15 @@ class Worker(QtCore.QObject): def run(self): FlatCAMApp.App.log.debug("Worker Started!") + + # Tasks are queued in the event listener. self.app.worker_task.connect(self.do_worker_task) def do_worker_task(self, task): FlatCAMApp.App.log.debug("Running task: %s" % str(task)) + + # 'worker_name' property of task allows to target + # specific worker. if 'worker_name' in task and task['worker_name'] == self.name: task['fcn'](*task['params']) return diff --git a/ObjectCollection.py b/ObjectCollection.py index 07a17cbf..bec42ea9 100644 --- a/ObjectCollection.py +++ b/ObjectCollection.py @@ -135,6 +135,11 @@ class ObjectCollection(QtCore.QAbstractListModel): self.endRemoveRows() def get_active(self): + """ + Returns the active object or None + + :return: FlatCAMObj or None + """ selections = self.view.selectedIndexes() if len(selections) == 0: return None @@ -143,8 +148,8 @@ class ObjectCollection(QtCore.QAbstractListModel): def set_active(self, name): """ - Selects object by name from the project list. This trigger the - list_selection_changed event and call on_list_selection changed. + Selects object by name from the project list. This triggers the + list_selection_changed event and call on_list_selection_changed. :param name: Name of the FlatCAM Object :return: None diff --git a/PlotCanvas.py b/PlotCanvas.py index 89737679..008b7856 100644 --- a/PlotCanvas.py +++ b/PlotCanvas.py @@ -53,6 +53,10 @@ class PlotCanvas: #self.container.attach(self.canvas, 0, 0, 600, 400) # TODO: Height and width are num. columns?? self.container.addWidget(self.canvas) # Qt + # Copy a bitmap of the canvas for quick animation. + # Update every time the canvas is re-drawn. + self.background = self.canvas.copy_from_bbox(self.axes.bbox) + # Events self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move) #self.canvas.connect('configure-event', self.auto_adjust_axes) @@ -194,6 +198,7 @@ class PlotCanvas: # Re-draw self.canvas.draw() + self.background = self.canvas.copy_from_bbox(self.axes.bbox) def auto_adjust_axes(self, *args): """ @@ -246,6 +251,7 @@ class PlotCanvas: # Re-draw self.canvas.draw() + self.background = self.canvas.copy_from_bbox(self.axes.bbox) def pan(self, x, y): xmin, xmax = self.axes.get_xlim() @@ -260,6 +266,7 @@ class PlotCanvas: # Re-draw self.canvas.draw() + self.background = self.canvas.copy_from_bbox(self.axes.bbox) def new_axes(self, name): """ diff --git a/defaults.json b/defaults.json index 9e26dfee..f543a9b6 100644 --- a/defaults.json +++ b/defaults.json @@ -1 +1 @@ -{} \ No newline at end of file +{"gerber_cutoutgapsize": 0.15, "gerber_noncopperrounded": false, "geometry_paintoverlap": 0.15, "cncjob_append": "", "excellon_feedrate": 3.0, "serial": "q808lhee8dc0k21d0o7b", "stats": {"on_file_openproject": 3, "on_options_app2project": 33, "save_defaults": 8918, "on_delete": 3, "on_about": 1, "geometry_on_paint_button": 4, "on_fileopengerber": 1, "on_toolbar_replot": 7, "gerber_on_generatebb_button": 1, "gerber_on_iso_button": 6, "geometry_on_generatecnc_button": 2, "on_file_new": 33, "on_file_saveproject": 1, "exec_command": 4}, "gerber_plot": true, "excellon_drillz": -0.1, "geometry_feedrate": 3.0, "units": "IN", "excellon_travelz": 0.1, "gerber_multicolored": false, "gerber_solid": true, "gerber_isopasses": 1, "excellon_plot": true, "gerber_isotooldia": 0.016, "cncjob_tooldia": 0.016, "geometry_travelz": 0.1, "gerber_cutoutmargin": 0.1, "excellon_solid": false, "geometry_paintmargin": 0.0, "geometry_cutz": -0.002, "gerber_noncoppermargin": 0.0, "gerber_cutouttooldia": 0.07, "gerber_gaps": "4", "gerber_bboxmargin": 0.0, "cncjob_plot": true, "geometry_plot": true, "gerber_isooverlap": 0.15, "gerber_bboxrounded": false, "geometry_cnctooldia": 0.016, "geometry_painttooldia": 0.07} \ No newline at end of file diff --git a/recent.json b/recent.json index 0637a088..8f6c0810 100644 --- a/recent.json +++ b/recent.json @@ -1 +1 @@ -[] \ No newline at end of file +[{"kind": "gerber", "filename": "C:/Users/jpcaram/Dropbox/CNC/pcbcam/test_files/Top2.gbr"}, {"kind": "project", "filename": "C:/Users/jpcaram/Dropbox/CNC/pcbcam/test_files/easy_eda_test/easy_eda.fc"}] \ No newline at end of file