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
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]
215 if "transform" in attrs:
216 m = parse_transform(attrs["transform"])
219 self.matrices.append(m)
221 self.matrices.append(currmat)
223 if name == "polygon":
224 self.handlePolygon(attrs)
226 if "viewBox" in 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]
279 def run_parser(path):
280 if BPyMessages.Error_NoFile(path):
283 editmode = Blender.Window.EditMode()
285 Blender.Window.EditMode(0)
286 Blender.Window.WaitCursor(1)
290 svg.setContentHandler(import_svg())
291 svg.setErrorHandler(import_svg())
293 Blender.Registry.SetKey("UVImportExportSVG", { "path" : path }, False)
296 print "Error:", e.msg, " -> aborting ...\n"
297 Blender.Draw.PupMenu("Error%t|" + e.msg)
299 Blender.Window.RedrawAll()
300 Blender.Window.WaitCursor(0)
302 Blender.Window.EditMode(1)
305 registry = Blender.Registry.GetKey("UVImportExportSVG", False)
306 if registry and "path" in registry and Blender.sys.exists(Blender.sys.expandpath(registry["path"])):
307 path = registry["path"]
311 Blender.Window.FileSelector(run_parser, "Import SVG", path)