]> git.mxchange.org Git - flightgear.git/blob - utils/Modeller/uv_import_svg.py
- improve Blender registry handling
[flightgear.git] / utils / Modeller / uv_import_svg.py
1 #!BPY
2
3 # """
4 # Name: 'UV: (Re)Import UV from SVG'
5 # Blender: 245
6 # Group: 'Image'
7 # Tooltip: 'Re-import UV layout from SVG file'
8 # """
9
10 __author__ = "Melchior FRANZ < mfranz # aon : at >"
11 __url__ = "http://members.aon.at/mfranz/flightgear/"
12 __version__ = "0.1"
13 __bpydoc__ = """\
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!
19 """
20
21
22 ID_SEPARATOR = '_.._'
23
24
25 import Blender, BPyMessages, sys, math, re
26 from xml.sax import saxexts
27
28
29 registry = {}
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+)?$')
34
35
36 class Abort(Exception):
37         def __init__(self, msg):
38                 self.msg = msg
39
40
41 class Matrix:
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
44
45         def __str__(self):
46                 return "[Matrix %f %f %f %f %f %f]" % (self.a, self.b, self.c, self.d, self.e, self.f)
47
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
56
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
60                 return (x, y)
61
62         def translate(self, dx, dy):
63                 self.multiply(Matrix(1, 0, 0, 1, dx, dy))
64
65         def scale(self, sx, sy):
66                 self.multiply(Matrix(sx, 0, 0, sy, 0, 0))
67
68         def rotate(self, a):
69                 a *= math.pi / 180
70                 self.multiply(Matrix(math.cos(a), math.sin(a), -math.sin(a), math.cos(a), 0, 0))
71
72         def skewX(self, a):
73                 a *= math.pi / 180
74                 self.multiply(Matrix(1, 0, math.tan(a), 1, 0, 0))
75
76         def skewY(self, a):
77                 a *= math.pi / 180
78                 self.multiply(Matrix(1, math.tan(a), 0, 1, 0, 0))
79
80
81 def parse_transform(s):
82         matrix = Matrix()
83         while True:
84                 match = istrans.match(s)
85                 if not match:
86                         break
87                 cmd = match.group(1)
88                 values = commawsp.split(match.group(2).strip())
89                 s = s[len(match.group(0)):]
90                 arg = []
91
92                 for value in values:
93                         match = isnumber.match(value)
94                         if not match:
95                                 raise Abort("bad transform value")
96
97                         arg.append(float(match.group(0)))
98
99                 num = len(arg)
100                 if cmd == "skewX":
101                         if num == 1:
102                                 matrix.skewX(arg[0])
103                                 continue
104
105                 elif cmd == "skewY":
106                         if num == 1:
107                                 matrix.skewY(arg[0])
108                                 continue
109
110                 elif cmd == "scale":
111                         if num == 1:
112                                 matrix.scale(arg[0], arg[0])
113                                 continue
114                         if num == 2:
115                                 matrix.scale(arg[0], arg[1])
116                                 continue
117
118                 elif cmd == "translate":
119                         if num == 1:
120                                 matrix.translate(arg[0], 0)
121                                 continue
122                         if num == 2:
123                                 matrix.translate(arg[0], arg[1])
124                                 continue
125
126                 elif cmd == "rotate":
127                         if num == 1:
128                                 matrix.rotate(arg[0])
129                                 continue
130                         if num == 3:
131                                 matrix.translate(-arg[1], -arg[2])
132                                 matrix.rotate(arg[0])
133                                 matrix.translate(arg[1], arg[2])
134                                 continue
135
136                 elif cmd == "matrix":
137                         if num == 6:
138                                 matrix.multiply(Matrix(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5]))
139                                 continue
140
141                 else:
142                         print "ERROR: unknown transform", cmd
143                         continue
144
145                 print "ERROR: '%s' with wrong argument number (%d)" % (cmd, num)
146
147         if len(s):
148                 print "ERROR: transform with trailing garbage (%s)" % s
149         return matrix
150
151
152 class import_svg:
153         # err_handler
154         def error(self, exception):
155                 raise Abort(str(exception))
156
157         def fatalError(self, exception):
158                 raise Abort(str(exception))
159
160         def warning(self, exception):
161                 print "WARNING: " + str(exception)
162
163         # doc_handler
164         def setDocumentLocator(self, whatever):
165                 pass
166
167         def startDocument(self):
168                 self.verified = False
169                 self.scandesc = False
170                 self.matrices = [None]
171                 self.meshes = {}
172                 for o in Blender.Scene.GetCurrent().objects:
173                         if o.type != "Mesh":
174                                 continue
175                         mesh = o.getData(mesh = 1)
176                         if not mesh.faceUV:
177                                 continue
178                         if mesh.name in self.meshes:
179                                 continue
180                         self.meshes[mesh.name] = mesh
181
182         def endDocument(self):
183                 pass
184
185         def characters(self, data, start, length):
186                 if not self.scandesc:
187                         return
188                 if data[start:start + length].startswith("uv_export_svg.py"):
189                         self.verified = True
190
191         def ignorableWhitespace(self, data, start, length):
192                 pass
193
194         def startElement(self, name, attrs):
195                 currmat = self.matrices[-1]
196                 if "transform" in attrs:
197                         m = parse_transform(attrs["transform"])
198                         if currmat != None:
199                                 m.multiply(currmat)
200                         self.matrices.append(m)
201                 else:
202                         self.matrices.append(currmat)
203
204                 if name == "polygon":
205                         self.handlePolygon(attrs)
206                 elif name == "svg":
207                         if "viewBox" in attrs:
208                                 x, y, w, h = commawsp.split(attrs["viewBox"], 4)
209                                 if int(x) or int(y):
210                                         raise Abort("bad viewBox")
211                                 self.width = int(w)
212                                 self.height = int(h)
213                                 if self.width != self.height:
214                                         raise Abort("viewBox isn't a square")
215                         else:
216                                 raise Abort("no viewBox")
217                 elif name == "desc" and not self.verified:
218                         self.scandesc = True
219
220         def endElement(self, name):
221                 self.scandesc = False
222                 self.matrices.pop()
223
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)
229
230                 if not ident or not points:
231                         print('bad polygon "%s"' % ident)
232                         return
233
234                 try:
235                         meshname, num = ident.strip().split(ID_SEPARATOR, 2)
236                 except:
237                         print('broken id "%s"' % ident)
238                         return
239
240                 if not meshname in self.meshes:
241                         print('unknown mesh "%s"' % meshname)
242                         return
243
244                 #print 'mesh %s face %d: ' % (meshname, num)
245                 matrix = self.matrices[-1]
246                 transuv = []
247                 for p in numwsp.split(points.strip()):
248                         u, v = commawsp.split(p.strip(), 2)
249                         u = float(u)
250                         v = float(v)
251                         if matrix:
252                                 u, v = matrix.transform(u, v)
253                         transuv.append((u / self.width, 1 - v / self.height))
254
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]
258
259
260 def run_parser(path):
261         if BPyMessages.Error_NoFile(path):
262                 return
263
264         editmode = Blender.Window.EditMode()
265         if editmode:
266                 Blender.Window.EditMode(0)
267         Blender.Window.WaitCursor(1)
268
269         try:
270                 svg = saxexts.ParserFactory().make_parser("xml.sax.drivers.drv_xmlproc")
271                 svg.setDocumentHandler(import_svg())
272                 svg.setErrorHandler(import_svg())
273                 svg.parse(path)
274                 Blender.Registry.SetKey("UVImportExportSVG", { "path" : path }, False)
275
276         except Abort, e:
277                 print "Error:", e.msg, "  -> aborting ...\n"
278                 Blender.Draw.PupMenu("Error%t|" + e.msg)
279
280         Blender.Window.RedrawAll()
281         Blender.Window.WaitCursor(0)
282         if editmode:
283                 Blender.Window.EditMode(1)
284
285
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"]
289 else:
290         path = ""
291
292 Blender.Window.FileSelector(run_parser, "Import SVG", path)
293