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 saxexts
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 x = u * self.a + v * self.c + self.e
77 y = u * self.b + v * self.d + self.f
80 def translate(self, dx, dy):
81 self.multiply(Matrix(1, 0, 0, 1, dx, dy))
83 def scale(self, sx, sy):
84 self.multiply(Matrix(sx, 0, 0, sy, 0, 0))
88 self.multiply(Matrix(math.cos(a), math.sin(a), -math.sin(a), math.cos(a), 0, 0))
92 self.multiply(Matrix(1, 0, math.tan(a), 1, 0, 0))
96 self.multiply(Matrix(1, math.tan(a), 0, 1, 0, 0))
99 def parse_transform(s):
102 match = istrans.match(s)
106 values = commawsp.split(match.group(2).strip())
107 s = s[len(match.group(0)):]
111 match = isnumber.match(value)
113 raise Abort("bad transform value")
115 arg.append(float(match.group(0)))
130 matrix.scale(arg[0], arg[0])
133 matrix.scale(arg[0], arg[1])
136 elif cmd == "translate":
138 matrix.translate(arg[0], 0)
141 matrix.translate(arg[0], arg[1])
144 elif cmd == "rotate":
146 matrix.rotate(arg[0])
149 matrix.translate(-arg[1], -arg[2])
150 matrix.rotate(arg[0])
151 matrix.translate(arg[1], arg[2])
154 elif cmd == "matrix":
156 matrix.multiply(Matrix(*arg))
160 print "ERROR: unknown transform", cmd
163 print "ERROR: '%s' with wrong argument number (%d)" % (cmd, num)
166 print "ERROR: transform with trailing garbage (%s)" % s
172 def error(self, exception):
173 raise Abort(str(exception))
175 def fatalError(self, exception):
176 raise Abort(str(exception))
178 def warning(self, exception):
179 print "WARNING: " + str(exception)
182 def setDocumentLocator(self, whatever):
185 def startDocument(self):
186 self.verified = False
187 self.scandesc = False
188 self.matrices = [None]
190 for o in Blender.Scene.GetCurrent().objects:
193 mesh = o.getData(mesh = 1)
196 if mesh.name in self.meshes:
198 self.meshes[mesh.name] = mesh
200 def endDocument(self):
203 def characters(self, data, start, length):
204 if not self.scandesc:
206 if data[start:start + length].startswith("uv_export_svg.py"):
209 def ignorableWhitespace(self, data, start, length):
212 def startElement(self, name, attrs):
213 currmat = self.matrices[-1]
214 if "transform" in attrs:
215 m = parse_transform(attrs["transform"])
218 self.matrices.append(m)
220 self.matrices.append(currmat)
222 if name == "polygon":
223 self.handlePolygon(attrs)
225 if "viewBox" in attrs:
226 x, y, w, h = commawsp.split(attrs["viewBox"], 4)
228 raise Abort("bad viewBox")
231 if self.width != self.height:
232 raise Abort("viewBox isn't a square")
234 raise Abort("no viewBox")
235 elif name == "desc" and not self.verified:
238 def endElement(self, name):
239 self.scandesc = False
242 def handlePolygon(self, attrs):
243 if not self.verified:
244 raise Abort("this file wasn't written by uv_export_svg.py")
245 ident = attrs.get("id", None)
246 points = attrs.get("points", None)
248 if not ident or not points:
249 print('bad polygon "%s"' % ident)
253 meshname, num = ident.strip().split(ID_SEPARATOR, 2)
255 print('broken id "%s"' % ident)
258 if not meshname in self.meshes:
259 print('unknown mesh "%s"' % meshname)
262 #print 'mesh %s face %d: ' % (meshname, num)
263 matrix = self.matrices[-1]
265 for p in numwsp.split(points.strip()):
266 u, v = commawsp.split(p.strip(), 2)
270 u, v = matrix.transform(u, v)
271 transuv.append((u / self.width, 1 - v / self.height))
273 for i, uv in enumerate(self.meshes[meshname].faces[int(num)].uv):
274 uv[0] = transuv[i][0]
275 uv[1] = transuv[i][1]
278 def run_parser(path):
279 if BPyMessages.Error_NoFile(path):
282 editmode = Blender.Window.EditMode()
284 Blender.Window.EditMode(0)
285 Blender.Window.WaitCursor(1)
288 svg = saxexts.ParserFactory().make_parser("xml.sax.drivers.drv_xmlproc")
289 svg.setDocumentHandler(import_svg())
290 svg.setErrorHandler(import_svg())
292 Blender.Registry.SetKey("UVImportExportSVG", { "path" : path }, False)
295 print "Error:", e.msg, " -> aborting ...\n"
296 Blender.Draw.PupMenu("Error%t|" + e.msg)
298 Blender.Window.RedrawAll()
299 Blender.Window.WaitCursor(0)
301 Blender.Window.EditMode(1)
304 registry = Blender.Registry.GetKey("UVImportExportSVG", False)
305 if registry and "path" in registry and Blender.sys.exists(Blender.sys.expandpath(registry["path"])):
306 path = registry["path"]
310 Blender.Window.FileSelector(run_parser, "Import SVG", path)