293 lines
7 KiB
Python
Executable file
293 lines
7 KiB
Python
Executable file
#!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)
|
|
|