785 lines
26 KiB
Python
785 lines
26 KiB
Python
import bpy
|
|
|
|
from .functions import (
|
|
findMatchBones,
|
|
fromWidgetFindBone,
|
|
symmetrizeWidget_helper,
|
|
boneMatrix,
|
|
createWidget,
|
|
editWidget,
|
|
returnToArmature,
|
|
addRemoveWidgets,
|
|
getWidgetData,
|
|
getCollection,
|
|
getViewLayerCollection,
|
|
recurLayerCollection,
|
|
deleteUnusedWidgets,
|
|
clearBoneWidgets,
|
|
resyncWidgetNames,
|
|
addObjectAsWidget,
|
|
importWidgetLibrary,
|
|
exportWidgetLibrary,
|
|
advanced_options_toggled,
|
|
removeCustomImage,
|
|
copyCustomImage,
|
|
getWidgetData,
|
|
updateCustomImage,
|
|
resetDefaultImages,
|
|
updateWidgetLibrary,
|
|
)
|
|
|
|
from bpy.props import FloatProperty, BoolProperty, FloatVectorProperty, StringProperty
|
|
|
|
|
|
class BONEWIDGET_OT_sharedPropertyGroup(bpy.types.PropertyGroup):
|
|
"""Storage class for Shared Attribute Properties"""
|
|
|
|
custom_image_data = ("", "")
|
|
import_library_filepath = ""
|
|
|
|
|
|
class BONEWIDGET_OT_createWidget(bpy.types.Operator):
|
|
"""Creates a widget for selected bone"""
|
|
bl_idname = "bonewidget.create_widget"
|
|
bl_label = "Create"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (context.object and context.object.mode == 'POSE')
|
|
|
|
relative_size: BoolProperty(
|
|
name="Scale to Bone length",
|
|
default=True,
|
|
description="Scale Widget to bone length"
|
|
)
|
|
|
|
use_face_data: BoolProperty(
|
|
name="Use Face Data",
|
|
default=False,
|
|
description="When enabled this option will include the widget's face data (if available)"
|
|
)
|
|
|
|
advanced_options: BoolProperty(
|
|
name="Advanced options",
|
|
default=False,
|
|
description="Show advanced options",
|
|
update=advanced_options_toggled
|
|
)
|
|
|
|
global_size_simple: FloatProperty(
|
|
name="Global Size",
|
|
default=1.0,
|
|
description="Global Size"
|
|
)
|
|
|
|
global_size_advanced: FloatVectorProperty(
|
|
name="Global Size",
|
|
default=(1.0, 1.0, 1.0),
|
|
subtype='XYZ',
|
|
description="Global Size"
|
|
)
|
|
|
|
slide_simple: FloatProperty(
|
|
name="Slide",
|
|
default=0.0,
|
|
subtype='NONE',
|
|
unit='NONE',
|
|
description="Slide widget along bone y axis"
|
|
)
|
|
|
|
slide_advanced: FloatVectorProperty(
|
|
name="Slide",
|
|
default=(0.0, 0.0, 0.0),
|
|
subtype='XYZ',
|
|
unit='NONE',
|
|
description="Slide widget along bone xyz axes"
|
|
)
|
|
|
|
rotation: FloatVectorProperty(
|
|
name="Rotation",
|
|
description="Rotate the widget",
|
|
default=(0.0, 0.0, 0.0),
|
|
subtype='EULER',
|
|
unit='ROTATION',
|
|
precision=1,
|
|
)
|
|
|
|
wireframe_width: FloatProperty(
|
|
name="Wire Width",
|
|
default=2.0,
|
|
min=1.0,
|
|
max=16,
|
|
soft_max = 10,
|
|
description="Set the thickness of a wireframe widget"
|
|
)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
col = layout.column()
|
|
row = col.row(align=True)
|
|
row.prop(self, "relative_size")
|
|
row = col.row(align=True)
|
|
if self.advanced_options:
|
|
row.prop(self, "use_face_data")
|
|
row = col.row(align=True)
|
|
row.prop(self, "global_size_advanced" if self.advanced_options else "global_size_simple", expand=False)
|
|
row = col.row(align=True)
|
|
row.prop(self, "slide_advanced" if self.advanced_options else "slide_simple", text="Slide")
|
|
row = col.row(align=True)
|
|
row.prop(self, "rotation", text="Rotation")
|
|
row = col.row(align=True)
|
|
if bpy.app.version >= (4,2,0):
|
|
row.prop(self, "wireframe_width", text="Wire Width")
|
|
row = col.row(align=True)
|
|
row.prop(self, "advanced_options")
|
|
|
|
def execute(self, context):
|
|
widget_data = getWidgetData(context.window_manager.widget_list)
|
|
slide = self.slide_advanced if self.advanced_options else (0.0, self.slide_simple, 0.0)
|
|
global_size = self.global_size_advanced if self.advanced_options else (self.global_size_simple,) * 3
|
|
for bone in bpy.context.selected_pose_bones:
|
|
createWidget(bone, widget_data, self.relative_size, global_size, [
|
|
1, 1, 1], slide, self.rotation, getCollection(context), self.use_face_data, self.wireframe_width)
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_editWidget(bpy.types.Operator):
|
|
"""Edit the widget for selected bone"""
|
|
bl_idname = "bonewidget.edit_widget"
|
|
bl_label = "Edit"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (context.object and context.object.type == 'ARMATURE' and context.object.mode == 'POSE'
|
|
and context.active_pose_bone.custom_shape is not None)
|
|
|
|
def execute(self, context):
|
|
active_bone = context.active_pose_bone
|
|
try:
|
|
editWidget(active_bone)
|
|
except KeyError:
|
|
self.report({'INFO'}, 'This widget is the Widget Collection')
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_returnToArmature(bpy.types.Operator):
|
|
"""Switch back to the armature"""
|
|
bl_idname = "bonewidget.return_to_armature"
|
|
bl_label = "Return to armature"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (context.object and context.object.type == 'MESH'
|
|
and context.object.mode in ['EDIT', 'OBJECT'])
|
|
|
|
def execute(self, context):
|
|
b = bpy.context.object
|
|
if fromWidgetFindBone(bpy.context.object):
|
|
returnToArmature(bpy.context.object)
|
|
else:
|
|
self.report({'INFO'}, 'Object is not a bone widget')
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_matchBoneTransforms(bpy.types.Operator):
|
|
"""Match the widget to the bone transforms"""
|
|
bl_idname = "bonewidget.match_bone_transforms"
|
|
bl_label = "Match bone transforms"
|
|
|
|
def execute(self, context):
|
|
if bpy.context.mode == "POSE":
|
|
for bone in bpy.context.selected_pose_bones:
|
|
boneMatrix(bone.custom_shape, bone)
|
|
|
|
else:
|
|
for ob in bpy.context.selected_objects:
|
|
if ob.type == 'MESH':
|
|
matchBone = fromWidgetFindBone(ob)
|
|
if matchBone:
|
|
boneMatrix(ob, matchBone)
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_matchSymmetrizeShape(bpy.types.Operator):
|
|
"""Symmetrize to the opposite side ONLY if it is named with a .L or .R (default settings)"""
|
|
bl_idname = "bonewidget.symmetrize_shape"
|
|
bl_label = "Symmetrize"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (context.object and context.object.type == 'ARMATURE'
|
|
and context.object.mode in ['POSE'])
|
|
|
|
|
|
def execute(self, context):
|
|
widget = bpy.context.active_pose_bone.custom_shape
|
|
if widget is None:
|
|
self.report({"INFO"}, "There is no widget on this bone.")
|
|
return {'FINISHED'}
|
|
collection = getViewLayerCollection(context, widget)
|
|
widgetsAndBones = findMatchBones()[0]
|
|
activeObject = findMatchBones()[1]
|
|
widgetsAndBones = findMatchBones()[0]
|
|
|
|
if not activeObject:
|
|
self.report({"INFO"}, "No active bone or object")
|
|
return {'FINISHED'}
|
|
|
|
for bone in widgetsAndBones:
|
|
symmetrizeWidget_helper(bone, collection, activeObject, widgetsAndBones)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_imageSelect(bpy.types.Operator):
|
|
"""Open a Fileselect browser and get the image location"""
|
|
bl_idname = "bonewidget.image_select"
|
|
bl_label = "Select Image"
|
|
|
|
|
|
filter_glob: StringProperty(
|
|
default='*.jpg;*.jpeg;*.png;*.tif;',
|
|
options={'HIDDEN'}
|
|
)
|
|
|
|
filename: StringProperty(
|
|
name='Filename',
|
|
description='Name of custom image',
|
|
)
|
|
|
|
filepath: StringProperty(
|
|
subtype="FILE_PATH"
|
|
)
|
|
|
|
def invoke(self, context, event):
|
|
self.filename = ""
|
|
context.window_manager.fileselect_add(self)
|
|
if context.area:
|
|
context.area.tag_redraw()
|
|
return {'RUNNING_MODAL'}
|
|
|
|
def execute(self, context):
|
|
bpy.context.window_manager.prop_grp.custom_image_name = self.filename
|
|
setattr(BONEWIDGET_OT_sharedPropertyGroup, "custom_image_data", (self.filepath, self.filename))
|
|
context.area.tag_redraw()
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_addCustomImage(bpy.types.Operator):
|
|
"""Add a custom image to selected preview panel widget"""
|
|
bl_idname = "bonewidget.add_custom_image"
|
|
bl_label = "Select Image"
|
|
|
|
filter_glob: StringProperty(
|
|
default='*.jpg;*.jpeg;*.png;*.tif;',
|
|
options={'HIDDEN'}
|
|
)
|
|
|
|
filename: StringProperty(
|
|
name='Filename',
|
|
description='Name of custom image',
|
|
)
|
|
|
|
filepath: StringProperty(
|
|
subtype="FILE_PATH"
|
|
)
|
|
|
|
def invoke(self, context, event):
|
|
self.filename = ""
|
|
context.window_manager.fileselect_add(self)
|
|
return {'RUNNING_MODAL'}
|
|
|
|
def execute(self, context):
|
|
if self.filepath:
|
|
# first remove previous custom image if present
|
|
current_widget = context.window_manager.widget_list
|
|
removeCustomImage(getWidgetData(current_widget).get("image"))
|
|
# copy over the image to custom folder
|
|
copyCustomImage(self.filepath, self.filename)
|
|
# update the json files with new image data
|
|
updateCustomImage(self.filename)
|
|
|
|
self.report({'INFO'}, "Custom image has been added!")
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_addWidgets(bpy.types.Operator):
|
|
"""Add selected mesh object to Bone Widget Library"""
|
|
bl_idname = "bonewidget.add_widgets"
|
|
bl_label = "Add New Widget to Library"
|
|
|
|
|
|
widget_name: StringProperty(
|
|
name="Widget Name",
|
|
default="",
|
|
description="The name of the new widget",
|
|
options={"TEXTEDIT_UPDATE"},
|
|
)
|
|
|
|
custom_image: BoolProperty(
|
|
name="Custom Image",
|
|
default=False,
|
|
description="Use a custom image for the new widget"
|
|
)
|
|
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (context.object and context.object.type == 'MESH' and context.object.mode == 'OBJECT'
|
|
and context.active_object is not None)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
row = layout.row()
|
|
row.label(text="Widget Name:")
|
|
row.prop(self, "widget_name", text="")
|
|
row = layout.row()
|
|
|
|
# adding custom image this way doesn't work in blender 3.6
|
|
if bpy.app.version > (3, 7, 0):
|
|
row.prop(self, "custom_image", text="Custom Image")
|
|
|
|
if self.custom_image:
|
|
row = layout.row()
|
|
if bpy.app.version >= (4,1,0):
|
|
row.prop(bpy.context.window_manager.prop_grp, "custom_image_name", text="", placeholder="Choose an image...", icon="FILE_IMAGE")
|
|
else:
|
|
row.prop(bpy.context.window_manager.prop_grp, "custom_image_name", text="", icon="FILE_IMAGE")
|
|
row.operator('bonewidget.image_select', icon='FILEBROWSER', text="")
|
|
|
|
def invoke(self, context, event):
|
|
if bpy.context.selected_objects:
|
|
self.widget_name = context.active_object.name
|
|
setattr(BONEWIDGET_OT_sharedPropertyGroup, "custom_image_name", StringProperty(name="Image Name"))
|
|
return context.window_manager.invoke_props_dialog(self)
|
|
|
|
self.report({'WARNING'}, 'Please select an object first!')
|
|
return {'CANCELLED'}
|
|
|
|
def execute(self, context):
|
|
objects = []
|
|
if bpy.context.mode == "POSE":
|
|
for bone in bpy.context.selected_pose_bones:
|
|
objects.append(bone.custom_shape)
|
|
else:
|
|
for ob in bpy.context.selected_objects:
|
|
if ob.type == 'MESH':
|
|
objects.append(ob)
|
|
|
|
if not objects:
|
|
self.report({'WARNING'}, 'Select Meshes or Pose bones')
|
|
return {'CANCELLED'}
|
|
|
|
# make sure widget name isn't empty
|
|
if not self.widget_name:
|
|
self.report({'WARNING'}, "Widget name can't be empty!")
|
|
return {'CANCELLED'}
|
|
|
|
# get filepath to custom image if specified and transfer to custom image folder
|
|
custom_image_name = ""
|
|
custom_image_path = ""
|
|
message_extra = ""
|
|
if self.custom_image:
|
|
custom_image_path, custom_image_name = bpy.context.window_manager.prop_grp.custom_image_data #context.window_manager.custom_image
|
|
|
|
# no image path found
|
|
if not custom_image_path:
|
|
# check if user pasted an image path into text field
|
|
text_field = bpy.context.window_manager.prop_grp.custom_image_name
|
|
import os
|
|
if os.path.isfile(text_field) and text_field.endswith((".jpg", ".jpeg" ".png", ".tif")):
|
|
custom_image_name = os.path.basename(text_field)
|
|
custom_image_path = text_field
|
|
else:
|
|
message_extra = " - WARNING - No custom image specified!"
|
|
|
|
if custom_image_name and custom_image_path:
|
|
copyCustomImage(custom_image_path, custom_image_name)
|
|
|
|
bpy.context.window_manager.prop_grp.custom_image_name = "" # make sure the field is empty for next time
|
|
|
|
message_type, return_message = addRemoveWidgets(context, "add", bpy.types.WindowManager.widget_list.keywords['items'],
|
|
objects, self.widget_name, custom_image_name)
|
|
|
|
if return_message:
|
|
self.report({message_type}, return_message + message_extra)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_removeWidgets(bpy.types.Operator):
|
|
"""Remove selected widget object from the Bone Widget Library"""
|
|
bl_idname = "bonewidget.remove_widgets"
|
|
bl_label = "Remove Widgets"
|
|
|
|
def execute(self, context):
|
|
objects = bpy.context.window_manager.widget_list
|
|
|
|
# try and remove the image - will abort if no custom image assigned or if missing
|
|
removeCustomImage(getWidgetData(objects).get("image"))
|
|
|
|
message_type, return_message = addRemoveWidgets(context, "remove", bpy.types.WindowManager.widget_list.keywords['items'], objects)
|
|
|
|
if return_message:
|
|
self.report({message_type}, return_message)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_importWidgetsSummaryPopup(bpy.types.Operator):
|
|
"""Display summary of imported Widget Library"""
|
|
bl_idname = "bonewidget.widget_summary_popup"
|
|
bl_label = "Imported Widget Summary"
|
|
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.scale_x = 1.2
|
|
|
|
layout.separator()
|
|
row = layout.row()
|
|
row.label(text=f"Imported Widgets: {context.window_manager.custom_data.new_widgets}")
|
|
|
|
row = layout.row()
|
|
row.label(text=f"Skipped Widgets: {context.window_manager.custom_data.skipped()}")
|
|
|
|
row = layout.row()
|
|
row.label(text=f"Failed Widgets: {context.window_manager.custom_data.failed()}")
|
|
|
|
|
|
def invoke(self, context, event):
|
|
return context.window_manager.invoke_props_dialog(self)
|
|
|
|
def execute(self, context):
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_importWidgetsAskPopup(bpy.types.Operator):
|
|
"""Ask user how to handle name collisions from the imported Widget Library"""
|
|
bl_idname = "bonewidget.widget_ask_popup"
|
|
bl_label = "Imported Widget Choice Popup"
|
|
|
|
widgetImportData = None
|
|
|
|
import_options = [
|
|
("OVERWRITE", "Overwrite", "Overwrite existing widget"),
|
|
("SKIP", "Skip", "Skip widget"),
|
|
("RENAME", "Rename", "Rename widget"),
|
|
]
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.scale_x = 1.2
|
|
|
|
layout.separator()
|
|
row = layout.row()
|
|
row.label(text="Choose an action:")
|
|
|
|
row = layout.row()
|
|
for i, _ in enumerate(self.widgetImportData.skipped_widgets):
|
|
if getattr(context.window_manager.prop_grp, f"ImportOptions{i}") == self.import_options[2][0]: # Rename
|
|
row.prop(context.window_manager.prop_grp, f"EditName{i}", text="")
|
|
else:
|
|
row.label(text=str(getattr(context.window_manager.prop_grp, f"EditName{i}")))
|
|
row.prop(context.window_manager.prop_grp, f"ImportOptions{i}", text=" ")
|
|
row = layout.row()
|
|
|
|
def invoke(self, context, event):
|
|
self.widgetImportData = bpy.context.window_manager.custom_data
|
|
|
|
# generate the x number of drop down lists and widget names needed
|
|
for n, widget in enumerate(self.widgetImportData.skipped_widgets):
|
|
widget_name = next(iter(widget.keys()))
|
|
setattr(BONEWIDGET_OT_sharedPropertyGroup, f"ImportOptions{n}", bpy.props.EnumProperty(
|
|
name=f"ImportOptions{n}",
|
|
description="Choose an option",
|
|
items=self.import_options,
|
|
default="SKIP"
|
|
))
|
|
setattr(bpy.context.window_manager.prop_grp, f"ImportOptions{n}", "SKIP")
|
|
|
|
setattr(BONEWIDGET_OT_sharedPropertyGroup, f"EditName{n}", StringProperty(
|
|
name=f"EditName{n}",
|
|
default=widget_name,
|
|
description="The name of the widget",
|
|
))
|
|
setattr(bpy.context.window_manager.prop_grp, f"EditName{n}", widget_name)
|
|
|
|
return context.window_manager.invoke_props_dialog(self)
|
|
|
|
def execute(self, context):
|
|
widget_results = {}
|
|
widget_images = set()
|
|
|
|
for i, widget in enumerate(self.widgetImportData.skipped_widgets):
|
|
widget_name, widget_data = next(iter(widget.items()))
|
|
widget_image = widget_data.get('image')
|
|
widget_image = widget_image if widget_image != "user_defined.png" else "" # only append custom images
|
|
action = getattr(bpy.context.window_manager.prop_grp, f"ImportOptions{i}")
|
|
new_widget_name = getattr(bpy.context.window_manager.prop_grp, f"EditName{i}")
|
|
|
|
# error check before proceeding - widget renamed to empty string
|
|
if widget_name != new_widget_name and new_widget_name.strip() == "":
|
|
self.widgetImportData.failed_widgets.update(widget)
|
|
continue
|
|
|
|
if action == self.import_options[0][0]: # overwrite
|
|
widget_results.update(widget)
|
|
if widget_image: widget_images.add(widget_image)
|
|
self.widgetImportData.new_widgets += 1
|
|
self.widgetImportData.skipped_widgets.remove(widget)
|
|
elif action == self.import_options[2][0]: # Rename
|
|
widget_results.update({new_widget_name: widget_data})
|
|
if widget_image: widget_images.add(widget_image)
|
|
self.widgetImportData.new_widgets += 1
|
|
self.widgetImportData.skipped_widgets.remove(widget)
|
|
|
|
updateWidgetLibrary(widget_results, widget_images, bpy.context.window_manager.prop_grp.import_library_filepath)
|
|
|
|
# clean up the data from the property group
|
|
for i in range(self.widgetImportData.skipped()):
|
|
delattr(BONEWIDGET_OT_sharedPropertyGroup, f"ImportOptions{i}")
|
|
delattr(BONEWIDGET_OT_sharedPropertyGroup, f"EditName{i}")
|
|
|
|
#del bpy.types.WindowManager.custom_data
|
|
self.widgetImportData = None
|
|
|
|
# display summary of imported widgets
|
|
bpy.ops.bonewidget.widget_summary_popup('INVOKE_DEFAULT')
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_importLibrary(bpy.types.Operator):
|
|
"""Import User Defined Widgets"""
|
|
bl_idname = "bonewidget.import_library"
|
|
bl_label = "Import Library"
|
|
|
|
|
|
filter_glob: StringProperty(
|
|
default='*.zip',
|
|
options={'HIDDEN'}
|
|
)
|
|
|
|
filename: StringProperty(
|
|
name='Filename',
|
|
description='Name of file to be imported',
|
|
)
|
|
|
|
filepath: StringProperty(
|
|
subtype="FILE_PATH"
|
|
)
|
|
|
|
import_option : bpy.props.EnumProperty(
|
|
name="Import Option",
|
|
items=[
|
|
("OVERWRITE", "Overwrite", "Overwrite existing widget"),
|
|
("SKIP", "Skip", "Skip widget"),
|
|
("ASK", "Ask", "Ask user what to do")],
|
|
default="ASK",
|
|
)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
row = layout.row(align=True)
|
|
row.label(text="If duplicates are found:")
|
|
row = layout.row(align=True)
|
|
row.prop(self, "import_option", expand=True)
|
|
|
|
def execute(self, context):
|
|
if self.filepath and self.import_option:
|
|
importLibraryData = importWidgetLibrary(self.filepath, self.import_option)
|
|
bpy.context.window_manager.prop_grp.import_library_filepath = self.filepath
|
|
|
|
bpy.types.WindowManager.custom_data = importLibraryData
|
|
|
|
if self.import_option == "ASK":
|
|
bpy.types.WindowManager.custom_data = importLibraryData
|
|
bpy.ops.bonewidget.widget_ask_popup('INVOKE_DEFAULT')
|
|
|
|
elif self.import_option == "OVERWRITE" or self.import_option == "SKIP":
|
|
widget_images = set()
|
|
|
|
# extract image names if any
|
|
for _, value in importLibraryData.widgets.items():
|
|
widget_images.add(value['image'])
|
|
|
|
updateWidgetLibrary(importLibraryData.widgets, widget_images, self.filepath)
|
|
|
|
bpy.ops.bonewidget.widget_summary_popup('INVOKE_DEFAULT')
|
|
else:
|
|
bpy.ops.bonewidget.widget_summary_popup('INVOKE_DEFAULT')
|
|
|
|
return {'FINISHED'}
|
|
|
|
def invoke(self, context, event):
|
|
self.filename = ""
|
|
context.window_manager.fileselect_add(self)
|
|
return {'RUNNING_MODAL'}
|
|
|
|
|
|
class BONEWIDGET_OT_exportLibrary(bpy.types.Operator):
|
|
"""Export User Defined Widgets"""
|
|
bl_idname = "bonewidget.export_library"
|
|
bl_label = "Export Library"
|
|
|
|
|
|
filter_glob: StringProperty(
|
|
default='*.zip',
|
|
options={'HIDDEN'}
|
|
)
|
|
|
|
filename: StringProperty(
|
|
name='Filename',
|
|
description='Name of file to be exported',
|
|
)
|
|
|
|
filepath: StringProperty(
|
|
subtype="FILE_PATH"
|
|
)
|
|
|
|
def execute(self, context):
|
|
if self.filepath and self.filename:
|
|
num_widgets = exportWidgetLibrary(self.filepath)
|
|
self.report({'INFO'}, f"{num_widgets} user defined widgets exported successfully!")
|
|
return {'FINISHED'}
|
|
|
|
def invoke(self, context, event):
|
|
self.filename = "widgetLibrary.zip"
|
|
context.window_manager.fileselect_add(self)
|
|
return {'RUNNING_MODAL'}
|
|
|
|
|
|
class BONEWIDGET_OT_toggleCollectionVisibility(bpy.types.Operator):
|
|
"""Show/hide the bone widget collection"""
|
|
bl_idname = "bonewidget.toggle_collection_visibilty"
|
|
bl_label = "Collection Visibilty"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (context.object and context.object.type == 'ARMATURE' and context.object.mode == 'POSE')
|
|
|
|
def execute(self, context):
|
|
if not context.preferences.addons[__package__].preferences.use_rigify_defaults:
|
|
bw_collection_name = context.preferences.addons[__package__].preferences.bonewidget_collection_name
|
|
else:
|
|
bw_collection_name = 'WGTS_' + context.active_object.name
|
|
|
|
bw_collection = recurLayerCollection(bpy.context.view_layer.layer_collection, bw_collection_name)
|
|
bw_collection.hide_viewport = not bw_collection.hide_viewport
|
|
#need to recursivly search for the view_layer
|
|
bw_collection.exclude = False
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_deleteUnusedWidgets(bpy.types.Operator):
|
|
"""Delete unused objects in the WGT collection"""
|
|
bl_idname = "bonewidget.delete_unused_widgets"
|
|
bl_label = "Delete Unused Widgets"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (context.object and context.object.type == 'ARMATURE' and context.object.mode == 'POSE')
|
|
|
|
def execute(self, context):
|
|
try:
|
|
deleteUnusedWidgets()
|
|
except:
|
|
self.report({'INFO'}, "Can't find the Widget Collection. Does it exist?")
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_clearBoneWidgets(bpy.types.Operator):
|
|
"""Clears widgets from selected pose bones but doesn't remove them from the scene."""
|
|
bl_idname = "bonewidget.clear_widgets"
|
|
bl_label = "Clear Widgets"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (context.object and context.object.type == 'ARMATURE' and context.object.mode == 'POSE')
|
|
|
|
def execute(self, context):
|
|
clearBoneWidgets()
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_resyncWidgetNames(bpy.types.Operator):
|
|
"""Clear widgets from selected pose bones"""
|
|
bl_idname = "bonewidget.resync_widget_names"
|
|
bl_label = "Resync Widget Names"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (context.object and context.object.type == 'ARMATURE' and context.object.mode == 'POSE')
|
|
|
|
def execute(self, context):
|
|
resyncWidgetNames()
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_addObjectAsWidget(bpy.types.Operator):
|
|
"""Add selected object as widget for active bone."""
|
|
bl_idname = "bonewidget.add_as_widget"
|
|
bl_label = "Confirm selected Object as widget shape"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (len(context.selected_objects) == 2 and context.object.mode == 'POSE')
|
|
|
|
def execute(self, context):
|
|
addObjectAsWidget(context, getCollection(context))
|
|
return {'FINISHED'}
|
|
|
|
|
|
class BONEWIDGET_OT_resetDefaultImages(bpy.types.Operator):
|
|
"""Resets the thumbnails for all default widgets"""
|
|
bl_idname = "bonewidget.reset_default_images"
|
|
bl_label = "Reset"
|
|
|
|
def execute(self, context):
|
|
resetDefaultImages()
|
|
return {'FINISHED'}
|
|
|
|
|
|
classes = (
|
|
BONEWIDGET_OT_removeWidgets,
|
|
BONEWIDGET_OT_addWidgets,
|
|
BONEWIDGET_OT_importLibrary,
|
|
BONEWIDGET_OT_exportLibrary,
|
|
BONEWIDGET_OT_matchSymmetrizeShape,
|
|
BONEWIDGET_OT_matchBoneTransforms,
|
|
BONEWIDGET_OT_returnToArmature,
|
|
BONEWIDGET_OT_editWidget,
|
|
BONEWIDGET_OT_createWidget,
|
|
BONEWIDGET_OT_toggleCollectionVisibility,
|
|
BONEWIDGET_OT_deleteUnusedWidgets,
|
|
BONEWIDGET_OT_clearBoneWidgets,
|
|
BONEWIDGET_OT_resyncWidgetNames,
|
|
BONEWIDGET_OT_addObjectAsWidget,
|
|
BONEWIDGET_OT_importWidgetsSummaryPopup,
|
|
BONEWIDGET_OT_importWidgetsAskPopup,
|
|
BONEWIDGET_OT_sharedPropertyGroup,
|
|
BONEWIDGET_OT_imageSelect,
|
|
BONEWIDGET_OT_addCustomImage,
|
|
BONEWIDGET_OT_resetDefaultImages,
|
|
)
|
|
|
|
|
|
def register():
|
|
from bpy.utils import register_class
|
|
for cls in classes:
|
|
register_class(cls)
|
|
|
|
bpy.types.WindowManager.prop_grp = bpy.props.PointerProperty(type=BONEWIDGET_OT_sharedPropertyGroup)
|
|
|
|
|
|
def unregister():
|
|
del bpy.types.WindowManager.prop_grp
|
|
from bpy.utils import unregister_class
|
|
for cls in classes:
|
|
unregister_class(cls)
|
|
|