PMX Import now works
This commit is contained in:
@@ -0,0 +1,776 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2014 MMD Tools authors
|
||||
# This file was originally part of the MMD Tools add-on for Blender
|
||||
# You can find MMD Tools here: https://github.com/MMD-Blender/blender_mmd_tools
|
||||
# Neoneko has modified this file to work with Avatar Toolkit and may of made changes or improvements.
|
||||
# MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit.
|
||||
|
||||
from typing import Optional, cast
|
||||
|
||||
import bpy
|
||||
from mathutils import Quaternion, Vector
|
||||
|
||||
from ..core.model import FnModel
|
||||
from .. import bpyutils, utils
|
||||
from ..core.exceptions import MaterialNotFoundError
|
||||
from ..core.material import FnMaterial
|
||||
from ..core.morph import FnMorph
|
||||
from ..utils import ItemMoveOp, ItemOp
|
||||
|
||||
|
||||
# Util functions
|
||||
def divide_vector_components(vec1, vec2):
|
||||
if len(vec1) != len(vec2):
|
||||
raise ValueError("Vectors should have the same number of components")
|
||||
result = []
|
||||
for v1, v2 in zip(vec1, vec2):
|
||||
if v2 == 0:
|
||||
if v1 == 0:
|
||||
v2 = 1 # If we have a 0/0 case we change the divisor to 1
|
||||
else:
|
||||
raise ZeroDivisionError("Invalid Input: a non-zero value can't be divided by zero")
|
||||
result.append(v1 / v2)
|
||||
return result
|
||||
|
||||
|
||||
def multiply_vector_components(vec1, vec2):
|
||||
if len(vec1) != len(vec2):
|
||||
raise ValueError("Vectors should have the same number of components")
|
||||
result = []
|
||||
for v1, v2 in zip(vec1, vec2):
|
||||
result.append(v1 * v2)
|
||||
return result
|
||||
|
||||
|
||||
def special_division(n1, n2):
|
||||
"""This function returns 0 in case of 0/0. If non-zero divided by zero case is found, an Exception is raised"""
|
||||
if n2 == 0:
|
||||
if n1 == 0:
|
||||
n2 = 1
|
||||
else:
|
||||
raise ZeroDivisionError("Invalid Input: a non-zero value can't be divided by zero")
|
||||
return n1 / n2
|
||||
|
||||
|
||||
class AddMorph(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.morph_add"
|
||||
bl_label = "Add Morph"
|
||||
bl_description = "Add a morph item to active morph list"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
morph_type = mmd_root.active_morph_type
|
||||
morphs = getattr(mmd_root, morph_type)
|
||||
morph, mmd_root.active_morph = ItemOp.add_after(morphs, mmd_root.active_morph)
|
||||
morph.name = "New Morph"
|
||||
if morph_type.startswith("uv"):
|
||||
morph.data_type = "VERTEX_GROUP"
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveMorph(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.morph_remove"
|
||||
bl_label = "Remove Morph"
|
||||
bl_description = "Remove morph item(s) from the list"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
all: bpy.props.BoolProperty(
|
||||
name="All",
|
||||
description="Delete all morph items",
|
||||
default=False,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
|
||||
morph_type = mmd_root.active_morph_type
|
||||
if morph_type.startswith("material"):
|
||||
bpy.ops.mmd_tools.clear_temp_materials()
|
||||
elif morph_type.startswith("uv"):
|
||||
bpy.ops.mmd_tools.clear_uv_morph_view()
|
||||
|
||||
morphs = getattr(mmd_root, morph_type)
|
||||
if self.all:
|
||||
morphs.clear()
|
||||
mmd_root.active_morph = 0
|
||||
else:
|
||||
morphs.remove(mmd_root.active_morph)
|
||||
mmd_root.active_morph = max(0, mmd_root.active_morph - 1)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class MoveMorph(bpy.types.Operator, ItemMoveOp):
|
||||
bl_idname = "mmd_tools.morph_move"
|
||||
bl_label = "Move Morph"
|
||||
bl_description = "Move active morph item up/down in the list"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
mmd_root.active_morph = self.move(
|
||||
getattr(mmd_root, mmd_root.active_morph_type),
|
||||
mmd_root.active_morph,
|
||||
self.type,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CopyMorph(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.morph_copy"
|
||||
bl_label = "Copy Morph"
|
||||
bl_description = "Make a copy of active morph in the list"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
mmd_root = root.mmd_root
|
||||
|
||||
morph_type = mmd_root.active_morph_type
|
||||
morphs = getattr(mmd_root, morph_type)
|
||||
morph = ItemOp.get_by_index(morphs, mmd_root.active_morph)
|
||||
if morph is None:
|
||||
return {"CANCELLED"}
|
||||
|
||||
name_orig, name_tmp = morph.name, "_tmp%s" % str(morph.as_pointer())
|
||||
|
||||
if morph_type.startswith("vertex"):
|
||||
for obj in FnModel.iterate_mesh_objects(root):
|
||||
FnMorph.copy_shape_key(obj, name_orig, name_tmp)
|
||||
|
||||
elif morph_type.startswith("uv"):
|
||||
if morph.data_type == "VERTEX_GROUP":
|
||||
for obj in FnModel.iterate_mesh_objects(root):
|
||||
FnMorph.copy_uv_morph_vertex_groups(obj, name_orig, name_tmp)
|
||||
|
||||
morph_new, mmd_root.active_morph = ItemOp.add_after(morphs, mmd_root.active_morph)
|
||||
for k, v in morph.items():
|
||||
morph_new[k] = v if k != "name" else name_tmp
|
||||
morph_new.name = name_orig + "_copy" # trigger name check
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class OverwriteBoneMorphsFromActionPose(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.morph_overwrite_from_active_action_pose"
|
||||
bl_label = "Overwrite Bone Morphs from active Action Pose"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
root = FnModel.find_root_object(context.active_object)
|
||||
if root is None:
|
||||
return False
|
||||
|
||||
return root.mmd_root.active_morph_type == "bone_morphs"
|
||||
|
||||
def execute(self, context):
|
||||
root = FnModel.find_root_object(context.active_object)
|
||||
FnMorph.overwrite_bone_morphs_from_action_pose(FnModel.find_armature_object(root))
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddMorphOffset(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.morph_offset_add"
|
||||
bl_label = "Add Morph Offset"
|
||||
bl_description = "Add a morph offset item to the list"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
morph_type = mmd_root.active_morph_type
|
||||
morph = ItemOp.get_by_index(getattr(mmd_root, morph_type), mmd_root.active_morph)
|
||||
if morph is None:
|
||||
return {"CANCELLED"}
|
||||
|
||||
item, morph.active_data = ItemOp.add_after(morph.data, morph.active_data)
|
||||
|
||||
if morph_type.startswith("material"):
|
||||
if obj.type == "MESH" and obj.mmd_type == "NONE":
|
||||
item.related_mesh = obj.data.name
|
||||
active_material = obj.active_material
|
||||
if active_material and "_temp" not in active_material.name:
|
||||
item.material = active_material.name
|
||||
|
||||
elif morph_type.startswith("bone"):
|
||||
pose_bone = context.active_pose_bone
|
||||
if pose_bone:
|
||||
item.bone = pose_bone.name
|
||||
item.location = pose_bone.location
|
||||
item.rotation = pose_bone.rotation_quaternion
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveMorphOffset(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.morph_offset_remove"
|
||||
bl_label = "Remove Morph Offset"
|
||||
bl_description = "Remove morph offset item(s) from the list"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
all: bpy.props.BoolProperty(
|
||||
name="All",
|
||||
description="Delete all morph offset items",
|
||||
default=False,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
mmd_root = root.mmd_root
|
||||
morph_type = mmd_root.active_morph_type
|
||||
morph = ItemOp.get_by_index(getattr(mmd_root, morph_type), mmd_root.active_morph)
|
||||
if morph is None:
|
||||
return {"CANCELLED"}
|
||||
|
||||
if morph_type.startswith("material"):
|
||||
bpy.ops.mmd_tools.clear_temp_materials()
|
||||
|
||||
if self.all:
|
||||
if morph_type.startswith("vertex"):
|
||||
for obj in FnModel.iterate_mesh_objects(root):
|
||||
FnMorph.remove_shape_key(obj, morph.name)
|
||||
return {"FINISHED"}
|
||||
elif morph_type.startswith("uv"):
|
||||
if morph.data_type == "VERTEX_GROUP":
|
||||
for obj in FnModel.iterate_mesh_objects(root):
|
||||
FnMorph.store_uv_morph_data(obj, morph)
|
||||
return {"FINISHED"}
|
||||
morph.data.clear()
|
||||
morph.active_data = 0
|
||||
else:
|
||||
morph.data.remove(morph.active_data)
|
||||
morph.active_data = max(0, morph.active_data - 1)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class InitMaterialOffset(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.material_morph_offset_init"
|
||||
bl_label = "Init Material Offset"
|
||||
bl_description = "Set all offset values to target value"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
target_value: bpy.props.FloatProperty(
|
||||
name="Target Value",
|
||||
description="Target value",
|
||||
default=0,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
morph = mmd_root.material_morphs[mmd_root.active_morph]
|
||||
mat_data = morph.data[morph.active_data]
|
||||
|
||||
val = self.target_value
|
||||
mat_data.diffuse_color = mat_data.edge_color = (val,) * 4
|
||||
mat_data.specular_color = mat_data.ambient_color = (val,) * 3
|
||||
mat_data.shininess = mat_data.edge_weight = val
|
||||
mat_data.texture_factor = mat_data.toon_texture_factor = mat_data.sphere_texture_factor = (val,) * 4
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ApplyMaterialOffset(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.apply_material_morph_offset"
|
||||
bl_label = "Apply Material Offset"
|
||||
bl_description = "Calculates the offsets and apply them, then the temporary material is removed"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
morph = mmd_root.material_morphs[mmd_root.active_morph]
|
||||
mat_data = morph.data[morph.active_data]
|
||||
|
||||
if not mat_data.related_mesh:
|
||||
self.report({"ERROR"}, "You need to choose a Related Mesh first")
|
||||
return {"CANCELLED"}
|
||||
meshObj = FnModel.find_mesh_object_by_name(morph.id_data, mat_data.related_mesh)
|
||||
if meshObj is None:
|
||||
self.report({"ERROR"}, "The model mesh can't be found")
|
||||
return {"CANCELLED"}
|
||||
try:
|
||||
work_mat_name = mat_data.material + "_temp"
|
||||
work_mat, base_mat = FnMaterial.swap_materials(meshObj, work_mat_name, mat_data.material)
|
||||
except MaterialNotFoundError:
|
||||
self.report({"ERROR"}, "Material not found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
base_mmd_mat = base_mat.mmd_material
|
||||
work_mmd_mat = work_mat.mmd_material
|
||||
|
||||
if mat_data.offset_type == "MULT":
|
||||
try:
|
||||
diffuse_offset = divide_vector_components(work_mmd_mat.diffuse_color, base_mmd_mat.diffuse_color) + [special_division(work_mmd_mat.alpha, base_mmd_mat.alpha)]
|
||||
specular_offset = divide_vector_components(work_mmd_mat.specular_color, base_mmd_mat.specular_color)
|
||||
edge_offset = divide_vector_components(work_mmd_mat.edge_color, base_mmd_mat.edge_color)
|
||||
mat_data.diffuse_color = diffuse_offset
|
||||
mat_data.specular_color = specular_offset
|
||||
mat_data.shininess = special_division(work_mmd_mat.shininess, base_mmd_mat.shininess)
|
||||
mat_data.ambient_color = divide_vector_components(work_mmd_mat.ambient_color, base_mmd_mat.ambient_color)
|
||||
mat_data.edge_color = edge_offset
|
||||
mat_data.edge_weight = special_division(work_mmd_mat.edge_weight, base_mmd_mat.edge_weight)
|
||||
|
||||
except ZeroDivisionError:
|
||||
mat_data.offset_type = "ADD" # If there is any 0 division we automatically switch it to type ADD
|
||||
except ValueError:
|
||||
self.report({"ERROR"}, "An unexpected error happened")
|
||||
# We should stop on our tracks and re-raise the exception
|
||||
raise
|
||||
|
||||
if mat_data.offset_type == "ADD":
|
||||
diffuse_offset = list(work_mmd_mat.diffuse_color - base_mmd_mat.diffuse_color) + [work_mmd_mat.alpha - base_mmd_mat.alpha]
|
||||
specular_offset = list(work_mmd_mat.specular_color - base_mmd_mat.specular_color)
|
||||
edge_offset = Vector(work_mmd_mat.edge_color) - Vector(base_mmd_mat.edge_color)
|
||||
mat_data.diffuse_color = diffuse_offset
|
||||
mat_data.specular_color = specular_offset
|
||||
mat_data.shininess = work_mmd_mat.shininess - base_mmd_mat.shininess
|
||||
mat_data.ambient_color = work_mmd_mat.ambient_color - base_mmd_mat.ambient_color
|
||||
mat_data.edge_color = list(edge_offset)
|
||||
mat_data.edge_weight = work_mmd_mat.edge_weight - base_mmd_mat.edge_weight
|
||||
|
||||
FnMaterial.clean_materials(meshObj, can_remove=lambda m: m == work_mat)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CreateWorkMaterial(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.create_work_material"
|
||||
bl_label = "Create Work Material"
|
||||
bl_description = "Creates a temporary material to edit this offset"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
morph = mmd_root.material_morphs[mmd_root.active_morph]
|
||||
mat_data = morph.data[morph.active_data]
|
||||
|
||||
if not mat_data.related_mesh:
|
||||
self.report({"ERROR"}, "You need to choose a Related Mesh first")
|
||||
return {"CANCELLED"}
|
||||
meshObj = FnModel.find_mesh_object_by_name(morph.id_data, mat_data.related_mesh)
|
||||
if meshObj is None:
|
||||
self.report({"ERROR"}, "The model mesh can't be found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
base_mat = meshObj.data.materials.get(mat_data.material, None)
|
||||
if base_mat is None:
|
||||
self.report({"ERROR"}, 'Material "%s" not found' % mat_data.material)
|
||||
return {"CANCELLED"}
|
||||
|
||||
work_mat_name = base_mat.name + "_temp"
|
||||
if work_mat_name in bpy.data.materials:
|
||||
self.report({"ERROR"}, 'Temporary material "%s" is in use' % work_mat_name)
|
||||
return {"CANCELLED"}
|
||||
|
||||
work_mat = base_mat.copy()
|
||||
work_mat.name = work_mat_name
|
||||
meshObj.data.materials.append(work_mat)
|
||||
FnMaterial.swap_materials(meshObj, base_mat.name, work_mat.name)
|
||||
base_mmd_mat = base_mat.mmd_material
|
||||
work_mmd_mat = work_mat.mmd_material
|
||||
work_mmd_mat.material_id = -1
|
||||
|
||||
# Apply the offsets
|
||||
if mat_data.offset_type == "MULT":
|
||||
diffuse_offset = multiply_vector_components(base_mmd_mat.diffuse_color, mat_data.diffuse_color[0:3])
|
||||
specular_offset = multiply_vector_components(base_mmd_mat.specular_color, mat_data.specular_color)
|
||||
edge_offset = multiply_vector_components(base_mmd_mat.edge_color, mat_data.edge_color)
|
||||
ambient_offset = multiply_vector_components(base_mmd_mat.ambient_color, mat_data.ambient_color)
|
||||
work_mmd_mat.diffuse_color = diffuse_offset
|
||||
work_mmd_mat.alpha *= mat_data.diffuse_color[3]
|
||||
work_mmd_mat.specular_color = specular_offset
|
||||
work_mmd_mat.shininess *= mat_data.shininess
|
||||
work_mmd_mat.ambient_color = ambient_offset
|
||||
work_mmd_mat.edge_color = edge_offset
|
||||
work_mmd_mat.edge_weight *= mat_data.edge_weight
|
||||
elif mat_data.offset_type == "ADD":
|
||||
diffuse_offset = Vector(base_mmd_mat.diffuse_color) + Vector(mat_data.diffuse_color[0:3])
|
||||
specular_offset = Vector(base_mmd_mat.specular_color) + Vector(mat_data.specular_color)
|
||||
edge_offset = Vector(base_mmd_mat.edge_color) + Vector(mat_data.edge_color)
|
||||
ambient_offset = Vector(base_mmd_mat.ambient_color) + Vector(mat_data.ambient_color)
|
||||
work_mmd_mat.diffuse_color = list(diffuse_offset)
|
||||
work_mmd_mat.alpha += mat_data.diffuse_color[3]
|
||||
work_mmd_mat.specular_color = list(specular_offset)
|
||||
work_mmd_mat.shininess += mat_data.shininess
|
||||
work_mmd_mat.ambient_color = list(ambient_offset)
|
||||
work_mmd_mat.edge_color = list(edge_offset)
|
||||
work_mmd_mat.edge_weight += mat_data.edge_weight
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ClearTempMaterials(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.clear_temp_materials"
|
||||
bl_label = "Clear Temp Materials"
|
||||
bl_description = "Clears all the temporary materials"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
for meshObj in FnModel.iterate_mesh_objects(root):
|
||||
|
||||
def __pre_remove(m):
|
||||
if m and "_temp" in m.name:
|
||||
base_mat_name = m.name.split("_temp")[0]
|
||||
try:
|
||||
FnMaterial.swap_materials(meshObj, m.name, base_mat_name)
|
||||
return True
|
||||
except MaterialNotFoundError:
|
||||
self.report({"WARNING"}, "Base material for %s was not found" % m.name)
|
||||
return False
|
||||
|
||||
FnMaterial.clean_materials(meshObj, can_remove=__pre_remove)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ViewBoneMorph(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.view_bone_morph"
|
||||
bl_label = "View Bone Morph"
|
||||
bl_description = "View the result of active bone morph"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
mmd_root = root.mmd_root
|
||||
armature = FnModel.find_armature_object(root)
|
||||
utils.selectSingleBone(context, armature, None, True)
|
||||
morph = mmd_root.bone_morphs[mmd_root.active_morph]
|
||||
for morph_data in morph.data:
|
||||
p_bone: Optional[bpy.types.PoseBone] = armature.pose.bones.get(morph_data.bone, None)
|
||||
if p_bone:
|
||||
p_bone.bone.select = True
|
||||
mtx = (p_bone.matrix_basis.to_3x3() @ Quaternion(*morph_data.rotation.to_axis_angle()).to_matrix()).to_4x4()
|
||||
mtx.translation = p_bone.location + morph_data.location
|
||||
p_bone.matrix_basis = mtx
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ClearBoneMorphView(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.clear_bone_morph_view"
|
||||
bl_label = "Clear Bone Morph View"
|
||||
bl_description = "Reset transforms of all bones to their default values"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
armature = FnModel.find_armature_object(root)
|
||||
for p_bone in armature.pose.bones:
|
||||
p_bone.matrix_basis.identity()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ApplyBoneMorph(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.apply_bone_morph"
|
||||
bl_label = "Apply Bone Morph"
|
||||
bl_description = "Apply current pose to active bone morph"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
armature = FnModel.find_armature_object(root)
|
||||
mmd_root = root.mmd_root
|
||||
morph = mmd_root.bone_morphs[mmd_root.active_morph]
|
||||
morph.data.clear()
|
||||
morph.active_data = 0
|
||||
for p_bone in armature.pose.bones:
|
||||
if p_bone.location.length > 0 or p_bone.matrix_basis.decompose()[1].angle > 0:
|
||||
item = morph.data.add()
|
||||
item.bone = p_bone.name
|
||||
item.location = p_bone.location
|
||||
item.rotation = p_bone.rotation_quaternion if p_bone.rotation_mode == "QUATERNION" else p_bone.matrix_basis.to_quaternion()
|
||||
p_bone.bone.select = True
|
||||
else:
|
||||
p_bone.bone.select = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectRelatedBone(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.select_bone_morph_offset_bone"
|
||||
bl_label = "Select Related Bone"
|
||||
bl_description = "Select the bone assigned to this offset in the armature"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
mmd_root = root.mmd_root
|
||||
armature = FnModel.find_armature_object(root)
|
||||
morph = mmd_root.bone_morphs[mmd_root.active_morph]
|
||||
morph_data = morph.data[morph.active_data]
|
||||
utils.selectSingleBone(context, armature, morph_data.bone)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditBoneOffset(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.edit_bone_morph_offset"
|
||||
bl_label = "Edit Related Bone"
|
||||
bl_description = "Applies the location and rotation of this offset to the bone"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
mmd_root = root.mmd_root
|
||||
armature = FnModel.find_armature_object(root)
|
||||
morph = mmd_root.bone_morphs[mmd_root.active_morph]
|
||||
morph_data = morph.data[morph.active_data]
|
||||
p_bone = armature.pose.bones[morph_data.bone]
|
||||
mtx = Quaternion(*morph_data.rotation.to_axis_angle()).to_matrix().to_4x4()
|
||||
mtx.translation = morph_data.location
|
||||
p_bone.matrix_basis = mtx
|
||||
utils.selectSingleBone(context, armature, p_bone.name)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ApplyBoneOffset(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.apply_bone_morph_offset"
|
||||
bl_label = "Apply Bone Morph Offset"
|
||||
bl_description = "Stores the current bone location and rotation into this offset"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
mmd_root = root.mmd_root
|
||||
armature = FnModel.find_armature_object(root)
|
||||
assert armature is not None
|
||||
morph = mmd_root.bone_morphs[mmd_root.active_morph]
|
||||
morph_data = morph.data[morph.active_data]
|
||||
p_bone = armature.pose.bones[morph_data.bone]
|
||||
morph_data.location = p_bone.location
|
||||
morph_data.rotation = p_bone.rotation_quaternion if p_bone.rotation_mode == "QUATERNION" else p_bone.matrix_basis.to_quaternion()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ViewUVMorph(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.view_uv_morph"
|
||||
bl_label = "View UV Morph"
|
||||
bl_description = "View the result of active UV morph on current mesh object"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
mmd_root = root.mmd_root
|
||||
|
||||
meshes = tuple(FnModel.iterate_mesh_objects(root))
|
||||
if len(meshes) == 1:
|
||||
obj = meshes[0]
|
||||
elif obj not in meshes:
|
||||
self.report({"ERROR"}, "Please select a mesh object")
|
||||
return {"CANCELLED"}
|
||||
meshObj = obj
|
||||
|
||||
bpy.ops.mmd_tools.clear_uv_morph_view()
|
||||
|
||||
selected = meshObj.select_get()
|
||||
with bpyutils.select_object(meshObj):
|
||||
mesh = cast(bpy.types.Mesh, meshObj.data)
|
||||
morph = mmd_root.uv_morphs[mmd_root.active_morph]
|
||||
uv_textures = mesh.uv_layers
|
||||
|
||||
base_uv_layers = [l for l in mesh.uv_layers if not l.name.startswith("_")]
|
||||
if morph.uv_index >= len(base_uv_layers):
|
||||
self.report({"ERROR"}, "Invalid uv index: %d" % morph.uv_index)
|
||||
return {"CANCELLED"}
|
||||
|
||||
uv_layer_name = base_uv_layers[morph.uv_index].name
|
||||
if morph.uv_index == 0 or uv_textures.active.name not in {uv_layer_name, "_" + uv_layer_name}:
|
||||
uv_textures.active = uv_textures[uv_layer_name]
|
||||
|
||||
uv_layer_name = uv_textures.active.name
|
||||
uv_tex = uv_textures.new(name="__uv.%s" % uv_layer_name)
|
||||
if uv_tex is None:
|
||||
self.report({"ERROR"}, "Failed to create a temporary uv layer")
|
||||
return {"CANCELLED"}
|
||||
|
||||
offsets = FnMorph.get_uv_morph_offset_map(meshObj, morph).items()
|
||||
offsets = {k: getattr(Vector(v), "zw" if uv_layer_name.startswith("_") else "xy") for k, v in offsets}
|
||||
if len(offsets) > 0:
|
||||
base_uv_data = mesh.uv_layers.active.data
|
||||
temp_uv_data = mesh.uv_layers[uv_tex.name].data
|
||||
for i, l in enumerate(mesh.loops):
|
||||
select = temp_uv_data[i].select = l.vertex_index in offsets
|
||||
if select:
|
||||
temp_uv_data[i].uv = base_uv_data[i].uv + offsets[l.vertex_index]
|
||||
|
||||
uv_textures.active = uv_tex
|
||||
uv_tex.active_render = True
|
||||
meshObj.hide_set(False)
|
||||
meshObj.select_set(selected)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ClearUVMorphView(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.clear_uv_morph_view"
|
||||
bl_label = "Clear UV Morph View"
|
||||
bl_description = "Clear all temporary data of UV morphs"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
for m in FnModel.iterate_mesh_objects(root):
|
||||
mesh = m.data
|
||||
uv_textures = getattr(mesh, "uv_textures", mesh.uv_layers)
|
||||
for t in uv_textures:
|
||||
if t.name.startswith("__uv."):
|
||||
uv_textures.remove(t)
|
||||
if len(uv_textures) > 0:
|
||||
uv_textures[0].active_render = True
|
||||
uv_textures.active_index = 0
|
||||
|
||||
animation_data = mesh.animation_data
|
||||
if animation_data:
|
||||
nla_tracks = animation_data.nla_tracks
|
||||
for t in nla_tracks:
|
||||
if t.name.startswith("__uv."):
|
||||
nla_tracks.remove(t)
|
||||
if animation_data.action and animation_data.action.name.startswith("__uv."):
|
||||
animation_data.action = None
|
||||
if animation_data.action is None and len(nla_tracks) == 0:
|
||||
mesh.animation_data_clear()
|
||||
|
||||
for act in bpy.data.actions:
|
||||
if act.name.startswith("__uv.") and act.users < 1:
|
||||
bpy.data.actions.remove(act)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditUVMorph(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.edit_uv_morph"
|
||||
bl_label = "Edit UV Morph"
|
||||
bl_description = "Edit UV morph on a temporary UV layer (use UV Editor to edit the result)"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
if obj.type != "MESH":
|
||||
return False
|
||||
active_uv_layer = obj.data.uv_layers.active
|
||||
return active_uv_layer and active_uv_layer.name.startswith("__uv.")
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
meshObj = obj
|
||||
|
||||
selected = meshObj.select_get()
|
||||
with bpyutils.select_object(meshObj):
|
||||
mesh = cast(bpy.types.Mesh, meshObj.data)
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
bpy.ops.mesh.select_mode(type="VERT", action="ENABLE")
|
||||
bpy.ops.mesh.reveal() # unhide all vertices
|
||||
bpy.ops.mesh.select_all(action="DESELECT")
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
vertices = mesh.vertices
|
||||
for l, d in zip(mesh.loops, mesh.uv_layers.active.data):
|
||||
if d.select:
|
||||
vertices[l.vertex_index].select = True
|
||||
|
||||
polygons = mesh.polygons
|
||||
polygons.active = getattr(next((p for p in polygons if all(vertices[i].select for i in p.vertices)), None), "index", polygons.active)
|
||||
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
meshObj.select_set(selected)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ApplyUVMorph(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.apply_uv_morph"
|
||||
bl_label = "Apply UV Morph"
|
||||
bl_description = "Calculate the UV offsets of selected vertices and apply to active UV morph"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
if obj.type != "MESH":
|
||||
return False
|
||||
active_uv_layer = obj.data.uv_layers.active
|
||||
return active_uv_layer and active_uv_layer.name.startswith("__uv.")
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
meshObj = obj
|
||||
|
||||
selected = meshObj.select_get()
|
||||
with bpyutils.select_object(meshObj):
|
||||
mesh = cast(bpy.types.Mesh, meshObj.data)
|
||||
morph = mmd_root.uv_morphs[mmd_root.active_morph]
|
||||
|
||||
base_uv_name = mesh.uv_layers.active.name[5:]
|
||||
if base_uv_name not in mesh.uv_layers:
|
||||
self.report({"ERROR"}, ' * UV map "%s" not found' % base_uv_name)
|
||||
return {"CANCELLED"}
|
||||
|
||||
base_uv_data = mesh.uv_layers[base_uv_name].data
|
||||
temp_uv_data = mesh.uv_layers.active.data
|
||||
axis_type = "ZW" if base_uv_name.startswith("_") else "XY"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
__OffsetData = namedtuple("OffsetData", "index, offset")
|
||||
offsets = {}
|
||||
vertices = mesh.vertices
|
||||
for l, i0, i1 in zip(mesh.loops, base_uv_data, temp_uv_data):
|
||||
if vertices[l.vertex_index].select and l.vertex_index not in offsets:
|
||||
dx, dy = i1.uv - i0.uv
|
||||
if abs(dx) > 0.0001 or abs(dy) > 0.0001:
|
||||
offsets[l.vertex_index] = __OffsetData(l.vertex_index, (dx, dy, dx, dy))
|
||||
|
||||
FnMorph.store_uv_morph_data(meshObj, morph, offsets.values(), axis_type)
|
||||
morph.data_type = "VERTEX_GROUP"
|
||||
|
||||
meshObj.select_set(selected)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CleanDuplicatedMaterialMorphs(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.clean_duplicated_material_morphs"
|
||||
bl_label = "Clean Duplicated Material Morphs"
|
||||
bl_description = "Clean duplicated material morphs"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return FnModel.find_root_object(context.active_object) is not None
|
||||
|
||||
def execute(self, context: bpy.types.Context):
|
||||
mmd_root_object = FnModel.find_root_object(context.active_object)
|
||||
FnMorph.clean_duplicated_material_morphs(mmd_root_object)
|
||||
|
||||
return {"FINISHED"}
|
||||
Reference in New Issue
Block a user