Fix invalid string comparisons
[blender-addons.git] / io_curve_svg / import_svg.py
blob6b45d91d057b714a364fd900ab6fde906a36569a
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8 compliant>
21 import re
22 import xml.dom.minidom
23 from math import cos, sin, tan, atan2, pi, ceil
25 import bpy
26 from mathutils import Vector, Matrix
28 from . import svg_colors
30 #### Common utilities ####
32 # TODO: "em" and "ex" aren't actually supported
33 SVGUnits = {"": 1.0,
34 "px": 1.0,
35 "in": 90.0,
36 "mm": 90.0 / 25.4,
37 "cm": 90.0 / 2.54,
38 "pt": 1.25,
39 "pc": 15.0,
40 "em": 1.0,
41 "ex": 1.0,
42 "INVALID": 1.0, # some DocBook files contain this
45 SVGEmptyStyles = {'useFill': None,
46 'fill': None}
48 def srgb_to_linearrgb(c):
49 if c < 0.04045:
50 return 0.0 if c < 0.0 else c * (1.0 / 12.92)
51 else:
52 return pow((c + 0.055) * (1.0 / 1.055), 2.4)
54 def check_points_equal(point_a, point_b):
55 return (abs(point_a[0] - point_b[0]) < 1e-6 and
56 abs(point_a[1] - point_b[1]) < 1e-6)
59 def SVGParseFloat(s, i=0):
60 """
61 Parse first float value from string
63 Returns value as string
64 """
66 start = i
67 n = len(s)
68 token = ''
70 # Skip leading whitespace characters
71 while i < n and (s[i].isspace() or s[i] == ','):
72 i += 1
74 if i == n:
75 return None, i
77 # Read sign
78 if s[i] == '-':
79 token += '-'
80 i += 1
81 elif s[i] == '+':
82 i += 1
84 # Read integer part
85 if s[i].isdigit():
86 while i < n and s[i].isdigit():
87 token += s[i]
88 i += 1
90 # Fractional part
91 if i < n and s[i] == '.':
92 token += '.'
93 i += 1
95 if s[i].isdigit():
96 while i < n and s[i].isdigit():
97 token += s[i]
98 i += 1
99 elif s[i].isspace() or s[i] == ',':
100 # Inkscape sometimes uses weird float format with missed
101 # fractional part after dot. Suppose zero fractional part
102 # for this case
103 pass
104 else:
105 raise Exception('Invalid float value near ' + s[start:start + 10])
107 # Degree
108 if i < n and (s[i] == 'e' or s[i] == 'E'):
109 token += s[i]
110 i += 1
111 if s[i] == '+' or s[i] == '-':
112 token += s[i]
113 i += 1
115 if s[i].isdigit():
116 while i < n and s[i].isdigit():
117 token += s[i]
118 i += 1
119 else:
120 raise Exception('Invalid float value near ' + s[start:start + 10])
122 return token, i
125 def SVGCreateCurve():
127 Create new curve object to hold splines in
130 cu = bpy.data.curves.new("Curve", 'CURVE')
131 obj = bpy.data.objects.new("Curve", cu)
132 bpy.context.scene.objects.link(obj)
134 return obj
137 def SVGFinishCurve():
139 Finish curve creation
142 pass
145 def SVGFlipHandle(x, y, x1, y1):
147 Flip handle around base point
150 x = x + (x - x1)
151 y = y + (y - y1)
153 return x, y
156 def SVGParseCoord(coord, size):
158 Parse coordinate component to common basis
160 Needed to handle coordinates set in cm, mm, iches..
163 token, last_char = SVGParseFloat(coord)
164 val = float(token)
165 unit = coord[last_char:].strip() # strip() in case there is a space
167 if unit == '%':
168 return float(size) / 100.0 * val
169 else:
170 return val * SVGUnits[unit]
172 return val
175 def SVGRectFromNode(node, context):
177 Get display rectangle from node
180 w = context['rect'][0]
181 h = context['rect'][1]
183 if node.getAttribute('viewBox'):
184 viewBox = node.getAttribute('viewBox').replace(',', ' ').split()
185 w = SVGParseCoord(viewBox[2], w)
186 h = SVGParseCoord(viewBox[3], h)
187 else:
188 if node.getAttribute('width'):
189 w = SVGParseCoord(node.getAttribute('width'), w)
191 if node.getAttribute('height'):
192 h = SVGParseCoord(node.getAttribute('height'), h)
194 return (w, h)
197 def SVGMatrixFromNode(node, context):
199 Get transformation matrix from given node
202 tagName = node.tagName.lower()
203 tags = ['svg:svg', 'svg:use', 'svg:symbol']
205 if tagName not in tags and 'svg:' + tagName not in tags:
206 return Matrix()
208 rect = context['rect']
209 has_user_coordinate = (len(context['rects']) > 1)
211 m = Matrix()
212 x = SVGParseCoord(node.getAttribute('x') or '0', rect[0])
213 y = SVGParseCoord(node.getAttribute('y') or '0', rect[1])
214 w = SVGParseCoord(node.getAttribute('width') or str(rect[0]), rect[0])
215 h = SVGParseCoord(node.getAttribute('height') or str(rect[1]), rect[1])
217 m = Matrix.Translation(Vector((x, y, 0.0)))
218 if has_user_coordinate:
219 if rect[0] != 0 and rect[1] != 0:
220 m = m * Matrix.Scale(w / rect[0], 4, Vector((1.0, 0.0, 0.0)))
221 m = m * Matrix.Scale(h / rect[1], 4, Vector((0.0, 1.0, 0.0)))
223 if node.getAttribute('viewBox'):
224 viewBox = node.getAttribute('viewBox').replace(',', ' ').split()
225 vx = SVGParseCoord(viewBox[0], w)
226 vy = SVGParseCoord(viewBox[1], h)
227 vw = SVGParseCoord(viewBox[2], w)
228 vh = SVGParseCoord(viewBox[3], h)
230 if vw == 0 or vh == 0:
231 return m
233 if has_user_coordinate or (w != 0 and h != 0):
234 sx = w / vw
235 sy = h / vh
236 scale = min(sx, sy)
237 else:
238 scale = 1.0
239 w = vw
240 h = vh
242 tx = (w - vw * scale) / 2
243 ty = (h - vh * scale) / 2
244 m = m * Matrix.Translation(Vector((tx, ty, 0.0)))
246 m = m * Matrix.Translation(Vector((-vx, -vy, 0.0)))
247 m = m * Matrix.Scale(scale, 4, Vector((1.0, 0.0, 0.0)))
248 m = m * Matrix.Scale(scale, 4, Vector((0.0, 1.0, 0.0)))
250 return m
253 def SVGParseTransform(transform):
255 Parse transform string and return transformation matrix
258 m = Matrix()
259 r = re.compile('\s*([A-z]+)\s*\((.*?)\)')
261 for match in r.finditer(transform):
262 func = match.group(1)
263 params = match.group(2)
264 params = params.replace(',', ' ').split()
266 proc = SVGTransforms.get(func)
267 if proc is None:
268 raise Exception('Unknown trasnform function: ' + func)
270 m = m * proc(params)
272 return m
275 def SVGGetMaterial(color, context):
277 Get material for specified color
280 materials = context['materials']
281 rgb_re = re.compile('^\s*rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,(\d+)\s*\)\s*$')
283 if color in materials:
284 return materials[color]
286 diff = None
287 if color.startswith('#'):
288 color = color[1:]
290 if len(color) == 3:
291 color = color[0] * 2 + color[1] * 2 + color[2] * 2
293 diff = (int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16))
294 elif color in svg_colors.SVGColors:
295 diff = svg_colors.SVGColors[color]
296 elif rgb_re.match(color):
297 c = rgb_re.findall(color)[0]
298 diff = (float(c[0]), float(c[1]), float(c[2]))
299 else:
300 return None
302 diffuse_color = ([x / 255.0 for x in diff])
304 if context['do_colormanage']:
305 diffuse_color[0] = srgb_to_linearrgb(diffuse_color[0])
306 diffuse_color[1] = srgb_to_linearrgb(diffuse_color[1])
307 diffuse_color[2] = srgb_to_linearrgb(diffuse_color[2])
309 mat = bpy.data.materials.new(name='SVGMat')
310 mat.diffuse_color = diffuse_color
311 mat.diffuse_intensity = 1.0
313 materials[color] = mat
315 return mat
318 def SVGTransformTranslate(params):
320 translate SVG transform command
323 tx = float(params[0])
324 ty = float(params[1]) if len(params) > 1 else 0.0
326 return Matrix.Translation(Vector((tx, ty, 0.0)))
329 def SVGTransformMatrix(params):
331 matrix SVG transform command
334 a = float(params[0])
335 b = float(params[1])
336 c = float(params[2])
337 d = float(params[3])
338 e = float(params[4])
339 f = float(params[5])
341 return Matrix(((a, c, 0.0, e),
342 (b, d, 0.0, f),
343 (0, 0, 1.0, 0),
344 (0, 0, 0.0, 1)))
347 def SVGTransformScale(params):
349 scale SVG transform command
352 sx = float(params[0])
353 sy = float(params[1]) if len(params) > 1 else sx
355 m = Matrix()
357 m = m * Matrix.Scale(sx, 4, Vector((1.0, 0.0, 0.0)))
358 m = m * Matrix.Scale(sy, 4, Vector((0.0, 1.0, 0.0)))
360 return m
363 def SVGTransformSkewX(params):
365 skewX SVG transform command
368 ang = float(params[0]) * pi / 180.0
370 return Matrix(((1.0, 0.0, 0.0),
371 (tan(ang), 1.0, 0.0),
372 (0.0, 0.0, 1.0))).to_4x4()
375 def SVGTransformSkewY(params):
377 skewX SVG transform command
380 ang = float(params[0]) * pi / 180.0
382 return Matrix(((1.0, tan(ang), 0.0),
383 (0.0, 1.0, 0.0),
384 (0.0, 0.0, 1.0))).to_4x4()
387 def SVGTransformRotate(params):
389 skewX SVG transform command
392 ang = float(params[0]) * pi / 180.0
393 cx = cy = 0.0
395 if len(params) >= 3:
396 cx = float(params[1])
397 cy = float(params[2])
399 tm = Matrix.Translation(Vector((cx, cy, 0.0)))
400 rm = Matrix.Rotation(ang, 4, Vector((0.0, 0.0, 1.0)))
402 return tm * rm * tm.inverted()
404 SVGTransforms = {'translate': SVGTransformTranslate,
405 'scale': SVGTransformScale,
406 'skewX': SVGTransformSkewX,
407 'skewY': SVGTransformSkewY,
408 'matrix': SVGTransformMatrix,
409 'rotate': SVGTransformRotate}
412 def SVGParseStyles(node, context):
414 Parse node to get different styles for displaying geometries
415 (materials, filling flags, etc..)
418 styles = SVGEmptyStyles.copy()
420 style = node.getAttribute('style')
421 if style:
422 elems = style.split(';')
423 for elem in elems:
424 s = elem.split(':')
426 if len(s) != 2:
427 continue
429 name = s[0].strip().lower()
430 val = s[1].strip()
432 if name == 'fill':
433 val = val.lower()
434 if val == 'none':
435 styles['useFill'] = False
436 else:
437 styles['useFill'] = True
438 styles['fill'] = SVGGetMaterial(val, context)
440 if styles['useFill'] is None:
441 styles['useFill'] = True
442 styles['fill'] = SVGGetMaterial('#000', context)
444 return styles
446 if styles['useFill'] is None:
447 fill = node.getAttribute('fill')
448 if fill:
449 fill = fill.lower()
450 if fill == 'none':
451 styles['useFill'] = False
452 else:
453 styles['useFill'] = True
454 styles['fill'] = SVGGetMaterial(fill, context)
456 if styles['useFill'] is None and context['style']:
457 styles = context['style'].copy()
459 if styles['useFill'] is None:
460 styles['useFill'] = True
461 styles['fill'] = SVGGetMaterial('#000', context)
463 return styles
465 #### SVG path helpers ####
468 class SVGPathData:
470 SVG Path data token supplier
473 __slots__ = ('_data', # List of tokens
474 '_index', # Index of current token in tokens list
475 '_len') # Length of tokens list
477 def __init__(self, d):
479 Initialize new path data supplier
481 d - the definition of the outline of a shape
484 spaces = ' ,\t'
485 commands = {'m', 'l', 'h', 'v', 'c', 's', 'q', '', 't', 'a', 'z'}
486 tokens = []
488 i = 0
489 n = len(d)
490 while i < n:
491 c = d[i]
493 if c in spaces:
494 pass
495 elif c.lower() in commands:
496 tokens.append(c)
497 elif c in ['-', '.'] or c.isdigit():
498 token, last_char = SVGParseFloat(d, i)
499 tokens.append(token)
501 # in most cases len(token) and (last_char - i) are the same
502 # but with whitespace or ',' prefix they are not.
504 i += (last_char - i) - 1
506 i += 1
508 self._data = tokens
509 self._index = 0
510 self._len = len(tokens)
512 def eof(self):
514 Check if end of data reached
517 return self._index >= self._len
519 def cur(self):
521 Return current token
524 if self.eof():
525 return None
527 return self._data[self._index]
529 def lookupNext(self):
531 get next token without moving pointer
534 if self.eof():
535 return None
537 return self._data[self._index]
539 def next(self):
541 Return current token and go to next one
544 if self.eof():
545 return None
547 token = self._data[self._index]
548 self._index += 1
550 return token
552 def nextCoord(self):
554 Return coordinate created from current token and move to next token
557 token = self.next()
559 if token is None:
560 return None
562 return float(token)
565 class SVGPathParser:
567 Parser of SVG path data
570 __slots__ = ('_data', # Path data supplird
571 '_point', # Current point coorfinate
572 '_handle', # Last handle coordinate
573 '_splines', # List of all splies created during parsing
574 '_spline', # Currently handling spline
575 '_commands', # Hash of all supported path commands
576 '_use_fill', # Splines would be filled, so expected to be closed
579 def __init__(self, d, use_fill):
581 Initialize path parser
583 d - the definition of the outline of a shape
586 self._data = SVGPathData(d)
587 self._point = None # Current point
588 self._handle = None # Last handle
589 self._splines = [] # List of splines in path
590 self._spline = None # Current spline
591 self._use_fill = use_fill
593 self._commands = {'M': self._pathMoveTo,
594 'L': self._pathLineTo,
595 'H': self._pathLineTo,
596 'V': self._pathLineTo,
597 'C': self._pathCurveToCS,
598 'S': self._pathCurveToCS,
599 'Q': self._pathCurveToQT,
600 'T': self._pathCurveToQT,
601 'A': self._pathCurveToA,
602 'Z': self._pathClose,
604 'm': self._pathMoveTo,
605 'l': self._pathLineTo,
606 'h': self._pathLineTo,
607 'v': self._pathLineTo,
608 'c': self._pathCurveToCS,
609 's': self._pathCurveToCS,
610 'q': self._pathCurveToQT,
611 't': self._pathCurveToQT,
612 'a': self._pathCurveToA,
613 'z': self._pathClose}
615 def _getCoordPair(self, relative, point):
617 Get next coordinate pair
620 x = self._data.nextCoord()
621 y = self._data.nextCoord()
623 if relative and point is not None:
624 x += point[0]
625 y += point[1]
627 return x, y
629 def _appendPoint(self, x, y, handle_left=None, handle_left_type='VECTOR',
630 handle_right=None, handle_right_type='VECTOR'):
632 Append point to spline
634 If there's no active spline, create one and set it's first point
635 to current point coordinate
638 if self._spline is None:
639 self._spline = {'points': [],
640 'closed': False}
642 self._splines.append(self._spline)
644 if len(self._spline['points']) > 0:
645 # Not sure about specifications, but Illustrator could create
646 # last point at the same position, as start point (which was
647 # reached by MoveTo command) to set needed handle coords.
648 # It's also could use last point at last position to make path
649 # filled.
651 first = self._spline['points'][0]
652 if check_points_equal((first['x'], first['y']), (x, y)):
653 if handle_left is not None:
654 first['handle_left'] = handle_left
655 first['handle_left_type'] = 'FREE'
657 if handle_left_type != 'VECTOR':
658 first['handle_left_type'] = handle_left_type
660 if self._data.eof() or self._data.lookupNext().lower() == 'm':
661 self._spline['closed'] = True
663 return
665 last = self._spline['points'][-1]
666 if last['handle_right_type'] == 'VECTOR' and handle_left_type == 'FREE':
667 last['handle_right'] = (last['x'], last['y'])
668 last['handle_right_type'] = 'FREE'
669 if last['handle_right_type'] == 'FREE' and handle_left_type == 'VECTOR':
670 handle_left = (x, y)
671 handle_left_type = 'FREE'
673 point = {'x': x,
674 'y': y,
676 'handle_left': handle_left,
677 'handle_left_type': handle_left_type,
679 'handle_right': handle_right,
680 'handle_right_type': handle_right_type}
682 self._spline['points'].append(point)
684 def _updateHandle(self, handle=None, handle_type=None):
686 Update right handle of previous point when adding new point to spline
689 point = self._spline['points'][-1]
691 if handle_type is not None:
692 point['handle_right_type'] = handle_type
694 if handle is not None:
695 point['handle_right'] = handle
697 def _pathMoveTo(self, code):
699 MoveTo path command
702 relative = code.islower()
703 x, y = self._getCoordPair(relative, self._point)
705 self._spline = None # Flag to start new spline
706 self._point = (x, y)
708 cur = self._data.cur()
709 while cur is not None and not cur.isalpha():
710 x, y = self._getCoordPair(relative, self._point)
712 if self._spline is None:
713 self._appendPoint(self._point[0], self._point[1])
715 self._appendPoint(x, y)
717 self._point = (x, y)
718 cur = self._data.cur()
720 self._handle = None
722 def _pathLineTo(self, code):
724 LineTo path command
727 c = code.lower()
729 cur = self._data.cur()
730 while cur is not None and not cur.isalpha():
731 if c == 'l':
732 x, y = self._getCoordPair(code == 'l', self._point)
733 elif c == 'h':
734 x = self._data.nextCoord()
735 y = self._point[1]
736 else:
737 x = self._point[0]
738 y = self._data.nextCoord()
740 if code == 'h':
741 x += self._point[0]
742 elif code == 'v':
743 y += self._point[1]
745 if self._spline is None:
746 self._appendPoint(self._point[0], self._point[1])
748 self._appendPoint(x, y)
750 self._point = (x, y)
751 cur = self._data.cur()
753 self._handle = None
755 def _pathCurveToCS(self, code):
757 Cubic BEZIER CurveTo path command
760 c = code.lower()
761 cur = self._data.cur()
762 while cur is not None and not cur.isalpha():
763 if c == 'c':
764 x1, y1 = self._getCoordPair(code.islower(), self._point)
765 x2, y2 = self._getCoordPair(code.islower(), self._point)
766 else:
767 if self._handle is not None:
768 x1, y1 = SVGFlipHandle(self._point[0], self._point[1],
769 self._handle[0], self._handle[1])
770 else:
771 x1, y1 = self._point
773 x2, y2 = self._getCoordPair(code.islower(), self._point)
775 x, y = self._getCoordPair(code.islower(), self._point)
777 if self._spline is None:
778 self._appendPoint(self._point[0], self._point[1],
779 handle_left_type='FREE', handle_left=self._point,
780 handle_right_type='FREE', handle_right=(x1, y1))
781 else:
782 self._updateHandle(handle=(x1, y1), handle_type='FREE')
784 self._appendPoint(x, y,
785 handle_left_type='FREE', handle_left=(x2, y2),
786 handle_right_type='FREE', handle_right=(x, y))
788 self._point = (x, y)
789 self._handle = (x2, y2)
790 cur = self._data.cur()
792 def _pathCurveToQT(self, code):
794 Quadratic BEZIER CurveTo path command
797 c = code.lower()
798 cur = self._data.cur()
800 while cur is not None and not cur.isalpha():
801 if c == 'q':
802 x1, y1 = self._getCoordPair(code.islower(), self._point)
803 else:
804 if self._handle is not None:
805 x1, y1 = SVGFlipHandle(self._point[0], self._point[1],
806 self._handle[0], self._handle[1])
807 else:
808 x1, y1 = self._point
810 x, y = self._getCoordPair(code.islower(), self._point)
812 if not check_points_equal((x, y), self._point):
813 if self._spline is None:
814 self._appendPoint(self._point[0], self._point[1],
815 handle_left_type='FREE', handle_left=self._point,
816 handle_right_type='FREE', handle_right=self._point)
818 self._appendPoint(x, y,
819 handle_left_type='FREE', handle_left=(x1, y1),
820 handle_right_type='FREE', handle_right=(x, y))
822 self._point = (x, y)
823 self._handle = (x1, y1)
824 cur = self._data.cur()
826 def _calcArc(self, rx, ry, ang, fa, fs, x, y):
828 Calc arc paths
830 Copied and adoptedfrom paths_svg2obj.py script for Blender 2.49
831 which is Copyright (c) jm soler juillet/novembre 2004-april 2009,
834 cpx = self._point[0]
835 cpy = self._point[1]
836 rx = abs(rx)
837 ry = abs(ry)
838 px = abs((cos(ang) * (cpx - x) + sin(ang) * (cpy - y)) * 0.5) ** 2.0
839 py = abs((cos(ang) * (cpy - y) - sin(ang) * (cpx - x)) * 0.5) ** 2.0
840 rpx = rpy = 0.0
842 if abs(rx) > 0.0:
843 px = px / (rx ** 2.0)
845 if abs(ry) > 0.0:
846 rpy = py / (ry ** 2.0)
848 pl = rpx + rpy
849 if pl > 1.0:
850 pl = pl ** 0.5
851 rx *= pl
852 ry *= pl
854 carx = sarx = cary = sary = 0.0
856 if abs(rx) > 0.0:
857 carx = cos(ang) / rx
858 sarx = sin(ang) / rx
860 if abs(ry) > 0.0:
861 cary = cos(ang) / ry
862 sary = sin(ang) / ry
864 x0 = carx * cpx + sarx * cpy
865 y0 = -sary * cpx + cary * cpy
866 x1 = carx * x + sarx * y
867 y1 = -sary * x + cary * y
868 d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)
870 if abs(d) > 0.0:
871 sq = 1.0 / d - 0.25
872 else:
873 sq = -0.25
875 if sq < 0.0:
876 sq = 0.0
878 sf = sq ** 0.5
879 if fs == fa:
880 sf = -sf
882 xc = 0.5 * (x0 + x1) - sf * (y1 - y0)
883 yc = 0.5 * (y0 + y1) + sf * (x1 - x0)
884 ang_0 = atan2(y0 - yc, x0 - xc)
885 ang_1 = atan2(y1 - yc, x1 - xc)
886 ang_arc = ang_1 - ang_0
888 if ang_arc < 0.0 and fs == 1:
889 ang_arc += 2.0 * pi
890 elif ang_arc > 0.0 and fs == 0:
891 ang_arc -= 2.0 * pi
893 n_segs = int(ceil(abs(ang_arc * 2.0 / (pi * 0.5 + 0.001))))
895 if self._spline is None:
896 self._appendPoint(cpx, cpy,
897 handle_left_type='FREE', handle_left=(cpx, cpy),
898 handle_right_type='FREE', handle_right=(cpx, cpy))
900 for i in range(n_segs):
901 ang0 = ang_0 + i * ang_arc / n_segs
902 ang1 = ang_0 + (i + 1) * ang_arc / n_segs
903 ang_demi = 0.25 * (ang1 - ang0)
904 t = 2.66666 * sin(ang_demi) * sin(ang_demi) / sin(ang_demi * 2.0)
905 x1 = xc + cos(ang0) - t * sin(ang0)
906 y1 = yc + sin(ang0) + t * cos(ang0)
907 x2 = xc + cos(ang1)
908 y2 = yc + sin(ang1)
909 x3 = x2 + t * sin(ang1)
910 y3 = y2 - t * cos(ang1)
912 coord1 = ((cos(ang) * rx) * x1 + (-sin(ang) * ry) * y1,
913 (sin(ang) * rx) * x1 + (cos(ang) * ry) * y1)
914 coord2 = ((cos(ang) * rx) * x3 + (-sin(ang) * ry) * y3,
915 (sin(ang) * rx) * x3 + (cos(ang) * ry) * y3)
916 coord3 = ((cos(ang) * rx) * x2 + (-sin(ang) * ry) * y2,
917 (sin(ang) * rx) * x2 + (cos(ang) * ry) * y2)
919 self._updateHandle(handle=coord1, handle_type='FREE')
921 self._appendPoint(coord3[0], coord3[1],
922 handle_left_type='FREE', handle_left=coord2,
923 handle_right_type='FREE', handle_right=coord3)
925 def _pathCurveToA(self, code):
927 Elliptical arc CurveTo path command
930 cur = self._data.cur()
932 while cur is not None and not cur.isalpha():
933 rx = float(self._data.next())
934 ry = float(self._data.next())
935 ang = float(self._data.next()) / 180 * pi
936 fa = float(self._data.next())
937 fs = float(self._data.next())
938 x, y = self._getCoordPair(code.islower(), self._point)
940 self._calcArc(rx, ry, ang, fa, fs, x, y)
942 self._point = (x, y)
943 self._handle = None
944 cur = self._data.cur()
946 def _pathClose(self, code):
948 Close path command
951 if self._spline:
952 self._spline['closed'] = True
954 cv = self._spline['points'][0]
955 self._point = (cv['x'], cv['y'])
957 def parse(self):
959 Execute parser
962 closed = False
964 while not self._data.eof():
965 code = self._data.next()
966 cmd = self._commands.get(code)
968 if cmd is None:
969 raise Exception('Unknown path command: {0}' . format(code))
971 if cmd in {'Z', 'z'}:
972 closed = True
973 else:
974 closed = False
976 cmd(code)
977 if self._use_fill and not closed:
978 self._pathClose('z')
980 def getSplines(self):
982 Get splines definitions
985 return self._splines
988 class SVGGeometry:
990 Abstract SVG geometry
993 __slots__ = ('_node', # XML node for geometry
994 '_context', # Global SVG context (holds matrices stack, i.e.)
995 '_creating') # Flag if geometry is already creating
996 # for this node
997 # need to detect cycles for USE node
999 def __init__(self, node, context):
1001 Initialize SVG geometry
1004 self._node = node
1005 self._context = context
1006 self._creating = False
1008 if hasattr(node, 'getAttribute'):
1009 defs = context['defines']
1011 attr_id = node.getAttribute('id')
1012 if attr_id and defs.get('#' + attr_id) is None:
1013 defs['#' + attr_id] = self
1015 className = node.getAttribute('class')
1016 if className and defs.get(className) is None:
1017 defs[className] = self
1019 def _pushRect(self, rect):
1021 Push display rectangle
1024 self._context['rects'].append(rect)
1025 self._context['rect'] = rect
1027 def _popRect(self):
1029 Pop display rectangle
1032 self._context['rects'].pop()
1033 self._context['rect'] = self._context['rects'][-1]
1035 def _pushMatrix(self, matrix):
1037 Push transformation matrix
1040 self._context['transform'].append(matrix)
1041 self._context['matrix'] = self._context['matrix'] * matrix
1043 def _popMatrix(self):
1045 Pop transformation matrix
1048 matrix = self._context['transform'].pop()
1049 self._context['matrix'] = self._context['matrix'] * matrix.inverted()
1051 def _pushStyle(self, style):
1053 Push style
1056 self._context['styles'].append(style)
1057 self._context['style'] = style
1059 def _popStyle(self):
1061 Pop style
1064 self._context['styles'].pop()
1065 self._context['style'] = self._context['styles'][-1]
1067 def _transformCoord(self, point):
1069 Transform SVG-file coords
1072 v = Vector((point[0], point[1], 0.0))
1074 return self._context['matrix'] * v
1076 def getNodeMatrix(self):
1078 Get transformation matrix of node
1081 return SVGMatrixFromNode(self._node, self._context)
1083 def parse(self):
1085 Parse XML node to memory
1088 pass
1090 def _doCreateGeom(self, instancing):
1092 Internal handler to create real geometries
1095 pass
1097 def getTransformMatrix(self):
1099 Get matrix created from "transform" attribute
1102 transform = self._node.getAttribute('transform')
1104 if transform:
1105 return SVGParseTransform(transform)
1107 return None
1109 def createGeom(self, instancing):
1111 Create real geometries
1114 if self._creating:
1115 return
1117 self._creating = True
1119 matrix = self.getTransformMatrix()
1120 if matrix is not None:
1121 self._pushMatrix(matrix)
1123 self._doCreateGeom(instancing)
1125 if matrix is not None:
1126 self._popMatrix()
1128 self._creating = False
1131 class SVGGeometryContainer(SVGGeometry):
1133 Container of SVG geometries
1136 __slots__ = ('_geometries', # List of chold geometries
1137 '_styles') # Styles, used for displaying
1139 def __init__(self, node, context):
1141 Initialize SVG geometry container
1144 super().__init__(node, context)
1146 self._geometries = []
1147 self._styles = SVGEmptyStyles
1149 def parse(self):
1151 Parse XML node to memory
1154 if type(self._node) is xml.dom.minidom.Element:
1155 self._styles = SVGParseStyles(self._node, self._context)
1157 self._pushStyle(self._styles)
1159 for node in self._node.childNodes:
1160 if type(node) is not xml.dom.minidom.Element:
1161 continue
1163 ob = parseAbstractNode(node, self._context)
1164 if ob is not None:
1165 self._geometries.append(ob)
1167 self._popStyle()
1169 def _doCreateGeom(self, instancing):
1171 Create real geometries
1174 for geom in self._geometries:
1175 geom.createGeom(instancing)
1177 def getGeometries(self):
1179 Get list of parsed geometries
1182 return self._geometries
1185 class SVGGeometryPATH(SVGGeometry):
1187 SVG path geometry
1190 __slots__ = ('_splines', # List of splines after parsing
1191 '_styles') # Styles, used for displaying
1193 def __init__(self, node, context):
1195 Initialize SVG path
1198 super().__init__(node, context)
1200 self._splines = []
1201 self._styles = SVGEmptyStyles
1203 def parse(self):
1205 Parse SVG path node
1208 d = self._node.getAttribute('d')
1210 self._styles = SVGParseStyles(self._node, self._context)
1212 pathParser = SVGPathParser(d, self._styles['useFill'])
1213 pathParser.parse()
1215 self._splines = pathParser.getSplines()
1217 def _doCreateGeom(self, instancing):
1219 Create real geometries
1222 ob = SVGCreateCurve()
1223 cu = ob.data
1225 if self._node.getAttribute('id'):
1226 cu.name = self._node.getAttribute('id')
1228 if self._styles['useFill']:
1229 cu.dimensions = '2D'
1230 cu.materials.append(self._styles['fill'])
1231 else:
1232 cu.dimensions = '3D'
1234 for spline in self._splines:
1235 act_spline = None
1237 if spline['closed'] and len(spline['points']) >= 2:
1238 first = spline['points'][0]
1239 last = spline['points'][-1]
1240 if ( first['handle_left_type'] == 'FREE' and
1241 last['handle_right_type'] == 'VECTOR'):
1242 last['handle_right_type'] = 'FREE'
1243 last['handle_right'] = (last['x'], last['y'])
1244 if ( last['handle_right_type'] == 'FREE' and
1245 first['handle_left_type'] == 'VECTOR'):
1246 first['handle_left_type'] = 'FREE'
1247 first['handle_left'] = (first['x'], first['y'])
1249 for point in spline['points']:
1250 co = self._transformCoord((point['x'], point['y']))
1252 if act_spline is None:
1253 cu.splines.new('BEZIER')
1255 act_spline = cu.splines[-1]
1256 act_spline.use_cyclic_u = spline['closed']
1257 else:
1258 act_spline.bezier_points.add()
1260 bezt = act_spline.bezier_points[-1]
1261 bezt.co = co
1263 bezt.handle_left_type = point['handle_left_type']
1264 if point['handle_left'] is not None:
1265 handle = point['handle_left']
1266 bezt.handle_left = self._transformCoord(handle)
1268 bezt.handle_right_type = point['handle_right_type']
1269 if point['handle_right'] is not None:
1270 handle = point['handle_right']
1271 bezt.handle_right = self._transformCoord(handle)
1273 SVGFinishCurve()
1276 class SVGGeometryDEFS(SVGGeometryContainer):
1278 Container for referenced elements
1281 def createGeom(self, instancing):
1283 Create real geometries
1286 pass
1289 class SVGGeometrySYMBOL(SVGGeometryContainer):
1291 Referenced element
1294 def _doCreateGeom(self, instancing):
1296 Create real geometries
1299 self._pushMatrix(self.getNodeMatrix())
1301 super()._doCreateGeom(False)
1303 self._popMatrix()
1305 def createGeom(self, instancing):
1307 Create real geometries
1310 if not instancing:
1311 return
1313 super().createGeom(instancing)
1316 class SVGGeometryG(SVGGeometryContainer):
1318 Geometry group
1321 pass
1324 class SVGGeometryUSE(SVGGeometry):
1326 User of referenced elements
1329 def _doCreateGeom(self, instancing):
1331 Create real geometries
1334 ref = self._node.getAttribute('xlink:href')
1335 geom = self._context['defines'].get(ref)
1337 if geom is not None:
1338 rect = SVGRectFromNode(self._node, self._context)
1339 self._pushRect(rect)
1341 self._pushMatrix(self.getNodeMatrix())
1343 geom.createGeom(True)
1345 self._popMatrix()
1347 self._popRect()
1350 class SVGGeometryRECT(SVGGeometry):
1352 SVG rectangle
1355 __slots__ = ('_rect', # coordinate and dimensions of rectangle
1356 '_radius', # Rounded corner radiuses
1357 '_styles') # Styles, used for displaying
1359 def __init__(self, node, context):
1361 Initialize new rectangle
1364 super().__init__(node, context)
1366 self._rect = ('0', '0', '0', '0')
1367 self._radius = ('0', '0')
1368 self._styles = SVGEmptyStyles
1370 def parse(self):
1372 Parse SVG rectangle node
1375 self._styles = SVGParseStyles(self._node, self._context)
1377 rect = []
1378 for attr in ['x', 'y', 'width', 'height']:
1379 val = self._node.getAttribute(attr)
1380 rect.append(val or '0')
1382 self._rect = (rect)
1384 rx = self._node.getAttribute('rx')
1385 ry = self._node.getAttribute('ry')
1387 self._radius = (rx, ry)
1389 def _appendCorner(self, spline, coord, firstTime, rounded):
1391 Append new corner to rectangle
1394 handle = None
1395 if len(coord) == 3:
1396 handle = self._transformCoord(coord[2])
1397 coord = (coord[0], coord[1])
1399 co = self._transformCoord(coord)
1401 if not firstTime:
1402 spline.bezier_points.add()
1404 bezt = spline.bezier_points[-1]
1405 bezt.co = co
1407 if rounded:
1408 if handle:
1409 bezt.handle_left_type = 'VECTOR'
1410 bezt.handle_right_type = 'FREE'
1412 bezt.handle_right = handle
1413 else:
1414 bezt.handle_left_type = 'FREE'
1415 bezt.handle_right_type = 'VECTOR'
1416 bezt.handle_left = co
1418 else:
1419 bezt.handle_left_type = 'VECTOR'
1420 bezt.handle_right_type = 'VECTOR'
1422 def _doCreateGeom(self, instancing):
1424 Create real geometries
1427 # Run-time parsing -- percents would be correct only if
1428 # parsing them now
1429 crect = self._context['rect']
1430 rect = []
1432 for i in range(4):
1433 rect.append(SVGParseCoord(self._rect[i], crect[i % 2]))
1435 r = self._radius
1436 rx = ry = 0.0
1438 if r[0] and r[1]:
1439 rx = min(SVGParseCoord(r[0], rect[0]), rect[2] / 2)
1440 ry = min(SVGParseCoord(r[1], rect[1]), rect[3] / 2)
1441 elif r[0]:
1442 rx = min(SVGParseCoord(r[0], rect[0]), rect[2] / 2)
1443 ry = min(rx, rect[3] / 2)
1444 rx = ry = min(rx, ry)
1445 elif r[1]:
1446 ry = min(SVGParseCoord(r[1], rect[1]), rect[3] / 2)
1447 rx = min(ry, rect[2] / 2)
1448 rx = ry = min(rx, ry)
1450 radius = (rx, ry)
1452 # Geometry creation
1453 ob = SVGCreateCurve()
1454 cu = ob.data
1456 if self._styles['useFill']:
1457 cu.dimensions = '2D'
1458 cu.materials.append(self._styles['fill'])
1459 else:
1460 cu.dimensions = '3D'
1462 cu.splines.new('BEZIER')
1464 spline = cu.splines[-1]
1465 spline.use_cyclic_u = True
1467 x, y = rect[0], rect[1]
1468 w, h = rect[2], rect[3]
1469 rx, ry = radius[0], radius[1]
1470 rounded = False
1472 if rx or ry:
1474 # 0 _______ 1
1475 # / \
1476 # / \
1477 # 7 2
1478 # | |
1479 # | |
1480 # 6 3
1481 # \ /
1482 # \ /
1483 # 5 _______ 4
1486 # Optional third component -- right handle coord
1487 coords = [(x + rx, y),
1488 (x + w - rx, y, (x + w, y)),
1489 (x + w, y + ry),
1490 (x + w, y + h - ry, (x + w, y + h)),
1491 (x + w - rx, y + h),
1492 (x + rx, y + h, (x, y + h)),
1493 (x, y + h - ry),
1494 (x, y + ry, (x, y))]
1496 rounded = True
1497 else:
1498 coords = [(x, y), (x + w, y), (x + w, y + h), (x, y + h)]
1500 firstTime = True
1501 for coord in coords:
1502 self._appendCorner(spline, coord, firstTime, rounded)
1503 firstTime = False
1505 SVGFinishCurve()
1508 class SVGGeometryELLIPSE(SVGGeometry):
1510 SVG ellipse
1513 __slots__ = ('_cx', # X-coordinate of center
1514 '_cy', # Y-coordinate of center
1515 '_rx', # X-axis radius of circle
1516 '_ry', # Y-axis radius of circle
1517 '_styles') # Styles, used for displaying
1519 def __init__(self, node, context):
1521 Initialize new ellipse
1524 super().__init__(node, context)
1526 self._cx = '0.0'
1527 self._cy = '0.0'
1528 self._rx = '0.0'
1529 self._ry = '0.0'
1530 self._styles = SVGEmptyStyles
1532 def parse(self):
1534 Parse SVG ellipse node
1537 self._styles = SVGParseStyles(self._node, self._context)
1539 self._cx = self._node.getAttribute('cx') or '0'
1540 self._cy = self._node.getAttribute('cy') or '0'
1541 self._rx = self._node.getAttribute('rx') or '0'
1542 self._ry = self._node.getAttribute('ry') or '0'
1544 def _doCreateGeom(self, instancing):
1546 Create real geometries
1549 # Run-time parsing -- percents would be correct only if
1550 # parsing them now
1551 crect = self._context['rect']
1553 cx = SVGParseCoord(self._cx, crect[0])
1554 cy = SVGParseCoord(self._cy, crect[1])
1555 rx = SVGParseCoord(self._rx, crect[0])
1556 ry = SVGParseCoord(self._ry, crect[1])
1558 if not rx or not ry:
1559 # Automaic handles will work incorrect in this case
1560 return
1562 # Create circle
1563 ob = SVGCreateCurve()
1564 cu = ob.data
1566 if self._node.getAttribute('id'):
1567 cu.name = self._node.getAttribute('id')
1569 if self._styles['useFill']:
1570 cu.dimensions = '2D'
1571 cu.materials.append(self._styles['fill'])
1572 else:
1573 cu.dimensions = '3D'
1575 coords = [((cx - rx, cy),
1576 (cx - rx, cy + ry * 0.552),
1577 (cx - rx, cy - ry * 0.552)),
1579 ((cx, cy - ry),
1580 (cx - rx * 0.552, cy - ry),
1581 (cx + rx * 0.552, cy - ry)),
1583 ((cx + rx, cy),
1584 (cx + rx, cy - ry * 0.552),
1585 (cx + rx, cy + ry * 0.552)),
1587 ((cx, cy + ry),
1588 (cx + rx * 0.552, cy + ry),
1589 (cx - rx * 0.552, cy + ry))]
1591 spline = None
1592 for coord in coords:
1593 co = self._transformCoord(coord[0])
1594 handle_left = self._transformCoord(coord[1])
1595 handle_right = self._transformCoord(coord[2])
1597 if spline is None:
1598 cu.splines.new('BEZIER')
1599 spline = cu.splines[-1]
1600 spline.use_cyclic_u = True
1601 else:
1602 spline.bezier_points.add()
1604 bezt = spline.bezier_points[-1]
1605 bezt.co = co
1606 bezt.handle_left_type = 'FREE'
1607 bezt.handle_right_type = 'FREE'
1608 bezt.handle_left = handle_left
1609 bezt.handle_right = handle_right
1611 SVGFinishCurve()
1614 class SVGGeometryCIRCLE(SVGGeometryELLIPSE):
1616 SVG circle
1619 def parse(self):
1621 Parse SVG circle node
1624 self._styles = SVGParseStyles(self._node, self._context)
1626 self._cx = self._node.getAttribute('cx') or '0'
1627 self._cy = self._node.getAttribute('cy') or '0'
1629 r = self._node.getAttribute('r') or '0'
1630 self._rx = self._ry = r
1633 class SVGGeometryLINE(SVGGeometry):
1635 SVG line
1638 __slots__ = ('_x1', # X-coordinate of beginning
1639 '_y1', # Y-coordinate of beginning
1640 '_x2', # X-coordinate of ending
1641 '_y2') # Y-coordinate of ending
1643 def __init__(self, node, context):
1645 Initialize new line
1648 super().__init__(node, context)
1650 self._x1 = '0.0'
1651 self._y1 = '0.0'
1652 self._x2 = '0.0'
1653 self._y2 = '0.0'
1655 def parse(self):
1657 Parse SVG line node
1660 self._x1 = self._node.getAttribute('x1') or '0'
1661 self._y1 = self._node.getAttribute('y1') or '0'
1662 self._x2 = self._node.getAttribute('x2') or '0'
1663 self._y2 = self._node.getAttribute('y2') or '0'
1665 def _doCreateGeom(self, instancing):
1667 Create real geometries
1670 # Run-time parsing -- percents would be correct only if
1671 # parsing them now
1672 crect = self._context['rect']
1674 x1 = SVGParseCoord(self._x1, crect[0])
1675 y1 = SVGParseCoord(self._y1, crect[1])
1676 x2 = SVGParseCoord(self._x2, crect[0])
1677 y2 = SVGParseCoord(self._y2, crect[1])
1679 # Create cline
1680 ob = SVGCreateCurve()
1681 cu = ob.data
1683 coords = [(x1, y1), (x2, y2)]
1684 spline = None
1686 for coord in coords:
1687 co = self._transformCoord(coord)
1689 if spline is None:
1690 cu.splines.new('BEZIER')
1691 spline = cu.splines[-1]
1692 spline.use_cyclic_u = True
1693 else:
1694 spline.bezier_points.add()
1696 bezt = spline.bezier_points[-1]
1697 bezt.co = co
1698 bezt.handle_left_type = 'VECTOR'
1699 bezt.handle_right_type = 'VECTOR'
1701 SVGFinishCurve()
1704 class SVGGeometryPOLY(SVGGeometry):
1706 Abstract class for handling poly-geometries
1707 (polylines and polygons)
1710 __slots__ = ('_points', # Array of points for poly geometry
1711 '_styles', # Styles, used for displaying
1712 '_closed') # Should generated curve be closed?
1714 def __init__(self, node, context):
1716 Initialize new poly geometry
1719 super().__init__(node, context)
1721 self._points = []
1722 self._styles = SVGEmptyStyles
1723 self._closed = False
1725 def parse(self):
1727 Parse poly node
1730 self._styles = SVGParseStyles(self._node, self._context)
1732 points = self._node.getAttribute('points')
1733 points = points.replace(',', ' ').replace('-', ' -')
1734 points = points.split()
1736 prev = None
1737 self._points = []
1739 for p in points:
1740 if prev is None:
1741 prev = p
1742 else:
1743 self._points.append((float(prev), float(p)))
1744 prev = None
1746 def _doCreateGeom(self, instancing):
1748 Create real geometries
1751 ob = SVGCreateCurve()
1752 cu = ob.data
1754 if self._closed and self._styles['useFill']:
1755 cu.dimensions = '2D'
1756 cu.materials.append(self._styles['fill'])
1757 else:
1758 cu.dimensions = '3D'
1760 spline = None
1762 for point in self._points:
1763 co = self._transformCoord(point)
1765 if spline is None:
1766 cu.splines.new('BEZIER')
1767 spline = cu.splines[-1]
1768 spline.use_cyclic_u = self._closed
1769 else:
1770 spline.bezier_points.add()
1772 bezt = spline.bezier_points[-1]
1773 bezt.co = co
1774 bezt.handle_left_type = 'VECTOR'
1775 bezt.handle_right_type = 'VECTOR'
1777 SVGFinishCurve()
1780 class SVGGeometryPOLYLINE(SVGGeometryPOLY):
1782 SVG polyline geometry
1785 pass
1788 class SVGGeometryPOLYGON(SVGGeometryPOLY):
1790 SVG polygon geometry
1793 def __init__(self, node, context):
1795 Initialize new polygon geometry
1798 super().__init__(node, context)
1800 self._closed = True
1803 class SVGGeometrySVG(SVGGeometryContainer):
1805 Main geometry holder
1808 def _doCreateGeom(self, instancing):
1810 Create real geometries
1813 rect = SVGRectFromNode(self._node, self._context)
1815 matrix = self.getNodeMatrix()
1817 # Better Inkscape compatibility: match document origin with
1818 # 3D space origin.
1819 if self._node.getAttribute('inkscape:version'):
1820 raw_height = self._node.getAttribute('height')
1821 document_height = SVGParseCoord(raw_height, 1.0)
1822 matrix = matrix * Matrix.Translation([0.0, -document_height , 0.0])
1824 self._pushMatrix(matrix)
1825 self._pushRect(rect)
1827 super()._doCreateGeom(False)
1829 self._popRect()
1830 self._popMatrix()
1833 class SVGLoader(SVGGeometryContainer):
1835 SVG file loader
1838 def getTransformMatrix(self):
1840 Get matrix created from "transform" attribute
1843 # SVG document doesn't support transform specification
1844 # it can't even hold attributes
1846 return None
1848 def __init__(self, filepath, do_colormanage):
1850 Initialize SVG loader
1853 node = xml.dom.minidom.parse(filepath)
1855 m = Matrix()
1856 m = m * Matrix.Scale(1.0 / 90.0 * 0.3048 / 12.0, 4, Vector((1.0, 0.0, 0.0)))
1857 m = m * Matrix.Scale(-1.0 / 90.0 * 0.3048 / 12.0, 4, Vector((0.0, 1.0, 0.0)))
1859 rect = (0, 0)
1861 self._context = {'defines': {},
1862 'transform': [],
1863 'rects': [rect],
1864 'rect': rect,
1865 'matrix': m,
1866 'materials': {},
1867 'styles': [None],
1868 'style': None,
1869 'do_colormanage': do_colormanage}
1871 super().__init__(node, self._context)
1874 svgGeometryClasses = {
1875 'svg': SVGGeometrySVG,
1876 'path': SVGGeometryPATH,
1877 'defs': SVGGeometryDEFS,
1878 'symbol': SVGGeometrySYMBOL,
1879 'use': SVGGeometryUSE,
1880 'rect': SVGGeometryRECT,
1881 'ellipse': SVGGeometryELLIPSE,
1882 'circle': SVGGeometryCIRCLE,
1883 'line': SVGGeometryLINE,
1884 'polyline': SVGGeometryPOLYLINE,
1885 'polygon': SVGGeometryPOLYGON,
1886 'g': SVGGeometryG}
1889 def parseAbstractNode(node, context):
1890 name = node.tagName.lower()
1892 if name.startswith('svg:'):
1893 name = name[4:]
1895 geomClass = svgGeometryClasses.get(name)
1897 if geomClass is not None:
1898 ob = geomClass(node, context)
1899 ob.parse()
1901 return ob
1903 return None
1906 def load_svg(filepath, do_colormanage):
1908 Load specified SVG file
1911 if bpy.ops.object.mode_set.poll():
1912 bpy.ops.object.mode_set(mode='OBJECT')
1914 loader = SVGLoader(filepath, do_colormanage)
1915 loader.parse()
1916 loader.createGeom(False)
1919 def load(operator, context, filepath=""):
1921 # error in code should raise exceptions but loading
1922 # non SVG files can give useful messages.
1923 do_colormanage = context.scene.display_settings.display_device != 'NONE'
1924 try:
1925 load_svg(filepath, do_colormanage)
1926 except (xml.parsers.expat.ExpatError, UnicodeEncodeError) as e:
1927 import traceback
1928 traceback.print_exc()
1930 operator.report({'WARNING'}, "Unable to parse XML, %s:%s for file %r" % (type(e).__name__, e, filepath))
1931 return {'CANCELLED'}
1933 return {'FINISHED'}