From a5ff8c574a07e4662f32c8e345b5e20ce0fb689d Mon Sep 17 00:00:00 2001 From: grbd Date: Mon, 21 Mar 2016 11:38:14 +0000 Subject: [PATCH 01/10] Added initial svg export functionality --- FlatCAMApp.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++- FlatCAMGUI.py | 4 +++ camlib.py | 17 ++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index e5b94893..3069eaf3 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -25,7 +25,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 ######################################## ## App ## @@ -451,6 +451,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)) @@ -1577,6 +1578,42 @@ 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 + + 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. @@ -1661,6 +1698,31 @@ class App(QtCore.QObject): else: self.inform.emit("Project copy saved to: " + self.project_filename) + + def export_svg(self, obj_name, filename, outname=None): + """ + 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 + + # TODO needs size of board / dpi information + + with self.proc_container.new("Exporting SVG") as proc: + svg_elem = obj.export_svg() + svg_elem = "" + svg_elem + "" + 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 @@ -2109,6 +2171,10 @@ class App(QtCore.QObject): return str(self.collection.get_names()) + def export_svg(name, filename): + + self.export_svg(str(name), str(filename)) + def import_svg(filename, *args): a, kwa = h(*args) types = {'outname': str} @@ -3225,6 +3291,13 @@ class App(QtCore.QObject): "> import_svg " + " filename: Path to the file to import." }, + 'export_svg': { + 'fcn': export_svg, + 'help': "Export a Geometry Object as a SVG File\n" + + "> export_svg \n" + + " name: Name of the geometry object to export.\n" + + " filename: Path to the file to export." + }, 'open_gerber': { 'fcn': open_gerber, 'help': "Opens a Gerber file.\n" diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 8bb2445e..3c01d124 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -48,6 +48,10 @@ class FlatCAMGUI(QtGui.QMainWindow): self.menufileimportsvg = QtGui.QAction(QtGui.QIcon('share/folder16.png'), 'Import &SVG ...', self) self.menufile.addAction(self.menufileimportsvg) + # Export SVG ... + self.menufileexportsvg = QtGui.QAction(QtGui.QIcon('share/folder16.png'), 'Export &SVG ...', self) + self.menufile.addAction(self.menufileexportsvg) + # Save Project self.menufilesaveproject = QtGui.QAction(QtGui.QIcon('share/floppy16.png'), '&Save Project', self) self.menufile.addAction(self.menufilesaveproject) diff --git a/camlib.py b/camlib.py index f576ed98..8bbd49ca 100644 --- a/camlib.py +++ b/camlib.py @@ -869,6 +869,14 @@ class Geometry(object): """ self.solid_geometry = [cascaded_union(self.solid_geometry)] + def export_svg(self): + """ + Exports the Gemoetry Object as a SVG Element + + :return: SVG Element + """ + svg_elem = self.solid_geometry.svg() + return svg_elem class ApertureMacro: """ @@ -3313,6 +3321,15 @@ class CNCjob(Geometry): self.create_geometry() + def export_svg(self): + """ + Exports the CNC Job as a SVG Element + + :return: SVG Element + """ + self.solid_geometry = cascaded_union([geo['geom'] for geo in self.gcode_parsed]) + svg_elem = self.solid_geometry.svg() + return svg_elem # def get_bounds(geometry_set): # xmin = Inf From b272329384f5aa49a87ddbd04792abc9444201cd Mon Sep 17 00:00:00 2001 From: grbd Date: Mon, 21 Mar 2016 17:25:46 +0000 Subject: [PATCH 02/10] Initial scaling fixes for svg export --- FlatCAMApp.py | 10 +++++++--- camlib.py | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 3069eaf3..f6f74d47 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1714,11 +1714,15 @@ class App(QtCore.QObject): except: return "Could not retrieve object: %s" % obj_name - # TODO needs size of board / dpi information + # TODO needs size of board determining + # TODO needs seperate colours for CNCPath Export with self.proc_container.new("Exporting SVG") as proc: - svg_elem = obj.export_svg() - svg_elem = "" + svg_elem + "" + svg_header = ' Date: Mon, 21 Mar 2016 19:34:33 +0000 Subject: [PATCH 03/10] Fixed the scaling issues with the svg export --- FlatCAMApp.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index f6f74d47..fb600282 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1714,15 +1714,32 @@ class App(QtCore.QObject): except: return "Could not retrieve object: %s" % obj_name - # TODO needs size of board determining # TODO needs seperate colours for CNCPath Export + # The line thickness is only affected by the scaling factor not the tool size + # Use the tool size to determine the scaling factor for line thickness with self.proc_container.new("Exporting SVG") as proc: + exported_svg = obj.export_svg() + + # Determine bounding area for svg export + svgwidth = obj.solid_geometry.bounds[2] - obj.solid_geometry.bounds[0] + svgheight = obj.solid_geometry.bounds[3] - obj.solid_geometry.bounds[1] + minx = obj.solid_geometry.bounds[0] + miny = obj.solid_geometry.bounds[1] - svgheight + + svgwidth = str(svgwidth) + svgheight = str(svgheight) + minx = str(minx) + miny = str(miny) + uom = obj.units.lower() + svg_header = '' svg_header += '' svg_footer = ' ' - svg_elem = svg_header + obj.export_svg() + svg_footer + svg_elem = svg_header + exported_svg + svg_footer doc = parse_xml_string(svg_elem) with open(filename, 'w') as fp: fp.write(doc.toprettyxml()) From 532a821c765d163185aaed1f16c6e7ec4820df06 Mon Sep 17 00:00:00 2001 From: grbd Date: Mon, 21 Mar 2016 21:46:29 +0000 Subject: [PATCH 04/10] Fixed the colors with svg exports from cnc jobs for Visicut --- camlib.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/camlib.py b/camlib.py index c9f2f7ff..bc42b5d3 100644 --- a/camlib.py +++ b/camlib.py @@ -3327,8 +3327,28 @@ class CNCjob(Geometry): :return: SVG Element """ + + # This appears to match up distance wise with inkscape + scale = self.options['tooldia'] / 2 + if scale == 0: + scale = 0.05 + + cuts = [] + travels = [] + for g in self.gcode_parsed: + if g['kind'][0] == 'C': cuts.append(g) + if g['kind'][0] == 'T': travels.append(g) + + # Used to determine board size self.solid_geometry = cascaded_union([geo['geom'] for geo in self.gcode_parsed]) - svg_elem = self.solid_geometry.svg(scale_factor=0.05) + + # Seperate the travels from the cuts for laser cutting under Visicut + travelsgeom = cascaded_union([geo['geom'] for geo in travels]) + cutsgeom = cascaded_union([geo['geom'] for geo in cuts]) + + svg_elem = travelsgeom.svg(scale_factor=scale, stroke_color="#F0E24D") + svg_elem += cutsgeom.svg(scale_factor=scale, stroke_color="#5E6CFF") + return svg_elem # def get_bounds(geometry_set): From 10e9fa74c3226dc076c8c2de00f79344bf0959bf Mon Sep 17 00:00:00 2001 From: grbd Date: Tue, 22 Mar 2016 02:25:07 +0000 Subject: [PATCH 05/10] Added some additional checks for the types when exporting, and additional comments --- FlatCAMApp.py | 23 ++++++++++++++++++----- camlib.py | 9 +++++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index fb600282..76c72876 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1598,6 +1598,18 @@ class App(QtCore.QObject): msgbox.exec_() return + # Check for more compatible types and add as required + # Excellon not yet supported, there seems to be a list within the Polygon Geometry that shapely's svg export doesn't like + + if (not isinstance(obj, FlatCAMGeometry) and not isinstance(obj, FlatCAMGerber) and not isinstance(obj, FlatCAMCNCjob)): + 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: @@ -1714,10 +1726,6 @@ class App(QtCore.QObject): except: return "Could not retrieve object: %s" % obj_name - # TODO needs seperate colours for CNCPath Export - # The line thickness is only affected by the scaling factor not the tool size - # Use the tool size to determine the scaling factor for line thickness - with self.proc_container.new("Exporting SVG") as proc: exported_svg = obj.export_svg() @@ -1727,12 +1735,15 @@ class App(QtCore.QObject): minx = obj.solid_geometry.bounds[0] miny = obj.solid_geometry.bounds[1] - svgheight + # Convert everything to strings for use in the xml doc svgwidth = str(svgwidth) svgheight = str(svgheight) minx = str(minx) miny = str(miny) 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 = ' Date: Tue, 22 Mar 2016 09:54:57 +0000 Subject: [PATCH 06/10] This adds a bunch of fixes when exporting svg's from geom's or cncjobs generated from drill files, also adds support for exporting drill files directly as svg's, and should capture any objects that use list within the solid_geometry attribute --- FlatCAMApp.py | 20 +++++++++++++------- camlib.py | 22 +++++++++++++++++----- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 76c72876..e0dac238 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1599,9 +1599,8 @@ class App(QtCore.QObject): return # Check for more compatible types and add as required - # Excellon not yet supported, there seems to be a list within the Polygon Geometry that shapely's svg export doesn't like - - if (not isinstance(obj, FlatCAMGeometry) and not isinstance(obj, FlatCAMGerber) and not isinstance(obj, FlatCAMCNCjob)): + 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) @@ -1729,11 +1728,18 @@ class App(QtCore.QObject): with self.proc_container.new("Exporting SVG") as proc: exported_svg = obj.export_svg() + # Sometimes obj.solid_geometry can be a list instead of a Shapely class + # Make sure we see it as a Shapely Geometry class + geom = obj.solid_geometry + if type(obj.solid_geometry) is list: + geom = [cascaded_union(obj.solid_geometry)][0] + + # Determine bounding area for svg export - svgwidth = obj.solid_geometry.bounds[2] - obj.solid_geometry.bounds[0] - svgheight = obj.solid_geometry.bounds[3] - obj.solid_geometry.bounds[1] - minx = obj.solid_geometry.bounds[0] - miny = obj.solid_geometry.bounds[1] - svgheight + svgwidth = geom.bounds[2] - geom.bounds[0] + svgheight = geom.bounds[3] - geom.bounds[1] + minx = geom.bounds[0] + miny = geom.bounds[1] - svgheight # Convert everything to strings for use in the xml doc svgwidth = str(svgwidth) diff --git a/camlib.py b/camlib.py index e59b18c0..9cd11e96 100644 --- a/camlib.py +++ b/camlib.py @@ -875,7 +875,14 @@ class Geometry(object): :return: SVG Element """ - svg_elem = self.solid_geometry.svg(scale_factor=0.05) + # Sometimes self.solid_geometry can be a list instead of a Shapely class + # Make sure we see it as a Shapely Geometry class + geom = self.solid_geometry + if type(self.solid_geometry) is list: + geom = [cascaded_union(self.solid_geometry)][0] + + # Convert to a SVG + svg_elem = geom.svg(scale_factor=0.05) return svg_elem class ApertureMacro: @@ -3345,14 +3352,19 @@ class CNCjob(Geometry): self.solid_geometry = cascaded_union([geo['geom'] for geo in self.gcode_parsed]) # Convert the cuts and travels into single geometry objects we can render as svg xml - travelsgeom = cascaded_union([geo['geom'] for geo in travels]) - cutsgeom = cascaded_union([geo['geom'] for geo in cuts]) + if travels: + travelsgeom = cascaded_union([geo['geom'] for geo in travels]) + if cuts: + cutsgeom = cascaded_union([geo['geom'] for geo in cuts]) # Render the SVG Xml # The scale factor affects the size of the lines, and the stroke color adds different formatting for each set # It's better to have the travels sitting underneath the cuts for visicut - svg_elem = travelsgeom.svg(scale_factor=scale, stroke_color="#F0E24D") - svg_elem += cutsgeom.svg(scale_factor=scale, stroke_color="#5E6CFF") + svg_elem = "" + if travels: + svg_elem = travelsgeom.svg(scale_factor=scale, stroke_color="#F0E24D") + if cuts: + svg_elem += cutsgeom.svg(scale_factor=scale, stroke_color="#5E6CFF") return svg_elem From ee43d8b920b1d0db22e80e43a5822762a9299000 Mon Sep 17 00:00:00 2001 From: grbd Date: Tue, 22 Mar 2016 18:56:04 +0000 Subject: [PATCH 07/10] Additional fixes for export size and flattening the geometry list --- FlatCAMApp.py | 19 +++++++------------ camlib.py | 4 +--- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index e0dac238..2f540588 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1730,22 +1730,17 @@ class App(QtCore.QObject): # Sometimes obj.solid_geometry can be a list instead of a Shapely class # Make sure we see it as a Shapely Geometry class - geom = obj.solid_geometry - if type(obj.solid_geometry) is list: - geom = [cascaded_union(obj.solid_geometry)][0] - + geom = cascaded_union(obj.flatten()) # Determine bounding area for svg export - svgwidth = geom.bounds[2] - geom.bounds[0] - svgheight = geom.bounds[3] - geom.bounds[1] - minx = geom.bounds[0] - miny = geom.bounds[1] - svgheight + bounds = obj.bounds() + size = obj.size() # Convert everything to strings for use in the xml doc - svgwidth = str(svgwidth) - svgheight = str(svgheight) - minx = str(minx) - miny = str(miny) + 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 diff --git a/camlib.py b/camlib.py index 9cd11e96..2ae8471a 100644 --- a/camlib.py +++ b/camlib.py @@ -877,9 +877,7 @@ class Geometry(object): """ # Sometimes self.solid_geometry can be a list instead of a Shapely class # Make sure we see it as a Shapely Geometry class - geom = self.solid_geometry - if type(self.solid_geometry) is list: - geom = [cascaded_union(self.solid_geometry)][0] + geom = cascaded_union(self.flatten()) # Convert to a SVG svg_elem = geom.svg(scale_factor=0.05) From 039a2dd4dc6f7fa7b26346d34887029883998396 Mon Sep 17 00:00:00 2001 From: grbd Date: Tue, 22 Mar 2016 23:22:02 +0000 Subject: [PATCH 08/10] Made scale_factor optional for cli, added more comments, removed redundant code --- FlatCAMApp.py | 24 ++++++++++++++---------- camlib.py | 40 ++++++++++++++++++++++++++++------------ 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 2f540588..6593d9a2 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1710,7 +1710,7 @@ class App(QtCore.QObject): self.inform.emit("Project copy saved to: " + self.project_filename) - def export_svg(self, obj_name, filename, outname=None): + def export_svg(self, obj_name, filename, scale_factor=0.00): """ Exports a Geometry Object to a SVG File @@ -1726,11 +1726,7 @@ class App(QtCore.QObject): return "Could not retrieve object: %s" % obj_name with self.proc_container.new("Exporting SVG") as proc: - exported_svg = obj.export_svg() - - # Sometimes obj.solid_geometry can be a list instead of a Shapely class - # Make sure we see it as a Shapely Geometry class - geom = cascaded_union(obj.flatten()) + exported_svg = obj.export_svg(scale_factor=scale_factor) # Determine bounding area for svg export bounds = obj.bounds() @@ -2206,9 +2202,16 @@ class App(QtCore.QObject): return str(self.collection.get_names()) - def export_svg(name, filename): + def export_svg(name, filename, *args): + a, kwa = h(*args) + types = {'scale_factor': float} - self.export_svg(str(name), str(filename)) + 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) @@ -3329,9 +3332,10 @@ class App(QtCore.QObject): 'export_svg': { 'fcn': export_svg, 'help': "Export a Geometry Object as a SVG File\n" + - "> export_svg \n" + + "> export_svg [-scale_factor <0.0 (float)>]\n" + " name: Name of the geometry object to export.\n" + - " filename: Path to the file to export." + " filename: Path to the file to export.\n" + + " scale_factor: Multiplication factor used for scaling line widths during export." }, 'open_gerber': { 'fcn': open_gerber, diff --git a/camlib.py b/camlib.py index 2ae8471a..23bf1bcf 100644 --- a/camlib.py +++ b/camlib.py @@ -869,18 +869,25 @@ class Geometry(object): """ self.solid_geometry = [cascaded_union(self.solid_geometry)] - def export_svg(self): + def export_svg(self, scale_factor=0.00): """ Exports the Gemoetry Object as a SVG Element :return: SVG Element """ - # Sometimes self.solid_geometry can be a list instead of a Shapely class - # Make sure we see it as a Shapely Geometry class + # Make sure we see a Shapely Geometry class and not a list geom = cascaded_union(self.flatten()) + # scale_factor is a multiplication factor for the SVG stroke-width used within shapely's svg export + + # If 0 or less which is invalid then default to 0.05 + # This value appears to work for zooming, and getting the output svg line width + # to match that viewed on screen with FlatCam + if scale_factor <= 0: + scale_factor = 0.05 + # Convert to a SVG - svg_elem = geom.svg(scale_factor=0.05) + svg_elem = geom.svg(scale_factor=scale_factor) return svg_elem class ApertureMacro: @@ -3326,17 +3333,26 @@ class CNCjob(Geometry): self.create_geometry() - def export_svg(self): + def export_svg(self, scale_factor=0.00): """ Exports the CNC Job as a SVG Element - :return: SVG Element + :scale_factor: float + :return: SVG Element string """ + # scale_factor is a multiplication factor for the SVG stroke-width used within shapely's svg export + # If not specified then try and use the tool diameter + # This way what is on screen will match what is outputed for the svg + # This is quite a useful feature for svg's used with visicut - # This appears to match up distance wise with inkscape - scale = self.options['tooldia'] / 2 - if scale == 0: - scale = 0.05 + if scale_factor <= 0: + scale_factor = self.options['tooldia'] / 2 + + # If still 0 then defailt to 0.05 + # This value appears to work for zooming, and getting the output svg line width + # to match that viewed on screen with FlatCam + if scale_factor == 0: + scale_factor = 0.05 # Seperate the list of cuts and travels into 2 distinct lists # This way we can add different formatting / colors to both @@ -3360,9 +3376,9 @@ class CNCjob(Geometry): # It's better to have the travels sitting underneath the cuts for visicut svg_elem = "" if travels: - svg_elem = travelsgeom.svg(scale_factor=scale, stroke_color="#F0E24D") + svg_elem = travelsgeom.svg(scale_factor=scale_factor, stroke_color="#F0E24D") if cuts: - svg_elem += cutsgeom.svg(scale_factor=scale, stroke_color="#5E6CFF") + svg_elem += cutsgeom.svg(scale_factor=scale_factor, stroke_color="#5E6CFF") return svg_elem From 790f53dd55e26e80ee56541073a892d4fe4fe391 Mon Sep 17 00:00:00 2001 From: Juan Pablo Caram Date: Wed, 23 Mar 2016 11:06:48 -0400 Subject: [PATCH 09/10] Blocking in shell functions. Test for exception handling. See #196. --- FlatCAMApp.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index e5b94893..6626cac9 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -2107,7 +2107,27 @@ class App(QtCore.QObject): except Exception as e: return str(e) - return str(self.collection.get_names()) + def mytest2(*args): + to = int(args[0]) + + try: + 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()) + + except Exception as e: + return str(e) def import_svg(filename, *args): a, kwa = h(*args) @@ -3215,6 +3235,10 @@ class App(QtCore.QObject): 'fcn': mytest, 'help': "Test function. Only for testing." }, + 'mytest2': { + 'fcn': mytest2, + 'help': "Test function. Only for testing." + }, 'help': { 'fcn': shelp, 'help': "Shows list of commands." From 95676f21e2d973c0486e55f87beaede434f15764 Mon Sep 17 00:00:00 2001 From: Juan Pablo Caram Date: Wed, 23 Mar 2016 14:58:53 -0400 Subject: [PATCH 10/10] Blocking in shell functions. Correctly report exceptions in threads. See #196. --- FlatCAMApp.py | 99 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 30 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 6626cac9..82ec8dd8 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -2059,30 +2059,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]) @@ -2110,24 +2120,45 @@ class App(QtCore.QObject): def mytest2(*args): to = int(args[0]) - try: - for rec in self.recent: - if rec['kind'] == 'gerber': - self.open_gerber(str(rec['filename'])) - break + 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") + 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() + with wait_signal(self.new_object_available, to): + 1/0 # Force exception + iso.generatecncjob() - return str(self.collection.get_names()) + return str(self.collection.get_names()) - except Exception as e: - return str(e) + 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 import_svg(filename, *args): a, kwa = h(*args) @@ -3239,6 +3270,14 @@ class App(QtCore.QObject): '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."