4 # Name: 'SVG: Re-Import UV layout from SVG file'
7 # Tooltip: 'Re-import UV layout from SVG file'
10 __author__ = "Melchior FRANZ < mfranz # aon : at >"
11 __url__ = "http://members.aon.at/mfranz/flightgear/"
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 ID_SEPARATOR = '_____'
25 import Blender, sys, math, re
26 from xml.sax import saxexts
30 numwsp = re.compile('(?<=[\d.])\s+(?=[-+.\d])')
31 commawsp = re.compile('\s+|\s*,\s*')
32 istrans = re.compile('^\s*(skewX|skewY|scale|translate|rotate|matrix)\s*\(([^\)]*)\)\s*')
33 isnumber = re.compile('^[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?$')
36 class Abort(Exception):
37 def __init__(self, msg):
42 def __init__(self, a = 1, b = 0, c = 0, d = 1, e = 0, f = 0):
43 self.a = a; self.b = b; self.c = c; self.d = d; self.e = e; self.f = f
46 return "[Matrix %f %f %f %f %f %f]" % (self.a, self.b, self.c, self.d, self.e, self.f)
48 def multiply(self, mat):
49 a = self.a * mat.a + self.c * mat.b
50 b = self.b * mat.a + self.d * mat.b
51 c = self.a * mat.c + self.c * mat.d
52 d = self.b * mat.c + self.d * mat.d
53 e = self.a * mat.e + self.c * mat.f + self.e
54 f = self.b * mat.e + self.d * mat.f + self.f
55 self.a = a; self.b = b; self.c = c; self.d = d; self.e = e; self.f = f
57 def transform(self, u, v):
58 x = u * self.a + v * self.c + self.e
59 y = u * self.b + v * self.d + self.f
62 def translate(self, dx, dy):
63 self.multiply(Matrix(1, 0, 0, 1, dx, dy))
65 def scale(self, sx, sy):
66 self.multiply(Matrix(sx, 0, 0, sy, 0, 0))
70 self.multiply(Matrix(math.cos(a), math.sin(a), -math.sin(a), math.cos(a), 0, 0))
74 self.multiply(Matrix(1, 0, math.tan(a), 1, 0, 0))
78 self.multiply(Matrix(1, math.tan(a), 0, 1, 0, 0))
81 def parse_transform(s):
84 match = istrans.match(s)
88 values = commawsp.split(match.group(2).strip())
89 s = s[len(match.group(0)):]
93 match = isnumber.match(value)
95 raise Abort("bad transform value")
97 arg.append(float(match.group(0)))
112 matrix.scale(arg[0], arg[0])
115 matrix.scale(arg[0], arg[1])
118 elif cmd == "translate":
120 matrix.translate(arg[0], 0)
123 matrix.translate(arg[0], arg[1])
126 elif cmd == "rotate":
128 matrix.rotate(arg[0])
131 matrix.translate(-arg[1], -arg[2])
132 matrix.rotate(arg[0])
133 matrix.translate(arg[1], arg[2])
136 elif cmd == "matrix":
138 matrix.multiply(Matrix(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5]))
142 print "ERROR: unknown transform", cmd
145 print "ERROR: '%s' with wrong argument number (%d)" % (cmd, num)
148 print "ERROR: transform with trailing garbage (%s)" % s
154 def error(self, exception):
155 raise Abort(str(exception))
157 def fatalError(self, exception):
158 raise Abort(str(exception))
160 def warning(self, exception):
161 print "WARNING: " + str(exception)
164 def setDocumentLocator(self, whatever):
167 def startDocument(self):
168 self.verified = False
169 self.scandesc = False
170 self.matrices = [None]
172 for o in Blender.Scene.GetCurrent().objects:
175 mesh = o.getData(mesh = 1)
178 if mesh.name in self.meshes:
180 self.meshes[mesh.name] = mesh
182 def endDocument(self):
185 def characters(self, data, start, length):
186 if not self.scandesc:
188 if data[start:start + length].startswith("uv_export_svg.py"):
191 def ignorableWhitespace(self, data, start, length):
194 def startElement(self, name, attrs):
195 currmat = self.matrices[-1]
196 if "transform" in attrs:
197 m = parse_transform(attrs["transform"])
200 self.matrices.append(m)
202 self.matrices.append(currmat)
204 if name == "polygon":
205 self.handlePolygon(attrs)
207 if "viewBox" in attrs:
208 x, y, w, h = commawsp.split(attrs["viewBox"], 4)
210 raise Abort("bad viewBox")
213 if self.width != self.height:
214 raise Abort("viewBox isn't a square")
216 raise Abort("no viewBox")
217 elif name == "desc" and not self.verified:
220 def endElement(self, name):
221 self.scandesc = False
222 self.matrices = self.matrices[:-1]
224 def handlePolygon(self, attrs):
225 if not self.verified:
226 raise Abort("this file wasn't written by uv_export_svg.py")
227 ident = attrs.get("id", None)
228 points = attrs.get("points", None)
230 if not ident or not points:
231 print('bad polygon "%s"' % ident)
234 sep = ident.find(ID_SEPARATOR)
236 print('broken id "%s"' % ident)
239 meshname = str(ident[:sep])
240 num = int(ident[sep + 1:])
242 if not meshname in self.meshes:
243 print('unknown mesh "%s"' % meshname)
246 #print 'mesh %s face %d: ' % (meshname, num)
247 matrix = self.matrices[-1]
249 for p in numwsp.split(points.strip()):
250 u, v = commawsp.split(p.strip(), 2)
254 u, v = matrix.transform(u, v)
255 transuv.append((u / self.width, 1 - v / self.height))
257 for i, uv in enumerate(self.meshes[meshname].faces[num].uv):
258 uv[0] = transuv[i][0]
259 uv[1] = transuv[i][1]
262 def run_parser(filename):
263 editmode = Blender.Window.EditMode()
265 Blender.Window.EditMode(0)
266 Blender.Window.WaitCursor(1)
269 svg = saxexts.ParserFactory().make_parser("xml.sax.drivers.drv_xmlproc")
270 svg.setDocumentHandler(import_svg())
271 svg.setErrorHandler(import_svg())
275 print "Error:", e.msg, " -> aborting ...\n"
276 Blender.Draw.PupMenu("Error%t|" + e.msg)
278 Blender.Window.RedrawAll()
279 Blender.Window.WaitCursor(0)
281 Blender.Window.EditMode(1)
284 active = Blender.Scene.GetCurrent().objects.active
285 (basename, extname) = Blender.sys.splitext(Blender.Get("filename"))
286 filename = Blender.sys.basename(basename) + "-" + active.name + ".svg"
288 registry = Blender.Registry.GetKey("UVImportExportSVG", False)
289 if registry and basename in registry:
290 filename = registry[basename]
292 Blender.Window.FileSelector(run_parser, "Import SVG", filename)