Basic geometry editor for goemetry objects started.
This commit is contained in:
parent
d5ddd01851
commit
73aa0c38a0
|
@ -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):
|
||||
|
|
|
@ -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()
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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}
|
|
@ -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"}]
|
Loading…
Reference in New Issue