diff --git a/svgparse.py b/svgparse.py
index f2ecb522..544f0ba6 100644
--- a/svgparse.py
+++ b/svgparse.py
@@ -64,6 +64,7 @@ def path2shapely(path, res=1.0):
:param res: Resolution (minimum step along path)
:return: Shapely geometry object
"""
+
points = []
for component in path:
@@ -86,6 +87,12 @@ def path2shapely(path, res=1.0):
# How many points to use in the dicrete representation.
length = component.length(res / 10.0)
steps = int(length / res + 0.5)
+
+ # solve error when step is below 1,
+ # it may cause other problems, but LineString needs at least two points
+ if steps == 0:
+ steps = 1
+
frac = 1.0 / steps
# print length, steps, frac
@@ -117,9 +124,16 @@ def svgrect2shapely(rect, n_points=32):
"""
w = svgparselength(rect.get('width'))[0]
h = svgparselength(rect.get('height'))[0]
- x = svgparselength(rect.get('x'))[0]
- y = svgparselength(rect.get('y'))[0]
-
+ x_obj = rect.get('x')
+ if x_obj is not None:
+ x = svgparselength(x_obj)[0]
+ else:
+ x = 0
+ y_obj = rect.get('y')
+ if y_obj is not None:
+ y = svgparselength(y_obj)[0]
+ else:
+ y = 0
rxstr = rect.get('rx')
rystr = rect.get('ry')
@@ -305,29 +319,31 @@ def getsvggeo(node):
log.warning("Unknown kind: " + kind)
geo = None
- # Transformations
- if 'transform' in node.attrib:
- trstr = node.get('transform')
- trlist = parse_svg_transform(trstr)
- #log.debug(trlist)
+ # ignore transformation for unknown kind
+ if geo is not None:
+ # Transformations
+ if 'transform' in node.attrib:
+ trstr = node.get('transform')
+ trlist = parse_svg_transform(trstr)
+ #log.debug(trlist)
- # Transformations are applied in reverse order
- for tr in trlist[::-1]:
- if tr[0] == 'translate':
- geo = [translate(geoi, tr[1], tr[2]) for geoi in geo]
- elif tr[0] == 'scale':
- geo = [scale(geoi, tr[0], tr[1], origin=(0, 0))
- for geoi in geo]
- elif tr[0] == 'rotate':
- geo = [rotate(geoi, tr[1], origin=(tr[2], tr[3]))
- for geoi in geo]
- elif tr[0] == 'skew':
- geo = [skew(geoi, tr[1], tr[2], origin=(0, 0))
- for geoi in geo]
- elif tr[0] == 'matrix':
- geo = [affine_transform(geoi, tr[1:]) for geoi in geo]
- else:
- raise Exception('Unknown transformation: %s', tr)
+ # Transformations are applied in reverse order
+ for tr in trlist[::-1]:
+ if tr[0] == 'translate':
+ geo = [translate(geoi, tr[1], tr[2]) for geoi in geo]
+ elif tr[0] == 'scale':
+ geo = [scale(geoi, tr[0], tr[1], origin=(0, 0))
+ for geoi in geo]
+ elif tr[0] == 'rotate':
+ geo = [rotate(geoi, tr[1], origin=(tr[2], tr[3]))
+ for geoi in geo]
+ elif tr[0] == 'skew':
+ geo = [skew(geoi, tr[1], tr[2], origin=(0, 0))
+ for geoi in geo]
+ elif tr[0] == 'matrix':
+ geo = [affine_transform(geoi, tr[1:]) for geoi in geo]
+ else:
+ raise Exception('Unknown transformation: %s', tr)
return geo
@@ -346,7 +362,7 @@ def parse_svg_point_list(ptliststr):
pos = 0
i = 0
- for match in re.finditer(r'(\s*,\s*)|(\s+)', ptliststr):
+ for match in re.finditer(r'(\s*,\s*)|(\s+)', ptliststr.strip(' ')):
val = float(ptliststr[pos:match.start()])
@@ -435,7 +451,7 @@ def parse_svg_transform(trstr):
r'(?:' + comma_or_space_re_str + \
r'(' + number_re_str + r')' + \
comma_or_space_re_str + \
- r'(' + number_re_str + r'))?\*\)'
+ r'(' + number_re_str + r'))?\s*\)'
matrix_re_str = r'matrix\s*\(\s*' + \
r'(' + number_re_str + r')' + comma_or_space_re_str + \
r'(' + number_re_str + r')' + comma_or_space_re_str + \
diff --git a/tclCommands/TclCommand.py b/tclCommands/TclCommand.py
index b93ec752..470358fb 100644
--- a/tclCommands/TclCommand.py
+++ b/tclCommands/TclCommand.py
@@ -388,7 +388,11 @@ class TclCommandSignaled(TclCommand):
return self.output
except Exception as unknown:
- error_info=sys.exc_info()
+ # if error happens inside thread execution, then pass correct error_info to display
+ if self.error_info is not None:
+ error_info = self.error_info
+ else:
+ error_info=sys.exc_info()
self.log.error("TCL command '%s' failed." % str(self))
self.app.display_tcl_error(unknown, error_info)
self.raise_tcl_unknown_error(unknown)
\ No newline at end of file
diff --git a/tclCommands/TclCommandImportSvg.py b/tclCommands/TclCommandImportSvg.py
new file mode 100644
index 00000000..51cc1901
--- /dev/null
+++ b/tclCommands/TclCommandImportSvg.py
@@ -0,0 +1,81 @@
+from ObjectCollection import *
+import TclCommand
+
+
+class TclCommandImportSvg(TclCommand.TclCommandSignaled):
+ """
+ Tcl shell command to import an SVG file as a Geometry Object.
+ """
+
+ # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
+ aliases = ['import_svg']
+
+ # dictionary of types from Tcl command, needs to be ordered
+ arg_names = collections.OrderedDict([
+ ('filename', str)
+ ])
+
+ # dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
+ option_types = collections.OrderedDict([
+ ('type', str),
+ ('outname', str)
+ ])
+
+ # array of mandatory options for current Tcl command: required = {'name','outname'}
+ required = ['filename']
+
+ # structured help for current command, args needs to be ordered
+ help = {
+ 'main': "Import an SVG file as a Geometry Object..",
+ 'args': collections.OrderedDict([
+ ('filename', 'Path to file to open.'),
+ ('type', 'Import as gerber or geometry(default).'),
+ ('outname', 'Name of the resulting Geometry object.')
+ ]),
+ 'examples': []
+ }
+
+ def execute(self, args, unnamed_args):
+ """
+ execute current TCL shell command
+
+ :param args: array of known named arguments and options
+ :param unnamed_args: array of other values which were passed into command
+ without -somename and we do not have them in known arg_names
+ :return: None or exception
+ """
+
+ # How the object should be initialized
+ def obj_init(geo_obj, app_obj):
+
+ if not isinstance(geo_obj, Geometry):
+ self.raise_tcl_error('Expected Geometry or Gerber, got %s %s.' % (outname, type(geo_obj)))
+
+ geo_obj.import_svg(filename)
+
+ filename = args['filename']
+
+ if 'outname' in args:
+ outname = args['outname']
+ else:
+ outname = filename.split('/')[-1].split('\\')[-1]
+
+ if 'type' in args:
+ obj_type = args['type']
+ else:
+ obj_type = 'geometry'
+
+ if obj_type != "geometry" and obj_type != "gerber":
+ self.raise_tcl_error("Option type can be 'geopmetry' or 'gerber' only, got '%s'." % obj_type)
+
+ with self.app.proc_container.new("Import SVG"):
+
+ # Object creation
+ self.app.new_object(obj_type, outname, obj_init)
+
+ # Register recent file
+ self.app.file_opened.emit("svg", filename)
+
+ # GUI feedback
+ self.app.inform.emit("Opened: " + filename)
+
diff --git a/tclCommands/__init__.py b/tclCommands/__init__.py
index 2f733017..0885d143 100644
--- a/tclCommands/__init__.py
+++ b/tclCommands/__init__.py
@@ -8,6 +8,7 @@ import tclCommands.TclCommandCncjob
import tclCommands.TclCommandDrillcncjob
import tclCommands.TclCommandExportGcode
import tclCommands.TclCommandExteriors
+import tclCommands.TclCommandImportSvg
import tclCommands.TclCommandInteriors
import tclCommands.TclCommandIsolate
import tclCommands.TclCommandNew
diff --git a/tests/svg/7segment_9,9.svg b/tests/svg/7segment_9,9.svg
new file mode 100644
index 00000000..ffe7c653
--- /dev/null
+++ b/tests/svg/7segment_9,9.svg
@@ -0,0 +1,34 @@
+
+
+
+
diff --git a/tests/svg/Arduino Nano3_pcb.svg b/tests/svg/Arduino Nano3_pcb.svg
new file mode 100644
index 00000000..f1f3b0c2
--- /dev/null
+++ b/tests/svg/Arduino Nano3_pcb.svg
@@ -0,0 +1,468 @@
+
+
+
+
diff --git a/tests/svg/usb_connector.svg b/tests/svg/usb_connector.svg
new file mode 100644
index 00000000..25db7071
--- /dev/null
+++ b/tests/svg/usb_connector.svg
@@ -0,0 +1,77 @@
+
\ No newline at end of file
diff --git a/tests/test_tcl_shell.py b/tests/test_tcl_shell.py
index d36f30ed..cc415f7f 100644
--- a/tests/test_tcl_shell.py
+++ b/tests/test_tcl_shell.py
@@ -4,6 +4,8 @@ from PyQt4 import QtGui
from PyQt4.QtCore import QThread
from FlatCAMApp import App
+from os import listdir
+from os.path import isfile
from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMCNCjob, FlatCAMExcellon
from ObjectUI import GerberObjectUI, GeometryObjectUI
from time import sleep
@@ -12,11 +14,15 @@ import tempfile
class TclShellTest(unittest.TestCase):
+ svg_files = 'tests/svg'
+ svg_filename = 'Arduino Nano3_pcb.svg'
gerber_files = 'tests/gerber_files'
copper_bottom_filename = 'detector_copper_bottom.gbr'
copper_top_filename = 'detector_copper_top.gbr'
cutout_filename = 'detector_contour.gbr'
excellon_filename = 'detector_drill.txt'
+ gerber_name = "gerber"
+ geometry_name = "geometry"
excellon_name = "excellon"
gerber_top_name = "top"
gerber_bottom_name = "bottom"
@@ -177,4 +183,65 @@ class TclShellTest(unittest.TestCase):
# mirror bottom excellon
self.fc.exec_command_test('mirror %s -box %s -axis X' % (self.excellon_name, self.gerber_cutout_name))
- # TODO: tests for tcl
\ No newline at end of file
+ # TODO: tests for tcl
+
+ def test_import_svg(self):
+ """
+ Test all SVG files inside svg directory.
+ Problematic SVG files shold be put there as test reference.
+ :return:
+ """
+
+ self.fc.exec_command_test('set_sys units MM')
+ self.fc.exec_command_test('new')
+
+ file_list = listdir(self.svg_files)
+
+ for svg_file in file_list:
+
+ # import without outname
+ self.fc.exec_command_test('import_svg "%s/%s"' % (self.svg_files, svg_file))
+
+ obj = self.fc.collection.get_by_name(svg_file)
+ self.assertTrue(isinstance(obj, FlatCAMGeometry),
+ "Expected FlatCAMGeometry, instead, %s is %s" %
+ (svg_file, type(obj)))
+
+ # import with outname
+ outname='%s-%s' % (self.geometry_name, svg_file)
+ self.fc.exec_command_test('import_svg "%s/%s" -outname "%s"' % (self.svg_files, svg_file, outname))
+
+ obj = self.fc.collection.get_by_name(outname)
+ self.assertTrue(isinstance(obj, FlatCAMGeometry),
+ "Expected FlatCAMGeometry, instead, %s is %s" %
+ (outname, type(obj)))
+
+ names = self.fc.collection.get_names()
+ self.assertEqual(len(names), len(file_list)*2,
+ "Expected %d objects, found %d" % (len(file_list)*2, len(file_list)))
+
+ def test_import_svg_as_geometry(self):
+ self.fc.exec_command_test('set_sys units MM')
+ self.fc.exec_command_test('new')
+ self.fc.exec_command_test('import_svg "%s/%s" -type geometry -outname "%s"' % (self.svg_files, self.svg_filename, self.geometry_name))
+
+ obj = self.fc.collection.get_by_name(self.geometry_name)
+ self.assertTrue(isinstance(obj, FlatCAMGeometry) and not isinstance(obj, FlatCAMGerber),
+ "Expected FlatCAMGeometry, instead, %s is %s" %
+ (self.geometry_name, type(obj)))
+
+ def test_import_svg_as_gerber(self):
+ self.fc.exec_command_test('set_sys units MM')
+ self.fc.exec_command_test('new')
+ self.fc.exec_command_test('import_svg "%s/%s" -type gerber -outname "%s"' % (self.svg_files, self.svg_filename, self.gerber_name))
+
+ obj = self.fc.collection.get_by_name(self.gerber_name)
+ self.assertTrue(isinstance(obj, FlatCAMGerber),
+ "Expected FlatCAMGerber, instead, %s is %s" %
+ (self.gerber_name, type(obj)))
+
+ self.fc.exec_command_test('isolate "%s"' % self.gerber_name)
+ obj = self.fc.collection.get_by_name(self.gerber_name+'_iso')
+ self.assertTrue(isinstance(obj, FlatCAMGeometry),
+ "Expected FlatCAMGeometry, instead, %s is %s" %
+ (self.gerber_name+'_iso', type(obj)))