つかいかた: アーマチュアを選択した状態で、nキーを押すと出てくるメニューのtoolにあるApply Subbonesボタンを押すだけ (https://twitter.com/totegamma/status/1574051804765376512/photo/1)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import bpy | |
import re | |
import sys | |
bl_info = { | |
"name" : "Apply Subbones", | |
"author" : "totegamma", | |
"version" : (0, 1, 0), | |
"blender" : (3, 3, 0), | |
"description" : "Apply Subbone weights and cleanup them.", | |
"category" : "Rigging" | |
} | |
class APPLYSUBBONES_OT_Main(bpy.types.Operator): | |
bl_idname="applysubbones.main" | |
bl_label="Apply Subbones" | |
bl_options = {'REGISTER', 'UNDO'} | |
@staticmethod | |
def ShowMessageBox(message = "", title = "Message Box", icon = 'INFO'): | |
def draw(self, context): | |
self.layout.label(text=message) | |
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon) | |
@staticmethod | |
def findProperBone(bone): | |
itr = bone | |
while True: | |
if re.match(bpy.context.scene.subbone_filter, itr.parent.name): | |
itr = itr.parent | |
else: | |
return itr.parent | |
@staticmethod | |
def createModifiers(): | |
target = bpy.context.active_object | |
if (target.type != "ARMATURE"): | |
ShowMessageBox("please select armature", "error", 'ERROR') | |
return | |
bones = target.data.bones | |
subbones = list(filter(lambda x: re.match(bpy.context.scene.subbone_filter, x.name), bones)) | |
print(list(map(lambda x: f"{x.name} -> {x.parent.name}", subbones))) | |
targetmeshes = list(filter(lambda x: x.type == 'MESH', target.children)) | |
print(targetmeshes) | |
for mesh in targetmeshes: | |
for bone in subbones: | |
parent = APPLYSUBBONES_OT_Main.findProperBone(bone) | |
print(f'add {bone} to {mesh}') | |
if not ((bone.name in mesh.vertex_groups) and (parent.name in mesh.vertex_groups)): | |
continue | |
mod = mesh.modifiers.new(f"ApplySubBoneWeight: {bone.name} to {parent.name}", "VERTEX_WEIGHT_MIX") | |
mod.vertex_group_a = parent.name | |
mod.vertex_group_b = bone.name | |
mod.mix_set = 'ALL' | |
mod.mix_mode = 'ADD' | |
mod.show_expanded = False | |
@staticmethod | |
def applyAndCleanup(): | |
target = bpy.context.active_object | |
if (target.type != "ARMATURE"): | |
ShowMessageBox("please select armature", "error", 'ERROR') | |
return | |
bones = target.data.bones | |
subbones = list(filter(lambda x: re.match(bpy.context.scene.subbone_filter, x.name), bones)) | |
targetmeshes = list(filter(lambda x: x.type == 'MESH', target.children)) | |
for mesh in targetmeshes: | |
bpy.ops.object.select_all(action='DESELECT') | |
if not mesh.name in bpy.context.view_layer.objects: | |
continue | |
bpy.context.view_layer.objects.active = mesh | |
for mod in list(filter(lambda x: x.type == 'VERTEX_WEIGHT_MIX', mesh.modifiers)): | |
bpy.ops.object.modifier_apply(modifier=mod.name) | |
for vg in list(map(lambda x: x.name, subbones)): | |
if vg in mesh.vertex_groups: | |
bpy.ops.object.vertex_group_set_active(group=vg) | |
bpy.ops.object.vertex_group_remove() | |
bpy.ops.object.select_all(action='DESELECT') | |
bpy.context.view_layer.objects.active = target | |
bpy.ops.object.mode_set(mode='EDIT') | |
subbones = list(filter(lambda x: re.match(bpy.context.scene.subbone_filter, x.name), target.data.edit_bones)) | |
for bone in subbones: | |
target.data.edit_bones.remove(bone) | |
bpy.ops.object.mode_set(mode='OBJECT') | |
def execute(self, context): | |
self.createModifiers() | |
self.applyAndCleanup() | |
self.ShowMessageBox("complete!", "message") | |
return {'FINISHED'} | |
class APPLYSUBBONES_PT_Toolpane(bpy.types.Panel): | |
bl_idname = "object.custom_panel" | |
bl_label = "Apply Subbones" | |
bl_space_type = "VIEW_3D" | |
bl_region_type = "UI" | |
bl_category = "Tool" | |
bl_context = "objectmode" | |
def draw(self, context): | |
layout = self.layout | |
layout.prop(bpy.context.scene, "subbone_filter") | |
layout.operator(APPLYSUBBONES_OT_Main.bl_idname, text = "Apply Subbones") | |
def register(): | |
bpy.types.Scene.subbone_filter = bpy.props.StringProperty( | |
name = "filter Regex", | |
description = "regex for filtering subbones", | |
default = ".*\.[0-9]+$" | |
) | |
bpy.utils.register_class(APPLYSUBBONES_OT_Main) | |
bpy.utils.register_class(APPLYSUBBONES_PT_Toolpane) | |
def unregister(): | |
bpy.utils.unregister_class(APPLYSUBBONES_OT_Main) | |
bpy.utils.unregister_class(APPLYSUBBONES_PT_Toolpane) | |
if __name__ == "__main__": | |
register() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment