Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Sharybin <sergey.vfx@gmail.com>2011-02-21 00:13:12 +0300
committerSergey Sharybin <sergey.vfx@gmail.com>2011-02-21 00:13:12 +0300
commitb01f11eed8fae9bd6257408fafc8569a6a0bc6aa (patch)
tree07f35bdc5a97e14ea4761df84fdc5adce063b867 /io_curve_svg
parent9554fe9062e5f821f79a378f6f9f13d6b2b8d897 (diff)
Initial commit of SVG importer for Blender 2.5
Only <path> is supported at this moment, other geometries would be added a bit later. Transform attribute, defined, groups and uses should work pretty fine. Work is in progress, so please repoer issues to me, not to tracker :)
Diffstat (limited to 'io_curve_svg')
-rw-r--r--io_curve_svg/__init__.py83
-rw-r--r--io_curve_svg/import_svg.py1156
-rw-r--r--io_curve_svg/svg_colors.py172
3 files changed, 1411 insertions, 0 deletions
diff --git a/io_curve_svg/__init__.py b/io_curve_svg/__init__.py
new file mode 100644
index 00000000..c3ddc8d2
--- /dev/null
+++ b/io_curve_svg/__init__.py
@@ -0,0 +1,83 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+bl_info = {
+ "name": "Scalable Vector Graphics (SVG) 1.1 format",
+ "author": "Sergey Sharybin",
+ "blender": (2, 5, 6),
+ "api": 34996,
+ "location": "File > Import-Export",
+ "description": "Import SVG",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Import-Export/SVG",
+ "tracker_url": "",
+ "support": 'OFFICIAL',
+ "category": "Import-Export"}
+
+# To support reload properly, try to access a package var,
+# if it's there, reload everything
+if "bpy" in locals():
+ import imp
+ if "import_svg" in locals():
+ imp.reload(import_svg)
+
+
+import bpy
+from bpy.props import *
+from io_utils import ImportHelper, ExportHelper
+
+
+class ImportSVG(bpy.types.Operator, ImportHelper):
+ '''Load a SVG file'''
+ bl_idname = "import_curve.svg"
+ bl_label = "Import SVG"
+
+ filename_ext = ".svg"
+ filter_glob = StringProperty(default="*.svg", options={'HIDDEN'})
+
+ def execute(self, context):
+ from . import import_svg
+
+ return import_svg.load(self, context,
+ **self.as_keywords(ignore=("filter_glob",)))
+
+
+def menu_func_import(self, context):
+ self.layout.operator(ImportSVG.bl_idname,
+ text="Scalable Vector Graphics (.svg)")
+
+
+def register():
+ bpy.utils.register_module(__name__)
+
+ bpy.types.INFO_MT_file_import.append(menu_func_import)
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
+
+ bpy.types.INFO_MT_file_import.remove(menu_func_import)
+
+# NOTES
+# - blender version is hardcoded
+
+if __name__ == "__main__":
+ register()
diff --git a/io_curve_svg/import_svg.py b/io_curve_svg/import_svg.py
new file mode 100644
index 00000000..4443565a
--- /dev/null
+++ b/io_curve_svg/import_svg.py
@@ -0,0 +1,1156 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+import re
+import xml.dom.minidom
+from math import cos, sin, tan, atan2, pi, ceil
+
+import bpy
+from mathutils import Vector, Matrix
+
+from . import svg_colors
+
+#### Common utilities ####
+
+# TODO: 'em' and 'ex' aren't actually supported
+SVGUnits = {'': 1.0,
+ 'px': 1.0,
+ 'in': 90,
+ 'mm': 90 * 0.254,
+ 'cm': 90 * 2.54,
+ 'pt': 1.25,
+ 'pc': 15.0,
+ 'em': 1.0,
+ 'ex': 1.0}
+
+
+def SVGCreateCurve():
+ """
+ Create new curve object to hold splines in
+ """
+
+ cu = bpy.data.curves.new("Curve", 'CURVE')
+ obj = bpy.data.objects.new("Curve", cu)
+ bpy.context.scene.objects.link(obj)
+
+ return obj
+
+
+def SVGFinishCurve():
+ """
+ Finish curve creation
+ """
+
+ pass
+
+
+def SVGFlipHandle(x, y, x1, y1):
+ """
+ Flip handle around base point
+ """
+
+ x = x + (x - x1)
+ y = y + (y - y1)
+
+ return x, y
+
+
+def SVGParseCoord(coord, size):
+ """
+ Parse coordinate component to common basis
+
+ Needed to handle coordinates set in cm, mm, iches..
+ """
+
+ r = re.compile('([0-9\\-\\+\\.])([A-z%]*)')
+ val = float(r.sub('\\1', coord))
+ unit = r.sub('\\2', coord).lower()
+
+ if unit == '%':
+ return float(size) / 100.0 * val
+ else:
+ global SVGUnits
+
+ return val * SVGUnits[unit]
+
+ return val
+
+
+def SVGRectFromNode(node, context):
+ """
+ Get display rectangle from node
+ """
+
+ w = context['rect'][0]
+ h = context['rect'][1]
+
+ if node.getAttribute('viewBox'):
+ viewBox = node.getAttribute('viewBox').split()
+ w = SVGParseCoord(viewBox[2], w)
+ h = SVGParseCoord(viewBox[3], h)
+ else:
+ if node.getAttribute('width'):
+ w = SVGParseCoord(node.getAttribute('width'), w)
+
+ if node.getAttribute('height'):
+ h = SVGParseCoord(node.getAttribute('height'), h)
+
+
+ return (w, h)
+
+
+def SVGMatrixFromNode(node, context):
+ """
+ Get transformation matrix from given node
+ """
+
+ rect = context['rect']
+
+ m = Matrix()
+ x = SVGParseCoord(node.getAttribute('x') or '0', rect[0])
+ y = SVGParseCoord(node.getAttribute('y') or '0', rect[1])
+ w = SVGParseCoord(node.getAttribute('width') or str(rect[0]), rect[0])
+ h = SVGParseCoord(node.getAttribute('height') or str(rect[1]), rect[1])
+
+ m = m.Translation(Vector((x, y, 0.0)))
+ if len(context['rects']) > 1:
+ m = m * m.Scale(w / rect[0], 4, Vector((1.0, 0.0, 0.0)))
+ m = m * m.Scale(h / rect[1], 4, Vector((0.0, 1.0, 0.0)))
+
+ if node.getAttribute('viewBox'):
+ viewBox = node.getAttribute('viewBox').split()
+ vx = SVGParseCoord(viewBox[0], w)
+ vy = SVGParseCoord(viewBox[1], h)
+ vw = SVGParseCoord(viewBox[2], w)
+ vh = SVGParseCoord(viewBox[3], h)
+
+ m = m * m.Translation(Vector((-vx, -vy, 0.0)))
+ m = m * m.Scale(w / vw, 4, Vector((1.0, 0.0, 0.0)))
+ m = m * m.Scale(h / vh, 4, Vector((0.0, 1.0, 0.0)))
+
+ return m
+
+
+def SVGParseTransform(transform):
+ """
+ Parse transform string and return transformation matrix
+ """
+
+ m = Matrix()
+ r = re.compile('\s*([A-z]+)\s*\((.*?)\)')
+
+ for match in r.finditer(transform):
+ func = match.group(1)
+ params = match.group(2)
+ params = params.replace(',', ' ').split()
+
+ proc = SVGTransforms.get(func)
+ if proc is None:
+ raise Exception('Unknown trasnform function: ' + func)
+
+ m = m * proc(params)
+
+ return m
+
+
+def SVGGetMaterial(color, context):
+ """
+ Get material for specified color
+ """
+
+ materials = context['materials']
+
+ if color in materials:
+ return materials[color]
+
+ diff = None
+ if color.startswith('#'):
+ color = color[1:]
+
+ if len(color) == 3:
+ color = color[0] * 2 + color[1] * 2 + color[2] * 2
+
+ diff = (int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16))
+ elif color in svg_colors.SVGColors:
+ diff = svg_colors.SVGColors[color]
+ else:
+ return None
+
+ mat = bpy.data.materials.new(name='SVGMat')
+ mat.diffuse_color = diff
+
+ materials[color] = mat
+
+ return mat
+
+
+def SVGTransformTranslate(params):
+ """
+ translate SVG transform command
+ """
+
+ tx = float(params[0])
+ ty = float(params[1])
+ return Matrix.Translation(Vector((tx, ty, 0.0)))
+
+
+def SVGTransformMatrix(params):
+ """
+ matrix SVG transform command
+ """
+
+ a = float(params[0])
+ b = float(params[1])
+ c = float(params[2])
+ d = float(params[3])
+ e = float(params[4])
+ f = float(params[5])
+
+ return Matrix(((a, c, 0.0, 0.0),
+ (b, d, 0.0, 0.0),
+ (0, 0, 1.0, 0.0),
+ (e, f, 0.0, 1.0)))
+
+
+def SVGTransformScale(params):
+ """
+ scale SVG transform command
+ """
+
+ sx = sy = float(params[0])
+ if len(params) > 1:
+ sy = float(params[1])
+
+ m = Matrix()
+
+ m = m * m.Scale(sx, 4, Vector((1.0, 0.0, 0.0)))
+ m = m * m.Scale(sy, 4, Vector((0.0, 1.0, 0.0)))
+
+ return m
+
+
+def SVGTransformSkewX(params):
+ """
+ skewX SVG transform command
+ """
+
+ ang = float(params[0]) * pi / 180.0
+
+ return Matrix(((1.0, 0.0, 0.0),
+ (tan(ang), 1.0, 0.0),
+ (0.0, 0.0, 1.0))).to_4x4()
+
+
+def SVGTransformSkewY(params):
+ """
+ skewX SVG transform command
+ """
+
+ ang = float(params[0]) * pi / 180.0
+
+ return Matrix(((1.0, tan(ang), 0.0),
+ (0.0, 1.0, 0.0),
+ (0.0, 0.0, 1.0))).to_4x4()
+
+
+def SVGTransformRotate(params):
+ """
+ skewX SVG transform command
+ """
+
+ ang = float(params[0]) * pi / 180.0
+ cx = cy = 0.0
+ if len(params) >= 3:
+ cx = float(params[1])
+ cy = float(params[2])
+
+ tm = Matrix.Translation(Vector((cx, cy, 0.0)))
+ rm = Matrix.Rotation(ang, 4, Vector((0.0, 0.0, 1.0)))
+
+ return tm * rm * tm.inverted()
+
+SVGTransforms = {'translate': SVGTransformTranslate,
+ 'scale': SVGTransformScale,
+ 'skewX': SVGTransformSkewX,
+ 'skewY': SVGTransformSkewY,
+ 'matrix': SVGTransformMatrix,
+ 'rotate': SVGTransformRotate}
+
+#### SVG path helpers ####
+
+
+class SVGPathData:
+ """
+ SVG Path data token supplier
+ """
+
+ __slots__ = ('_data', # List of tokens
+ '_index', # Index of current token in tokens list
+ '_len') # Lenght og tokens list
+
+ def __init__(self, d):
+ """
+ Initialize new path data supplier
+
+ d - the definition of the outline of a shape
+ """
+
+ # Convert to easy-to-parse format
+ d = d.replace(',', ' ')
+ d = re.sub('([A-z])', ' \\1 ', d)
+
+ self._data = d.split()
+ self._index = 0
+ self._len = len(self._data)
+
+ def eof(self):
+ """
+ Check if end of data reached
+ """
+
+ return self._index >= self._len
+
+ def cur(self):
+ """
+ Return current token
+ """
+
+ if self.eof():
+ return None
+
+ return self._data[self._index]
+
+ def next(self):
+ """
+ Return current token and go to next one
+ """
+
+ if self.eof():
+ return None
+
+ token = self._data[self._index]
+ self._index += 1
+
+ return token
+
+ def nextCoord(self):
+ """
+ Return coordinate created from current token and move to next token
+ """
+
+ token = self.next()
+
+ if token is None:
+ return None
+
+ return float(token)
+
+
+class SVGPathParser:
+ """
+ Parser of SVG path data
+ """
+
+ __slots__ = ('_data', # Path data supplird
+ '_point', # Current point coorfinate
+ '_handle', # Last handle coordinate
+ '_splines', # List of all splies created during parsing
+ '_spline', # Currently handling spline
+ '_commands') # Hash of all supported path commands
+
+ def __init__(self, d):
+ """
+ Initialize path parser
+
+ d - the definition of the outline of a shape
+ """
+
+ self._data = SVGPathData(d)
+ self._point = None # Current point
+ self._handle = None # Last handle
+ self._splines = [] # List of splines in path
+ self._spline = None # Current spline
+
+ self._commands = {'M': self._pathMoveTo,
+ 'L': self._pathLineTo,
+ 'H': self._pathLineTo,
+ 'V': self._pathLineTo,
+ 'C': self._pathCurveToCS,
+ 'S': self._pathCurveToCS,
+ 'Q': self._pathCurveToQT,
+ 'T': self._pathCurveToQT,
+ 'A': self._pathCurveToA,
+ 'Z': self._pathClose,
+
+ 'm': self._pathMoveTo,
+ 'l': self._pathLineTo,
+ 'h': self._pathLineTo,
+ 'v': self._pathLineTo,
+ 'c': self._pathCurveToCS,
+ 's': self._pathCurveToCS,
+ 'q': self._pathCurveToQT,
+ 't': self._pathCurveToQT,
+ 'a': self._pathCurveToA,
+ 'z': self._pathClose}
+
+ def _getCoordPair(self, relative, point):
+ """
+ Get next coordinate pair
+ """
+
+ x = self._data.nextCoord()
+ y = self._data.nextCoord()
+
+ if relative and point is not None:
+ x += point[0]
+ y += point[1]
+
+ return x, y
+
+ def _appendPoint(self, x, y, handle_left=None, handle_left_type='VECTOR',
+ handle_right=None, handle_right_type='VECTOR'):
+ """
+ Append point to spline
+
+ If there's no active spline, create one and set it's first point
+ to current point coordinate
+ """
+
+ if self._spline is None:
+ self._spline = {'points': [],
+ 'closed': False}
+
+ self._splines.append(self._spline)
+
+ point = {'x': x,
+ 'y': y,
+
+ 'handle_left': handle_left,
+ 'handle_left_type': handle_left_type,
+
+ 'handle_right': handle_right,
+ 'handle_right_type': handle_right_type}
+
+ self._spline['points'].append(point)
+
+ def _updateHandle(self, handle=None, handle_type=None):
+ """
+ Update right handle of previous point when adding new point to spline
+ """
+
+ point = self._spline['points'][-1]
+
+ if handle_type is not None:
+ point['handle_right_type'] = handle_type
+
+ if handle is not None:
+ point['handle_right'] = handle
+
+ def _pathMoveTo(self, code):
+ """
+ MoveTo path command
+ """
+
+ relative = code.islower()
+ x, y = self._getCoordPair(relative, self._point)
+
+ self._spline = None # Flag to start new spline
+ self._point = (x, y)
+
+ cur = self._data.cur()
+ while cur is not None and not cur.isalpha():
+ x, y = self._getCoordPair(relative, self._point)
+
+ if self._spline is None:
+ self._appendPoint(self._point[0], self._point[1])
+
+ self._appendPoint(x, y)
+
+ self._point = (x, y)
+ cur = self._data.cur()
+
+ self._handle = None
+
+ def _pathLineTo(self, code):
+ """
+ LineTo path command
+ """
+
+ c = code.lower()
+
+ cur = self._data.cur()
+ while cur is not None and not cur.isalpha():
+ if c == 'l':
+ x, y = self._getCoordPair(code == 'l', self._point)
+ elif c == 'h':
+ x = self._data.nextCoord()
+ y = self._point[1]
+ else:
+ x = self._point[0]
+ y = self._data.nextCoord()
+
+ if code == 'h':
+ x += self._point[0]
+ elif code == 'v':
+ y += self._point[1]
+
+ if self._spline is None:
+ self._appendPoint(self._point[0], self._point[1])
+
+ self._appendPoint(x, y)
+
+ self._point = (x, y)
+ cur = self._data.cur()
+
+ self._handle = None
+
+ def _pathCurveToCS(self, code):
+ """
+ Cubic BEZIER CurveTo path command
+ """
+
+ c = code.lower()
+ cur = self._data.cur()
+ while cur is not None and not cur.isalpha():
+ if c == 'c':
+ x1, y1 = self._getCoordPair(code.islower(), self._point)
+ x2, y2 = self._getCoordPair(code.islower(), self._point)
+ else:
+ if self._handle is not None:
+ x1, y1 = SVGFlipHandle(self._point[0], self._point[1],
+ self._handle[0], self._handle[1])
+ else:
+ x1, y1 = self._point
+
+ x2, y2 = self._getCoordPair(code.islower(), self._point)
+
+ x, y = self._getCoordPair(code.islower(), self._point)
+
+ if self._spline is None:
+ self._appendPoint(self._point[0], self._point[1],
+ handle_left_type='FREE', handle_left=self._point,
+ handle_right_type='FREE', handle_right=(x1, y1))
+ else:
+ self._updateHandle(handle=(x1, y1), handle_type='FREE')
+
+ self._appendPoint(x, y,
+ handle_left_type='FREE', handle_left=(x2, y2),
+ handle_right_type='FREE', handle_right=(x, y))
+
+ self._point = (x, y)
+ self._handle = (x2, y2)
+ cur = self._data.cur()
+
+ def _pathCurveToQT(self, code):
+ """
+ Qyadracic BEZIER CurveTo path command
+ """
+
+ c = code.lower()
+ cur = self._data.cur()
+
+ while cur is not None and not cur.isalpha():
+ if c == 'q':
+ x1, y1 = self._getCoordPair(code.islower(), self._point)
+ else:
+ if self._handle is not None:
+ x1, y1 = SVGFlipHandle(self._point[0], self._point[1],
+ self._handle[0], self._handle[1])
+ else:
+ x1, y1 = self._point
+
+ x, y = self._getCoordPair(code.islower(), self._point)
+
+ if self._spline is None:
+ self._appendPoint(self._point[0], self._point[1],
+ handle_left_type='FREE', handle_left=self._point,
+ handle_right_type='FREE', handle_right=self._point)
+
+ self._appendPoint(x, y,
+ handle_left_type='FREE', handle_left=(x1, y1),
+ handle_right_type='FREE', handle_right=(x, y))
+
+ self._point = (x, y)
+ self._handle = (x1, y1)
+ cur = self._data.cur()
+
+ def _calcArc(self, rx, ry, ang, fa, fs, x, y):
+ """
+ Calc arc paths
+
+ Copied and adoptedfrom paths_svg2obj.py scring for Blender 2.49
+ which is Copyright (c) jm soler juillet/novembre 2004-april 2009,
+ """
+
+ cpx = self._point[0]
+ cpy = self._point[1]
+ rx = abs(rx)
+ ry = abs(ry)
+ px = abs((cos(ang) * (cpx - x) + sin(ang) * (cpy - y)) * 0.5) ** 2.0
+ py = abs((cos(ang) * (cpy - y) - sin(ang) * (cpx - x)) * 0.5) ** 2.0
+ rpx = rpy = 0.0
+
+ if abs(rx) > 0.0:
+ px = px / (rx ** 2.0)
+
+ if abs(ry) > 0.0:
+ rpy = py / (ry ** 2.0)
+
+ pl = rpx + rpy
+ if pl > 1.0:
+ pl = pl ** 0.5
+ rx *= pl
+ ry *= pl
+
+ carx = sarx = cary = sary = 0.0
+
+ if abs(rx) > 0.0:
+ carx = cos(ang) / rx
+ sarx = sin(ang) / rx
+
+ if abs(ry) > 0.0:
+ cary = cos(ang) / ry
+ sary = sin(ang) / ry
+
+ x0 = carx * cpx + sarx * cpy
+ y0 = -sary * cpx + cary * cpy
+ x1 = carx * x + sarx * y
+ y1 = -sary * x + cary * y
+ d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)
+
+ if abs(d) > 0.0:
+ sq = 1.0 / d - 0.25
+ else:
+ sq = -0.25
+
+ if sq < 0.0:
+ sq = 0.0
+
+ sf = sq ** 0.5
+ if fs == fa:
+ sf = -sf
+
+ xc = 0.5 * (x0 + x1) - sf * (y1 - y0)
+ yc = 0.5 * (y0 + y1) + sf * (x1 - x0)
+ ang_0 = atan2(y0 - yc, x0 - xc)
+ ang_1 = atan2(y1 - yc, x1 - xc)
+ ang_arc = ang_1 - ang_0
+
+ if ang_arc < 0.0 and fs == 1:
+ ang_arc += 2.0 * pi
+ elif ang_arc > 0.0 and fs == 0:
+ ang_arc -= 2.0 * pi
+
+ n_segs = int(ceil(abs(ang_arc * 2.0 / (pi * 0.5 + 0.001))))
+
+ if self._spline is None:
+ self._appendPoint(cpx, cpy,
+ handle_left_type='FREE', handle_left=(cpx, cpy),
+ handle_right_type='FREE', handle_right=(cpx, cpy))
+
+ for i in range(n_segs):
+ ang0 = ang_0 + i * ang_arc / n_segs
+ ang1 = ang_0 + (i + 1) * ang_arc / n_segs
+ ang_demi = 0.25 * (ang1 - ang0)
+ t = 2.66666 * sin(ang_demi) * sin(ang_demi) / sin(ang_demi * 2.0)
+ x1 = xc + cos(ang0) - t * sin(ang0)
+ y1 = yc + sin(ang0) + t * cos(ang0)
+ x2 = xc + cos(ang1)
+ y2 = yc + sin(ang1)
+ x3 = x2 + t * sin(ang1)
+ y3 = y2 - t * cos(ang1)
+
+ coord1 = ((cos(ang) * rx) * x1 + (-sin(ang) * ry) * y1,
+ (sin(ang) * rx) * x1 + (cos(ang) * ry) * y1)
+ coord2 = ((cos(ang) * rx) * x3 + (-sin(ang) * ry) * y3,
+ (sin(ang) * rx) * x3 + (cos(ang) * ry) * y3)
+ coord3 = ((cos(ang) * rx) * x2 + (-sin(ang) * ry) * y2,
+ (sin(ang) * rx) * x2 + (cos(ang) * ry) * y2)
+
+ self._updateHandle(handle=coord1, handle_type='FREE')
+
+ self._appendPoint(coord3[0], coord3[1],
+ handle_left_type='FREE', handle_left=coord2,
+ handle_right_type='FREE', handle_right=coord3)
+
+ def _pathCurveToA(self, code):
+ """
+ Elliptical arc CurveTo path command
+ """
+
+ c = code.lower()
+ cur = self._data.cur()
+
+ while cur is not None and not cur.isalpha():
+ rx = float(self._data.next())
+ ry = float(self._data.next())
+ ang = float(self._data.next()) / 180 * pi
+ fa = float(self._data.next())
+ fs = float(self._data.next())
+ x, y = self._getCoordPair(code.islower(), self._point)
+
+ self._calcArc(rx, ry, ang, fa, fs, x, y)
+
+ self._point = (x, y)
+ self._handle = None
+ cur = self._data.cur()
+
+ def _pathClose(self, code):
+ """
+ Close path command
+ """
+
+ if self._spline:
+ self._spline['closed'] = True
+
+ def parse(self):
+ """
+ Execute parser
+ """
+
+ while not self._data.eof():
+ code = self._data.next()
+ cmd = self._commands.get(code)
+
+ if cmd is None:
+ raise Exception('Unknown path command: {0}' . format(code))
+
+ cmd(code)
+
+ def getSplines(self):
+ """
+ Get splines definitions
+ """
+
+ return self._splines
+
+
+class SVGGeometry:
+ """
+ Abstract SVG geometry
+ """
+
+ __slots__ = ('_node', # XML node for geometry
+ '_context', # Global SVG context (holds matrices stack, i.e.)
+ '_creating') # Flag if geometry is already creating for this node
+ # need to detect cycles for USE node
+
+ def __init__(self, node, context):
+ """
+ Initialize SVG geometry
+ """
+
+ self._node = node
+ self._context = context
+ self._creating = False
+
+ if hasattr(node, 'getAttribute'):
+ defs = context['defines']
+
+ id = node.getAttribute('id')
+ if id and defs.get('#' + id) is None:
+ defs['#' + id] = self
+
+ className = node.getAttribute('class')
+ if className and defs.get(className) is None:
+ defs[className] = self
+
+ def _pushRect(self, rect):
+ """
+ Push display rectangle
+ """
+
+ self._context['rects'].append(rect)
+ self._context['rect'] = rect
+
+ def _popRect(self):
+ """
+ Pop display rectangle
+ """
+
+ self._context['rects'].pop
+ self._context['rect'] = self._context['rects'][-1]
+
+ def _pushMatrix(self, matrix):
+ """
+ Push transformation matrix
+ """
+
+ self._context['transform'].append(matrix)
+ self._context['matrix'] = self._context['matrix'] * matrix
+
+ def _popMatrix(self):
+ """
+ Pop transformation matrix
+ """
+
+ matrix = self._context['transform'].pop()
+ self._context['matrix'] = self._context['matrix'] * matrix.inverted()
+
+ def _transformCoord(self, point):
+ """
+ Transform SVG-file coords
+ """
+
+ v = Vector((point[0], point[1], 0.0))
+
+ return v * self._context['matrix']
+
+ def getNodeMatrix(self):
+ """
+ Get transformation matrix of node
+ """
+
+ return SVGMatrixFromNode(self._node, self._context)
+
+ def parse(self):
+ """
+ Parse XML node to memory
+ """
+
+ pass
+
+ def _doCreateGeom(self):
+ """
+ Internal handler to create real geometries
+ """
+
+ pass
+
+ def _getTranformMatrix(self):
+ """
+ Get matrix created from "transform" attribute
+ """
+
+ if not hasattr(self._node, 'getAttribute'):
+ return None
+
+ transform = self._node.getAttribute('transform')
+
+ if transform:
+ return SVGParseTransform(transform)
+
+ return None
+
+ def createGeom(self):
+ """
+ Create real geometries
+ """
+
+ if self._creating:
+ return
+
+ self._creating = True
+
+ matrix = self._getTranformMatrix()
+ if matrix is not None:
+ self._pushMatrix(matrix)
+
+ self._doCreateGeom()
+
+ if matrix is not None:
+ self._popMatrix()
+
+ self._creating = False
+
+
+class SVGGeometryContainer(SVGGeometry):
+ """
+ Container of SVG geometries
+ """
+
+ __slots__ = ('_geometries') # List of chold geometries
+
+ def __init__(self, node, context):
+ """
+ Initialize SVG geometry container
+ """
+
+ super().__init__(node, context)
+
+ self._geometries = []
+
+ def parse(self):
+ """
+ Parse XML node to memory
+ """
+
+ for node in self._node.childNodes:
+ if type(node) is not xml.dom.minidom.Element:
+ continue
+
+ ob = parseAbstractNode(node, self._context)
+ if ob is not None:
+ self._geometries.append(ob)
+
+ def _doCreateGeom(self):
+ """
+ Create real geometries
+ """
+
+ for geom in self._geometries:
+ geom.createGeom()
+
+ def getGeometries(self):
+ """
+ Get list of parsed geometries
+ """
+
+ return self._geometries
+
+
+class SVGGeometryPATH(SVGGeometry):
+ """
+ SVG path geometry
+ """
+
+ __slots__ = ('_splines', # List of splines after parsing
+ '_useFill', # Should path be filled?
+ '_fill') # Material used for filling
+
+ def __init__(self, node, context):
+ """
+ Initialize SVG path
+ """
+
+ super().__init__(node, context)
+
+ self._splines = []
+ self._fill = None
+ self._useFill = False
+
+ def parse(self):
+ """
+ Parse SVG path node
+ """
+
+ d = self._node.getAttribute('d')
+
+ pathParser = SVGPathParser(d)
+ pathParser.parse()
+
+ self._splines = pathParser.getSplines()
+ self._fill = None
+ self._useFill = False
+
+ fill = self._node.getAttribute('fill')
+ if fill:
+ self._useFill = True
+ self._fill = SVGGetMaterial(fill, self._context)
+
+ def _doCreateGeom(self):
+ """
+ Create real geometries
+ """
+
+ ob = SVGCreateCurve()
+ cu = ob.data
+
+ if self._useFill:
+ cu.dimensions = '2D'
+ cu.materials.append(self._fill)
+ else:
+ cu.dimensions = '3D'
+
+ for spline in self._splines:
+ act_spline = None
+ for point in spline['points']:
+ loc = self._transformCoord((point['x'], point['y']))
+
+ if act_spline is None:
+ cu.splines.new('BEZIER')
+
+ act_spline = cu.splines[-1]
+ act_spline.use_cyclic_u = spline['closed']
+ else:
+ act_spline.bezier_points.add()
+
+ bezt = act_spline.bezier_points[-1]
+ bezt.select_control_point = True
+ bezt.select_left_handle = True
+ bezt.select_right_handle = True
+ bezt.co = loc
+
+ bezt.handle_left_type = point['handle_left_type']
+ if point['handle_left'] is not None:
+ handle = point['handle_left']
+ bezt.handle_left = self._transformCoord(handle)
+
+ bezt.handle_right_type = point['handle_right_type']
+ if point['handle_right'] is not None:
+ handle = point['handle_right']
+ bezt.handle_right = self._transformCoord(handle)
+
+ SVGFinishCurve()
+
+
+class SVGGeometryDEFS(SVGGeometryContainer):
+ """
+ Container for referenced elements
+ """
+
+ def _doCreateGeom(self):
+ """
+ Create real geometries
+ """
+
+ pass
+
+
+class SVGGeometrySYMBOL(SVGGeometryContainer):
+ """
+ Referenced element
+ """
+
+ def _doCreateGeom(self):
+ """
+ Create real geometries
+ """
+
+ pass
+
+
+class SVGGeometryG(SVGGeometryContainer):
+ """
+ Geometry group
+ """
+
+ pass
+
+
+class SVGGeometryUSE(SVGGeometry):
+ """
+ User of referenced elements
+ """
+
+ def _doCreateGeom(self):
+ """
+ Create real geometries
+ """
+
+ geometries = []
+ ref = self._node.getAttribute('xlink:href')
+ geom = self._context['defines'].get(ref)
+
+ if geom is not None:
+ self._pushMatrix(self.getNodeMatrix())
+ self._pushMatrix(geom.getNodeMatrix())
+
+ if isinstance(geom, SVGGeometryContainer):
+ geometries = geom.getGeometries()
+ else:
+ geometries = [geom]
+
+ for g in geometries:
+ g.createGeom()
+
+ self._popMatrix()
+ self._popMatrix()
+
+
+class SVGGeometrySVG(SVGGeometryContainer):
+ """
+ Main geometry holder
+ """
+
+ def _doCreateGeom(self):
+ """
+ Create real geometries
+ """
+
+ rect = SVGRectFromNode(self._node, self._context)
+
+ self._pushMatrix(self.getNodeMatrix())
+ self._pushRect(rect)
+
+ super()._doCreateGeom()
+
+ self._popRect()
+ self._popMatrix()
+
+
+class SVGLoader(SVGGeometryContainer):
+ """
+ SVG file loader
+ """
+
+ def __init__(self, filepath):
+ """
+ Initialize SVG loader
+ """
+
+ node = xml.dom.minidom.parse(filepath)
+
+ m = Matrix()
+ m = m * m.Scale(1.0 / 90.0, 4, Vector((1.0, 0.0, 0.0)))
+ m = m * m.Scale(-1.0 / 90.0, 4, Vector((0.0, 1.0, 0.0)))
+
+ rect = (1, 1)
+
+ self._context = {'defines': {},
+ 'transform': [],
+ 'rects': [rect],
+ 'rect': rect,
+ 'matrix': m,
+ 'materials': {}}
+
+ super().__init__(node, self._context)
+
+
+svgGeometryClasses = {
+ 'svg': SVGGeometrySVG,
+ 'path': SVGGeometryPATH,
+ 'defs': SVGGeometryDEFS,
+ 'symbol': SVGGeometrySYMBOL,
+ 'use': SVGGeometryUSE,
+ 'g': SVGGeometryG}
+
+
+def parseAbstractNode(node, context):
+ name = node.tagName.lower()
+ geomClass = svgGeometryClasses.get(name)
+
+ if geomClass is not None:
+ ob = geomClass(node, context)
+ ob.parse()
+
+ return ob
+
+ return None
+
+
+def load_svg(filepath):
+ """
+ Load specified SVG file
+ """
+
+ if bpy.ops.object.mode_set.poll():
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ loader = SVGLoader(filepath)
+ loader.parse()
+ loader.createGeom()
+
+
+def load(operator, context, filepath=""):
+
+ load_svg(filepath)
+
+ return {'FINISHED'}
diff --git a/io_curve_svg/svg_colors.py b/io_curve_svg/svg_colors.py
new file mode 100644
index 00000000..fd5e9548
--- /dev/null
+++ b/io_curve_svg/svg_colors.py
@@ -0,0 +1,172 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+#
+# Copied and adoptedfrom paths_svg2obj.py scring for Blender 2.49 which is
+# Copyright (c) jm soler juillet/novembre 2004-april 2009,
+#
+
+SVGColors = {'aliceblue': (240, 248, 255),
+ 'antiquewhite': (250, 235, 215),
+ 'aqua': (0, 255, 255),
+ 'aquamarine': (127, 255, 212),
+ 'azure': (240, 255, 255),
+ 'beige': (245, 245, 220),
+ 'bisque': (255, 228, 196),
+ 'black': (0, 0, 0),
+ 'blanchedalmond': (255, 235, 205),
+ 'blue': (0, 0, 255),
+ 'blueviolet': (138, 43, 226),
+ 'brown': (165, 42, 42),
+ 'burlywood': (222, 184, 135),
+ 'cadetblue': (95, 158, 160),
+ 'chartreuse': (127, 255, 0),
+ 'chocolate': (210, 105, 30),
+ 'coral': (255, 127, 80),
+ 'cornflowerblue': (100, 149, 237),
+ 'cornsilk': (255, 248, 220),
+ 'crimson': (220, 20, 60),
+ 'cyan': (0, 255, 255),
+ 'darkblue': (0, 0, 139),
+ 'darkcyan': (0, 139, 139),
+ 'darkgoldenrod': (184, 134, 11),
+ 'darkgray': (169, 169, 169),
+ 'darkgreen': (0, 100, 0),
+ 'darkgrey': (169, 169, 169),
+ 'darkkhaki': (189, 183, 107),
+ 'darkmagenta': (139, 0, 139),
+ 'darkolivegreen': (85, 107, 47),
+ 'darkorange': (255, 140, 0),
+ 'darkorchid': (153, 50, 204),
+ 'darkred': (139, 0, 0),
+ 'darksalmon': (233, 150, 122),
+ 'darkseagreen': (143, 188, 143),
+ 'darkslateblue': (72, 61, 139),
+ 'darkslategray': (47, 79, 79),
+ 'darkslategrey': (47, 79, 79),
+ 'darkturquoise': (0, 206, 209),
+ 'darkviolet': (148, 0, 211),
+ 'deeppink': (255, 20, 147),
+ 'deepskyblue': (0, 191, 255),
+ 'dimgray': (105, 105, 105),
+ 'dimgrey': (105, 105, 105),
+ 'dodgerblue': (30, 144, 255),
+ 'firebrick': (178, 34, 34),
+ 'floralwhite': (255, 250, 240),
+ 'forestgreen': (34, 139, 34),
+ 'fuchsia': (255, 0, 255),
+ 'gainsboro': (220, 220, 220),
+ 'ghostwhite': (248, 248, 255),
+ 'gold': (255, 215, 0),
+ 'goldenrod': (218, 165, 32),
+ 'gray': (128, 128, 128),
+ 'grey': (128, 128, 128),
+ 'green': (0, 128, 0),
+ 'greenyellow': (173, 255, 47),
+ 'honeydew': (240, 255, 240),
+ 'hotpink': (255, 105, 180),
+ 'indianred': (205, 92, 92),
+ 'indigo': (75, 0, 130),
+ 'ivory': (255, 255, 240),
+ 'khaki': (240, 230, 140),
+ 'lavender': (230, 230, 250),
+ 'lavenderblush': (255, 240, 245),
+ 'lawngreen': (124, 252, 0),
+ 'lemonchiffon': (255, 250, 205),
+ 'lightblue': (173, 216, 230),
+ 'lightcoral': (240, 128, 128),
+ 'lightcyan': (224, 255, 255),
+ 'lightgoldenrodyellow': (250, 250, 210),
+ 'lightgray': (211, 211, 211),
+ 'lightgreen': (144, 238, 144),
+ 'lightgrey': (211, 211, 211),
+ 'lightpink': (255, 182, 193),
+ 'lightsalmon': (255, 160, 122),
+ 'lightseagreen': (32, 178, 170),
+ 'lightskyblue': (135, 206, 250),
+ 'lightslategray': (119, 136, 153),
+ 'lightslategrey': (119, 136, 153),
+ 'lightsteelblue': (176, 196, 222),
+ 'lightyellow': (255, 255, 224),
+ 'lime': (0, 255, 0),
+ 'limegreen': (50, 205, 50),
+ 'linen': (250, 240, 230),
+ 'magenta': (255, 0, 255),
+ 'maroon': (128, 0, 0),
+ 'mediumaquamarine': (102, 205, 170),
+ 'mediumblue': (0, 0, 205),
+ 'mediumorchid': (186, 85, 211),
+ 'mediumpurple': (147, 112, 219),
+ 'mediumseagreen': (60, 179, 113),
+ 'mediumslateblue': (123, 104, 238),
+ 'mediumspringgreen': (0, 250, 154),
+ 'mediumturquoise': (72, 209, 204),
+ 'mediumvioletred': (199, 21, 133),
+ 'midnightblue': (25, 25, 112),
+ 'mintcream': (245, 255, 250),
+ 'mistyrose': (255, 228, 225),
+ 'moccasin': (255, 228, 181),
+ 'navajowhite': (255, 222, 173),
+ 'navy': (0, 0, 128),
+ 'oldlace': (253, 245, 230),
+ 'olive': (128, 128, 0),
+ 'olivedrab': (107, 142, 35),
+ 'orange': (255, 165, 0),
+ 'orangered': (255, 69, 0),
+ 'orchid': (218, 112, 214),
+ 'palegoldenrod': (238, 232, 170),
+ 'palegreen': (152, 251, 152),
+ 'paleturquoise': (175, 238, 238),
+ 'palevioletred': (219, 112, 147),
+ 'papayawhip': (255, 239, 213),
+ 'peachpuff': (255, 218, 185),
+ 'peru': (205, 133, 63),
+ 'pink': (255, 192, 203),
+ 'plum': (221, 160, 221),
+ 'powderblue': (176, 224, 230),
+ 'purple': (128, 0, 128),
+ 'red': (255, 0, 0),
+ 'rosybrown': (188, 143, 143),
+ 'royalblue': (65, 105, 225),
+ 'saddlebrown': (139, 69, 19),
+ 'salmon': (250, 128, 114),
+ 'sandybrown': (244, 164, 96),
+ 'seagreen': (46, 139, 87),
+ 'seashell': (255, 245, 238),
+ 'sienna': (160, 82, 45),
+ 'silver': (192, 192, 192),
+ 'skyblue': (135, 206, 235),
+ 'slateblue': (106, 90, 205),
+ 'slategray': (112, 128, 144),
+ 'slategrey': (112, 128, 144),
+ 'snow': (255, 250, 250),
+ 'springgreen': (0, 255, 127),
+ 'steelblue': (70, 130, 180),
+ 'tan': (210, 180, 140),
+ 'teal': (0, 128, 128),
+ 'thistle': (216, 191, 216),
+ 'tomato': (255, 99, 71),
+ 'turquoise': (64, 224, 208),
+ 'violet': (238, 130, 238),
+ 'wheat': (245, 222, 179),
+ 'white': (255, 255, 255),
+ 'whitesmoke': (245, 245, 245),
+ 'yellow': (255, 255, 0),
+ 'yellowgreen': (154, 205, 50)}