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/", "http://cvs.flightgear.org/viewvc/source/utils/Modeller/uv_import_svg.py"]
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 mappings 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, xml.sax
47 numwsp = re.compile('(?<=[\d.])\s+(?=[-+.\d])')
48 commawsp = re.compile('\s+|\s*,\s*')
49 istrans = re.compile('^\s*(skewX|skewY|scale|translate|rotate|matrix)\s*\(([^\)]*)\)\s*')
50 isnumber = re.compile('^[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?$')
53 class Abort(Exception):
54 def __init__(self, msg):
59 def __init__(self, a = 1, b = 0, c = 0, d = 1, e = 0, f = 0):
60 self.a = a; self.b = b; self.c = c; self.d = d; self.e = e; self.f = f
63 return "[Matrix %f %f %f %f %f %f]" % (self.a, self.b, self.c, self.d, self.e, self.f)
65 def multiply(self, mat):
66 a = mat.a * self.a + mat.c * self.b
67 b = mat.b * self.a + mat.d * self.b
68 c = mat.a * self.c + mat.c * self.d
69 d = mat.b * self.c + mat.d * self.d
70 e = mat.a * self.e + mat.c * self.f + mat.e
71 f = mat.b * self.e + mat.d * self.f + mat.f
72 self.a = a; self.b = b; self.c = c; self.d = d; self.e = e; self.f = f
74 def transform(self, u, v):
75 return u * self.a + v * self.c + self.e, u * self.b + v * self.d + self.f
77 def translate(self, dx, dy):
78 self.multiply(Matrix(1, 0, 0, 1, dx, dy))
80 def scale(self, sx, sy):
81 self.multiply(Matrix(sx, 0, 0, sy, 0, 0))
85 self.multiply(Matrix(math.cos(a), math.sin(a), -math.sin(a), math.cos(a), 0, 0))
89 self.multiply(Matrix(1, 0, math.tan(a), 1, 0, 0))
93 self.multiply(Matrix(1, math.tan(a), 0, 1, 0, 0))
96 def parse_transform(s):
99 match = istrans.match(s)
103 values = commawsp.split(match.group(2).strip())
104 s = s[len(match.group(0)):]
108 match = isnumber.match(value)
110 raise Abort("bad transform value")
112 arg.append(float(match.group(0)))
127 matrix.scale(arg[0], arg[0])
130 matrix.scale(arg[0], arg[1])
133 elif cmd == "translate":
135 matrix.translate(arg[0], 0)
138 matrix.translate(arg[0], arg[1])
141 elif cmd == "rotate":
143 matrix.rotate(arg[0])
146 matrix.translate(-arg[1], -arg[2])
147 matrix.rotate(arg[0])
148 matrix.translate(arg[1], arg[2])
151 elif cmd == "matrix":
153 matrix.multiply(Matrix(*arg))
157 print "ERROR: unknown transform", cmd
160 print "ERROR: '%s' with wrong argument number (%d)" % (cmd, num)
163 print "ERROR: transform with trailing garbage (%s)" % s
167 class import_svg(xml.sax.handler.ContentHandler):
169 def error(self, exception):
170 raise Abort(str(exception))
172 def fatalError(self, exception):
173 raise Abort(str(exception))
175 def warning(self, exception):
176 print "WARNING: " + str(exception)
179 def setDocumentLocator(self, whatever):
182 def startDocument(self):
183 self.verified = False
184 self.scandesc = False
185 self.matrices = [None]
187 for o in Blender.Scene.GetCurrent().objects:
190 mesh = o.getData(mesh = 1)
193 if mesh.name in self.meshes:
195 self.meshes[mesh.name] = mesh
197 def endDocument(self):
200 def characters(self, data):
201 if not self.scandesc:
203 if data.startswith("uv_export_svg.py"):
206 def ignorableWhitespace(self, data, start, length):
209 def processingInstruction(self, target, data):
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 xml.sax.parse(path, import_svg(), import_svg())
289 Blender.Registry.SetKey("UVImportExportSVG", { "path" : path }, False)
292 print "Error:", e.msg, " -> aborting ...\n"
293 Blender.Draw.PupMenu("Error%t|" + e.msg)
295 Blender.Window.RedrawAll()
296 Blender.Window.WaitCursor(0)
298 Blender.Window.EditMode(1)
301 registry = Blender.Registry.GetKey("UVImportExportSVG", False)
302 if registry and "path" in registry and Blender.sys.exists(Blender.sys.expandpath(registry["path"])):
303 path = registry["path"]
307 Blender.Window.FileSelector(run_parser, "Import SVG", path)