Skip to content

Instantly share code, notes, and snippets.

@totegamma
Last active October 25, 2023 11:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save totegamma/b72c0f6b6af56d13e225f843a842d663 to your computer and use it in GitHub Desktop.
Save totegamma/b72c0f6b6af56d13e225f843a842d663 to your computer and use it in GitHub Desktop.
つかいかた: アーマチュアを選択した状態で、nキーを押すと出てくるメニューのtoolにあるApply Subbonesボタンを押すだけ (https://gammalab.net/works/applysubbones/)
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