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://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!
25 import Blender, BPyMessages, 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 = mat.a * self.a + mat.c * self.b
50 b = mat.b * self.a + mat.d * self.b
51 c = mat.a * self.c + mat.c * self.d
52 d = mat.b * self.c + mat.d * self.d
53 e = mat.a * self.e + mat.c * self.f + mat.e
54 f = mat.b * self.e + mat.d * self.f + mat.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
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)
235 meshname, num = ident.strip().split(ID_SEPARATOR, 2)
237 print('broken id "%s"' % ident)
240 if not meshname in self.meshes:
241 print('unknown mesh "%s"' % meshname)
244 #print 'mesh %s face %d: ' % (meshname, num)
245 matrix = self.matrices[-1]
247 for p in numwsp.split(points.strip()):
248 u, v = commawsp.split(p.strip(), 2)
252 u, v = matrix.transform(u, v)
253 transuv.append((u / self.width, 1 - v / self.height))
255 for i, uv in enumerate(self.meshes[meshname].faces[int(num)].uv):
256 uv[0] = transuv[i][0]
257 uv[1] = transuv[i][1]
260 def run_parser(path):
261 if BPyMessages.Error_NoFile(path):
264 editmode = Blender.Window.EditMode()
266 Blender.Window.EditMode(0)
267 Blender.Window.WaitCursor(1)
270 svg = saxexts.ParserFactory().make_parser("xml.sax.drivers.drv_xmlproc")
271 svg.setDocumentHandler(import_svg())
272 svg.setErrorHandler(import_svg())
274 Blender.Registry.SetKey("UVImportExportSVG", { "path" : path }, False)
277 print "Error:", e.msg, " -> aborting ...\n"
278 Blender.Draw.PupMenu("Error%t|" + e.msg)
280 Blender.Window.RedrawAll()
281 Blender.Window.WaitCursor(0)
283 Blender.Window.EditMode(1)
286 registry = Blender.Registry.GetKey("UVImportExportSVG", False)
287 if registry and "path" in registry and Blender.sys.exists(Blender.sys.expandpath(registry["path"])):
288 path = registry["path"]
292 Blender.Window.FileSelector(run_parser, "Import SVG", path)