--- /dev/null
+#!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)
--- /dev/null
+#!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)
+