From d3ed12e5def933997c9f3e13995339c5e671c3d6 Mon Sep 17 00:00:00 2001 From: Juan Pablo Caram Date: Fri, 18 Dec 2015 16:43:47 -0500 Subject: [PATCH] Added SVG importing support to the GUI menu. See issue #179. --- FlatCAMApp.py | 24 ++++++++++++++++++++++++ FlatCAMGUI.py | 6 +++++- camlib.py | 1 + svgparse.py | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 9ed0c8e3..c15347f1 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -430,6 +430,7 @@ class App(QtCore.QObject): self.ui.menufileopenexcellon.triggered.connect(self.on_fileopenexcellon) 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.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)) @@ -1543,6 +1544,29 @@ class App(QtCore.QObject): # thread safe. The new_project() self.open_project(filename) + def on_file_importsvg(self): + """ + Callback for menu item File->Import SVG. + + :return: None + """ + self.report_usage("on_file_importsvg") + App.log.debug("on_file_importsvg()") + + try: + filename = QtGui.QFileDialog.getOpenFileName(caption="Import SVG", + directory=self.get_last_folder()) + except TypeError: + filename = QtGui.QFileDialog.getOpenFileName(caption="Import SVG") + + filename = str(filename) + + if str(filename) == "": + self.inform.emit("Open cancelled.") + else: + self.worker_task.emit({'fcn': self.import_svg, + 'params': [filename]}) + def on_file_saveproject(self): """ Callback for menu item File->Save Project. Saves the project to diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 4e5d467a..a0ffe96f 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -28,7 +28,7 @@ class FlatCAMGUI(QtGui.QMainWindow): # Recent self.recent = self.menufile.addMenu(QtGui.QIcon('share/folder16.png'), "Open recent ...") - # Open gerber + # Open gerber ... self.menufileopengerber = QtGui.QAction(QtGui.QIcon('share/folder16.png'), 'Open &Gerber ...', self) self.menufile.addAction(self.menufileopengerber) @@ -40,6 +40,10 @@ class FlatCAMGUI(QtGui.QMainWindow): self.menufileopengcode = QtGui.QAction(QtGui.QIcon('share/folder16.png'), 'Open G-&Code ...', self) self.menufile.addAction(self.menufileopengcode) + # Import SVG ... + self.menufileimportsvg = QtGui.QAction(QtGui.QIcon('share/folder16.png'), 'Import &SVG ...', self) + self.menufile.addAction(self.menufileimportsvg) + # Open Project ... self.menufileopenproject = QtGui.QAction(QtGui.QIcon('share/folder16.png'), 'Open &Project ...', self) self.menufile.addAction(self.menufileopenproject) diff --git a/camlib.py b/camlib.py index 15ef5c0f..576aac04 100644 --- a/camlib.py +++ b/camlib.py @@ -358,6 +358,7 @@ class Geometry(object): Imports shapes from an SVG file into the object's geometry. :param filename: Path to the SVG file. + :type filename: str :return: None """ diff --git a/svgparse.py b/svgparse.py index 89f858e7..7643a6df 100644 --- a/svgparse.py +++ b/svgparse.py @@ -2,15 +2,24 @@ # FlatCAM: 2D Post-processing for Manufacturing # # http://flatcam.org # # Author: Juan Pablo Caram (c) # -# Date: 12/18/2015 # +# Date: 12/18/2015 # # MIT Licence # +# # +# SVG Features supported: # +# * Groups # +# * Rectangles # +# * Circles # +# * Paths # +# * All transformations # +# # +# Reference: www.w3.org/TR/SVG/Overview.html # ############################################################ import xml.etree.ElementTree as ET import re import itertools from svg.path import Path, Line, Arc, CubicBezier, QuadraticBezier, parse_path -from shapely.geometry import LinearRing, LineString +from shapely.geometry import LinearRing, LineString, Point from shapely.affinity import translate, rotate, scale, skew, affine_transform @@ -43,10 +52,13 @@ def path2shapely(path, res=1.0): if isinstance(component, Arc) or \ isinstance(component, CubicBezier) or \ isinstance(component, QuadraticBezier): + + # How many points to use in the dicrete representation. length = component.length(res / 10.0) steps = int(length / res + 0.5) frac = 1.0 / steps - print length, steps, frac + + # print length, steps, frac for i in range(steps): point = component.point(i * frac) x, y = point.real, point.imag @@ -66,6 +78,16 @@ def path2shapely(path, res=1.0): def svgrect2shapely(rect): + """ + Converts an SVG rect into Shapely geometry. + + :param rect: Rect Element + :type rect: xml.etree.ElementTree.Element + :return: shapely.geometry.polygon.LinearRing + + :param rect: + :return: + """ w = float(rect.get('width')) h = float(rect.get('height')) x = float(rect.get('x')) @@ -76,6 +98,22 @@ def svgrect2shapely(rect): return LinearRing(pts) +def svgcircle2shapely(circle): + """ + Converts an SVG circle into Shapely geometry. + + :param circle: Circle Element + :type circle: xml.etree.ElementTree.Element + :return: shapely.geometry.polygon.LinearRing + """ + cx = float(circle.get('cx')) + cy = float(circle.get('cy')) + r = float(circle.get('r')) + + # TODO: No resolution specified. + return Point(cx, cy).buffer(r) + + def getsvggeo(node): """ Extracts and flattens all geometry from an SVG node @@ -106,6 +144,11 @@ def getsvggeo(node): R = svgrect2shapely(node) geo = [R] + elif kind == 'circle': + print "***CIRCLE***" + C = svgcircle2shapely(node) + geo = [C] + else: print "Unknown kind:", kind geo = None