487 lines
18 KiB
Python
487 lines
18 KiB
Python
# -*- 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.
|
|
|
|
import bpy
|
|
|
|
from ..bpyutils import FnContext
|
|
from ..core.bone import FnBone, MigrationFnBone
|
|
from ..core.model import FnModel, Model
|
|
|
|
|
|
class MorphSliderSetup(bpy.types.Operator):
|
|
bl_idname = "mmd_tools.morph_slider_setup"
|
|
bl_label = "Morph Slider Setup"
|
|
bl_description = "Translate MMD morphs of selected object into format usable by Blender"
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
type: bpy.props.EnumProperty(
|
|
name="Type",
|
|
description="Select type",
|
|
items=[
|
|
("CREATE", "Create", "Create placeholder object for morph sliders", "SHAPEKEY_DATA", 0),
|
|
("BIND", "Bind", "Bind morph sliders", "DRIVER", 1),
|
|
("UNBIND", "Unbind", "Unbind morph sliders", "X", 2),
|
|
],
|
|
default="CREATE",
|
|
)
|
|
|
|
def execute(self, context: bpy.types.Context):
|
|
active_object = context.active_object
|
|
root_object = FnModel.find_root_object(active_object)
|
|
assert root_object is not None
|
|
|
|
with FnContext.temp_override_active_layer_collection(context, root_object):
|
|
rig = Model(root_object)
|
|
if self.type == "BIND":
|
|
rig.morph_slider.bind()
|
|
elif self.type == "UNBIND":
|
|
rig.morph_slider.unbind()
|
|
else:
|
|
rig.morph_slider.create()
|
|
FnContext.set_active_object(context, active_object)
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
class CleanRiggingObjects(bpy.types.Operator):
|
|
bl_idname = "mmd_tools.clean_rig"
|
|
bl_label = "Clean Rig"
|
|
bl_description = "Delete temporary physics objects of selected object and revert physics to default MMD state"
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
def execute(self, context):
|
|
root_object = FnModel.find_root_object(context.active_object)
|
|
assert root_object is not None
|
|
|
|
rig = Model(root_object)
|
|
rig.clean()
|
|
FnContext.set_active_object(context, root_object)
|
|
return {"FINISHED"}
|
|
|
|
|
|
class BuildRig(bpy.types.Operator):
|
|
bl_idname = "mmd_tools.build_rig"
|
|
bl_label = "Build Rig"
|
|
bl_description = "Translate physics of selected object into format usable by Blender"
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
non_collision_distance_scale: bpy.props.FloatProperty(
|
|
name="Non-Collision Distance Scale",
|
|
description="The distance scale for creating extra non-collision constraints while building physics",
|
|
min=0,
|
|
soft_max=10,
|
|
default=1.5,
|
|
)
|
|
|
|
collision_margin: bpy.props.FloatProperty(
|
|
name="Collision Margin",
|
|
description="The collision margin between rigid bodies. If 0, the default value for each shape is adopted.",
|
|
unit="LENGTH",
|
|
min=0,
|
|
soft_max=10,
|
|
default=1e-06,
|
|
)
|
|
|
|
def execute(self, context):
|
|
root_object = FnModel.find_root_object(context.active_object)
|
|
|
|
with FnContext.temp_override_active_layer_collection(context, root_object):
|
|
rig = Model(root_object)
|
|
rig.build(self.non_collision_distance_scale, self.collision_margin)
|
|
FnContext.set_active_object(context, root_object)
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
class CleanAdditionalTransformConstraints(bpy.types.Operator):
|
|
bl_idname = "mmd_tools.clean_additional_transform"
|
|
bl_label = "Clean Additional Transform"
|
|
bl_description = "Delete shadow bones of selected object and revert bones to default MMD state"
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
def execute(self, context):
|
|
active_object = context.active_object
|
|
root_object = FnModel.find_root_object(active_object)
|
|
assert root_object is not None
|
|
FnBone.clean_additional_transformation(FnModel.find_armature_object(root_object))
|
|
FnContext.set_active_object(context, active_object)
|
|
return {"FINISHED"}
|
|
|
|
|
|
class ApplyAdditionalTransformConstraints(bpy.types.Operator):
|
|
bl_idname = "mmd_tools.apply_additional_transform"
|
|
bl_label = "Apply Additional Transform"
|
|
bl_description = "Translate appended bones of selected object for Blender"
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
def execute(self, context):
|
|
active_object = context.active_object
|
|
root_object = FnModel.find_root_object(active_object)
|
|
assert root_object is not None
|
|
|
|
armature_object = FnModel.find_armature_object(root_object)
|
|
assert armature_object is not None
|
|
|
|
MigrationFnBone.fix_mmd_ik_limit_override(armature_object)
|
|
FnBone.apply_additional_transformation(armature_object)
|
|
FnContext.set_active_object(context, active_object)
|
|
return {"FINISHED"}
|
|
|
|
|
|
class SetupBoneFixedAxes(bpy.types.Operator):
|
|
bl_idname = "mmd_tools.bone_fixed_axis_setup"
|
|
bl_label = "Setup Bone Fixed Axis"
|
|
bl_description = "Setup fixed axis of selected bones"
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
type: bpy.props.EnumProperty(
|
|
name="Type",
|
|
description="Select type",
|
|
items=[
|
|
("DISABLE", "Disable", "Disable MMD fixed axis of selected bones", 0),
|
|
("LOAD", "Load", "Load/Enable MMD fixed axis of selected bones from their Y-axis or the only rotatable axis", 1),
|
|
("APPLY", "Apply", "Align bone axes to MMD fixed axis of each bone", 2),
|
|
],
|
|
default="LOAD",
|
|
)
|
|
|
|
def execute(self, context):
|
|
armature_object = context.active_object
|
|
if not armature_object or armature_object.type != "ARMATURE":
|
|
self.report({"ERROR"}, "Active object is not an armature object")
|
|
return {"CANCELLED"}
|
|
|
|
if self.type == "APPLY":
|
|
FnBone.apply_bone_fixed_axis(armature_object)
|
|
FnBone.apply_additional_transformation(armature_object)
|
|
else:
|
|
FnBone.load_bone_fixed_axis(armature_object, enable=(self.type == "LOAD"))
|
|
return {"FINISHED"}
|
|
|
|
|
|
class SetupBoneLocalAxes(bpy.types.Operator):
|
|
bl_idname = "mmd_tools.bone_local_axes_setup"
|
|
bl_label = "Setup Bone Local Axes"
|
|
bl_description = "Setup local axes of each bone"
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
type: bpy.props.EnumProperty(
|
|
name="Type",
|
|
description="Select type",
|
|
items=[
|
|
("DISABLE", "Disable", "Disable MMD local axes of selected bones", 0),
|
|
("LOAD", "Load", "Load/Enable MMD local axes of selected bones from their bone axes", 1),
|
|
("APPLY", "Apply", "Align bone axes to MMD local axes of each bone", 2),
|
|
],
|
|
default="LOAD",
|
|
)
|
|
|
|
def execute(self, context):
|
|
armature_object = context.active_object
|
|
if not armature_object or armature_object.type != "ARMATURE":
|
|
self.report({"ERROR"}, "Active object is not an armature object")
|
|
return {"CANCELLED"}
|
|
|
|
if self.type == "APPLY":
|
|
FnBone.apply_bone_local_axes(armature_object)
|
|
FnBone.apply_additional_transformation(armature_object)
|
|
else:
|
|
FnBone.load_bone_local_axes(armature_object, enable=(self.type == "LOAD"))
|
|
return {"FINISHED"}
|
|
|
|
|
|
class AddMissingVertexGroupsFromBones(bpy.types.Operator):
|
|
bl_idname = "mmd_tools.add_missing_vertex_groups_from_bones"
|
|
bl_label = "Add Missing Vertex Groups from Bones"
|
|
bl_description = "Add the missing vertex groups to the selected mesh"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
search_in_all_meshes: bpy.props.BoolProperty(
|
|
name="Search in all meshes",
|
|
description="Search for vertex groups in all meshes",
|
|
default=False,
|
|
)
|
|
|
|
@classmethod
|
|
def poll(cls, context: bpy.types.Context):
|
|
return FnModel.find_root_object(context.active_object) is not None
|
|
|
|
def execute(self, context: bpy.types.Context):
|
|
active_object: bpy.types.Object = context.active_object
|
|
root_object = FnModel.find_root_object(active_object)
|
|
assert root_object is not None
|
|
|
|
bone_order_mesh_object = FnModel.find_bone_order_mesh_object(root_object)
|
|
if bone_order_mesh_object is None:
|
|
return {"CANCELLED"}
|
|
|
|
FnModel.add_missing_vertex_groups_from_bones(root_object, bone_order_mesh_object, self.search_in_all_meshes)
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
class CreateMMDModelRoot(bpy.types.Operator):
|
|
bl_idname = "mmd_tools.create_mmd_model_root_object"
|
|
bl_label = "Create a MMD Model Root Object"
|
|
bl_description = "Create a MMD model root object with a basic armature"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
name_j: bpy.props.StringProperty(
|
|
name="Name",
|
|
description="The name of the MMD model",
|
|
default="New MMD Model",
|
|
)
|
|
name_e: bpy.props.StringProperty(
|
|
name="Name(Eng)",
|
|
description="The english name of the MMD model",
|
|
default="New MMD Model",
|
|
)
|
|
scale: bpy.props.FloatProperty(
|
|
name="Scale",
|
|
description="Scale",
|
|
default=0.08,
|
|
)
|
|
|
|
def execute(self, context):
|
|
rig = Model.create(self.name_j, self.name_e, self.scale, add_root_bone=True)
|
|
rig.initialDisplayFrames()
|
|
return {"FINISHED"}
|
|
|
|
def invoke(self, context, event):
|
|
vm = context.window_manager
|
|
return vm.invoke_props_dialog(self)
|
|
|
|
|
|
class ConvertToMMDModel(bpy.types.Operator):
|
|
bl_idname = "mmd_tools.convert_to_mmd_model"
|
|
bl_label = "Convert to a MMD Model"
|
|
bl_description = "Convert active armature with its meshes to a MMD model (experimental)"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
ambient_color_source: bpy.props.EnumProperty(
|
|
name="Ambient Color Source",
|
|
description="Select ambient color source",
|
|
items=[
|
|
("DIFFUSE", "Diffuse", "Diffuse color", 0),
|
|
("MIRROR", "Mirror", 'Mirror color (if property "mirror_color" is available)', 1),
|
|
],
|
|
default="DIFFUSE",
|
|
)
|
|
edge_threshold: bpy.props.FloatProperty(
|
|
name="Edge Threshold",
|
|
description="MMD toon edge will not be enabled if freestyle line color alpha less than this value",
|
|
min=0,
|
|
max=1.001,
|
|
precision=3,
|
|
step=0.1,
|
|
default=0.1,
|
|
)
|
|
edge_alpha_min: bpy.props.FloatProperty(
|
|
name="Minimum Edge Alpha",
|
|
description="Minimum alpha of MMD toon edge color",
|
|
min=0,
|
|
max=1,
|
|
precision=3,
|
|
step=0.1,
|
|
default=0.5,
|
|
)
|
|
scale: bpy.props.FloatProperty(
|
|
name="Scale",
|
|
description="Scaling factor for converting the model",
|
|
default=0.08,
|
|
)
|
|
convert_material_nodes: bpy.props.BoolProperty(
|
|
name="Convert Material Nodes",
|
|
default=True,
|
|
)
|
|
middle_joint_bones_lock: bpy.props.BoolProperty(
|
|
name="Middle Joint Bones Lock",
|
|
description="Lock specific bones for backward compatibility.",
|
|
default=False,
|
|
)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
obj = context.active_object
|
|
return obj and obj.type == "ARMATURE" and obj.mode != "EDIT"
|
|
|
|
def invoke(self, context, event):
|
|
vm = context.window_manager
|
|
return vm.invoke_props_dialog(self)
|
|
|
|
def execute(self, context):
|
|
# TODO convert some basic MMD properties
|
|
armature_object = context.active_object
|
|
scale = self.scale
|
|
model_name = "New MMD Model"
|
|
|
|
root_object = FnModel.find_root_object(armature_object)
|
|
if root_object is None or root_object != armature_object.parent:
|
|
Model.create(model_name, model_name, scale, armature_object=armature_object)
|
|
|
|
self.__attach_meshes_to(armature_object, FnContext.get_scene_objects(context))
|
|
self.__configure_rig(context, Model(armature_object.parent))
|
|
return {"FINISHED"}
|
|
|
|
def __attach_meshes_to(self, armature_object: bpy.types.Object, objects: bpy.types.SceneObjects):
|
|
def __is_child_of_armature(mesh):
|
|
if mesh.parent is None:
|
|
return False
|
|
return mesh.parent == armature_object or __is_child_of_armature(mesh.parent)
|
|
|
|
def __is_using_armature(mesh):
|
|
for m in mesh.modifiers:
|
|
if m.type == "ARMATURE" and m.object == armature_object:
|
|
return True
|
|
return False
|
|
|
|
def __get_root(mesh):
|
|
if mesh.parent is None:
|
|
return mesh
|
|
return __get_root(mesh.parent)
|
|
|
|
for x in objects:
|
|
if __is_using_armature(x) and not __is_child_of_armature(x):
|
|
x_root = __get_root(x)
|
|
m = x_root.matrix_world
|
|
x_root.parent_type = "OBJECT"
|
|
x_root.parent = armature_object
|
|
x_root.matrix_world = m
|
|
|
|
def __configure_rig(self, context: bpy.types.Context, mmd_model: Model):
|
|
root_object = mmd_model.rootObject()
|
|
armature_object = mmd_model.armature()
|
|
mesh_objects = tuple(mmd_model.meshes())
|
|
|
|
mmd_model.loadMorphs()
|
|
|
|
if self.middle_joint_bones_lock:
|
|
vertex_groups = {g.name for mesh in mesh_objects for g in mesh.vertex_groups}
|
|
for pose_bone in armature_object.pose.bones:
|
|
if not pose_bone.parent:
|
|
continue
|
|
if not pose_bone.bone.use_connect and pose_bone.name not in vertex_groups:
|
|
continue
|
|
pose_bone.lock_location = (True, True, True)
|
|
|
|
from ..core.material import FnMaterial
|
|
|
|
FnMaterial.set_nodes_are_readonly(not self.convert_material_nodes)
|
|
try:
|
|
for m in (x for mesh in mesh_objects for x in mesh.data.materials if x):
|
|
FnMaterial.convert_to_mmd_material(m, context)
|
|
mmd_material = m.mmd_material
|
|
if self.ambient_color_source == "MIRROR" and hasattr(m, "mirror_color"):
|
|
mmd_material.ambient_color = m.mirror_color
|
|
else:
|
|
mmd_material.ambient_color = [0.5 * c for c in mmd_material.diffuse_color]
|
|
|
|
if hasattr(m, "line_color"): # freestyle line color
|
|
line_color = list(m.line_color)
|
|
mmd_material.enabled_toon_edge = line_color[3] >= self.edge_threshold
|
|
mmd_material.edge_color = line_color[:3] + [max(line_color[3], self.edge_alpha_min)]
|
|
finally:
|
|
FnMaterial.set_nodes_are_readonly(False)
|
|
from .display_item import DisplayItemQuickSetup
|
|
|
|
FnBone.sync_display_item_frames_from_bone_collections(armature_object)
|
|
mmd_model.initialDisplayFrames(reset=False) # ensure default frames
|
|
DisplayItemQuickSetup.load_facial_items(root_object.mmd_root)
|
|
root_object.mmd_root.active_display_item_frame = 0
|
|
|
|
|
|
class ResetObjectVisibility(bpy.types.Operator):
|
|
bl_idname = "mmd_tools.reset_object_visibility"
|
|
bl_label = "Reset Object Visivility"
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
@classmethod
|
|
def poll(cls, context: bpy.types.Context):
|
|
active_object: bpy.types.Object = context.active_object
|
|
return FnModel.find_root_object(active_object) is not None
|
|
|
|
def execute(self, context: bpy.types.Context):
|
|
active_object: bpy.types.Object = context.active_object
|
|
mmd_root_object = FnModel.find_root_object(active_object)
|
|
assert mmd_root_object is not None
|
|
mmd_root = mmd_root_object.mmd_root
|
|
|
|
mmd_root_object.hide_set(False)
|
|
|
|
rigid_group_object = FnModel.find_rigid_group_object(mmd_root_object)
|
|
if rigid_group_object:
|
|
rigid_group_object.hide_set(True)
|
|
|
|
joint_group_object = FnModel.find_joint_group_object(mmd_root_object)
|
|
if joint_group_object:
|
|
joint_group_object.hide_set(True)
|
|
|
|
temporary_group_object = FnModel.find_temporary_group_object(mmd_root_object)
|
|
if temporary_group_object:
|
|
temporary_group_object.hide_set(True)
|
|
|
|
mmd_root.show_meshes = True
|
|
mmd_root.show_armature = True
|
|
mmd_root.show_temporary_objects = False
|
|
mmd_root.show_rigid_bodies = False
|
|
mmd_root.show_names_of_rigid_bodies = False
|
|
mmd_root.show_joints = False
|
|
mmd_root.show_names_of_joints = False
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
class AssembleAll(bpy.types.Operator):
|
|
bl_idname = "mmd_tools.assemble_all"
|
|
bl_label = "Assemble All"
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
def execute(self, context):
|
|
active_object = context.active_object
|
|
root_object = FnModel.find_root_object(active_object)
|
|
assert root_object is not None
|
|
|
|
with FnContext.temp_override_active_layer_collection(context, root_object) as context:
|
|
rig = Model(root_object)
|
|
MigrationFnBone.fix_mmd_ik_limit_override(rig.armature())
|
|
FnBone.apply_additional_transformation(rig.armature())
|
|
rig.build()
|
|
rig.morph_slider.bind()
|
|
|
|
with context.temp_override(selected_objects=[active_object]):
|
|
bpy.ops.mmd_tools.sdef_bind()
|
|
root_object.mmd_root.use_property_driver = True
|
|
|
|
FnContext.set_active_object(context, active_object)
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
class DisassembleAll(bpy.types.Operator):
|
|
bl_idname = "mmd_tools.disassemble_all"
|
|
bl_label = "Disassemble All"
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
def execute(self, context):
|
|
active_object = context.active_object
|
|
root_object = FnModel.find_root_object(active_object)
|
|
assert root_object is not None
|
|
|
|
with FnContext.temp_override_active_layer_collection(context, root_object) as context:
|
|
root_object.mmd_root.use_property_driver = False
|
|
with context.temp_override(selected_objects=[active_object]):
|
|
bpy.ops.mmd_tools.sdef_unbind()
|
|
|
|
rig = Model(root_object)
|
|
rig.morph_slider.unbind()
|
|
rig.clean()
|
|
FnBone.clean_additional_transformation(rig.armature())
|
|
|
|
FnContext.set_active_object(context, active_object)
|
|
|
|
return {"FINISHED"}
|