792 lines
23 KiB
Python
792 lines
23 KiB
Python
|
|
import bpy
|
|
from bpy.types import Operator
|
|
from bpy.props import BoolProperty, IntProperty, FloatProperty, FloatVectorProperty, \
|
|
StringProperty
|
|
|
|
from bone_widget import ctx
|
|
from .transform_utils import transform_matrix, apply_mat_to_verts, get_bone_size_factor, \
|
|
get_bone_matrix
|
|
from .icon_utils import render_widget
|
|
from .shape_utils import get_clean_shape, symmetrize_bone_shape, link_to_col, get_bone, custom_shape_matrix
|
|
|
|
import re
|
|
from pathlib import Path
|
|
import os
|
|
from tempfile import gettempdir
|
|
from mathutils import Matrix
|
|
import json
|
|
|
|
|
|
class BW_OT_copy_widgets(Operator):
|
|
bl_idname = 'bonewidget.copy_widgets'
|
|
bl_label = "Copy Widgets"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object and context.object.type == 'ARMATURE'
|
|
|
|
def execute(self, context):
|
|
from tempfile import gettempdir
|
|
|
|
blend_file = Path(gettempdir())/'bone_widgets.blend'
|
|
|
|
shapes = []
|
|
|
|
ob = context.object
|
|
if ob.mode == 'POSE':
|
|
bones = ctx.selected_bones
|
|
else:
|
|
bones = ctx.bones
|
|
|
|
for b in bones:
|
|
if b.custom_shape:
|
|
s = b.custom_shape.copy()
|
|
s.data = s.data.copy()
|
|
|
|
if not b.use_custom_shape_bone_size:
|
|
mat = transform_matrix(scale=(1/b.bone.length,)*3)
|
|
s.data.transform(mat)
|
|
|
|
#s.data.transform(s.matrix_world.inverted())
|
|
#s.matrix_world = Matrix()
|
|
|
|
s['.bw_bone'] = b.name
|
|
|
|
if bpy.app.version_string < '3.0.0':
|
|
s['.bw_custom_shape_scale'] = b.custom_shape_scale
|
|
else:
|
|
s['.bw_custom_shape_translation'] = b.custom_shape_translation
|
|
s['.bw_custom_shape_rotation_euler'] = b.custom_shape_rotation_euler
|
|
s['.bw_custom_shape_scale_xyz'] = b.custom_shape_scale_xyz
|
|
|
|
s['.bw_custom_shape_transform'] = b.custom_shape_transform
|
|
s['.bw_use_custom_shape_bone_size'] = b.use_custom_shape_bone_size
|
|
#s['.bw_size_factor'] = get_bone_size_factor(b, s, b.use_custom_shape_bone_size)
|
|
|
|
shapes.append(s)
|
|
|
|
bpy.data.libraries.write(str(blend_file), set(shapes))
|
|
|
|
for s in shapes:
|
|
data = s.data
|
|
data_type = s.type
|
|
|
|
bpy.data.objects.remove(s)
|
|
|
|
if data_type == 'MESH':
|
|
bpy.data.meshes.remove(data)
|
|
elif data_type == 'CURVE':
|
|
bpy.data.curves.remove(data)
|
|
|
|
|
|
# for b in ctx.bones:
|
|
# if b.custom_shape:
|
|
# for k in b.custom_shape.keys():
|
|
# if k.startswith('.bw'):
|
|
# del b.custom_shape[k]
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_paste_widgets(Operator):
|
|
bl_idname = 'bonewidget.paste_widgets'
|
|
bl_label = "Paste Widgets"
|
|
|
|
path: StringProperty(subtype='FILE_PATH')
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object and context.object.type == 'ARMATURE'
|
|
|
|
def execute(self, context):
|
|
from tempfile import gettempdir
|
|
|
|
rig = context.object
|
|
|
|
#for b in rig.pose.bones:
|
|
# if b.custom_shape:
|
|
# bpy.data.objects.remove(b.custom_shape)
|
|
#b.custom_shape = get_clean_shape(b, b.custom_shape, separate=b.custom_shape.users>2)
|
|
|
|
if self.path:
|
|
blend_file = Path(self.path)
|
|
else:
|
|
blend_file = Path(gettempdir())/'bone_widgets.blend'
|
|
|
|
with bpy.data.libraries.load(str(blend_file), link=False) as (data_src, data_dst):
|
|
data_dst.objects = data_src.objects
|
|
shapes = data_src.objects
|
|
|
|
#old_shapes = set()
|
|
for s in shapes:
|
|
bone = rig.pose.bones.get(s['.bw_bone'])
|
|
if not bone:
|
|
print('No bone found for', s)
|
|
continue
|
|
|
|
if bpy.app.version_string < '3.0.0':
|
|
bone.custom_shape_scale = s['.bw_custom_shape_scale']
|
|
else:
|
|
bone.custom_shape_scale_xyz = s['.bw_custom_shape_scale_xyz']
|
|
|
|
link_to_col(s, ctx.widget_col)
|
|
#if bone.custom_shape:
|
|
#size_factor = max(s.dimensions)#get_bone_size_factor(bone, s, relative=False)
|
|
#size_factor /= s['.bw_size_factor']
|
|
#size_factor = s['.bw_size_factor']
|
|
#mat = transform_matrix(scale=(size_factor,)*3)
|
|
#s.data.transform(Matrix.Scale(1/size_factor, 4))
|
|
#s.scale = 1,1,1
|
|
|
|
|
|
bone.custom_shape = get_clean_shape(bone, s, col=ctx.widget_col, prefix=ctx.prefs.prefix,
|
|
separate=False, apply_transforms=False)
|
|
|
|
if bpy.app.version_string < '3.0.0':
|
|
#bone.custom_shape_scale = s['.bw_custom_shape_scale']
|
|
bone.custom_shape_scale = 1
|
|
else:
|
|
bone.custom_shape_translation = s['.bw_custom_shape_translation']
|
|
bone.custom_shape_rotation_euler = s['.bw_custom_shape_rotation_euler']
|
|
#bone.custom_shape_scale_xyz = s['.bw_custom_shape_scale_xyz']
|
|
bone.custom_shape_scale_xyz = 1,1,1
|
|
#use_custom_shape_bone_size
|
|
|
|
bone.custom_shape_transform = s['.bw_custom_shape_transform']
|
|
bone.use_custom_shape_bone_size = True#s['.bw_use_custom_shape_bone_size']
|
|
|
|
bone.bone.show_wire = not bool(s.data.polygons)
|
|
|
|
|
|
#mat = transform_matrix(scale=(s['.bw_size_factor'],)*3)
|
|
#s.data.transform(mat)
|
|
|
|
for b in rig.pose.bones:
|
|
if b.custom_shape:
|
|
for k in list(b.custom_shape.keys()):
|
|
if k.startswith('.bw'):
|
|
del b.custom_shape[k]
|
|
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_remove_unused_shape(Operator):
|
|
bl_idname = 'bonewidget.remove_unused_shape'
|
|
bl_label = "Remove Unused Shape"
|
|
|
|
def execute(self, context):
|
|
objects = list(ctx.widget_col.all_objects)
|
|
for ob in objects:
|
|
if not get_bone(ob) and ob in bpy.data.objects[:]:
|
|
bpy.data.objects.remove(ob)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_auto_color(Operator):
|
|
bl_idname = 'bonewidget.auto_color'
|
|
bl_label = "Auto Color"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def is_bone_protected(self, bone):
|
|
rig = bone.id_data
|
|
return any([i==j==True for i, j in zip(rig.data.layers_protected, bone.bone.layers)])
|
|
|
|
def execute(self, context):
|
|
ob = context.object
|
|
bones = context.selected_pose_bones or ob.pose.bones
|
|
|
|
for b in bones:
|
|
if self.is_bone_protected(b):
|
|
continue
|
|
for group in ob.pose.bone_groups:
|
|
if any(i in b.name.lower() for i in group.name.lower().split(' ')):
|
|
b.bone_group = group
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_load_default_color(Operator):
|
|
bl_idname = 'bonewidget.load_default_color'
|
|
bl_label = "Load Default Color"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
select_color: FloatVectorProperty(name='Color', subtype='COLOR', default=[0.0, 1.0, 1.0])
|
|
active_color: FloatVectorProperty(name='Color', subtype='COLOR', default=[1.0, 1.0, 1.0])
|
|
|
|
def execute(self, context):
|
|
ob = context.object
|
|
|
|
colors = {
|
|
'Black': [0, 0, 0],
|
|
'Yellow': [1, 1, 0],
|
|
'Red': [0, 0.035, 0.95],
|
|
'Blue': [1, 0, 0],
|
|
'Green': [0.733333, 0.937255, 0.356863],
|
|
'Pink': [1, 0.1, 0.85],
|
|
'Purple': [0.67, 0, 0.87],
|
|
'Brown': [0.75, 0.65, 0],
|
|
'Orange': [1, 0.6, 0],
|
|
}
|
|
|
|
for k, v in colors.items():
|
|
bone_group = ob.pose.bone_groups.get(k)
|
|
|
|
if not bone_group:
|
|
bone_group = ob.pose.bone_groups.new(name=k)
|
|
|
|
bone_group.color_set = 'CUSTOM'
|
|
|
|
bone_group.colors.normal = v
|
|
bone_group.colors.select = self.select_color
|
|
bone_group.colors.active = self.active_color
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_copy_bone_groups(Operator):
|
|
bl_idname = 'bonewidget.copy_bone_groups'
|
|
bl_label = "Copy Bone Groups"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
return context.object and context.object.type == 'ARMATURE'
|
|
|
|
def execute(self, context):
|
|
print('Copy Bone Group')
|
|
|
|
ob = context.object
|
|
|
|
bone_groups = {}
|
|
for bg in ob.pose.bone_groups:
|
|
bone_groups[bg.name] = {
|
|
'colors': [bg.colors.normal[:], bg.colors.select[:], bg.colors.active[:]],
|
|
'bones': [b.name for b in ob.pose.bones if b.bone_group == bg]
|
|
}
|
|
|
|
blend_file = Path(gettempdir()) / 'bw_copy_bone_groups.json'
|
|
|
|
blend_file.write_text(json.dumps(bone_groups))
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_paste_bone_groups(Operator):
|
|
bl_idname = 'bonewidget.paste_bone_groups'
|
|
bl_label = "Paste Bone Groups"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
clear: BoolProperty(default=False)
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
return context.object and context.object.type == 'ARMATURE'
|
|
|
|
def execute(self, context):
|
|
print('Paste Bone Group')
|
|
|
|
ob = context.object
|
|
|
|
blend_file = Path(gettempdir()) / 'bw_copy_bone_groups.json'
|
|
|
|
bone_groups = json.loads(blend_file.read_text())
|
|
|
|
if self.clear:
|
|
for bg in reversed(ob.pose.bone_groups):
|
|
ob.pose.bone_groups.remove(bg)
|
|
|
|
for bg_name, bg_data in bone_groups.items():
|
|
bg = ob.pose.bone_groups.get(bg_name)
|
|
if not bg:
|
|
bg = ob.pose.bone_groups.new(name=bg_name)
|
|
|
|
bg.color_set = 'CUSTOM'
|
|
|
|
bg.colors.normal,bg.colors.select, bg.colors.active = bg_data['colors']
|
|
|
|
for b in bg_data['bones']:
|
|
bone = ob.pose.bones.get(b)
|
|
if bone:
|
|
bone.bone_group = bg
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_add_folder(Operator):
|
|
bl_idname = 'bonewidget.add_folder'
|
|
bl_label = "Add Folder"
|
|
|
|
def execute(self, context):
|
|
folder = ctx.prefs.folders.add()
|
|
folder.path = ''
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_remove_folder(Operator):
|
|
bl_idname = 'bonewidget.remove_folder'
|
|
bl_label = "Remove Folder"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
index: IntProperty()
|
|
|
|
def execute(self, context):
|
|
|
|
ctx.prefs.folders.remove(self.index)
|
|
|
|
if self.index == 0:
|
|
ctx.prefs.folder_enum = str(ctx.default_folder_path)
|
|
|
|
#update_folder_items()
|
|
|
|
bpy.context.area.tag_redraw()
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_refresh_folders(Operator):
|
|
bl_idname = 'bonewidget.refresh_folders'
|
|
bl_label = "Refresh"
|
|
|
|
def execute(self, context):
|
|
for f in ctx.folders:
|
|
f.widgets.clear()
|
|
f.load_widgets()
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_rename_folder(Operator):
|
|
bl_idname = 'bonewidget.rename_folder'
|
|
bl_label = "Rename Folder"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
name: StringProperty(name='Name')
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return ctx.active_folder
|
|
|
|
def execute(self, context):
|
|
folder = ctx.active_folder
|
|
folder.rename(self.name)
|
|
|
|
return {'FINISHED'}
|
|
|
|
def invoke(self, context, event):
|
|
wm = context.window_manager
|
|
self.name = Path(ctx.prefs.folder_enum).name
|
|
|
|
return wm.invoke_props_dialog(self)
|
|
|
|
|
|
# class BW_OT_show_preferences(Operator):
|
|
# """Display the preferences to the tab panel"""
|
|
# bl_idname = 'bonewidget.show_preferences'
|
|
# bl_label = "Show Preferences"
|
|
# bl_options = {'REGISTER'}
|
|
|
|
# def execute(self, context):
|
|
# #ctx.init_transforms()
|
|
# return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_transform_widget(Operator):
|
|
"""Transform the Rotation Location or Scale of the selected shapes"""
|
|
bl_idname = 'bonewidget.transform_widget'
|
|
bl_label = "Transfom"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
symmetrize: BoolProperty(name='Symmetrize', default=True)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object and context.object.mode == 'POSE'
|
|
|
|
def execute(self, context):
|
|
ctx.init_transforms()
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_create_widget(Operator):
|
|
"""Create the widget shape and assign it to the bone"""
|
|
bl_idname = 'bonewidget.create_widget'
|
|
bl_label = "Create Widget"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
symmetrize: BoolProperty(name='Symmetrize', default=True)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return ctx.selected_bones and ctx.active_widget
|
|
|
|
def execute(self, context):
|
|
folder = ctx.active_folder
|
|
blend = folder.get_widget_path(folder.active_widget)
|
|
|
|
with bpy.data.libraries.load(str(blend), link=False) as (data_src, data_dst):
|
|
data_dst.objects = data_src.objects
|
|
shape = data_src.objects[0]
|
|
|
|
for bone in ctx.selected_bones:
|
|
#shape_copy = shape.copy()
|
|
|
|
if bpy.app.version_string < '3.0.0':
|
|
bone.custom_shape_scale = 1.0
|
|
else:
|
|
bone.custom_shape_scale_xyz = [1.0]*3 # for blender 3.0
|
|
|
|
bone.bone.show_wire = not bool(shape.data.polygons)
|
|
|
|
#if bone.custom_shape and bone.custom_shape.users == 2:
|
|
# bpy.data.objects.remove(bone.custom_shape)
|
|
|
|
#copy_shape = shape.copy()
|
|
#copy_shape.data = copy_shape.data.copy()
|
|
bone.custom_shape = get_clean_shape(bone, shape, col=ctx.widget_col, prefix=ctx.prefs.prefix)
|
|
|
|
if self.symmetrize:
|
|
symmetrize_bone_shape(bone, prefix=ctx.prefs.prefix)
|
|
|
|
bpy.data.objects.remove(shape)
|
|
ctx.init_transforms()
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_match_transform(Operator):
|
|
bl_idname = 'bonewidget.match_transform'
|
|
bl_label = "Match Transforms"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
relative: BoolProperty(default=False)
|
|
|
|
def execute(self, context):
|
|
for bone in ctx.selected_bones:
|
|
shape = bone.custom_shape
|
|
if not shape:
|
|
continue
|
|
|
|
size_factor = get_bone_size_factor(bone, shape, self.relative)
|
|
mat = transform_matrix(scale=(size_factor,)*3)
|
|
shape.data.transform(mat)
|
|
#apply_mat_to_verts(shape.data, mat)
|
|
|
|
ctx.init_transforms()
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_edit_widget(Operator):
|
|
bl_idname = 'bonewidget.edit_widget'
|
|
bl_label = "Edit Widget"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
clean: BoolProperty(name='Clean', default=True)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return ctx.bone and ctx.bone.custom_shape
|
|
|
|
def execute(self, context):
|
|
#ctx._rig = ctx.rig
|
|
widgets = ctx.selected_widgets
|
|
bones = ctx.selected_bones
|
|
rig = ctx.rig
|
|
prefs = ctx.prefs
|
|
|
|
if self.clean:
|
|
bpy.ops.bonewidget.clean_widget()
|
|
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
|
|
rig.hide_set(True)
|
|
|
|
ctx.widget_col.hide_viewport = False
|
|
ctx.widget_layer_col.exclude = False
|
|
ctx.widget_layer_col.hide_viewport = False
|
|
|
|
for bone in bones:
|
|
if not bone.custom_shape:
|
|
continue
|
|
#link_to_col(w, ctx.widget_col)
|
|
bone.custom_shape.select_set(True)
|
|
context.view_layer.objects.active = bone.custom_shape
|
|
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_return_to_rig(Operator):
|
|
bl_idname = 'bonewidget.return_to_rig'
|
|
bl_label = "Return to rig"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
clean: BoolProperty(name='Clean', default=True)
|
|
|
|
def invoke(self, context, event):
|
|
self.symmetrize = ctx.prefs.auto_symmetrize
|
|
return self.execute(context)
|
|
|
|
def execute(self, context):
|
|
rig = ctx.rig
|
|
|
|
#print('Rig', rig)
|
|
bone = ctx.bone
|
|
bones = ctx.selected_bones
|
|
|
|
|
|
ctx.widget_col.hide_viewport = False
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
|
|
rig.hide_set(False)
|
|
ctx.widget_col.hide_viewport = True
|
|
|
|
context.view_layer.objects.active = rig
|
|
|
|
bpy.ops.object.mode_set(mode='POSE')
|
|
rig.data.bones.active = bone.bone
|
|
|
|
for b in rig.pose.bones:
|
|
b.bone.select = b in bones+[bone]
|
|
|
|
if self.clean:
|
|
bpy.ops.bonewidget.clean_widget()
|
|
|
|
return {'FINISHED'}
|
|
|
|
'''
|
|
class BW_OT_symmetrize_widget(Operator):
|
|
bl_idname = 'bonewidget.symmetrize_widget'
|
|
bl_label = "Symmetrize"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
match_transform: BoolProperty(True)
|
|
|
|
def get_name_side(self, name, fallback=None):
|
|
if name.lower().endswith('.l'):
|
|
return 'LEFT'
|
|
elif name.lower().endswith('.r'):
|
|
return 'RIGHT'
|
|
return fallback
|
|
|
|
def mirror_name(self, name):
|
|
mirror = None
|
|
|
|
match = {
|
|
'R': 'L',
|
|
'r': 'l',
|
|
'L': 'R',
|
|
'l': 'r',
|
|
}
|
|
|
|
separator = ['.', '_']
|
|
|
|
if name.startswith(tuple(match.keys())):
|
|
if name[1] in separator:
|
|
mirror = match[name[0]] + name[1:]
|
|
|
|
if name.endswith(tuple(match.keys())):
|
|
if name[-2] in separator:
|
|
mirror = name[:-1] + match[name[-1]]
|
|
|
|
return mirror
|
|
|
|
def execute(self, context):
|
|
bones = ctx.selected_bones
|
|
if ctx.bone:
|
|
active_side = self.get_name_side(ctx.bone.name, 'LEFT')
|
|
if active_side:
|
|
bones = [b for b in ctx.selected_bones if self.get_name_side(b) == active_side]
|
|
|
|
for bone in bones:
|
|
flip_name = self.mirror_name(bone.name)
|
|
flip_bone = ctx.rig.pose.bones.get(flip_name)
|
|
|
|
if flip_bone:
|
|
shape = flip_bone.custom_shape
|
|
if shape.users <= 2:
|
|
bpy.data.objects.remove(shape)
|
|
|
|
shape = bone.custom_shape.copy()
|
|
if shape
|
|
|
|
flip_bone.custom_shape = shape
|
|
shape.matrix_world = get_bone_matrix(bone)
|
|
shape.data.transform(transform_matrix(scale=(-1, 1, 1)))
|
|
ctx.rename_shape(shape, flip_bone)
|
|
|
|
return {'FINISHED'}
|
|
'''
|
|
|
|
|
|
class BW_OT_add_widget(Operator):
|
|
bl_idname = 'bonewidget.add_widget'
|
|
bl_label = "Add Widget"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
replace: BoolProperty(default=False, name='Replace')
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return ctx.widget
|
|
|
|
def invoke(self, context, event):
|
|
if event.ctrl:
|
|
self.replace = True
|
|
|
|
return self.execute(context)
|
|
|
|
def execute(self, context):
|
|
folder = ctx.active_folder
|
|
shape = ctx.widget
|
|
bone = ctx.bone
|
|
|
|
if self.replace and ctx.active_widget:
|
|
name = ctx.active_widget.name
|
|
else: #Find a unique name
|
|
name = folder.get_widget_display_name(bone.name if bone else shape.name)
|
|
|
|
i = 0
|
|
org_name = name
|
|
while name in folder.widgets:
|
|
name = f'{org_name} {i:02d}'
|
|
i += 1
|
|
|
|
widget_path = folder.get_widget_path(name)
|
|
icon_path = folder.get_icon_path(name)
|
|
|
|
widget_path.parent.mkdir(exist_ok=True, parents=True)
|
|
bpy.data.libraries.write(str(widget_path), {shape})
|
|
|
|
# Copy the shape to apply the bone matrix to the mesh
|
|
shape_copy = shape.copy()
|
|
shape_copy.data = shape_copy.data.copy()
|
|
|
|
if bone:
|
|
shape_copy.matrix_world = get_bone_matrix(bone)
|
|
|
|
mat = custom_shape_matrix(bone)
|
|
shape_copy.data.transform(mat)
|
|
|
|
render_widget(shape_copy, icon_path)
|
|
folder.add_widget(name)
|
|
|
|
bpy.data.objects.remove(shape_copy)
|
|
|
|
bpy.context.area.tag_redraw()
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_remove_widget(Operator):
|
|
bl_idname = 'bonewidget.remove_widget'
|
|
bl_label = "Remove Widget"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return ctx.active_widget
|
|
|
|
def execute(self, context):
|
|
folder = ctx.active_folder
|
|
widget = ctx.active_widget
|
|
|
|
folder.remove_widget(widget)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BW_OT_clean_widget(Operator):
|
|
bl_idname = 'bonewidget.clean_widget'
|
|
bl_label = "Clean"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
all: BoolProperty(name='All', default=False)
|
|
symmetrize: BoolProperty(name='Symmetrize', default=True)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object and context.object.mode == 'POSE'
|
|
|
|
def invoke(self, context, event):
|
|
self.symmetrize = ctx.prefs.auto_symmetrize
|
|
return self.execute(context)
|
|
|
|
def execute(self, context):
|
|
scene = context.scene
|
|
prefs = ctx.prefs
|
|
|
|
if self.all:
|
|
bones = ctx.bones
|
|
else:
|
|
bones = ctx.selected_bones
|
|
|
|
for bone in bones:
|
|
shape = bone.custom_shape
|
|
if not shape:
|
|
continue
|
|
|
|
bone.custom_shape = get_clean_shape(bone, shape, separate=prefs.auto_separate,
|
|
rename=prefs.auto_rename, col=ctx.widget_col, prefix=prefs.prefix, match=prefs.auto_match_transform)
|
|
|
|
if self.symmetrize:
|
|
symmetrize_bone_shape(bone, prefix=ctx.prefs.prefix)
|
|
|
|
if shape in bpy.data.objects[:] and shape.users <= 1:
|
|
bpy.data.objects.remove(shape)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
classes = (
|
|
BW_OT_remove_unused_shape,
|
|
BW_OT_auto_color,
|
|
BW_OT_load_default_color,
|
|
BW_OT_add_folder,
|
|
BW_OT_remove_folder,
|
|
BW_OT_refresh_folders,
|
|
BW_OT_rename_folder,
|
|
BW_OT_transform_widget,
|
|
BW_OT_match_transform,
|
|
BW_OT_create_widget,
|
|
BW_OT_edit_widget,
|
|
BW_OT_return_to_rig,
|
|
#BW_OT_symmetrize_widget,
|
|
BW_OT_add_widget,
|
|
BW_OT_remove_widget,
|
|
BW_OT_clean_widget,
|
|
# BW_OT_show_preferences,
|
|
BW_OT_copy_widgets,
|
|
BW_OT_paste_widgets,
|
|
BW_OT_copy_bone_groups,
|
|
BW_OT_paste_bone_groups
|
|
)
|
|
|
|
def bw_bone_group_menu(self, context):
|
|
layout = self.layout
|
|
layout.operator("bonewidget.load_default_color", icon='IMPORT')
|
|
layout.operator("bonewidget.auto_color", icon='COLOR')
|
|
layout.operator("bonewidget.copy_bone_groups", icon='COPYDOWN')
|
|
layout.operator("bonewidget.paste_bone_groups", icon='PASTEDOWN')
|
|
|
|
def register():
|
|
for cls in classes:
|
|
bpy.utils.register_class(cls)
|
|
|
|
bpy.types.DATA_MT_bone_group_context_menu.prepend(bw_bone_group_menu)
|
|
|
|
def unregister():
|
|
bpy.types.DATA_MT_bone_group_context_menu.remove(bw_bone_group_menu)
|
|
|
|
for cls in reversed(classes):
|
|
bpy.utils.unregister_class(cls) |