diff --git a/__init__.py b/__init__.py index 5819c26..ee01b5f 100644 --- a/__init__.py +++ b/__init__.py @@ -1,8 +1,8 @@ bl_info = { "name": "Bone Widget", "author": "Christophe SEUX", - "version": (2, 0, 0), - "blender": (2, 92, 0), + "version": (2, 0, 1), + "blender": (3, 4, 1), "description": "Create custom shapes for bone controller", "warning": "", "wiki_url": "", @@ -12,22 +12,37 @@ bl_info = { import sys + if "bpy" in locals(): import importlib as imp imp.reload(context) + sys.modules.update({"bone_widget.ctx": context.BW_context()}) + + imp.reload(shape_utils) + imp.reload(transform_utils) + imp.reload(icon_utils) + imp.reload(properties) imp.reload(operators) imp.reload(ui) + + else: from . import context + sys.modules.update({"bone_widget.ctx": context.BW_context()}) + from . import operators from . import ui from . import properties + from . import shape_utils + from . import transform_utils + from . import icon_utils import bpy +import sys #sys.modules.update({"bone_widget.ctx": context.BW_ctx()}) -from bone_widget import ctx + def register(): @@ -38,6 +53,8 @@ def register(): #bpy.types.Scene.bone_widget = bpy.props.PointerProperty(type=BoneWidgetSettings) #get_widgets(DefaultFolder, DefaultShapes) #get_widgets(CustomFolder, CustomShapes) + from bone_widget import ctx + for f in ctx.folders: f.load_widgets() diff --git a/context.py b/context.py index db05b00..f1b8e5b 100644 --- a/context.py +++ b/context.py @@ -6,7 +6,7 @@ import os from bone_widget.shape_utils import get_bone -class BW_ctx: +class BW_context: def __init__(self): self.module_dir = Path(__file__).parent @@ -48,11 +48,15 @@ class BW_ctx: @property def show_prefs_op(self): ops = bpy.context.window_manager.operators - op_idname = self.get_op_id('show_preferences') + id_names = [ + self.get_op_id('show_preferences'), + self.get_op_id('add_folder'), + self.get_op_id('remove_folder') + ] #transforms = self.prefs.transforms - if ops and ops[-1].bl_idname == op_idname: - return ops[-1] + if ops and ops[-1].bl_idname in id_names: + return True @property def category(self): @@ -70,6 +74,9 @@ class BW_ctx: @property def widget_col(self): col_name = self.prefs.collection + + col_name = col_name.format(ob=bpy.context.object) + col = bpy.data.collections.get(col_name) if not col: col = bpy.data.collections.new(col_name) @@ -278,6 +285,6 @@ class BW_ctx: # ops_data = getattr(bpy.ops, ctx.id_name) # getattr(ops_data, op)() -sys.modules.update({"bone_widget.ctx": BW_ctx()}) + diff --git a/icon_utils.py b/icon_utils.py index 282dbce..8547ab3 100644 --- a/icon_utils.py +++ b/icon_utils.py @@ -16,6 +16,8 @@ def bounds_2d_co(coords, width, height): co_2d = [space_2d(region, rv3d, co) for co in coords] + print("co_2d", co_2d) + x_value = sorted(co_2d, key=lambda x:x[0]) y_value = sorted(co_2d, key=lambda x:x[1]) @@ -54,6 +56,9 @@ def render_widget(shape, filepath, width=32, height=32) : bm = bmesh.new() bm.from_object(shape, dg) + width *= 2 + height *= 2 + coords = [shape.matrix_world @ v.co for v in bm.verts] coords = bounds_2d_co(coords, width, height) @@ -66,45 +71,53 @@ def render_widget(shape, filepath, width=32, height=32) : batch = batch_for_shader(shader, 'LINES', {"pos": coords}, indices=indices) with offscreen.bind(): - bgl.glClearColor(0.0, 0.0, 0.0, 0.0) - bgl.glClear(bgl.GL_COLOR_BUFFER_BIT) + fb = gpu.state.active_framebuffer_get() + fb.clear(color=(0.0, 0.0, 0.0, 0.0)) with gpu.matrix.push_pop(): # reset matrices -> use normalized device coordinates [-1, 1] gpu.matrix.load_matrix(Matrix.Identity(4)) gpu.matrix.load_projection_matrix(Matrix.Identity(4)) - bgl.glLineWidth(4) - bgl.glEnable( bgl.GL_LINE_SMOOTH ) - bgl.glEnable(bgl.GL_BLEND) shader.bind() - shader.uniform_float("color", (0, 0, 0, 0.1)) + gpu.state.line_width_set(4*2) + #bgl.glEnable(bgl.GL_BLEND) + #bgl.glEnable( bgl.GL_LINE_SMOOTH ) + shader.uniform_float("color", (0, 0, 0, 0.2)) batch.draw(shader) - bgl.glLineWidth(2) + gpu.state.line_width_set(2*2) shader.uniform_float("color", (0.85, 0.85, 0.85, 1)) batch.draw(shader) - buffer = bgl.Buffer(bgl.GL_BYTE, width * height * 4) - bgl.glReadBuffer(bgl.GL_BACK) - bgl.glReadPixels(0, 0, width, height, bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer) + buffer = fb.read_color(0, 0, width, height, 4, 0, 'FLOAT') + buffer.dimensions = width * height * 4 + #buffer = bgl.Buffer(bgl.GL_BYTE, width * height * 4) + #bgl.glReadBuffer(bgl.GL_BACK) + #bgl.glReadPixels(0, 0, width, height, bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer) offscreen.free() #icon_name = '.' + shape.name + '_icon.png' - icon_name = shape.name + '_icon.png' + icon_name = f'.{shape.name}_icon.png' image = bpy.data.images.get(icon_name) if image: bpy.data.images.remove(image) - image = bpy.data.images.new(icon_name, width, height) + image = bpy.data.images.new(icon_name, width, height, alpha=True, float_buffer=True) #image_data = np.asarray(buffer, dtype=np.uint8) #image.pixels.foreach_set(image_data.flatten()/255) #image_data = np.asarray(buffer, dtype=np.uint8) - image.pixels = [v / 255 for v in buffer] + image.pixels.foreach_set(buffer) - image.save_render(str(filepath)) + image.scale(int(width/2), int(height/2)) + + image.filepath_raw = str(filepath) + + image.save() + + bpy.data.images.remove(image) diff --git a/operators.py b/operators.py index 8a90193..d15ead9 100644 --- a/operators.py +++ b/operators.py @@ -8,7 +8,7 @@ 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 +from .shape_utils import get_clean_shape, symmetrize_bone_shape, link_to_col, get_bone, custom_shape_matrix import re from pathlib import Path @@ -18,7 +18,7 @@ from mathutils import Matrix import json -class BW_OT_copy_widgets(Operator) : +class BW_OT_copy_widgets(Operator): bl_idname = 'bonewidget.copy_widgets' bl_label = "Copy Widgets" @@ -39,7 +39,6 @@ class BW_OT_copy_widgets(Operator) : else: bones = ctx.bones - for b in bones: if b.custom_shape: s = b.custom_shape.copy() @@ -90,7 +89,7 @@ class BW_OT_copy_widgets(Operator) : return {'FINISHED'} -class BW_OT_paste_widgets(Operator) : +class BW_OT_paste_widgets(Operator): bl_idname = 'bonewidget.paste_widgets' bl_label = "Paste Widgets" @@ -174,7 +173,7 @@ class BW_OT_paste_widgets(Operator) : return {'FINISHED'} -class BW_OT_remove_unused_shape(Operator) : +class BW_OT_remove_unused_shape(Operator): bl_idname = 'bonewidget.remove_unused_shape' bl_label = "Remove Unused Shape" @@ -187,7 +186,7 @@ class BW_OT_remove_unused_shape(Operator) : return {'FINISHED'} -class BW_OT_auto_color(Operator) : +class BW_OT_auto_color(Operator): bl_idname = 'bonewidget.auto_color' bl_label = "Auto Color" bl_options = {'REGISTER', 'UNDO'} @@ -210,26 +209,26 @@ class BW_OT_auto_color(Operator) : return {'FINISHED'} -class BW_OT_load_default_color(Operator) : +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]) + 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 = { - 'root': [0, 0, 0], - 'spine chest hips': [1, 1, 0], - '.R': [0, 0.035, 0.95], - '.L': [1, 0, 0], - 'ik.R': [1, 0.1, 0.85], - 'ik.L': [0.67, 0, 0.87], - 'tweak.L': [0.75, 0.65, 0], - 'tweak.R': [1, 0.6, 0], + 'Black': [0, 0, 0], + 'Yellow': [1, 1, 0], + 'Red': [0, 0.035, 0.95], + 'Blue': [1, 0, 0], + '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(): @@ -244,10 +243,10 @@ class BW_OT_load_default_color(Operator) : bone_group.colors.select = self.select_color bone_group.colors.active = self.active_color - return {'FINISHED'} -class BW_OT_copy_bone_groups(Operator) : + +class BW_OT_copy_bone_groups(Operator): bl_idname = 'bonewidget.copy_bone_groups' bl_label = "Copy Bone Groups" bl_options = {'REGISTER', 'UNDO'} @@ -265,23 +264,22 @@ class BW_OT_copy_bone_groups(Operator) : 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] + '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) : + +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) + clear: BoolProperty(default=False) @classmethod def poll(self, context): @@ -316,7 +314,7 @@ class BW_OT_paste_bone_groups(Operator) : return {'FINISHED'} -class BW_OT_add_folder(Operator) : +class BW_OT_add_folder(Operator): bl_idname = 'bonewidget.add_folder' bl_label = "Add Folder" @@ -327,12 +325,12 @@ class BW_OT_add_folder(Operator) : return {'FINISHED'} -class BW_OT_remove_folder(Operator) : +class BW_OT_remove_folder(Operator): bl_idname = 'bonewidget.remove_folder' bl_label = "Remove Folder" bl_options = {'REGISTER', 'UNDO'} - index : IntProperty() + index: IntProperty() def execute(self, context): @@ -348,7 +346,7 @@ class BW_OT_remove_folder(Operator) : return {'FINISHED'} -class BW_OT_refresh_folders(Operator) : +class BW_OT_refresh_folders(Operator): bl_idname = 'bonewidget.refresh_folders' bl_label = "Refresh" @@ -364,7 +362,7 @@ class BW_OT_rename_folder(Operator): bl_label = "Rename Folder" bl_options = {'REGISTER', 'UNDO'} - name : StringProperty(name='Name') + name: StringProperty(name='Name') @classmethod def poll(cls, context): @@ -384,6 +382,7 @@ class BW_OT_rename_folder(Operator): 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'} @@ -394,11 +393,12 @@ class BW_OT_show_preferences(Operator): 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) + symmetrize: BoolProperty(name='Symmetrize', default=True) @classmethod def poll(cls, context): @@ -410,11 +410,16 @@ class BW_OT_transform_widget(Operator): 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) + symmetrize: BoolProperty(name='Symmetrize', default=True) + + @classmethod + def poll(cls, context): + return ctx.bone and ctx.active_widget def invoke(self, context, event): self.symmetrize = ctx.prefs.symmetrize @@ -429,7 +434,7 @@ class BW_OT_create_widget(Operator): shape = data_src.objects[0] for bone in ctx.selected_bones: - shape_copy = shape.copy() + #shape_copy = shape.copy() if bpy.app.version_string < '3.0.0': bone.custom_shape_scale = 1.0 @@ -443,7 +448,7 @@ class BW_OT_create_widget(Operator): #copy_shape = shape.copy() #copy_shape.data = copy_shape.data.copy() - bone.custom_shape = get_clean_shape(bone, shape_copy, col=ctx.widget_col, prefix=ctx.prefs.prefix, separate=False) + 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) @@ -460,7 +465,7 @@ class BW_OT_match_transform(Operator): bl_label = "Match Transforms" bl_options = {'REGISTER', 'UNDO'} - relative : BoolProperty(default=False) + relative: BoolProperty(default=False) def execute(self, context): for bone in ctx.selected_bones: @@ -518,7 +523,7 @@ class BW_OT_return_to_rig(Operator): bl_label = "Return to rig" bl_options = {'REGISTER', 'UNDO'} - symmetrize : BoolProperty(name='Symmetrize', default=True) + symmetrize: BoolProperty(name='Symmetrize', default=True) def invoke(self, context, event): self.symmetrize = ctx.prefs.symmetrize @@ -560,7 +565,7 @@ class BW_OT_symmetrize_widget(Operator): bl_label = "Symmetrize" bl_options = {'REGISTER', 'UNDO'} - match_transform : BoolProperty(True) + match_transform: BoolProperty(True) def get_name_side(self, name, fallback=None): if name.lower().endswith('.l'): @@ -569,24 +574,24 @@ class BW_OT_symmetrize_widget(Operator): return 'RIGHT' return fallback - def mirror_name(self, name) : + def mirror_name(self, name): mirror = None match = { - 'R' : 'L', - 'r' : 'l', - 'L' : 'R', - 'l' : 'r', + 'R': 'L', + 'r': 'l', + 'L': 'R', + 'l': 'r', } separator = ['.', '_'] if name.startswith(tuple(match.keys())): - if name[1] in separator : + if name[1] in separator: mirror = match[name[0]] + name[1:] if name.endswith(tuple(match.keys())): - if name[-2] in separator : + if name[-2] in separator: mirror = name[:-1] + match[name[-1]] return mirror @@ -648,9 +653,19 @@ class BW_OT_add_widget(Operator): widget_path.parent.mkdir(exist_ok=True, parents=True) bpy.data.libraries.write(str(widget_path), {shape}) - render_widget(shape, icon_path) - widget = folder.add_widget(name) + # Copy the shape to apply the bone matrix to the mesh + shape_copy = shape.copy() + shape_copy.data = shape_copy.data.copy() + + 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() @@ -681,7 +696,7 @@ class BW_OT_clean_widget(Operator): bl_options = {'REGISTER', 'UNDO'} all: BoolProperty(name='All', default=False) - symmetrize : BoolProperty(name='Symmetrize', default=True) + symmetrize: BoolProperty(name='Symmetrize', default=True) @classmethod def poll(cls, context): diff --git a/properties.py b/properties.py index 0c69ee4..0d486b9 100644 --- a/properties.py +++ b/properties.py @@ -19,7 +19,7 @@ from .shape_utils import symmetrize_bone_shape, get_flipped_bone def undo_redo(self, context): wm = context.window_manager op_names = ('transform_widget','create_widget', 'match_transform', 'clean_widget') - op_ids = [ctx.get_op_id(o) for o in op_ids] + op_ids = [ctx.get_op_id(o) for o in op_names] if (ops and ops[-1].bl_idname in op_ids): bpy.ops.wm.undo() @@ -44,17 +44,17 @@ def transform_widgets(self, context): class BW_PG_transforms(PropertyGroup): - size : FloatProperty(name='Size', default=1.0, min=0, update=transform_widgets) - xz_scale : FloatProperty(name='XZ Scale', default=1.0, min=0, update=transform_widgets) - slide : FloatProperty(name='Slide', default=0.0, update=transform_widgets) + size: FloatProperty(name='Size', default=1.0, min=0, update=transform_widgets) + xz_scale: FloatProperty(name='XZ Scale', default=1.0, min=0, update=transform_widgets) + slide: FloatProperty(name='Slide', default=0.0, update=transform_widgets) - loc : FloatVectorProperty(name='Location', default=(0.0, 0.0, 0.0), + loc: FloatVectorProperty(name='Location', default=(0.0, 0.0, 0.0), subtype='TRANSLATION', update=transform_widgets) - rot : FloatVectorProperty(name = 'Rotation', default=(0.0, 0.0, 0.0), + rot: FloatVectorProperty(name = 'Rotation', default=(0.0, 0.0, 0.0), subtype='EULER', step=100, precision=0, update=transform_widgets) - scale : FloatVectorProperty(name='Scale', default=(1.0, 1.0, 1.0), + scale: FloatVectorProperty(name='Scale', default=(1.0, 1.0, 1.0), subtype='XYZ', update=transform_widgets) @@ -62,16 +62,19 @@ def rename_widget(self, context): ctx.active_folder.rename_widget(self, self.name) class BW_PG_widget(PropertyGroup): - name : StringProperty(update=rename_widget) - #path : StringProperty(subtype='FILE_PATH') + name: StringProperty(update=rename_widget) + source_bone: StringProperty() + #path: StringProperty(subtype='FILE_PATH') +def refresh(self, context): + bpy.ops.bonewidget.refresh_folders() class BW_PG_folder(PropertyGroup): icons = bpy.utils.previews.new() - path : StringProperty(subtype='FILE_PATH', default='Default') - expand : BoolProperty() - widgets : CollectionProperty(type=BW_PG_widget) - widget_index : IntProperty() + path: StringProperty(subtype='FILE_PATH', default='Default', update=refresh) + expand: BoolProperty() + widgets: CollectionProperty(type=BW_PG_widget) + widget_index: IntProperty() @property def abspath(self): @@ -82,28 +85,33 @@ class BW_PG_folder(PropertyGroup): return Path(self.path).name def load_icon(self, widget): - icon = self.get_widget_icon(widget) + + icon_name = self.get_icon_name(widget) + icon = self.icons.get(icon_name) if icon: icon.reload() - else : + else: icon_path = self.get_icon_path(widget) - self.icons.load(widget.name, str(icon_path), 'IMAGE', True) + self.icons.load(icon_name, str(icon_path), 'IMAGE', True) + + def get_icon_name(self, widget): + return f'{Path(self.path).stem}_{widget.name}' def get_widget_icon(self, widget): - return self.icons.get(widget.name) + return self.icons.get(self.get_icon_name(widget)) def add_widget(self, name): name = self.get_widget_display_name(name) w = self.widgets.get(name) if w: self.load_icon(w) + self.active_widget = w return w = self.widgets.add() w.name = name self.active_widget = w - self.load_icon(w) return w @@ -194,25 +202,25 @@ class BW_PG_bone_color(PropertyGroup): class BW_prefs(AddonPreferences): bl_idname = __package__ - default_folder : PointerProperty(type=BW_PG_folder) - folders : CollectionProperty(type=BW_PG_folder) - folder_index : IntProperty() - folder_enum : EnumProperty(name='Folders', items=lambda s,c : ctx.folder_items) - collection : StringProperty(name='Collection', default='Widget') - prefix : StringProperty(name='Prefix', default='WGT-') - category : StringProperty(name='Tab', default='Rigging', update=lambda s,c : update_tab()) + default_folder: PointerProperty(type=BW_PG_folder) + folders: CollectionProperty(type=BW_PG_folder) + folder_index: IntProperty() + folder_enum: EnumProperty(name='Folders', items=lambda s,c: ctx.folder_items) + collection: StringProperty(name='Collection', default='Widgets', description='Name of the widget collection use {ob.name} to include the name of the rig') + prefix: StringProperty(name='Prefix', default='WGT-', description='Prefix for the shape object and data name') + category: StringProperty(name='Tab', default='Rigging', update=lambda s,c: update_tab()) - symmetrize : BoolProperty(name='Symmetrize', default=True, update=undo_redo) - separate : BoolProperty(default=True, name="Separate", update=undo_redo) - match_transform : BoolProperty(default=True, name="Match Transform", update=undo_redo) - rename : BoolProperty(default=True, name="Rename", update=undo_redo) + symmetrize: BoolProperty(name='Symmetrize', default=True, update=undo_redo) + separate: BoolProperty(default=True, name="Separate", update=undo_redo) + match_transform: BoolProperty(default=True, name="Match Transform", update=undo_redo) + rename: BoolProperty(default=True, name="Rename", update=undo_redo) - show_transforms : BoolProperty(default=False) - transforms : PointerProperty(type=BW_PG_transforms) + show_transforms: BoolProperty(default=False) + transforms: PointerProperty(type=BW_PG_transforms) - grid_view : BoolProperty(name='Grid View', default=True) + #grid_view: BoolProperty(name='Grid View', default=True) - #use_custom_colors : BoolProperty(name='Custom Colors', default=False, update=set_default_colors) + #use_custom_colors: BoolProperty(name='Custom Colors', default=False, update=set_default_colors) def draw(self, context): draw_prefs(self.layout) @@ -231,6 +239,8 @@ classes = ( def register(): for cls in classes: bpy.utils.register_class(cls) + + #bpy.types.Armature.widget_collection = PointerProperty(type=bpy.types.Collection) #update_folder_items() @@ -240,6 +250,7 @@ def unregister(): #for f in ctx.folders: # bpy.utils.previews.remove(f.icons) + #del bpy.types.Armature.widget_collection for cls in classes: bpy.utils.unregister_class(cls) \ No newline at end of file diff --git a/shape_utils.py b/shape_utils.py index 359173e..cb02e05 100644 --- a/shape_utils.py +++ b/shape_utils.py @@ -1,6 +1,7 @@ import bpy from .transform_utils import get_bone_matrix, transform_matrix +from mathutils import Matrix #from bone_widget import ctx @@ -73,26 +74,42 @@ def link_to_col(shape, col): col.objects.link(shape) -def get_clean_shape(bone, shape, separate=True, rename=True, -col=None, match=True, prefix='', apply_transforms=True): +def custom_shape_matrix(bone): + loc = bone.custom_shape_translation.copy() + rot = bone.custom_shape_rotation_euler.copy() + scale = bone.custom_shape_scale_xyz.copy() - old_shape = shape - old_bone = get_bone(old_shape) - bone.custom_shape = None + if bone.use_custom_shape_bone_size: + loc /= bone.bone.length + else: + scale /= bone.bone.length + + return Matrix.LocRotScale(loc, rot, scale) + +def get_clean_shape(bone, shape, separate=True, rename=True, + col=None, match=True, prefix='', apply_transforms=True): + + #bone.custom_shape = None + + #Remove bone shape if no other bone are using it + if bone.custom_shape: + old_bones = get_bones(bone.custom_shape) + old_bones.remove(bone) + + print('old_bones', old_bones) + + if old_bones: + rename_shape(bone.custom_shape, old_bones[0], prefix=prefix) + elif bone.custom_shape != shape: + bpy.data.objects.remove(bone.custom_shape) if separate: - if old_bone: - rename_shape(old_shape, old_bone, prefix=prefix) - else: - bpy.data.objects.remove(old_shape) - shape = shape.copy() shape.data = shape.data.copy() bone.custom_shape = shape - if match: - shape.matrix_world = get_bone_matrix(bone) + if apply_transforms: if bpy.app.version_string < '3.0.0': @@ -108,23 +125,19 @@ col=None, match=True, prefix='', apply_transforms=True): bone.custom_shape_scale = 1 #mirror_bone.custom_shape_scale = else: - loc = bone.custom_shape_translation - rot = bone.custom_shape_rotation_euler - scale = bone.custom_shape_scale_xyz - - if not bone.use_custom_shape_bone_size: - scale /= bone.bone.length - - mat = transform_matrix(loc=loc, rot=rot, scale=scale) + mat = custom_shape_matrix(bone) shape.data.transform(mat) - bone.custom_shape_translation = 0, 0, 0 - bone.custom_shape_rotation_euler = 0, 0, 0 - bone.custom_shape_scale_xyz = 1, 1, 1 + bone.custom_shape_translation = (0, 0, 0) + bone.custom_shape_rotation_euler = (0, 0, 0) + bone.custom_shape_scale_xyz = (1, 1, 1) bone.use_custom_shape_bone_size = True + if match: + shape.matrix_world = get_bone_matrix(bone) + if rename: rename_shape(shape, bone, prefix=prefix) @@ -134,10 +147,14 @@ col=None, match=True, prefix='', apply_transforms=True): return shape -def get_bone(shape): +def get_bones(shape): armatures = [o for o in bpy.context.scene.objects if o.type == 'ARMATURE'] - return next((b for a in armatures for b in a.pose.bones if b.custom_shape == shape), None) + return [b for a in armatures for b in a.pose.bones if b.custom_shape == shape] +def get_bone(shape): + bones = get_bones(shape) + if bones: + return bones[0] def symmetrize_bone_shape(bone, prefix=None): active_side = get_side(bone.name, 'LEFT') diff --git a/ui.py b/ui.py index e24a10a..14c7721 100644 --- a/ui.py +++ b/ui.py @@ -59,24 +59,26 @@ def add_color_row(layout, data=None, name='', index=0): ''' def draw_prefs(layout): - layout = layout.column(align=False) - add_row(layout, 'category', name='Tab', icon='COPY_ID') - add_row(layout, 'collection', name='Collection', icon='OUTLINER_COLLECTION') - add_row(layout, 'prefix', name='Prefix', icon='SYNTAX_OFF') + box = layout.box() + col = box.column(align=False) - layout.separator() + add_row(col, 'category', name='Tab', icon='COPY_ID') + add_row(col, 'collection', name='Collection', icon='OUTLINER_COLLECTION') + add_row(col, 'prefix', name='Prefix', icon='SYNTAX_OFF') - add_row(layout, 'path', data=ctx.prefs.default_folder, name='Folders', + col.separator() + + add_row(col, 'path', data=ctx.prefs.default_folder, name='Folders', icon='ADD', operator='bonewidget.add_folder') for i, f in enumerate(ctx.prefs.folders): - add_row(layout, 'path', icon='REMOVE', + add_row(col, 'path', icon='REMOVE', operator='bonewidget.remove_folder', data=f, properties={'index': i}) - layout.separator() + col.separator() - split = layout.split(factor=0.33, align=True) + split = col.split(factor=0.33, align=True) split.alignment= 'RIGHT' split.label(text='Auto:') @@ -88,6 +90,10 @@ def draw_prefs(layout): add_bool_row(col, 'match_transform', name='Match Transform', icon='TRANSFORM_ORIGINS') add_bool_row(col, 'rename', name='Rename', icon='SYNTAX_OFF') + #bpy.ops.wm.save_userpref() + prefs_unsaved = bpy.context.preferences.is_dirty + layout.operator('wm.save_userpref', text="Save Preferences" + (" *" if prefs_unsaved else "")) + #col.prop(ctx.prefs, 'symmetrize', text='Symetrize', icon='MOD_MIRROR') #col.prop(ctx.prefs, 'separate', text='Separate', icon='UNLINKED') #col.prop(ctx.prefs, 'match_transform', text='Match Transform', icon='TRANSFORM_ORIGINS') @@ -115,17 +121,14 @@ def draw_prefs(layout): class BW_UL_widget(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag): - icon = data.icons.get(item.name) + icon = data.get_widget_icon(item) icon_id = icon.icon_id if icon else -1 #layout.scale_x = 1.5 + row = layout.row(align=True) + row.use_property_split = False - if self.layout_type in {'DEFAULT', 'COMPACT'}: - layout.prop(item, 'name', text="", emboss=False, icon_value=icon_id) - - elif self.layout_type in {'GRID'}: - layout.alignment = 'CENTER' - layout.label(text="", icon_value=icon_id) + row.prop(item, 'name', text="", emboss=False, icon_value=icon_id) class BW_MT_folder(Menu): @@ -142,7 +145,7 @@ class BW_MT_folder(Menu): layout.operator('bonewidget.copy_widgets', icon='COPYDOWN') layout.operator('bonewidget.paste_widgets', icon='PASTEDOWN') - layout.prop(ctx.prefs, 'grid_view') + #layout.prop(ctx.prefs, 'grid_view') class BW_PT_transforms(Panel): @@ -231,7 +234,7 @@ class BW_PT_main(Panel): if folder: widget_col.template_list('BW_UL_widget', 'BW_widgets', folder, 'widgets', folder, 'widget_index', - rows=5, columns=self.get_nb_col(context), type='GRID' if ctx.prefs.grid_view else 'DEFAULT') + rows=5, columns=self.get_nb_col(context), type='DEFAULT') #widget_col.template_list('UI_UL_list', 'BW_widgets', folder, 'widgets', folder, 'widget_index', rows=4) #layout.prop(self, 'folder_enum') @@ -267,13 +270,9 @@ class BW_PT_main(Panel): #opt_col = layout.column() elif ctx.show_prefs_op: - layout.separator() + #layout.separator() draw_prefs(layout) - #bpy.ops.wm.save_userpref() - prefs_unsaved = context.preferences.is_dirty - layout.operator('wm.save_userpref', text="Save Preferences" + (" *" if prefs_unsaved else "")) - classes = ( BW_UL_widget, diff --git a/widgets/Custom/hips.blend b/widgets/Custom/hips.blend new file mode 100644 index 0000000..9e55295 Binary files /dev/null and b/widgets/Custom/hips.blend differ diff --git a/widgets/Custom/hips.png b/widgets/Custom/hips.png new file mode 100644 index 0000000..57604a9 Binary files /dev/null and b/widgets/Custom/hips.png differ diff --git a/widgets/Custom/root.blend b/widgets/Custom/root.blend new file mode 100644 index 0000000..69f7321 Binary files /dev/null and b/widgets/Custom/root.blend differ diff --git a/widgets/Custom/root.png b/widgets/Custom/root.png new file mode 100644 index 0000000..2f06543 Binary files /dev/null and b/widgets/Custom/root.png differ diff --git a/widgets/Custome/root.blend b/widgets/Custome/root.blend new file mode 100644 index 0000000..85ca7f3 Binary files /dev/null and b/widgets/Custome/root.blend differ diff --git a/widgets/Custome/root.png b/widgets/Custome/root.png new file mode 100644 index 0000000..1ca7cf8 Binary files /dev/null and b/widgets/Custome/root.png differ diff --git a/widgets/Default/bone_l.blend b/widgets/Default/bone_l.blend new file mode 100644 index 0000000..dfd800c Binary files /dev/null and b/widgets/Default/bone_l.blend differ diff --git a/widgets/Default/bone_l.png b/widgets/Default/bone_l.png new file mode 100644 index 0000000..e63d916 Binary files /dev/null and b/widgets/Default/bone_l.png differ diff --git a/widgets/Default/chest.blend b/widgets/Default/chest.blend new file mode 100644 index 0000000..28a82b1 Binary files /dev/null and b/widgets/Default/chest.blend differ diff --git a/widgets/Default/chest.png b/widgets/Default/chest.png new file mode 100644 index 0000000..cb07096 Binary files /dev/null and b/widgets/Default/chest.png differ diff --git a/widgets/Default/pelvis.blend b/widgets/Default/pelvis.blend new file mode 100644 index 0000000..954e880 Binary files /dev/null and b/widgets/Default/pelvis.blend differ diff --git a/widgets/Default/pelvis.png b/widgets/Default/pelvis.png new file mode 100644 index 0000000..bb20140 Binary files /dev/null and b/widgets/Default/pelvis.png differ diff --git a/widgets/Default/root.blend b/widgets/Default/root.blend index 271e3bd..e505bcc 100644 Binary files a/widgets/Default/root.blend and b/widgets/Default/root.blend differ diff --git a/widgets/Default/root.png b/widgets/Default/root.png index 0e37b75..7e193d9 100644 Binary files a/widgets/Default/root.png and b/widgets/Default/root.png differ diff --git a/widgets/Default/thigh_ik_l.blend b/widgets/Default/thigh_ik_l.blend new file mode 100644 index 0000000..14993e6 Binary files /dev/null and b/widgets/Default/thigh_ik_l.blend differ diff --git a/widgets/Default/thigh_ik_l.png b/widgets/Default/thigh_ik_l.png new file mode 100644 index 0000000..a187d46 Binary files /dev/null and b/widgets/Default/thigh_ik_l.png differ diff --git a/widgets/Default/torso.blend b/widgets/Default/torso.blend new file mode 100644 index 0000000..e7d9fe1 Binary files /dev/null and b/widgets/Default/torso.blend differ diff --git a/widgets/Default/torso.png b/widgets/Default/torso.png new file mode 100644 index 0000000..9698a78 Binary files /dev/null and b/widgets/Default/torso.png differ