]> git.mxchange.org Git - flightgear.git/commitdiff
UV<->SVG exporter and importer: The importer loads an SVG file that was
authormfranz <mfranz>
Wed, 5 Mar 2008 12:30:01 +0000 (12:30 +0000)
committermfranz <mfranz>
Wed, 5 Mar 2008 12:30:01 +0000 (12:30 +0000)
saved before by the exporter. SVG editors like Inkscape can be used to
move the UV outlines around, to rotate and scale them.

utils/Modeller/uv_export_svg.py [new file with mode: 0644]
utils/Modeller/uv_import_svg.py [new file with mode: 0755]

diff --git a/utils/Modeller/uv_export_svg.py b/utils/Modeller/uv_export_svg.py
new file mode 100644 (file)
index 0000000..a270fe1
--- /dev/null
@@ -0,0 +1,130 @@
+#!BPY
+
+# """
+# Name: 'SVG: Export UV layout to SVG file'
+# Blender: 245
+# Group: 'UV'
+# Tooltip: 'Export selected objects to SVG file'
+# """
+
+__author__ = "Melchior FRANZ < mfranz # aon : at >"
+__url__ = "http://members.aon.at/mfranz/flightgear/"
+__version__ = "0.1"
+__bpydoc__ = """\
+Saves the UV mappings of all selected files to an SVG file. The uv_import_svg.py
+script can be used to re-import such a file. Each object and each group of adjacent
+faces therein will be put into an SVG group.
+"""
+
+ID_SEPARATOR = '#'
+
+
+import Blender, sys
+
+
+class Abort(Exception):
+       def __init__(self, msg):
+               self.msg = msg
+
+
+def get_adjacent(pool):
+       i, face = pool.popitem()
+       group = [face]
+
+       uvcoords = {}
+       for c in face.uv:
+               uvcoords[(c[0], c[1])] = True
+
+       while True:
+               found = []
+               for face in pool.itervalues():
+                       for c in face.uv:
+                               if (c[0], c[1]) in uvcoords:
+                                       for d in face.uv:
+                                               uvcoords[(d[0], d[1])] = True
+                                       found.append(face)
+                                       break
+               if not found:
+                       break
+               for face in found:
+                       group.append(face)
+                       del pool[face.index]
+
+       return group
+
+
+def write_svg(filename):
+       size = Blender.Draw.PupMenu("Image size%t|128|256|512|1024|2048|4096|8192")
+       if size < 0:
+               raise Abort('no image size chosen')
+       size = 1 << (size + 6)
+
+       print "exporting to '%s' (size %d) ... " % (filename, size),
+       svg = open(filename, "w")
+       svg.write('<?xml version="1.0" standalone="no"?>\n')
+       svg.write('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n\n')
+       svg.write('<svg width="%spx" height="%spx" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg"' \
+                       'version="1.1" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">\n'
+                       % (size, size, size, size))
+       svg.write("\t<desc>uv_export_svg.py: %s</desc>\n" % filename);
+       svg.write('\t<rect x="0" y="0" width="%d" height="%d" fill="none" stroke="blue" stroke-width="%f"/>\n'
+                       % (size, size, 1.0))
+
+       unique_meshes = {}
+       for o in Blender.Scene.GetCurrent().objects.selected:
+               if o.type != "Mesh":
+                       continue
+
+               mesh = o.getData(mesh = 1)
+               if not mesh.faceUV:
+                       continue
+               if mesh.name in unique_meshes:
+                       #print "dropping duplicate mesh", mesh.name, "of object", o.name
+                       continue
+               unique_meshes[mesh.name] = True
+
+               svg.write('\t<g style="fill:yellow; stroke:black stroke-width:1px" inkscape:label="%s" id="%s">\n' % (o.name, o.name))
+
+               pool = {}
+               for f in mesh.faces:
+                       pool[f.index] = f
+
+               while len(pool):
+                       svg.write('\t\t<g>\n')
+                       for f in get_adjacent(pool):
+                               svg.write('\t\t\t<polygon points="')
+                               for p in f.uv:
+                                       svg.write('%.8f,%.8f ' % (p[0] * size, size - p[1] * size))
+                               svg.write('" id="%s%s%d"/>\n' % (mesh.name, ID_SEPARATOR, f.index))
+                       svg.write('\t\t</g>\n')
+
+               svg.write("\t</g>\n")
+
+       svg.write('</svg>\n')
+       svg.close()
+       print "done."
+
+
+def export(filename):
+       registry = {}
+       registry[basename] = Blender.sys.basename(filename)
+       Blender.Registry.SetKey("UVImportExportSVG", registry, False)
+
+       editmode = Blender.Window.EditMode()
+       if editmode:
+               Blender.Window.EditMode(0)
+
+       try:
+               write_svg(filename)
+       except Abort, e:
+               print "Error:", e.msg, "  -> aborting ...\n"
+               Blender.Draw.PupMenu("Error%t|" + e.msg)
+
+       if editmode:
+               Blender.Window.EditMode(1)
+
+
+active = Blender.Scene.GetCurrent().objects.active
+(basename, extname) = Blender.sys.splitext(Blender.Get("filename"))
+filename = Blender.sys.basename(basename) + "-" + active.name + ".svg"
+Blender.Window.FileSelector(export, "Export to SVG", filename)
diff --git a/utils/Modeller/uv_import_svg.py b/utils/Modeller/uv_import_svg.py
new file mode 100755 (executable)
index 0000000..4d6fd41
--- /dev/null
@@ -0,0 +1,293 @@
+#!BPY
+
+# """
+# Name: 'SVG: Re-Import UV layout from SVG file'
+# Blender: 245
+# Group: 'UV'
+# Tooltip: 'Re-import UV layout from SVG file'
+# """
+
+__author__ = "Melchior FRANZ < mfranz # aon : at >"
+__url__ = "http://members.aon.at/mfranz/flightgear/"
+__version__ = "0.1"
+__bpydoc__ = """\
+Imports an SVG file containing UV maps, which has been saved by the
+uv_export.svg script. This allows to move, scale, and rotate object
+mapping in SVG editors like Inkscape. Note that all contained UV maps
+will be set, no matter which objects are actually selected at the moment.
+The choice has been made when the file was saved!
+"""
+
+
+ID_SEPARATOR = '#'
+
+
+import Blender, sys, math, re
+from xml.sax import saxexts
+
+
+registry = {}
+numwsp = re.compile('(?<=[\d.])\s+(?=[-+.\d])')
+commawsp = re.compile('\s+|\s*,\s*')
+istrans = re.compile('^\s*(skewX|skewY|scale|translate|rotate|matrix)\s*\(([^\)]*)\)\s*')
+isnumber = re.compile('^[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?$')
+
+
+class Abort(Exception):
+       def __init__(self, msg):
+               self.msg = msg
+
+
+class Matrix:
+       def __init__(self, a = 1, b = 0, c = 0, d = 1, e = 0, f = 0):
+               self.a = a; self.b = b; self.c = c; self.d = d; self.e = e; self.f = f
+
+       def __str__(self):
+               return "[Matrix %f %f %f %f %f %f]" % (self.a, self.b, self.c, self.d, self.e, self.f)
+
+       def multiply(self, mat):
+               a = self.a * mat.a + self.c * mat.b
+               b = self.b * mat.a + self.d * mat.b
+               c = self.a * mat.c + self.c * mat.d
+               d = self.b * mat.c + self.d * mat.d
+               e = self.a * mat.e + self.c * mat.f + self.e
+               f = self.b * mat.e + self.d * mat.f + self.f
+               self.a = a; self.b = b; self.c = c; self.d = d; self.e = e; self.f = f
+
+       def transform(self, u, v):
+               x = u * self.a + v * self.c + self.e
+               y = u * self.b + v * self.d + self.f
+               return (x, y)
+
+       def translate(self, dx, dy):
+               self.multiply(Matrix(1, 0, 0, 1, dx, dy))
+
+       def scale(self, sx, sy):
+               self.multiply(Matrix(sx, 0, 0, sy, 0, 0))
+
+       def rotate(self, a):
+               a *= math.pi / 180
+               self.multiply(Matrix(math.cos(a), math.sin(a), -math.sin(a), math.cos(a), 0, 0))
+
+       def skewX(self, a):
+               a *= math.pi / 180
+               self.multiply(Matrix(1, 0, math.tan(a), 1, 0, 0))
+
+       def skewY(self, a):
+               a *= math.pi / 180
+               self.multiply(Matrix(1, math.tan(a), 0, 1, 0, 0))
+
+
+def parse_transform(s):
+       matrix = Matrix()
+       while True:
+               match = istrans.match(s)
+               if not match:
+                       break
+               cmd = match.group(1)
+               values = commawsp.split(match.group(2).strip())
+               s = s[len(match.group(0)):]
+               arg = []
+
+               for value in values:
+                       match = isnumber.match(value)
+                       if not match:
+                               raise Abort("bad transform value")
+
+                       arg.append(float(match.group(0)))
+
+               num = len(arg)
+               if cmd == "skewX":
+                       if num == 1:
+                               matrix.skewX(arg[0])
+                               continue
+
+               elif cmd == "skewY":
+                       if num == 1:
+                               matrix.skewY(arg[0])
+                               continue
+
+               elif cmd == "scale":
+                       if num == 1:
+                               matrix.scale(arg[0], arg[0])
+                               continue
+                       if num == 2:
+                               matrix.scale(arg[0], arg[1])
+                               continue
+
+               elif cmd == "translate":
+                       if num == 1:
+                               matrix.translate(arg[0], 0)
+                               continue
+                       if num == 2:
+                               matrix.translate(arg[0], arg[1])
+                               continue
+
+               elif cmd == "rotate":
+                       if num == 1:
+                               matrix.rotate(arg[0])
+                               continue
+                       if num == 3:
+                               matrix.translate(-arg[1], -arg[2])
+                               matrix.rotate(arg[0])
+                               matrix.translate(arg[1], arg[2])
+                               continue
+
+               elif cmd == "matrix":
+                       if num == 6:
+                               matrix.multiply(Matrix(arg[0], arg[1], arg[2], arg[3], arg[4], arg[5]))
+                               continue
+
+               else:
+                       print "ERROR: unknown transform", cmd
+                       continue
+
+               print "ERROR: '%s' with wrong argument number (%d)" % (cmd, num)
+
+       if len(s):
+               print "ERROR: transform with trailing garbage (%s)" % s
+       return matrix
+
+
+class import_svg:
+       # err_handler
+       def error(self, exception):
+               raise Abort(str(exception))
+
+       def fatalError(self, exception):
+               raise Abort(str(exception))
+
+       def warning(self, exception):
+               print "WARNING: " + str(exception)
+
+       # doc_handler
+       def setDocumentLocator(self, whatever):
+               pass
+
+       def startDocument(self):
+               self.verified = False
+               self.scandesc = False
+               self.matrices = [None]
+               self.meshes = {}
+               for o in Blender.Scene.GetCurrent().objects:
+                       if o.type != "Mesh":
+                               continue
+                       mesh = o.getData(mesh = 1)
+                       if not mesh.faceUV:
+                               continue
+                       if mesh.name in self.meshes:
+                               continue
+                       self.meshes[mesh.name] = mesh
+
+       def endDocument(self):
+               pass
+
+       def characters(self, data, start, length):
+               if not self.scandesc:
+                       return
+               if data[start:start + length].startswith("uv_export_svg.py"):
+                       self.verified = True
+
+       def ignorableWhitespace(self, data, start, length):
+               pass
+
+       def startElement(self, name, attrs):
+               currmat = self.matrices[-1]
+               if "transform" in attrs:
+                       m = parse_transform(attrs["transform"])
+                       if currmat != None:
+                               m.multiply(currmat)
+                       self.matrices.append(m)
+               else:
+                       self.matrices.append(currmat)
+
+               if name == "polygon":
+                       self.handlePolygon(attrs)
+               elif name == "svg":
+                       if "viewBox" in attrs:
+                               x, y, w, h = commawsp.split(attrs["viewBox"], 4)
+                               if int(x) or int(y):
+                                       raise Abort("bad viewBox")
+                               self.width = int(w)
+                               self.height = int(h)
+                               if self.width != self.height:
+                                       raise Abort("viewBox isn't a square")
+                       else:
+                               raise Abort("no viewBox")
+               elif name == "desc" and not self.verified:
+                       self.scandesc = True
+
+       def endElement(self, name):
+               self.scandesc = False
+               self.matrices = self.matrices[:-1]
+
+       def handlePolygon(self, attrs):
+               if not self.verified:
+                       raise Abort("this file wasn't written by uv_export_svg.py")
+               ident = attrs.get("id", None)
+               points = attrs.get("points", None)
+
+               if not ident or not points:
+                       print('bad polygon "%s"' % ident)
+                       return
+
+               sep = ident.find(ID_SEPARATOR)
+               if sep < 0:
+                       print('broken id "%s"' % ident)
+                       return
+
+               meshname = str(ident[:sep])
+               num = int(ident[sep + 1:])
+
+               if not meshname in self.meshes:
+                       print('unknown mesh "%s"' % meshname)
+                       return
+
+               #print 'mesh %s face %d: ' % (meshname, num)
+               matrix = self.matrices[-1]
+               transuv = []
+               for p in numwsp.split(points.strip()):
+                       u, v = commawsp.split(p.strip(), 2)
+                       u = float(u)
+                       v = float(v)
+                       if matrix:
+                               u, v = matrix.transform(u, v)
+                       transuv.append((u / self.width, 1 - v / self.height))
+
+               for i, uv in enumerate(self.meshes[meshname].faces[num].uv):
+                       uv[0] = transuv[i][0]
+                       uv[1] = transuv[i][1]
+
+
+def run_parser(filename):
+       editmode = Blender.Window.EditMode()
+       if editmode:
+               Blender.Window.EditMode(0)
+       Blender.Window.WaitCursor(1)
+
+       try:
+               svg = saxexts.ParserFactory().make_parser("xml.sax.drivers.drv_xmlproc")
+               svg.setDocumentHandler(import_svg())
+               svg.setErrorHandler(import_svg())
+               svg.parse(filename)
+
+       except Abort, e:
+               print "Error:", e.msg, "  -> aborting ...\n"
+               Blender.Draw.PupMenu("Error%t|" + e.msg)
+
+       Blender.Window.RedrawAll()
+       Blender.Window.WaitCursor(0)
+       if editmode:
+               Blender.Window.EditMode(1)
+
+
+active = Blender.Scene.GetCurrent().objects.active
+(basename, extname) = Blender.sys.splitext(Blender.Get("filename"))
+filename = Blender.sys.basename(basename) + "-" + active.name + ".svg"
+
+registry = Blender.Registry.GetKey("UVImportExportSVG", False)
+if registry and basename in registry:
+       filename = registry[basename]
+
+Blender.Window.FileSelector(run_parser, "Import SVG", filename)
+