4 # Name: 'UV: (Re)Import UV from SVG'
7 # Tooltip: 'Re-import UV layout from SVG file'
10 __author__ = "Melchior FRANZ < mfranz # aon : at >"
11 __url__ = "http://www.flightgear.org/"
14 Imports an SVG file containing UV maps, which has been saved by the
15 uv_export.svg script. This allows to move, scale, and rotate object
16 mapping in SVG editors like Inkscape. Note that all contained UV maps
17 will be set, no matter which objects are actually selected at the moment.
18 The choice has been made when the file was saved!
22 #--------------------------------------------------------------------------------
23 # Copyright (C) 2008 Melchior FRANZ < mfranz # aon : at >
25 # This program is free software; you can redistribute it and/or
26 # modify it under the terms of the GNU General Public License as
27 # published by the Free Software Foundation; either version 2 of the
28 # License, or (at your option) any later version.
30 # This program is distributed in the hope that it will be useful, but
31 # WITHOUT ANY WARRANTY; without even the implied warranty of
32 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
33 # General Public License for more details.
35 # You should have received a copy of the GNU General Public License
36 # along with this program; if not, write to the Free Software
37 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
38 #--------------------------------------------------------------------------------
41 ID_SEPARATOR = '_.:._'
44 import Blender, BPyMessages, sys, math, re
45 from xml.sax import handler, make_parser
48 numwsp = re.compile('(?<=[\d.])\s+(?=[-+.\d])')
49 commawsp = re.compile('\s+|\s*,\s*')
50 istrans = re.compile('^\s*(skewX|skewY|scale|translate|rotate|matrix)\s*\(([^\)]*)\)\s*')
51 isnumber = re.compile('^[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?$')
54 class Abort(Exception):
55 def __init__(self, msg):
60 def __init__(self, a = 1, b = 0, c = 0, d = 1, e = 0, f = 0):
61 self.a = a; self.b = b; self.c = c; self.d = d; self.e = e; self.f = f
64 return "[Matrix %f %f %f %f %f %f]" % (self.a, self.b, self.c, self.d, self.e, self.f)
66 def multiply(self, mat):
67 a = mat.a * self.a + mat.c * self.b
68 b = mat.b * self.a + mat.d * self.b
69 c = mat.a * self.c + mat.c * self.d
70 d = mat.b * self.c + mat.d * self.d
71 e = mat.a * self.e + mat.c * self.f + mat.e
72 f = mat.b * self.e + mat.d * self.f + mat.f
73 self.a = a; self.b = b; self.c = c; self.d = d; self.e = e; self.f = f
75 def transform(self, u, v):
76 return u * self.a + v * self.c + self.e, u * self.b + v * self.d + self.f
78 def translate(self, dx, dy):
79 self.multiply(Matrix(1, 0, 0, 1, dx, dy))
81 def scale(self, sx, sy):
82 self.multiply(Matrix(sx, 0, 0, sy, 0, 0))
86 self.multiply(Matrix(math.cos(a), math.sin(a), -math.sin(a), math.cos(a), 0, 0))
90 self.multiply(Matrix(1, 0, math.tan(a), 1, 0, 0))
94 self.multiply(Matrix(1, math.tan(a), 0, 1, 0, 0))
97 def parse_transform(s):
100 match = istrans.match(s)
104 values = commawsp.split(match.group(2).strip())
105 s = s[len(match.group(0)):]
109 match = isnumber.match(value)
111 raise Abort("bad transform value")
113 arg.append(float(match.group(0)))
128 matrix.scale(arg[0], arg[0])
131 matrix.scale(arg[0], arg[1])
134 elif cmd == "translate":
136 matrix.translate(arg[0], 0)
139 matrix.translate(arg[0], arg[1])
142 elif cmd == "rotate":
144 matrix.rotate(arg[0])
147 matrix.translate(-arg[1], -arg[2])
148 matrix.rotate(arg[0])
149 matrix.translate(arg[1], arg[2])
152 elif cmd == "matrix":
154 matrix.multiply(Matrix(*arg))
158 print "ERROR: unknown transform", cmd
161 print "ERROR: '%s' with wrong argument number (%d)" % (cmd, num)
164 print "ERROR: transform with trailing garbage (%s)" % s
168 class import_svg(handler.ContentHandler):
170 def error(self, exception):
171 raise Abort(str(exception))
173 def fatalError(self, exception):
174 raise Abort(str(exception))
176 def warning(self, exception):
177 print "WARNING: " + str(exception)
180 def setDocumentLocator(self, whatever):
183 def startDocument(self):
184 self.verified = False
185 self.scandesc = False
186 self.matrices = [None]
188 for o in Blender.Scene.GetCurrent().objects:
191 mesh = o.getData(mesh = 1)
194 if mesh.name in self.meshes:
196 self.meshes[mesh.name] = mesh
198 def endDocument(self):
201 def characters(self, data):
202 if not self.scandesc:
204 if data.startswith("uv_export_svg.py"):
207 def ignorableWhitespace(self, data, start, length):
210 def processingInstruction(self, target, data):
213 def startElement(self, name, attrs):
214 currmat = self.matrices[-1]
216 m = parse_transform(attrs["transform"])
219 self.matrices.append(m)
221 self.matrices.append(currmat)
223 if name == "polygon":
224 self.handlePolygon(attrs)
227 x, y, w, h = commawsp.split(attrs["viewBox"], 4)
229 raise Abort("bad viewBox")
232 if self.width != self.height:
233 raise Abort("viewBox isn't a square")
235 raise Abort("no viewBox")
236 elif name == "desc" and not self.verified:
239 def endElement(self, name):
240 self.scandesc = False
243 def handlePolygon(self, attrs):
244 if not self.verified:
245 raise Abort("this file wasn't written by uv_export_svg.py")
246 ident = attrs.get("id", None)
247 points = attrs.get("points", None)
249 if not ident or not points:
250 print('bad polygon "%s"' % ident)
254 meshname, num = ident.strip().split(ID_SEPARATOR, 2)
256 print('broken id "%s"' % ident)
259 if not meshname in self.meshes:
260 print('unknown mesh "%s"' % meshname)
263 #print 'mesh %s face %d: ' % (meshname, num)
264 matrix = self.matrices[-1]
266 for p in numwsp.split(points.strip()):
267 u, v = commawsp.split(p.strip(), 2)
271 u, v = matrix.transform(u, v)
272 transuv.append((u / self.width, 1 - v / self.height))
274 for i, uv in enumerate(self.meshes[meshname].faces[int(num)].uv):
275 uv[0] = transuv[i][0]
276 uv[1] = transuv[i][1]
280 def run_parser(path):
281 if BPyMessages.Error_NoFile(path):
284 editmode = Blender.Window.EditMode()
286 Blender.Window.EditMode(0)
287 Blender.Window.WaitCursor(1)
291 svg.setContentHandler(import_svg())
292 svg.setErrorHandler(import_svg())
294 Blender.Registry.SetKey("UVImportExportSVG", { "path" : path }, False)
297 print "Error:", e.msg, " -> aborting ...\n"
298 Blender.Draw.PupMenu("Error%t|" + e.msg)
300 Blender.Window.RedrawAll()
301 Blender.Window.WaitCursor(0)
303 Blender.Window.EditMode(1)
306 registry = Blender.Registry.GetKey("UVImportExportSVG", False)
307 if registry and "path" in registry and Blender.sys.exists(Blender.sys.expandpath(registry["path"])):
308 path = registry["path"]
312 Blender.Window.FileSelector(run_parser, "Import SVG", path)