272 lines
8.3 KiB
Python
272 lines
8.3 KiB
Python
|
|
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) |