puppeteeadadada / export.py
dashdoas's picture
Upload 6 files
0834f69 verified
from collections import defaultdict
from typing import Union, Tuple
import argparse
import bpy # type: ignore
import numpy as np
import trimesh
from mathutils import Vector # type: ignore
def export_fbx(
path: str,
vertices: np.ndarray,
faces: Union[np.ndarray, None],
bones: np.ndarray,
parents: list[Union[int, None]],
names: list[str],
vertex_group: np.ndarray,
dfs_order: list[int],
group_per_vertex: int=4,
):
# clean bpy
for c in bpy.data.actions:
bpy.data.actions.remove(c)
for c in bpy.data.armatures:
bpy.data.armatures.remove(c)
for c in bpy.data.cameras:
bpy.data.cameras.remove(c)
for c in bpy.data.collections:
bpy.data.collections.remove(c)
for c in bpy.data.images:
bpy.data.images.remove(c)
for c in bpy.data.materials:
bpy.data.materials.remove(c)
for c in bpy.data.meshes:
bpy.data.meshes.remove(c)
for c in bpy.data.objects:
bpy.data.objects.remove(c)
for c in bpy.data.textures:
bpy.data.textures.remove(c)
# make mesh
mesh = bpy.data.meshes.new('mesh')
mesh.from_pydata(vertices, [], faces)
mesh.update()
# make object from mesh
object = bpy.data.objects.new('character', mesh)
# make collection
collection = bpy.data.collections.new('CA_collection')
bpy.context.scene.collection.children.link(collection)
# add object to scene collection
collection.objects.link(object)
# deselect mesh
# mesh.select_set(False)
bpy.ops.object.armature_add(enter_editmode=True)
armature = bpy.data.armatures.get('Armature')
edit_bones = armature.edit_bones
bone_root = edit_bones.get('Bone')
J = len(names)
def extrude_bone(
edit_bones,
name: str,
parent_name: str,
head: Tuple[float, float, float],
tail: Tuple[float, float, float],
is_root: bool=False,
):
if is_root:
bone = bone_root
else:
bone = edit_bones.new(name)
bone.head = Vector((head[0], head[1], head[2]))
bone.tail = Vector((tail[0], tail[1], tail[2]))
bone.name = name
if parent_name is not None:
parent_bone = edit_bones.get(parent_name)
bone.parent = parent_bone
else:
bone.parent = None
bone.use_connect = True
for k in range(J):
i = dfs_order[k]
edit_bones = armature.edit_bones
if parents[i] is None: # root
extrude_bone(edit_bones, names[i], None, bones[i, :3], bones[i, 3:], is_root=True)
else:
pname = 'Root' if parents[i] is None else names[parents[i]]
extrude_bone(edit_bones, names[i], pname, bones[i, :3], bones[i, 3:])
# must set to object mode to enable parent_set
bpy.ops.object.mode_set(mode='OBJECT')
objects = bpy.data.objects
for o in bpy.context.selected_objects:
o.select_set(False)
ob = objects['character']
arm = bpy.data.objects['Armature']
ob.select_set(True)
arm.select_set(True)
bpy.ops.object.parent_set(type='ARMATURE_NAME')
vis = []
for x in ob.vertex_groups:
vis.append(x.name)
#sparsify
nGroupPerVertex = group_per_vertex
argsorted = np.argsort(-vertex_group, axis=1)
vertex_group_reweight = vertex_group[np.arange(vertex_group.shape[0])[..., None],argsorted]
vertex_group_reweight = vertex_group_reweight / vertex_group_reweight[..., :nGroupPerVertex].sum(axis=1)[...,None]
for v, w in enumerate(vertex_group):
for ii in range(nGroupPerVertex):
i = argsorted[v,ii]
if i >= len(names):
continue
n = names[i]
if n not in vis:
continue
ob.vertex_groups[n].add([v], vertex_group_reweight[v, ii], 'REPLACE')
bpy.ops.export_scene.fbx(filepath=path, check_existing=False, add_leaf_bones=False)
def parse():
parser = argparse.ArgumentParser()
parser.add_argument("--mesh", type=str, required=True, help="obj path of original mesh")
parser.add_argument("--rig", type=str, required=True, help="path of prediction rigging")
parser.add_argument("--output", type=str, required=False, default="res.fbx")
return parser.parse_args()
if __name__ == "__main__":
args = parse()
mesh_path = args.mesh
rig_path = args.rig
output_path = args.output
# change to face -y axis
rot = np.array([
[1.0, 0.0, 0.0],
[0.0, 0.0,-1.0],
[0.0, 1.0, 0.0],
])
# load original model
mesh = trimesh.load(mesh_path, force='mesh', process=False, maintain_order=True)
vertices = (rot @ mesh.vertices.T).T
faces = mesh.faces
N = vertices.shape[0]
# handle RigNet output
f_info = open(rig_path, 'r')
joint_pos = {}
joint_hier = {}
joint_skin = []
id_mapping = {}
name_mapping = {}
parent_mapping = {}
root_name = None
root_pos = None
tot = 0
for line in f_info:
word = line.split()
if word[0] == 'joints':
joint_pos[word[1]] = rot @ np.array([float(word[2]), float(word[3]), float(word[4])])
id_mapping[word[1]] = tot
name_mapping[tot] = word[1]
tot += 1
if word[0] == 'root':
root_pos = joint_pos[word[1]]
root_name = word[1]
if word[0] == 'hier':
if word[1] not in joint_hier.keys():
joint_hier[word[1]] = [word[2]]
else:
joint_hier[word[1]].append(word[2])
if word[0] == 'skin':
skin_item = word[1:]
joint_skin.append(skin_item)
f_info.close()
J = len(joint_pos)
bones = np.zeros((J, 6))
parents = []
names = []
for name in joint_hier:
for son in joint_hier[name]:
parent_mapping[son] = name
son = defaultdict(list)
for i in range(J):
name = name_mapping[i]
names.append(name)
parents.append(None if name==root_name else id_mapping[parent_mapping[name]])
if name != root_name:
son[id_mapping[parent_mapping[name]]].append(i)
# extrude tails for blender
for i in range(J):
name = name_mapping[i]
head = joint_pos[name]
tail = head + np.array([0., 0., 0.1])
if len(joint_hier.get(name, [])) == 1:
tail = joint_pos[joint_hier[name][0]]
elif name != root_name:
pname = name_mapping[parents[i]]
direction = joint_pos[name] - joint_pos[pname]
tail = head + direction * 0.5
bones[i, :3] = head
bones[i, 3:] = tail
# assign skin weight
skin = np.zeros((N, J))
for skin_item in joint_skin:
u = int(skin_item[0])
for j in range(1, len(skin_item), 2):
id = id_mapping[skin_item[j]]
w = skin_item[j + 1]
skin[u, id] = w
dfs_order = []
Q = [id_mapping[root_name]]
while Q:
u = Q.pop()
dfs_order.append(u)
Q.extend(son[u])
export_fbx(
path=output_path,
vertices=vertices,
faces=faces,
bones=bones,
parents=parents,
names=names,
vertex_group=skin,
dfs_order=dfs_order,
)