Basic geometry editor for goemetry objects started.

This commit is contained in:
Juan Pablo Caram 2014-10-28 17:39:38 -04:00
parent d5ddd01851
commit 73aa0c38a0
9 changed files with 646 additions and 14 deletions

View File

@ -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):

553
FlatCAMDraw.py Normal file
View File

@ -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()

View File

@ -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")

View File

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

View File

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

View File

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

View File

@ -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):
"""

View File

@ -1 +1 @@
{}
{"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}

View File

@ -1 +1 @@
[]
[{"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"}]