merge new pull requests from FlatCAM->master

implement  executing of tasks  inside worker thread
cleanups, reimplement  Isolate/New/OpenGerber as OOP style Shell commands
disable edit  during shell execution,  show   some  progress
add ability for breakpoints in other threads and only if available
add X11 safe flag, not sure what happen on windows
This commit is contained in:
Kamil Sopko
2016-03-24 23:06:44 +01:00
parent 980638630d
commit e96ee1af29
19 changed files with 651 additions and 50 deletions

View File

@@ -10,6 +10,7 @@ import os
import Tkinter
from PyQt4 import QtCore
import time # Just used for debugging. Double check before removing.
from contextlib import contextmanager
########################################
## Imports part of FlatCAM ##
@@ -25,6 +26,7 @@ from FlatCAMDraw import FlatCAMDraw
from FlatCAMProcess import *
from MeasurementTool import Measurement
from DblSidedTool import DblSidedTool
from xml.dom.minidom import parseString as parse_xml_string
import tclCommands
########################################
@@ -103,6 +105,9 @@ class App(QtCore.QObject):
# and is ready to be used.
new_object_available = QtCore.pyqtSignal(object)
# Emmited when shell command is finished(one command only)
shell_command_finished = QtCore.pyqtSignal(object)
message = QtCore.pyqtSignal(str, str, str)
def __init__(self, user_defaults=True, post_gui=None):
@@ -451,6 +456,7 @@ class App(QtCore.QObject):
self.ui.menufileopengcode.triggered.connect(self.on_fileopengcode)
self.ui.menufileopenproject.triggered.connect(self.on_file_openproject)
self.ui.menufileimportsvg.triggered.connect(self.on_file_importsvg)
self.ui.menufileexportsvg.triggered.connect(self.on_file_exportsvg)
self.ui.menufilesaveproject.triggered.connect(self.on_file_saveproject)
self.ui.menufilesaveprojectas.triggered.connect(self.on_file_saveprojectas)
self.ui.menufilesaveprojectcopy.triggered.connect(lambda: self.on_file_saveprojectas(make_copy=True))
@@ -523,8 +529,8 @@ class App(QtCore.QObject):
self.shell.resize(*self.defaults["shell_shape"])
self.shell.append_output("FlatCAM %s\n(c) 2014-2015 Juan Pablo Caram\n\n" % self.version)
self.shell.append_output("Type help to get started.\n\n")
self.tcl = Tkinter.Tcl()
self.setup_shell()
self.init_tcl()
if self.cmd_line_shellfile:
try:
@@ -542,6 +548,17 @@ class App(QtCore.QObject):
App.log.debug("END of constructor. Releasing control.")
def init_tcl(self):
if hasattr(self,'tcl'):
# self.tcl = None
# TODO we need to clean non default variables and procedures here
# new object cannot be used here as it will not remember values created for next passes,
# because tcl was execudted in old instance of TCL
pass
else:
self.tcl = Tkinter.Tcl()
self.setup_shell()
def defaults_read_form(self):
for option in self.defaults_form_fields:
self.defaults[option] = self.defaults_form_fields[option].get_value()
@@ -676,12 +693,16 @@ class App(QtCore.QObject):
def exec_command(self, text):
"""
Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
Also handles execution in separated threads
:param text:
:return: output if there was any
"""
return self.exec_command_test(text, False)
self.report_usage('exec_command')
result = self.exec_command_test(text, False)
return result
def exec_command_test(self, text, reraise=True):
"""
@@ -692,11 +713,10 @@ class App(QtCore.QObject):
:return: output if there was any
"""
self.report_usage('exec_command')
text = str(text)
try:
self.shell.open_proccessing()
result = self.tcl.eval(str(text))
if result!='None':
self.shell.append_output(result + '\n')
@@ -708,6 +728,9 @@ class App(QtCore.QObject):
#show error in console and just return or in test raise exception
if reraise:
raise e
finally:
self.shell.close_proccessing()
pass
return result
"""
@@ -1491,6 +1514,9 @@ class App(QtCore.QObject):
self.plotcanvas.clear()
# tcl needs to be reinitialized, otherwise old shell variables etc remains
self.init_tcl()
self.collection.delete_all()
self.setup_component_editor()
@@ -1612,6 +1638,53 @@ class App(QtCore.QObject):
# thread safe. The new_project()
self.open_project(filename)
def on_file_exportsvg(self):
"""
Callback for menu item File->Export SVG.
:return: None
"""
self.report_usage("on_file_exportsvg")
App.log.debug("on_file_exportsvg()")
obj = self.collection.get_active()
if obj is None:
self.inform.emit("WARNING: No object selected.")
msg = "Please Select a Geometry object to export"
msgbox = QtGui.QMessageBox()
msgbox.setInformativeText(msg)
msgbox.setStandardButtons(QtGui.QMessageBox.Ok)
msgbox.setDefaultButton(QtGui.QMessageBox.Ok)
msgbox.exec_()
return
# Check for more compatible types and add as required
if (not isinstance(obj, FlatCAMGeometry) and not isinstance(obj, FlatCAMGerber) and not isinstance(obj, FlatCAMCNCjob)
and not isinstance(obj, FlatCAMExcellon)):
msg = "ERROR: Only Geometry, Gerber and CNCJob objects can be used."
msgbox = QtGui.QMessageBox()
msgbox.setInformativeText(msg)
msgbox.setStandardButtons(QtGui.QMessageBox.Ok)
msgbox.setDefaultButton(QtGui.QMessageBox.Ok)
msgbox.exec_()
return
name = self.collection.get_active().options["name"]
try:
filename = QtGui.QFileDialog.getSaveFileName(caption="Export SVG",
directory=self.get_last_folder(), filter="*.svg")
except TypeError:
filename = QtGui.QFileDialog.getSaveFileName(caption="Export SVG")
filename = str(filename)
if str(filename) == "":
self.inform.emit("Export SVG cancelled.")
return
else:
self.export_svg(name, filename)
def on_file_importsvg(self):
"""
Callback for menu item File->Import SVG.
@@ -1696,6 +1769,51 @@ class App(QtCore.QObject):
else:
self.inform.emit("Project copy saved to: " + self.project_filename)
def export_svg(self, obj_name, filename, scale_factor=0.00):
"""
Exports a Geometry Object to a SVG File
:param filename: Path to the SVG file to save to.
:param outname:
:return:
"""
self.log.debug("export_svg()")
try:
obj = self.collection.get_by_name(str(obj_name))
except:
return "Could not retrieve object: %s" % obj_name
with self.proc_container.new("Exporting SVG") as proc:
exported_svg = obj.export_svg(scale_factor=scale_factor)
# Determine bounding area for svg export
bounds = obj.bounds()
size = obj.size()
# Convert everything to strings for use in the xml doc
svgwidth = str(size[0])
svgheight = str(size[1])
minx = str(bounds[0])
miny = str(bounds[1] - size[1])
uom = obj.units.lower()
# Add a SVG Header and footer to the svg output from shapely
# The transform flips the Y Axis so that everything renders properly within svg apps such as inkscape
svg_header = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" '
svg_header += 'width="' + svgwidth + uom + '" '
svg_header += 'height="' + svgheight + uom + '" '
svg_header += 'viewBox="' + minx + ' ' + miny + ' ' + svgwidth + ' ' + svgheight + '">'
svg_header += '<g transform="scale(1,-1)">'
svg_footer = '</g> </svg>'
svg_elem = svg_header + exported_svg + svg_footer
# Parse the xml through a xml parser just to add line feeds and to make it look more pretty for the output
doc = parse_xml_string(svg_elem)
with open(filename, 'w') as fp:
fp.write(doc.toprettyxml())
def import_svg(self, filename, outname=None):
"""
Adds a new Geometry Object to the projects and populates
@@ -2079,7 +2197,7 @@ class App(QtCore.QObject):
return a, kwa
from contextlib import contextmanager
@contextmanager
def wait_signal(signal, timeout=10000):
"""Block loop until signal emitted, or timeout (ms) elapses."""
@@ -2094,30 +2212,40 @@ class App(QtCore.QObject):
yield
oeh = sys.excepthook
ex = []
def exceptHook(type_, value, traceback):
ex.append(value)
oeh(type_, value, traceback)
sys.excepthook = exceptHook
if timeout is not None:
QtCore.QTimer.singleShot(timeout, report_quit)
loop.exec_()
sys.excepthook = oeh
if ex:
self.raiseTclError(str(ex[0]))
if status['timed_out']:
raise Exception('Timed out!')
def wait_signal2(signal, timeout=10000):
"""Block loop until signal emitted, or timeout (ms) elapses."""
loop = QtCore.QEventLoop()
signal.connect(loop.quit)
status = {'timed_out': False}
def report_quit():
status['timed_out'] = True
loop.quit()
if timeout is not None:
QtCore.QTimer.singleShot(timeout, report_quit)
loop.exec_()
if status['timed_out']:
raise Exception('Timed out!')
# def wait_signal2(signal, timeout=10000):
# """Block loop until signal emitted, or timeout (ms) elapses."""
# loop = QtCore.QEventLoop()
# signal.connect(loop.quit)
# status = {'timed_out': False}
#
# def report_quit():
# status['timed_out'] = True
# loop.quit()
#
# if timeout is not None:
# QtCore.QTimer.singleShot(timeout, report_quit)
# loop.exec_()
#
# if status['timed_out']:
# raise Exception('Timed out!')
def mytest(*args):
to = int(args[0])
@@ -2142,8 +2270,60 @@ class App(QtCore.QObject):
except Exception as e:
return str(e)
def mytest2(*args):
to = int(args[0])
for rec in self.recent:
if rec['kind'] == 'gerber':
self.open_gerber(str(rec['filename']))
break
basename = self.collection.get_names()[0]
isolate(basename, '-passes', '10', '-combine', '1')
iso = self.collection.get_by_name(basename + "_iso")
with wait_signal(self.new_object_available, to):
1/0 # Force exception
iso.generatecncjob()
return str(self.collection.get_names())
def mytest3(*args):
to = int(args[0])
def sometask(*args):
time.sleep(2)
self.inform.emit("mytest3")
with wait_signal(self.inform, to):
self.worker_task.emit({'fcn': sometask, 'params': []})
return "mytest3 done"
def mytest4(*args):
to = int(args[0])
def sometask(*args):
time.sleep(2)
1/0 # Force exception
self.inform.emit("mytest4")
with wait_signal(self.inform, to):
self.worker_task.emit({'fcn': sometask, 'params': []})
return "mytest3 done"
def export_svg(name, filename, *args):
a, kwa = h(*args)
types = {'scale_factor': float}
for key in kwa:
if key not in types:
return 'Unknown parameter: %s' % key
kwa[key] = types[key](kwa[key])
self.export_svg(str(name), str(filename), **kwa)
def import_svg(filename, *args):
a, kwa = h(*args)
types = {'outname': str}
@@ -3274,6 +3454,18 @@ class App(QtCore.QObject):
'fcn': mytest,
'help': "Test function. Only for testing."
},
'mytest2': {
'fcn': mytest2,
'help': "Test function. Only for testing."
},
'mytest3': {
'fcn': mytest3,
'help': "Test function. Only for testing."
},
'mytest4': {
'fcn': mytest4,
'help': "Test function. Only for testing."
},
'help': {
'fcn': shelp,
'help': "Shows list of commands."
@@ -3284,6 +3476,14 @@ class App(QtCore.QObject):
"> import_svg <filename>" +
" filename: Path to the file to import."
},
'export_svg': {
'fcn': export_svg,
'help': "Export a Geometry Object as a SVG File\n" +
"> export_svg <name> <filename> [-scale_factor <0.0 (float)>]\n" +
" name: Name of the geometry object to export.\n" +
" filename: Path to the file to export.\n" +
" scale_factor: Multiplication factor used for scaling line widths during export."
},
'open_gerber': {
'fcn': open_gerber,
'help': "Opens a Gerber file.\n"