c31d25dd01
You can choose between errors, warning, info or full debug, errors will always log to ensure we don't have silent failures with debug on or off.
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"}
|