import bpy import shutil from pathlib import Path import os import bpy.utils.previews from bpy.types import PropertyGroup, AddonPreferences from bpy.props import StringProperty, BoolProperty, CollectionProperty, \ FloatProperty, IntProperty, PointerProperty, EnumProperty, FloatVectorProperty from bone_widget import ctx from .ui import update_tab, draw_prefs from .transform_utils import transform_mesh 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_names] if (ops and ops[-1].bl_idname in op_ids): bpy.ops.wm.undo() bpy.ops.wm.redo() def transform_widgets(self, context): #bones = [] shapes = ctx.shapes for shape, coords in shapes.items(): #bones.append(ctx.get_bone(shape)) #print(shape) transform_mesh(shape.data, self.loc, self.rot, self.scale, self.xz_scale, self.size, self.slide, coords=coords) if ctx.prefs.auto_symmetrize: for bone in ctx.selected_bones: flipped_bone = get_flipped_bone(bone) if flipped_bone and flipped_bone.custom_shape in shapes: continue symmetrize_bone_shape(bone, prefix=ctx.prefs.prefix) 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) 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), subtype='EULER', step=100, precision=0, update=transform_widgets) scale: FloatVectorProperty(name='Scale', default=(1.0, 1.0, 1.0), subtype='XYZ', update=transform_widgets) def rename_widget(self, context): ctx.active_folder.rename_widget(self, self.name) class BW_PG_widget(PropertyGroup): 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', update=refresh) expand: BoolProperty() widgets: CollectionProperty(type=BW_PG_widget) widget_index: IntProperty() @property def icons(self): prefs = ctx.prefs return prefs.get_folder_previews(self.as_pointer()) @property def abspath(self): return ctx.abspath(self.path) @property def name(self): return Path(self.path).name def load_icon(self, widget): icon_name = self.get_icon_name(widget) icon = self.icons.get(icon_name) if icon: icon.reload() else: icon_path = self.get_icon_path(widget) 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(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 def remove_widget(self, widget): index = self.widgets[:].index(widget) widget_path = self.get_widget_path(widget) icon_path = self.get_icon_path(widget) if widget_path.exists(): os.remove(widget_path) if icon_path.exists(): os.remove(icon_path) self.widgets.remove(index) self.widget_index = min(len(self.widgets)-1, index) @property def active_widget(self): if self.widgets and self.widget_index < len(self.widgets): return self.widgets[self.widget_index] @active_widget.setter def active_widget(self, widget): self.widget_index = list(self.widgets).index(widget) def get_widget_clean_name(self, widget): name = widget if isinstance(widget, str) else widget.name name = name.split('-', 1)[-1] return bpy.path.clean_name(name).lower() def get_widget_display_name(self, widget): name = widget if isinstance(widget, str) else widget.name return name.replace('_', ' ').title() def get_widget_path(self, widget): name = self.get_widget_clean_name(widget) return (self.abspath/name).with_suffix('.blend') def get_icon_path(self, widget): name = self.get_widget_clean_name(widget) return (self.abspath/name).with_suffix('.png') def rename_widget(self, widget, name): if not widget.get('_name'): widget['_name'] = name return src_blend = self.get_widget_path(widget['_name']) dst_blend = self.get_widget_path(name) src_icon = self.get_icon_path(widget['_name']) dst_icon = self.get_icon_path(name) widget['_name'] = name if not src_blend: return if not src_blend.exists(): self.remove_widget(widget) if src_blend != dst_blend: os.rename(str(src_blend), str(dst_blend)) if src_icon != dst_icon: os.rename(str(src_icon), str(dst_icon)) self.load_icon(widget) def load_widgets(self): self.widgets.clear() for widget_blend in self.abspath.glob('*.blend'): self.add_widget(widget_blend.stem) #class BW_PG_settings(PropertyGroup): # pass class BW_PG_bone_color(PropertyGroup): value: FloatVectorProperty(name='Color', subtype='COLOR', default=[0.0, 0.0, 0.0]) name: StringProperty(name='Name') class BW_prefs(AddonPreferences): bl_idname = __package__ previews = {} 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') use_custom_collection: BoolProperty(name='Custom Collection', default=False, description='Allow to pick a collection from the scene') 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()) auto_symmetrize: BoolProperty(name='Symmetrize', default=True, update=undo_redo) auto_separate: BoolProperty(default=True, name="Separate", update=undo_redo) auto_match_transform: BoolProperty(default=True, name="Match Transform", update=undo_redo) auto_rename: BoolProperty(default=True, name="Rename", update=undo_redo) show_transforms: BoolProperty(default=False) show_preferences: BoolProperty(default=False) transforms: PointerProperty(type=BW_PG_transforms) #grid_view: BoolProperty(name='Grid View', default=True) #use_custom_colors: BoolProperty(name='Custom Colors', default=False, update=set_default_colors) def get_folder_previews(self, adress): if adress not in self.previews: self.previews[adress] = bpy.utils.previews.new() return self.previews[adress] def draw(self, context): draw_prefs(self.layout) classes = ( BW_PG_bone_color, BW_PG_widget, BW_PG_transforms, BW_PG_folder, BW_prefs ) def register(): for cls in classes: bpy.utils.register_class(cls) bpy.types.Armature.widget_collection = PointerProperty(type=bpy.types.Collection) #update_folder_items() def unregister(): #bpy.utils.previews.remove(ctx.prefs.default_folder.icons) #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)