Просмотр исходного кода

Merged jpcgt/flatcam into master

Kamil Sopko 9 лет назад
Родитель
Сommit
39b849119e
3 измененных файлов с 264 добавлено и 17 удалено
  1. 192 17
      FlatCAMApp.py
  2. 4 0
      FlatCAMGUI.py
  3. 68 0
      camlib.py

+ 192 - 17
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,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.
@@ -1661,6 +1709,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
@@ -2059,30 +2152,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])
@@ -2107,8 +2210,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}
@@ -3215,6 +3370,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."
@@ -3225,6 +3392,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"

+ 4 - 0
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)

+ 68 - 0
camlib.py

@@ -869,6 +869,26 @@ class Geometry(object):
         """
         self.solid_geometry = [cascaded_union(self.solid_geometry)]
 
+    def export_svg(self, scale_factor=0.00):
+        """
+        Exports the Gemoetry Object as a SVG Element
+
+        :return: SVG Element
+        """
+        # 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=scale_factor)
+        return svg_elem
 
 class ApertureMacro:
     """
@@ -3313,6 +3333,54 @@ class CNCjob(Geometry):
 
         self.create_geometry()
 
+    def export_svg(self, scale_factor=0.00):
+        """
+        Exports the CNC Job as a 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
+
+        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
+        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 the overall board size
+        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
+        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 = ""
+        if travels:
+            svg_elem = travelsgeom.svg(scale_factor=scale_factor, stroke_color="#F0E24D")
+        if cuts:
+            svg_elem += cutsgeom.svg(scale_factor=scale_factor, stroke_color="#5E6CFF")
+
+        return svg_elem
 
 # def get_bounds(geometry_set):
 #     xmin = Inf