util.py 15.2 KB
# -*- coding: UTF-8 -*-

import bpy
import copy
import json
import math
import os
import pyutil
import shutil
import sys
import subprocess
import tempfile
from bmesh_collision import bmesh_check_intersect_objects

armature = bpy.context.scene.objects.get('Armature.001')

dict_bones = {
    "01_conf_direita":    ['BnDedo.1.R', 'BnDedo.1.R.006', 'BnDedo.1.R.005', 'BnDedo.1.R.001', 'BnDedo.1.R.008', 'BnDedo.1.R.007', 'BnDedo.1.R.002', 'BnDedo.1.R.010', 'BnDedo.1.R.009', 'BnDedo.1.R.003', 'BnDedo.1.R.012', 'BnDedo.1.R.011', 'BnDedo.1.R.004', 'BnDedo.1.R.014', 'BnDedo.1.R.013'],
    "02_conf_esquerda":   ['BnDedo.1.L', 'BnDedo.1.L.006', 'BnDedo.1.L.005', 'BnDedo.1.L.001', 'BnDedo.1.L.008', 'BnDedo.1.L.007', 'BnDedo.1.L.002', 'BnDedo.1.L.010', 'BnDedo.1.L.009', 'BnDedo.1.L.003', 'BnDedo.1.L.012', 'BnDedo.1.L.011', 'BnDedo.1.L.004', 'BnDedo.1.L.014', 'BnDedo.1.L.013'],
    "03_pa_direita":      ['BnPolyV.R', 'ik_FK.R'],
    "04_pa_esquerda":     ['BnPolyV.L', 'ik_FK.L'],
    "05_orient_direita":  ['BnMao.R'],
    "06_orient_esquerda": ['BnMao.L'],
    "07_facial":          ['BnPescoco', 'BnCabeca', 'BnSobrancCentro.L', 'BnSobrancCentro.R', 'BnSobrancLateral.L', 'BnSobrancLateral.R', 'BnPalpebSuper.L', 'BnPalpebInfe.L', 'BnSobrancCentro', 'BnLabioCentroSuper', 'BnBochecha.L', 'BnBochecha.R', 'BnLabioCentroInfer', 'BnBocaCanto.L', 'BnBocaCanto.R', 'BnMandibula', 'BnLingua', 'BnLingua.003', 'BnLingua.001', 'BnLingua.002', 'BnPalpebSuper.R', 'BnPalpebInfe.R', 'BnOlhosMira', 'BnOlhoMira.L', 'BnOlhoMira.R', 'BnOlho.L', 'BnOlho.R']
}

dict_interpolation = {
    "lento": 15,
    "normal": 10,
    "rapido": 5,
}

# Movimento coçar - Índices de poses
cocar_mao_aberta_index = 55
cocar_mao_fechada_index = 24
cocar_orientation_index = 11

# Action expressão facial
facial_expression_id = "07_facial"
facial_expression_action = [facial_expression_id]

# Actions mão direita
conf_direita_id = "01_conf_direita"
pa_direita_id = "03_pa_direita"
orient_direita_id = "05_orient_direita"
right_hand_actions = [conf_direita_id, pa_direita_id, orient_direita_id]

# Actions mão esquerda
conf_esquerda_id = "02_conf_esquerda"
pa_esquerda_id = "04_pa_esquerda"
orient_esquerda_id = "06_orient_esquerda"
left_hand_actions = [conf_esquerda_id, pa_esquerda_id, orient_esquerda_id]

all_actions = [conf_direita_id, conf_esquerda_id, pa_direita_id, pa_esquerda_id, orient_direita_id, orient_esquerda_id, facial_expression_id]

all_bones = dict_bones[conf_direita_id] + dict_bones[conf_esquerda_id] + dict_bones[pa_direita_id] + dict_bones[pa_esquerda_id] + dict_bones[orient_direita_id] + dict_bones[orient_esquerda_id] + dict_bones[facial_expression_id]

right_bones_conf = dict_bones[conf_direita_id] + dict_bones[pa_direita_id] + dict_bones[orient_direita_id]

left_bones_conf = dict_bones[conf_esquerda_id] + dict_bones[pa_esquerda_id] + dict_bones[orient_esquerda_id]

last_keyframe_dict = {}

# Invalid rotation lower bound - 60 degree
invalid_rotation_lower_bound = math.pi/3
# Invalid rotation upper bound - 330 degree
invalid_rotation_upper_bound = (11*math.pi)/6

def pose_default(current_frame, frame_jump):
    setPose(all_actions, [0, 0, 0, 0, 0, 0, 0], [current_frame], all_bones, False)
    return current_frame + frame_jump

def select_bones(bones = bpy.context.object.pose.bones):
    if (isinstance(bones, int) or isinstance(bones, str)):
        bpy.context.object.pose.bones[bones].bone.select = True
    elif (isinstance(bones, list)):
        for bone in bones:
            bpy.context.object.pose.bones[bone].bone.select = True
    elif (isinstance(bones, type(bpy.context.object.pose.bones))):
        for pose_bone in bones:
            pose_bone.bone.select = True
    return

def deselect_bones(bones = bpy.context.object.pose.bones):
    if (isinstance(bones, int) or isinstance(bones, str)):
        bpy.context.object.pose.bones[bones].bone.select = False
    elif (isinstance(bones, list)):
        for bone in bones:
            bpy.context.object.pose.bones[bone].bone.select = False
    elif (isinstance(bones, type(bpy.context.object.pose.bones))):
        for pose_bone in bones:
            pose_bone.bone.select = False
    return

def set_pose_mode():
    bpy.ops.object.mode_set(mode = 'OBJECT')
    bpy.ops.object.select_all(action = "DESELECT")
    bpy.ops.object.mode_set(mode = 'POSE')

def delete_all_keyframes():
    bpy.context.active_object.animation_data_clear()
    for obj in bpy.data.objects:
        obj.animation_data_clear()
    return

def setPose(actions, parametesConf, positionFrames, bones, collisionFlag = True):
    for x in range(len(positionFrames)):
        for l in range(len(actions)):
            action = actions[l]
            select_bones(bones)
            armature.pose_library = bpy.data.actions[action]
            bpy.ops.poselib.apply_pose(pose_index = parametesConf[l])
            deselect_bones(bones)
            bpy.context.object.pose_library = None
            for i in range(0, (len(bones))):
                bone = armature.pose.bones[bones[i]]
                validHandConf = action in [conf_direita_id, conf_esquerda_id] and "BnDedo" in bone.name
                validPA = action in [pa_direita_id, pa_esquerda_id] and ("ik_FK" in bone.name or "BnPolyV" in bone.name)
                validO = action in [orient_direita_id, orient_esquerda_id] and "BnMao" in bone.name
                if (validHandConf or validPA or validO):
                    keyframe_insert(bone, 'location', positionFrames[x], collisionFlag and validPA, validO)
                    keyframe_insert(bone, 'rotation_quaternion', positionFrames[x], collisionFlag and validPA, validO)

def internal_keyframe_insert(bone, path, positionFrame, collisionFlag = True, rotationFlag = False):
    # Limit hand depth
    if ("ik_FK" in bone.name and bone.location.z < 1):
        bone.location.z = 1
    bone.bone.select = True
    bone.keyframe_insert(data_path = path, index = -1, group = bone.name, frame = positionFrame)
    bone.bone.select = False
    keyframe_id = bone.name + "_" + path
    last_keyframe = last_keyframe_dict[keyframe_id] if keyframe_id in last_keyframe_dict else 0
    last_keyframe_dict[keyframe_id] = positionFrame
    if (rotationFlag and path == "rotation_quaternion"):
        checkRotation(bone, positionFrame, last_keyframe)
    if (collisionFlag and "ik_FK" in bone.name):
        checkCollision(bone, path, positionFrame, last_keyframe)

def keyframe_insert(bones, path, positionFrame, collisionFlag = True, rotationFlag = False):
    if (isinstance(bones, list)):
        for bone in bones:
            if (isinstance(bone, int) or isinstance(bone, str)):
                internal_keyframe_insert(bpy.context.object.pose.bones[bone], path, positionFrame, collisionFlag, rotationFlag)
            else:
                internal_keyframe_insert(bone, path, positionFrame, collisionFlag, rotationFlag)
    else:
            internal_keyframe_insert(bones, path, positionFrame, collisionFlag, rotationFlag)

def resetIKPosition(isRightHand):
    armature.pose_library = bpy.data.actions[pa_direita_id if isRightHand else pa_esquerda_id]
    bpy.ops.poselib.apply_pose(pose_index = 0)

def resetBnMaoPosition(isRightHand):
    armature.pose_library = bpy.data.actions[orient_direita_id if isRightHand else orient_esquerda_id]
    bpy.ops.poselib.apply_pose(pose_index = 0)

def get_bone_data_from_frame(bone, positionFrame, path):
    scene = bpy.context.scene
    frame_current = scene.frame_current
    scene.frame_set(positionFrame)
    result = copy.copy(bone.location) if path == 'location' else copy.copy(bone.rotation_quaternion)
    scene.frame_set(frame_current)
    return result

def checkRotation(bone, positionFrame, last_keyframe):
    boneRQ = get_bone_data_from_frame(bone, positionFrame, 'rotation_quaternion')
    isRightHand = ".R" in bone.name
    resetBnMaoPosition(isRightHand)
    valid_rotation = validate_rotation(bone, positionFrame, last_keyframe)
    if (not valid_rotation):
        new_rotation = boneRQ * (-1)
        bone.rotation_quaternion = new_rotation
        bone.keyframe_insert(data_path = 'rotation_quaternion', index = -1, frame = positionFrame)

def checkCollision(bone, path, positionFrame, last_keyframe):
    if (last_keyframe == positionFrame):
        return
    isRightHand = ".R" in bone.name
    resetIKPosition(isRightHand)
    handCollisionFrame = check_hand_collision(last_keyframe, positionFrame)
    if (handCollisionFrame != -1):
        handle_collision(bone, path, positionFrame, handCollisionFrame)
        return
    bodyCollisionFrame = check_body_collision(isRightHand, last_keyframe, positionFrame)
    if (bodyCollisionFrame != -1):
        handle_collision(bone, path, positionFrame, bodyCollisionFrame)
        return

def handle_collision(bone, path, positionFrame, collisionFrame, rollbackFrames = 0):
    scene = bpy.context.scene
    frame_current = scene.frame_current
    scene.frame_set(collisionFrame - rollbackFrames)
    bone.keyframe_insert(data_path = path, index = -1, frame = positionFrame)
    bpy.context.scene.frame_set(frame_current)

def check_hand_collision(initFrame, endFrame):
    return check_collision('right_hand_box', 'left_hand_box', initFrame, endFrame)

def check_collision(objName, otherObjName, initFrame, endFrame):
    scene = bpy.context.scene
    startFrame = initFrame + int(math.fabs((endFrame - initFrame)/2))
    collisionFrame = -1
    for i in range(startFrame, endFrame + 1, 1):
        scene.frame_set(i)
        obj = scene.objects.get(objName)
        otherObj = scene.objects.get(otherObjName)
        if (bmesh_check_intersect_objects(obj, otherObj)):
            collisionFrame = i
            break
    scene.frame_set(endFrame)
    return collisionFrame

def check_body_collision(isRightHand, initFrame, endFrame):
    hand_box = 'right_hand_box' if isRightHand else 'left_hand_box'
    body_box = 'body_box'
    result = check_collision(hand_box, body_box, initFrame, endFrame)
    return result

# Função que define as configurações de saida
def configure_output():
    bpy.context.scene.frame_start = 0
    bpy.context.scene.frame_current = bpy.context.scene.frame_start
    bpy.context.scene.frame_end = bpy.context.scene.frame_start
    bpy.context.scene.render.resolution_x = 640
    bpy.context.scene.render.resolution_y = 480
    bpy.context.scene.render.resolution_percentage = 100
    bpy.context.scene.render.image_settings.file_format = 'H264'
    bpy.context.scene.render.ffmpeg.format = 'MPEG4'
    bpy.context.scene.render.ffmpeg.codec = 'H264'
    # Otimização da renderização
    bpy.context.scene.render.use_shadows = False
    bpy.context.scene.render.use_raytrace = False
    bpy.context.scene.render.use_envmaps = False
    bpy.context.scene.render.use_motion_blur = False
    bpy.context.scene.render.use_shadows = False
    bpy.context.scene.render.tile_x = 320
    bpy.context.scene.render.tile_y = 240
    return

def render_sign(user_id = "", sinal = "", frame_final = bpy.context.scene.frame_end):
    bpy.context.scene.frame_end = frame_final #frame_final
    bpy.context.scene.render.filepath = tempfile.NamedTemporaryFile().name
    pyutil.log("Render Video Frames: %i" % (frame_final))
    temp_filename = ("%s%0.4i-%0.4i" % (bpy.context.scene.render.filepath, bpy.context.scene.frame_start, bpy.context.scene.frame_end))
    temp_filename_mp4 = temp_filename + ".mp4"
    temp_filename_webm = temp_filename + ".webm"
    pyutil.log("Temporary File: %s" % (temp_filename_mp4))
    bpy.ops.render.render(animation = True, write_still = False, layer = "", scene = "")
    if pyutil.file_exists(temp_filename_mp4):
        getcwd = os.path.dirname(os.path.abspath(__file__))
        user_directory = os.path.join(getcwd, "users", user_id)
        if (make_dir(user_directory)):
            pyutil.log("user dir created: %s" % (user_directory))
        subprocess.call(["avconv", "-loglevel", "0", "-y", "-i", temp_filename_mp4, "-r", "24", "-vcodec", "libvpx", temp_filename_webm])
        subprocess.call(["rm", "-f", temp_filename_mp4])
        if pyutil.file_exists(temp_filename_webm):
            user_full_path_webm = os.path.join(user_directory, sinal + ".webm")
            try:
                shutil.copy(temp_filename_webm, user_full_path_webm)
                subprocess.call(["rm", "-f", temp_filename_webm])
            except:
                pyutil.log("error while copy the video '%s' to ''" % (temp_filename_webm, user_full_path_webm))
    bpy.ops.wm.quit_blender()
    """
    getcwd = os.path.dirname(os.path.abspath(__file__))
    base_path = os.path.join(getcwd, "users", user_id, sinal)
    bpy.context.scene.render.filepath =  base_path + "_"
    bpy.context.scene.frame_end = frame_final
    pyutil.log("Gerando Video... Frames: %i" % (frame_final))
    bpy.ops.render.render(animation = True, write_still = False, layer = "", scene = "")
    renamed_video = pyutil.file_rename("%s%0.4i-%0.4i.mp4" % (bpy.context.scene.render.filepath, bpy.context.scene.frame_start, bpy.context.scene.frame_end))
    # Converte arquivo .mp4 para .webm
    subprocess.call(["avconv", "-loglevel", "0", "-y", "-i", renamed_video, "-r", "24", "-vcodec", "libvpx", base_path + ".webm"])
    subprocess.call(["rm", renamed_video])
    bpy.ops.wm.quit_blender()
    """
    return

def make_dir(directory):
    if not os.path.exists(directory):
        os.makedirs(directory)
        return True
    return False

def export_blend(directory, filename, frame_start, frame_current, frame_end):
    tmp_frame_start = bpy.context.scene.frame_start
    tmp_frame_current = bpy.context.scene.frame_current
    tmp_frame_end = bpy.context.scene.frame_end
    bpy.context.scene.frame_start = frame_start
    bpy.context.scene.frame_current = frame_current
    bpy.context.scene.frame_end = frame_end
    make_dir(directory)
    bpy.ops.wm.save_as_mainfile(filepath = os.path.join(directory, filename))
    bpy.context.scene.frame_start = tmp_frame_start
    bpy.context.scene.frame_current = tmp_frame_current
    bpy.context.scene.frame_end = tmp_frame_end

def export_json(obj, directory, filename, inline = True):
    make_dir(directory)
    fp = open(os.path.join(directory, filename), 'w', encoding = "utf-8")
    fp.write(
        json.dumps(
            obj,
            ensure_ascii = False,
            indent = None if (inline) else 4,
            separators = (",", ":") if (inline) else None
        )
    )
    fp.close
    return

def validate_rotation(bone, endFrame, startFrame = 0):
    if (endFrame - startFrame == 1):
        return True
    rotFrames = [[]]
    scene = bpy.context.scene
    for i in range(startFrame+1, endFrame+1, 1):
        scene.frame_set(i)
        rotFrames[-1] = bone.rotation_quaternion.to_euler()
        rotFrames.append([])
    rotFrames.remove([])
    scene.frame_set(endFrame)
    for k in range(1, len(rotFrames), 1):
        for i in range(0, 3, 1):
            delta_rotation = math.fabs(rotFrames[k][i] - rotFrames[k-1][i])
            if (delta_rotation > invalid_rotation_lower_bound and delta_rotation < invalid_rotation_upper_bound):
                return False
    return True

# Axis: "X", "Y" e "Z"
def apply_rotation(bone, axis, currentFrame, degree):
    new_rotation = bone.rotation_quaternion.to_euler()
    new_rotation.rotate_axis(axis, math.radians(degree))
    new_rotation = new_rotation.to_quaternion()
    bone.rotation_quaternion = new_rotation
    keyframe_insert(bone, 'rotation_quaternion', currentFrame, False, True)