247 lines
6.1 KiB
Python
247 lines
6.1 KiB
Python
import bpy
|
|
import math
|
|
import sys
|
|
import struct
|
|
|
|
model_name = sys.argv[sys.argv.index('--') + 1]
|
|
filename = sys.argv[sys.argv.index('--') + 2]
|
|
|
|
obj = None
|
|
mesh = None
|
|
if model_name in bpy.data.objects:
|
|
obj = bpy.data.objects[model_name]
|
|
else:
|
|
raise RuntimeError("Model " + model_name + " not found in file")
|
|
|
|
mesh = obj.data
|
|
|
|
colors = None
|
|
if len(mesh.vertex_colors) > 0:
|
|
colors = mesh.vertex_colors[0].data
|
|
print('Found vertex colors')
|
|
|
|
texcoords = None
|
|
if len(mesh.uv_layers) > 0:
|
|
texcoords = mesh.uv_layers[0].data
|
|
print('Found texture coordinates:', texcoords)
|
|
|
|
armature = None
|
|
bone_names = None
|
|
if obj.parent is not None and type(obj.parent.data) is bpy.types.Armature:
|
|
armature = obj.parent
|
|
print('Found armature with {} bones'.format(len(armature.data.bones)))
|
|
bone_names = [b.name for b in armature.data.bones]
|
|
|
|
POSITION_MASK = 1
|
|
NORMAL_MASK = 2
|
|
COLOR_MASK = 4
|
|
TEXCOORD_MASK = 8
|
|
WEIGHTS_MASK = 16
|
|
|
|
vertex_format = POSITION_MASK | NORMAL_MASK
|
|
if colors is not None:
|
|
vertex_format |= COLOR_MASK
|
|
if texcoords is not None:
|
|
vertex_format |= TEXCOORD_MASK
|
|
if armature is not None:
|
|
vertex_format |= WEIGHTS_MASK
|
|
print("Using vertex format", format(vertex_format, '05b'))
|
|
|
|
vertex_coords = []
|
|
vertex_normals = []
|
|
vertex_colors = []
|
|
vertex_texcoords = []
|
|
vertex_weights = []
|
|
|
|
indices = []
|
|
|
|
mesh.calc_loop_triangles()
|
|
|
|
smooth_index = {}
|
|
|
|
def append_vertex(v, n):
|
|
vertex_coords.append((v.co.x, v.co.y, v.co.z))
|
|
vertex_normals.append((n.x, n.y, n.z))
|
|
if colors is not None:
|
|
vertex_colors.append(tuple(colors[li].color))
|
|
if texcoords is not None:
|
|
vertex_texcoords.append((texcoords[li].uv[0], 1.0 - texcoords[li].uv[1]))
|
|
if armature is not None:
|
|
weights = []
|
|
for g in v.groups:
|
|
name = obj.vertex_groups[g.group].name
|
|
if name in bone_names:
|
|
weights.append((bone_names.index(name), g.weight))
|
|
for g, w in weights:
|
|
if g >= len(armature.data.bones):
|
|
print("Invalid vertex group index {} for armature weights".format(g), file=sys.stderr)
|
|
sys.exit(1)
|
|
while len(weights) < 2:
|
|
weights.append((0,0.0))
|
|
weights = list(sorted(weights, key=lambda p: -p[1]))[:2]
|
|
W = sum(p[1] for p in weights)
|
|
weights = [(i,w/W) for i,w in weights]
|
|
vertex_weights.append(weights)
|
|
|
|
for p in mesh.loop_triangles:
|
|
for li in p.loops:
|
|
vi = mesh.loops[li].vertex_index
|
|
v = mesh.vertices[vi]
|
|
n = p.normal
|
|
if p.use_smooth:
|
|
i = None
|
|
if vi not in smooth_index:
|
|
i = len(vertex_coords)
|
|
smooth_index[vi] = i
|
|
append_vertex(v, n)
|
|
else:
|
|
i = smooth_index[vi]
|
|
|
|
indices.append(i)
|
|
else:
|
|
i = len(vertex_coords)
|
|
append_vertex(v, n)
|
|
indices.append(i)
|
|
|
|
|
|
assert (len(indices) % 3) == 0
|
|
assert len(vertex_coords) == len(vertex_normals)
|
|
if colors is not None:
|
|
assert len(vertex_coords) == len(vertex_colors)
|
|
if texcoords is not None:
|
|
assert len(vertex_coords) == len(vertex_texcoords)
|
|
if armature is not None:
|
|
assert len(vertex_coords) == len(vertex_weights)
|
|
|
|
|
|
class Uint8:
|
|
def __init__(self, value):
|
|
self.value = int(value)
|
|
|
|
def __str__(self):
|
|
return str(self.value)
|
|
|
|
def __repr__(self):
|
|
return str(self.value)
|
|
|
|
|
|
if colors is not None:
|
|
for i in range(len(vertex_colors)):
|
|
c = 0
|
|
for k in (3, 2, 1, 0):
|
|
c = (c << 8) | int(max(0, min(255, pow(vertex_colors[i][k], 2.2) * 255)))
|
|
vertex_colors[i] = c
|
|
|
|
if armature is not None:
|
|
for i in range(len(vertex_weights)):
|
|
gs = []
|
|
for g, w in vertex_weights[i]:
|
|
gs.append(Uint8(g))
|
|
|
|
ws = []
|
|
for g, w in vertex_weights[i]:
|
|
ws.append(Uint8(w * 255.0))
|
|
ws[0].value += 255 - sum(w.value for w in ws)
|
|
|
|
assert len(gs) == 2
|
|
assert len(ws) == 2
|
|
vertex_weights[i] = tuple(gs + ws)
|
|
|
|
vertices = []
|
|
for i in range(len(vertex_coords)):
|
|
attribs = [vertex_coords[i], vertex_normals[i]]
|
|
if colors is not None:
|
|
attribs.append(vertex_colors[i])
|
|
if texcoords is not None:
|
|
attribs.append(vertex_texcoords[i])
|
|
if armature is not None:
|
|
attribs.append(vertex_weights[i])
|
|
vertices.append(tuple(attribs))
|
|
|
|
print(len(vertices), 'vertices')
|
|
print(len(indices), 'indices')
|
|
|
|
if armature is not None:
|
|
bones = []
|
|
for b in armature.data.bones:
|
|
p = None
|
|
if b.parent is None:
|
|
p = -1
|
|
else:
|
|
p = list(armature.data.bones).index(b.parent)
|
|
bones.append((p, tuple(b.head_local), tuple(map(tuple, b.matrix_local.to_3x3()))))
|
|
|
|
poses = []
|
|
if armature is not None and armature.pose_library is not None:
|
|
for index, pose in enumerate(armature.pose_library.pose_markers):
|
|
print("Found pose", pose.name)
|
|
|
|
# [rotation, scale, translation]
|
|
data = []
|
|
for i in range(len(bone_names)):
|
|
data.append([[1.0, 0.0, 0.0, 0.0], [1.0, 1.0, 1.0], [0.0, 0.0, 0.0]])
|
|
|
|
marker = armature.pose_library.pose_markers[index]
|
|
frame = marker.frame
|
|
action = bpy.data.actions[armature.pose_library.name]
|
|
for g in action.groups:
|
|
if not g.name in bone_names:
|
|
continue
|
|
bone_index = bone_names.index(g.name)
|
|
for ch in g.channels:
|
|
value = ch.evaluate(float(frame))
|
|
if ch.data_path.find('rotation_quaternion') != -1:
|
|
data[bone_index][0][ch.array_index] = value
|
|
elif ch.data_path.find('scale') != -1:
|
|
data[bone_index][1][ch.array_index] = value
|
|
elif ch.data_path.find('location') != -1:
|
|
data[bone_index][2][ch.array_index] = value
|
|
|
|
new_data = []
|
|
for d in data:
|
|
rotation = tuple([d[0][1], d[0][2], d[0][3], d[0][0]])
|
|
scale = d[1][0]
|
|
translation = tuple(d[2])
|
|
new_data.append((rotation, scale, translation))
|
|
poses.append((pose.name, new_data))
|
|
|
|
def to_bytes(obj):
|
|
if type(obj) == Uint8:
|
|
return struct.pack('<B', obj.value)
|
|
if type(obj) == int:
|
|
if obj < 0:
|
|
return struct.pack('<i', obj)
|
|
else:
|
|
return struct.pack('<I', obj)
|
|
if type(obj) == float:
|
|
return struct.pack('<f', obj)
|
|
if type(obj) == tuple:
|
|
res = bytes()
|
|
for x in obj:
|
|
res += to_bytes(x)
|
|
return res
|
|
if type(obj) == list:
|
|
res = to_bytes(len(obj))
|
|
for x in obj:
|
|
res += to_bytes(x)
|
|
return res
|
|
if type(obj) == str:
|
|
b = obj.encode('utf-8')
|
|
return struct.pack('<I', len(b)) + b
|
|
print("Type", type(obj), "not supported")
|
|
sys.exit(1)
|
|
|
|
SECTION_MESH = 1
|
|
SECTION_BONES = 2
|
|
SECTION_POSE = 3
|
|
|
|
data = to_bytes((SECTION_MESH, vertex_format, vertices, indices))
|
|
|
|
if armature:
|
|
data += to_bytes((SECTION_BONES, bones))
|
|
|
|
for name, pdata in poses:
|
|
data += to_bytes((SECTION_POSE, name, pdata))
|
|
|
|
with open(filename, 'wb') as f:
|
|
f.write(data)
|