266 lines
9.6 KiB
Python
266 lines
9.6 KiB
Python
#!/usr/bin/env python
|
|
|
|
import os, time
|
|
import bpy
|
|
from math import pi
|
|
import glob
|
|
from mathutils import Vector, Matrix
|
|
from math import radians, pi
|
|
|
|
class ExportMappingFemale:
|
|
blend_path = "assets/blender/vroid1-female.blend"
|
|
gltf_path = "godot/vroid1-female.gltf"
|
|
inner_path = "Object"
|
|
objs = ["skeleton", "body", "privates", "dress-noimp", "skirt-noimp", "top-ref-noimp"]
|
|
armature_name = "skeleton"
|
|
outfile = "tmp-female.blend"
|
|
default_action = 'default'
|
|
def __init__(self):
|
|
self.files = []
|
|
for fobj in self.objs:
|
|
self.files.append({"name": fobj})
|
|
|
|
class ExportMappingMale:
|
|
blend_path = "assets/blender/vroid1-man-animate.blend"
|
|
gltf_path = "godot/vroid1-man-animate.gltf"
|
|
inner_path = "Object"
|
|
objs = ["pxy", "body", "penis", "bottom-ref-noimp", "top-ref-noimp"]
|
|
armature_name = "pxy"
|
|
outfile = "tmp-male.blend"
|
|
default_action = 'default'
|
|
def __init__(self):
|
|
self.files = []
|
|
for fobj in self.objs:
|
|
self.files.append({"name": fobj})
|
|
|
|
basepath = os.getcwd()
|
|
def check_bone(bname):
|
|
ok = True
|
|
baddie = ["ctrl_", "mch_", "MCH_"]
|
|
for bd in baddie:
|
|
if bname.startswith(bd):
|
|
ok = False
|
|
break
|
|
return ok
|
|
|
|
def prepare_armature(mapping):
|
|
print("Preparing...")
|
|
bpy.ops.object.armature_add(enter_editmode=False)
|
|
new_armature = bpy.context.object
|
|
orig_armature = bpy.data.objects[mapping.armature_name]
|
|
armature_name = orig_armature.name
|
|
orig_armature.name = orig_armature.name + "_orig"
|
|
new_armature.name = armature_name
|
|
queue = []
|
|
if new_armature.animation_data is None:
|
|
new_armature.animation_data_create()
|
|
bpy.context.view_layer.objects.active = new_armature
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
for b in new_armature.data.edit_bones:
|
|
new_armature.data.edit_bones.remove(b)
|
|
bpy.context.view_layer.objects.active = orig_armature
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
for b in orig_armature.data.edit_bones:
|
|
print(b.name)
|
|
if b.parent is None:
|
|
queue.append(b.name)
|
|
print("Copying bones...")
|
|
while len(queue) > 0:
|
|
item = queue.pop(0)
|
|
print(item)
|
|
itemb = orig_armature.data.edit_bones[item]
|
|
if not itemb.use_deform and not check_bone(item):
|
|
continue
|
|
for cb in orig_armature.data.edit_bones:
|
|
if cb.parent == itemb:
|
|
queue.append(cb.name)
|
|
nb = new_armature.data.edit_bones.new(item)
|
|
nb.name = item
|
|
nb.head = itemb.head
|
|
nb.tail = itemb.tail
|
|
nb.matrix = itemb.matrix
|
|
nb.use_deform = itemb.use_deform
|
|
if itemb.parent is not None:
|
|
ok = True
|
|
pname = itemb.parent.name
|
|
while not check_bone(pname):
|
|
bparent = itemb.parent.parent
|
|
if bparent is None:
|
|
ok = False
|
|
break
|
|
pname = bparent.name
|
|
if ok:
|
|
nb.parent = new_armature.data.edit_bones[itemb.parent.name]
|
|
else:
|
|
nb.parent = None
|
|
else:
|
|
nb.parent = None
|
|
nb.use_connect = itemb.use_connect
|
|
# drivers_data = new_armature.animation_data.drivers
|
|
print("Creating constraints...")
|
|
bpy.context.view_layer.objects.active = new_armature
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
bpy.context.view_layer.objects.active = orig_armature
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
for b in new_armature.pose.bones:
|
|
print(b.name)
|
|
c = b.constraints.new(type='COPY_TRANSFORMS')
|
|
c.target = orig_armature
|
|
c.subtarget = b.name
|
|
for obj in bpy.data.objects:
|
|
if obj.parent == orig_armature:
|
|
obj.parent = new_armature
|
|
for mod in obj.modifiers:
|
|
if mod.type == 'ARMATURE':
|
|
mod.object = new_armature
|
|
print("Baking actions...")
|
|
bpy.context.view_layer.objects.active = new_armature
|
|
bpy.ops.object.mode_set(mode='POSE')
|
|
for track in orig_armature.animation_data.nla_tracks:
|
|
print(track.name)
|
|
for s in track.strips:
|
|
action = s.action
|
|
print(action.name)
|
|
orig_armature.animation_data.action = action
|
|
new_armature.animation_data.action = None
|
|
bpy.context.view_layer.objects.active = new_armature
|
|
firstFrame = int(s.action_frame_start)
|
|
lastFrame = int(s.action_frame_end)
|
|
bpy.ops.nla.bake(frame_start=firstFrame, frame_end=lastFrame, step=5, only_selected=False, visual_keying=True, clear_constraints=False, clean_curves=True, use_current_action=False, bake_types={'POSE'})
|
|
aname = orig_armature.animation_data.action.name
|
|
orig_armature.animation_data.action.name = "bake_" + aname
|
|
new_armature.animation_data.action.name = aname
|
|
track = new_armature.animation_data.nla_tracks.new()
|
|
track.name = aname
|
|
track.strips.new(track.name, int(new_armature.animation_data.action.frame_range[0]), new_armature.animation_data.action)
|
|
track.mute = True
|
|
track.lock = True
|
|
print("Removing constraints...")
|
|
for b in new_armature.pose.bones:
|
|
for c in b.constraints:
|
|
b.constraints.remove(c)
|
|
new_armature.animation_data.action = bpy.data.actions[mapping.default_action]
|
|
bpy.context.view_layer.objects.active = new_armature
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
# track = new_armature.animation_data.nla_tracks.new()
|
|
# track.name = action.name
|
|
|
|
|
|
def clamp_angle_deg(angle, min_angle_deg, max_angle_deg):
|
|
min_angle = radians(min_angle_deg)
|
|
max_angle = radians(max_angle_deg)
|
|
if angle < min_angle:
|
|
angle = min_angle
|
|
if angle > max_angle:
|
|
angle = max_angle
|
|
return angle
|
|
def angle_to_linear(angle, divider):
|
|
if angle < 0.0:
|
|
return angle / divider
|
|
else:
|
|
return 0.0
|
|
def angle_to_linear_x(bone, angle):
|
|
skel = bpy.data.objects["skeleton_orig"]
|
|
left_base = "ctrl_base_upperleg.L.001"
|
|
right_base = "ctrl_base_upperleg.R.001"
|
|
base = ""
|
|
if base == "":
|
|
for e in ["_R", ".R"]:
|
|
if bone.name.endswith(e):
|
|
base = right_base
|
|
break
|
|
if base == "":
|
|
for e in ["_L", ".L"]:
|
|
if bone.name.endswith(e):
|
|
base = left_base
|
|
break
|
|
if base == "":
|
|
for e in ["_R.", ".R."]:
|
|
if bone.name.find(e) >= 0:
|
|
base = right_base
|
|
break
|
|
if base == "":
|
|
for e in ["_L.", ".L."]:
|
|
if bone.name.find(e) >= 0:
|
|
base = left_base
|
|
break
|
|
mul = skel.pose.bones[base]["to_linear_x_base"]
|
|
offset = skel.pose.bones[base]["angle_offset"]
|
|
# print("bone: ", bone.name, "base: ", base, "mul: ", mul)
|
|
# print("angle: ", angle, " angle_offset: ", offset, " angle_sum: ", angle + offset)
|
|
print("offset: ", mul * (angle + offset), "bone: ", base, "angle: ", angle)
|
|
return (angle + offset) * mul
|
|
def extra_linear(angle, offset):
|
|
ret = 0.0
|
|
offt = offset * angle * 2.0 / -radians(-90)
|
|
if angle * 2.0 < -radians(65):
|
|
if angle * 2.0 > -radians(65):
|
|
ret += offset
|
|
else:
|
|
ret += offt
|
|
return ret
|
|
|
|
for mapping in [ExportMappingFemale(), ExportMappingMale()]:
|
|
print("Initializing...")
|
|
bpy.ops.wm.read_homefile(use_empty=True)
|
|
print("Preparing driver setup...")
|
|
bpy.app.driver_namespace["clamp_angle_deg"] = clamp_angle_deg
|
|
bpy.app.driver_namespace["angle_to_linear"] = angle_to_linear
|
|
bpy.app.driver_namespace["extra_linear"] = extra_linear
|
|
bpy.app.driver_namespace["angle_to_linear_x"] = angle_to_linear_x
|
|
print("Driver setup done...")
|
|
|
|
bpy.ops.wm.append(
|
|
filepath=os.path.join(mapping.blend_path, mapping.inner_path),
|
|
directory=os.path.join(mapping.blend_path, mapping.inner_path),
|
|
files=mapping.files)
|
|
print("Append done...")
|
|
|
|
prepare_armature(mapping)
|
|
print("Armature done...")
|
|
|
|
print("Removing original armature and actions...")
|
|
orig_arm = bpy.data.objects[mapping.armature_name + '_orig']
|
|
bpy.data.objects.remove(orig_arm)
|
|
for act in bpy.data.actions:
|
|
if act.name.startswith("bake_"):
|
|
act.name = act.name + "-noimp"
|
|
for act in bpy.data.actions:
|
|
if act.name.startswith("bake_"):
|
|
bpy.data.actions.remove(act)
|
|
for obj in bpy.data.objects:
|
|
if obj.type == 'MESH':
|
|
if not obj.name in mapping.objs and obj.parent is None:
|
|
if not obj.name.endswith("-noimp"):
|
|
obj.name = obj.name + "-noimp"
|
|
|
|
bpy.ops.wm.save_as_mainfile(filepath=(basepath + "/assets/blender/scripts/" + mapping.outfile))
|
|
|
|
bpy.ops.export_scene.gltf(filepath=mapping.gltf_path,
|
|
use_selection=False,
|
|
check_existing=False,
|
|
export_format='GLTF_SEPARATE',
|
|
export_texture_dir='textures', export_texcoords=True,
|
|
export_normals=True,
|
|
export_tangents=True,
|
|
export_materials='EXPORT',
|
|
export_colors=True,
|
|
use_mesh_edges=False,
|
|
use_mesh_vertices=False,
|
|
export_cameras=False,
|
|
use_visible=False,
|
|
use_renderable=False,
|
|
export_yup=True,
|
|
export_animations=True,
|
|
export_force_sampling=True,
|
|
export_def_bones=False,
|
|
export_current_frame=False,
|
|
export_morph=True,
|
|
export_morph_normal=True,
|
|
export_lights=False)
|
|
|
|
bpy.ops.wm.read_homefile(use_empty=True)
|
|
time.sleep(2)
|
|
bpy.ops.wm.quit_blender()
|