2025-07-01
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from .mainFunctions import *
|
||||
from .jsonFunctions import *
|
||||
from .previewFunctions import *
|
||||
@@ -0,0 +1,291 @@
|
||||
import bpy
|
||||
import os
|
||||
import json
|
||||
import numpy
|
||||
from .. import __package__
|
||||
|
||||
JSON_DEFAULT_WIDGETS = "widgets.json"
|
||||
JSON_USER_WIDGETS = "user_widgets.json"
|
||||
|
||||
widget_data = {}
|
||||
|
||||
|
||||
def objectDataToDico(object, custom_image):
|
||||
verts = []
|
||||
depsgraph = bpy.context.evaluated_depsgraph_get()
|
||||
mesh = object.evaluated_get(depsgraph).to_mesh()
|
||||
for v in mesh.vertices:
|
||||
verts.append(tuple(numpy.array(tuple(v.co)) *
|
||||
(object.scale[0], object.scale[1], object.scale[2])))
|
||||
|
||||
polygons = []
|
||||
for p in mesh.polygons:
|
||||
polygons.append(tuple(p.vertices))
|
||||
|
||||
edges = []
|
||||
for e in mesh.edges:
|
||||
edges.append(e.key)
|
||||
|
||||
custom_image = custom_image if custom_image != "" else "user_defined.png"
|
||||
|
||||
wgts = {"vertices": verts, "edges": edges, "faces": polygons, "image": custom_image}
|
||||
|
||||
return(wgts)
|
||||
|
||||
|
||||
def readWidgets(filename = ""):
|
||||
global widget_data
|
||||
wgts = {}
|
||||
|
||||
if not filename:
|
||||
files = [JSON_DEFAULT_WIDGETS, JSON_USER_WIDGETS]
|
||||
else:
|
||||
files = [filename]
|
||||
|
||||
for file in files:
|
||||
jsonFile = os.path.join(os.path.dirname(os.path.dirname(__file__)), file)
|
||||
if os.path.exists(jsonFile):
|
||||
f = open(jsonFile, 'r')
|
||||
wgts.update(json.load(f))
|
||||
f.close()
|
||||
|
||||
if not filename: # if both files have been read
|
||||
widget_data = wgts.copy()
|
||||
|
||||
return (wgts)
|
||||
|
||||
|
||||
def getWidgetData(widget):
|
||||
return widget_data[widget]
|
||||
|
||||
|
||||
def writeWidgets(wgts, file):
|
||||
jsonFile = os.path.join(os.path.dirname(os.path.dirname(__file__)), file)
|
||||
#if os.path.exists(jsonFile):
|
||||
f = open(jsonFile, 'w')
|
||||
f.write(json.dumps(wgts))
|
||||
f.close()
|
||||
|
||||
|
||||
def addRemoveWidgets(context, addOrRemove, items, widgets, widget_name="", custom_image=""):
|
||||
wgts = {}
|
||||
|
||||
# file from where the widget should be read or written to
|
||||
file = JSON_USER_WIDGETS
|
||||
|
||||
widget_items = []
|
||||
for widget_item in items:
|
||||
widget_items.append(widget_item[1])
|
||||
|
||||
activeShape = None
|
||||
ob_name = None
|
||||
return_message = ""
|
||||
if addOrRemove == 'add':
|
||||
wgts = readWidgets(file)
|
||||
bw_widget_prefix = bpy.context.preferences.addons[__package__].preferences.widget_prefix
|
||||
for ob in widgets:
|
||||
if not widget_name:
|
||||
if ob.name.startswith(bw_widget_prefix):
|
||||
ob_name = ob.name[len(bw_widget_prefix):]
|
||||
else:
|
||||
ob_name = ob.name
|
||||
else:
|
||||
ob_name = widget_name
|
||||
|
||||
if (ob_name) not in widget_items:
|
||||
widget_items.append(ob_name)
|
||||
wgts[ob_name] = objectDataToDico(ob, custom_image)
|
||||
activeShape = ob_name
|
||||
return_message = "Widget - " + ob_name + " has been added!"
|
||||
|
||||
elif addOrRemove == 'remove':
|
||||
user_widgets = readWidgets(file)
|
||||
if widgets in user_widgets:
|
||||
wgts = user_widgets
|
||||
else:
|
||||
file = JSON_DEFAULT_WIDGETS
|
||||
wgts = readWidgets(file)
|
||||
|
||||
del wgts[widgets]
|
||||
if widgets in widget_items:
|
||||
widget_index = widget_items.index(widgets)
|
||||
activeShape = widget_items[widget_index + 1] if widget_index == 0 else widget_items[widget_index - 1]
|
||||
widget_items.remove(widgets)
|
||||
return_message = "Widget - " + widgets + " has been removed!"
|
||||
|
||||
if activeShape is not None:
|
||||
|
||||
writeWidgets(wgts, file)
|
||||
|
||||
# to handle circular import error
|
||||
from .functions import createPreviewCollection
|
||||
|
||||
createPreviewCollection()
|
||||
|
||||
# trigger an update and display widget
|
||||
bpy.context.window_manager.widget_list = activeShape
|
||||
|
||||
return 'INFO', return_message
|
||||
elif ob_name is not None:
|
||||
return 'WARNING', "Widget - " + ob_name + " already exists!"
|
||||
|
||||
|
||||
def exportWidgetLibrary(filepath):
|
||||
wgts = readWidgets(JSON_USER_WIDGETS)
|
||||
|
||||
if wgts:
|
||||
# variables needed for exporting widgets
|
||||
dest_dir = os.path.dirname(filepath)
|
||||
json_dir = os.path.dirname(os.path.dirname(__file__))
|
||||
image_folder = 'custom_thumbnails'
|
||||
custom_image_dir = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', image_folder))
|
||||
|
||||
filename = os.path.basename(filepath)
|
||||
if not filename: filename = "widgetLibrary.zip"
|
||||
elif not filename.endswith('.zip'): filename += ".zip"
|
||||
|
||||
# start the zipping process
|
||||
from zipfile import ZipFile
|
||||
with ZipFile(os.path.join(dest_dir, filename), "w") as zip:
|
||||
# write the json file
|
||||
file = os.path.join(json_dir, JSON_USER_WIDGETS)
|
||||
arcname = os.path.basename(file)
|
||||
zip.write(file, arcname=arcname)
|
||||
|
||||
# write the custom images if present
|
||||
if os.path.exists(custom_image_dir):
|
||||
from pathlib import Path
|
||||
for filepath in Path(custom_image_dir).iterdir():
|
||||
arcname = os.path.join(image_folder, os.path.basename(filepath))
|
||||
zip.write(filepath, arcname=arcname)
|
||||
|
||||
return len(wgts)
|
||||
|
||||
|
||||
class WidgetImportStats:
|
||||
def __init__(self):
|
||||
self.new_widgets = 0
|
||||
self.failed_widgets = {}
|
||||
self.skipped_widgets = [] # to make sure the data won't change order
|
||||
self.widgets = {}
|
||||
|
||||
def skipped(self):
|
||||
return len(self.skipped_widgets)
|
||||
|
||||
def failed(self):
|
||||
return len(self.failed_widgets)
|
||||
|
||||
|
||||
def importWidgetLibrary(filepath, action=""):
|
||||
wgts = {}
|
||||
|
||||
from zipfile import ZipFile
|
||||
dest_dir = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..'))
|
||||
|
||||
if os.path.exists(filepath) and action:
|
||||
try:
|
||||
with ZipFile(filepath, 'r') as zip_file:
|
||||
# extract images
|
||||
for file in zip_file.namelist():
|
||||
if file.startswith('custom_thumbnails/'):
|
||||
zip_file.extract(file, dest_dir)
|
||||
elif file.endswith('.json'): # extract data from the .json file
|
||||
f = zip_file.read(file)
|
||||
json_data = f.decode('utf8').replace("'", '"')
|
||||
wgts = json.loads(json_data)
|
||||
|
||||
current_wgts = readWidgets(JSON_USER_WIDGETS)
|
||||
|
||||
widgetImport = WidgetImportStats()
|
||||
|
||||
# check for duplicate names
|
||||
for name, data in sorted(wgts.items()): # sorting by keys
|
||||
if action == "ASK":
|
||||
widgetImport.skipped_widgets.append({name : data})
|
||||
elif action == "OVERWRITE":
|
||||
widgetImport.widgets.update({name : data})
|
||||
widgetImport.new_widgets += 1
|
||||
elif action == "SKIP":
|
||||
if not name in current_wgts:
|
||||
widgetImport.widgets.update({name : data})
|
||||
widgetImport.new_widgets += 1
|
||||
else:
|
||||
widgetImport.skipped_widgets.append({name : data})
|
||||
else:
|
||||
widgetImport.failed_widgets.update({name : data})
|
||||
|
||||
except:
|
||||
pass
|
||||
return widgetImport
|
||||
|
||||
|
||||
def updateWidgetLibrary(new_widgets, new_images, zip_filepath):
|
||||
current_widget = bpy.context.window_manager.widget_list
|
||||
wgts = readWidgets(JSON_USER_WIDGETS)
|
||||
|
||||
wgts.update(new_widgets)
|
||||
|
||||
writeWidgets(wgts, JSON_USER_WIDGETS)
|
||||
|
||||
# extract any images needed from zip library
|
||||
if new_images:
|
||||
from zipfile import ZipFile
|
||||
dest_dir = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..'))
|
||||
if os.path.exists(zip_filepath):
|
||||
try:
|
||||
with ZipFile(zip_filepath, 'r') as zip_file:
|
||||
for file in zip_file.namelist():
|
||||
if file.startswith('custom_thumbnails/') and file.split("/")[1] in new_images:
|
||||
zip_file.extract(file, dest_dir)
|
||||
except:
|
||||
pass
|
||||
|
||||
# update the preview panel
|
||||
from .functions import createPreviewCollection
|
||||
createPreviewCollection()
|
||||
|
||||
# trigger an update and display original but updated widget
|
||||
bpy.context.window_manager.widget_list = current_widget
|
||||
|
||||
|
||||
def updateCustomImage(image_name):
|
||||
current_widget = bpy.context.window_manager.widget_list
|
||||
current_widget_data = getWidgetData(current_widget)
|
||||
|
||||
# swap out the image
|
||||
current_widget_data['image'] = image_name
|
||||
|
||||
# update and write the new data
|
||||
wgts = readWidgets(JSON_USER_WIDGETS)
|
||||
if current_widget in wgts:
|
||||
wgts[current_widget] = current_widget_data
|
||||
writeWidgets(wgts, JSON_USER_WIDGETS)
|
||||
else:
|
||||
wgts = readWidgets(JSON_DEFAULT_WIDGETS)
|
||||
wgts[current_widget] = current_widget_data
|
||||
writeWidgets(wgts, JSON_DEFAULT_WIDGETS)
|
||||
|
||||
# update the preview panel
|
||||
from .functions import createPreviewCollection
|
||||
createPreviewCollection()
|
||||
|
||||
# trigger an update and display original but updated widget
|
||||
bpy.context.window_manager.widget_list = current_widget
|
||||
|
||||
|
||||
def resetDefaultImages():
|
||||
current_widget = bpy.context.window_manager.widget_list
|
||||
wgts = readWidgets(JSON_DEFAULT_WIDGETS)
|
||||
|
||||
for name, data in wgts.items():
|
||||
image = f"{name}.png"
|
||||
data["image"] = image
|
||||
|
||||
writeWidgets(wgts, JSON_DEFAULT_WIDGETS)
|
||||
|
||||
# update the preview panel
|
||||
from .functions import createPreviewCollection
|
||||
createPreviewCollection()
|
||||
|
||||
# trigger an update and display original but updated widget
|
||||
bpy.context.window_manager.widget_list = current_widget
|
||||
@@ -0,0 +1,482 @@
|
||||
import bpy
|
||||
import numpy
|
||||
from mathutils import Matrix
|
||||
from .. import __package__
|
||||
|
||||
|
||||
def getCollection(context):
|
||||
#check user preferences for the name of the collection
|
||||
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
|
||||
|
||||
collection = recurLayerCollection(context.scene.collection, bw_collection_name)
|
||||
if collection: # if it already exists
|
||||
return collection
|
||||
|
||||
collection = bpy.data.collections.get(bw_collection_name)
|
||||
|
||||
if collection: # if it exists but not linked to scene
|
||||
context.scene.collection.children.link(collection)
|
||||
return collection
|
||||
|
||||
else: # create a new collection
|
||||
collection = bpy.data.collections.new(bw_collection_name)
|
||||
context.scene.collection.children.link(collection)
|
||||
# hide new collection
|
||||
viewlayer_collection = context.view_layer.layer_collection.children[collection.name]
|
||||
viewlayer_collection.hide_viewport = True
|
||||
return collection
|
||||
|
||||
|
||||
def recurLayerCollection(layer_collection, collection_name):
|
||||
found = None
|
||||
if (layer_collection.name == collection_name):
|
||||
return layer_collection
|
||||
for layer in layer_collection.children:
|
||||
found = recurLayerCollection(layer, collection_name)
|
||||
if found:
|
||||
return found
|
||||
|
||||
|
||||
def getViewLayerCollection(context, widget = None):
|
||||
widget_collection = bpy.data.collections[bpy.data.objects[widget.name].users_collection[0].name]
|
||||
#save current active layer_collection
|
||||
saved_layer_collection = bpy.context.view_layer.layer_collection
|
||||
# actually find the view_layer we want
|
||||
layer_collection = recurLayerCollection(saved_layer_collection, widget_collection.name)
|
||||
# make sure the collection (data level) is not hidden
|
||||
widget_collection.hide_viewport = False
|
||||
|
||||
# change the active view layer
|
||||
bpy.context.view_layer.active_layer_collection = layer_collection
|
||||
# make sure it isn't excluded so it can be edited
|
||||
layer_collection.exclude = False
|
||||
#return the active view layer to what it was
|
||||
bpy.context.view_layer.active_layer_collection = saved_layer_collection
|
||||
|
||||
return layer_collection
|
||||
|
||||
|
||||
def boneMatrix(widget, matchBone):
|
||||
if widget == None:
|
||||
return
|
||||
widget.matrix_local = matchBone.bone.matrix_local
|
||||
widget.matrix_world = matchBone.id_data.matrix_world @ matchBone.bone.matrix_local
|
||||
if matchBone.custom_shape_transform:
|
||||
#if it has a tranform override, apply this to the widget loc and rot
|
||||
org_scale = widget.matrix_world.to_scale()
|
||||
org_scale_mat = Matrix.Scale(1, 4, org_scale)
|
||||
target_matrix = matchBone.custom_shape_transform.id_data.matrix_world @ matchBone.custom_shape_transform.bone.matrix_local
|
||||
loc = target_matrix.to_translation()
|
||||
loc_mat = Matrix.Translation(loc)
|
||||
rot = target_matrix.to_euler().to_matrix()
|
||||
widget.matrix_world = loc_mat @ rot.to_4x4() @ org_scale_mat
|
||||
|
||||
if matchBone.use_custom_shape_bone_size:
|
||||
ob_scale = bpy.context.scene.objects[matchBone.id_data.name].scale
|
||||
widget.scale = [matchBone.bone.length * ob_scale[0], matchBone.bone.length * ob_scale[1], matchBone.bone.length * ob_scale[2]]
|
||||
|
||||
# if the user has added any custom transforms to the bone widget display - calculate this too
|
||||
loc = matchBone.custom_shape_translation
|
||||
rot = matchBone.custom_shape_rotation_euler
|
||||
scale = matchBone.custom_shape_scale_xyz
|
||||
widget.scale *= scale
|
||||
widget.matrix_world = widget.matrix_world @ Matrix.LocRotScale(loc , rot, widget.scale)
|
||||
|
||||
widget.data.update()
|
||||
|
||||
|
||||
def fromWidgetFindBone(widget):
|
||||
matchBone = None
|
||||
for ob in bpy.context.scene.objects:
|
||||
if ob.type == "ARMATURE":
|
||||
for bone in ob.pose.bones:
|
||||
if bone.custom_shape == widget:
|
||||
matchBone = bone
|
||||
return matchBone
|
||||
|
||||
|
||||
def createWidget(bone, widget, relative, size, scale, slide, rotation, collection, use_face_data, wireframe_width):
|
||||
C = bpy.context
|
||||
D = bpy.data
|
||||
|
||||
if not C.preferences.addons[__package__].preferences.use_rigify_defaults:
|
||||
bw_widget_prefix = C.preferences.addons[__package__].preferences.widget_prefix
|
||||
else:
|
||||
bw_widget_prefix = "WGT-" + C.active_object.name + "_"
|
||||
|
||||
matrixBone = bone
|
||||
|
||||
# delete the existing shape
|
||||
if bone.custom_shape:
|
||||
bpy.data.objects.remove(bpy.data.objects[bone.custom_shape.name], do_unlink=True)
|
||||
|
||||
# make the data name include the prefix
|
||||
newData = D.meshes.new(bw_widget_prefix + bone.name)
|
||||
|
||||
bone.use_custom_shape_bone_size = relative
|
||||
|
||||
# deal with face data
|
||||
faces = widget['faces'] if use_face_data else []
|
||||
|
||||
# add the verts
|
||||
newData.from_pydata(numpy.array(widget['vertices']) * [size[0] * scale[0], size[1] * scale[2],
|
||||
size[2] * scale[1]], widget['edges'], faces)
|
||||
|
||||
# Create tranform matrices (slide vector and rotation)
|
||||
widget_matrix = Matrix()
|
||||
trans = Matrix.Translation(slide)
|
||||
rot = rotation.to_matrix().to_4x4()
|
||||
|
||||
# Translate then rotate the matrix
|
||||
widget_matrix = widget_matrix @ trans
|
||||
widget_matrix = widget_matrix @ rot
|
||||
|
||||
# transform the widget with this matrix
|
||||
newData.transform(widget_matrix)
|
||||
|
||||
newData.update(calc_edges=True)
|
||||
|
||||
newObject = D.objects.new(bw_widget_prefix + bone.name, newData)
|
||||
|
||||
newObject.data = newData
|
||||
newObject.name = bw_widget_prefix + bone.name
|
||||
collection.objects.link(newObject)
|
||||
|
||||
newObject.matrix_world = bpy.context.active_object.matrix_world @ matrixBone.bone.matrix_local
|
||||
newObject.scale = [matrixBone.bone.length, matrixBone.bone.length, matrixBone.bone.length]
|
||||
layer = bpy.context.view_layer
|
||||
layer.update()
|
||||
|
||||
bone.custom_shape = newObject
|
||||
bone.bone.show_wire = not use_face_data # show faces if use face data is enabled
|
||||
|
||||
if bpy.app.version >= (4,2,0):
|
||||
bone.custom_shape_wire_width = wireframe_width
|
||||
|
||||
|
||||
def symmetrizeWidget(bone, collection):
|
||||
C = bpy.context
|
||||
D = bpy.data
|
||||
|
||||
if not C.preferences.addons[__package__].preferences.use_rigify_defaults:
|
||||
bw_widget_prefix = C.preferences.addons[__package__].preferences.widget_prefix
|
||||
rigify_object_name = ''
|
||||
else:
|
||||
bw_widget_prefix = "WGT-"
|
||||
rigify_object_name = C.active_object.name + "_"
|
||||
|
||||
widget = bone.custom_shape
|
||||
|
||||
if findMirrorObject(bone):
|
||||
mirrorBone = findMirrorObject(bone)
|
||||
|
||||
mirrorWidget = mirrorBone.custom_shape
|
||||
|
||||
if mirrorWidget is not None:
|
||||
if mirrorWidget != widget:
|
||||
if C.scene.objects.get(mirrorWidget.name):
|
||||
D.objects.remove(mirrorWidget)
|
||||
|
||||
newData = widget.data.copy()
|
||||
for vert in newData.vertices:
|
||||
vert.co = numpy.array(vert.co) * (-1, 1, 1)
|
||||
|
||||
newObject = widget.copy()
|
||||
newObject.data = newData
|
||||
newData.update()
|
||||
newObject.name = bw_widget_prefix + rigify_object_name + findMirrorObject(bone).name
|
||||
D.collections[collection.name].objects.link(newObject)
|
||||
|
||||
#if there is a override transform, use that bone matrix in the next step
|
||||
if findMirrorObject(bone).custom_shape_transform:
|
||||
mirrorBone = findMirrorObject(bone).custom_shape_transform
|
||||
|
||||
newObject.matrix_local = mirrorBone.bone.matrix_local
|
||||
newObject.scale = [mirrorBone.bone.length, mirrorBone.bone.length, mirrorBone.bone.length]
|
||||
newObject.data.flip_normals()
|
||||
|
||||
layer = bpy.context.view_layer
|
||||
layer.update()
|
||||
|
||||
findMirrorObject(bone).custom_shape = newObject
|
||||
mirrorBone.bone.show_wire = bone.bone.show_wire
|
||||
|
||||
if bpy.app.version >= (4,2,0):
|
||||
mirrorBone.custom_shape_wire_width = bone.custom_shape_wire_width
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
def symmetrizeWidget_helper(bone, collection, activeObject, widgetsAndBones):
|
||||
C = bpy.context
|
||||
|
||||
bw_symmetry_suffix = C.preferences.addons[__package__].preferences.symmetry_suffix
|
||||
bw_symmetry_suffix = bw_symmetry_suffix.split(";")
|
||||
|
||||
suffix_1 = bw_symmetry_suffix[0].replace(" ", "")
|
||||
suffix_2 = bw_symmetry_suffix[1].replace(" ", "")
|
||||
|
||||
if activeObject.name.endswith(suffix_1):
|
||||
if bone.name.endswith(suffix_1) and widgetsAndBones[bone]:
|
||||
symmetrizeWidget(bone, collection)
|
||||
elif activeObject.name.endswith(suffix_2):
|
||||
if bone.name.endswith(suffix_2) and widgetsAndBones[bone]:
|
||||
symmetrizeWidget(bone, collection)
|
||||
|
||||
|
||||
def deleteUnusedWidgets():
|
||||
C = bpy.context
|
||||
D = bpy.data
|
||||
|
||||
if not C.preferences.addons[__package__].preferences.use_rigify_defaults:
|
||||
bw_collection_name = C.preferences.addons[__package__].preferences.bonewidget_collection_name
|
||||
else:
|
||||
bw_collection_name = 'WGTS_' + C.active_object.name
|
||||
|
||||
collection = recurLayerCollection(C.scene.collection, bw_collection_name)
|
||||
widgetList = []
|
||||
|
||||
for ob in D.objects:
|
||||
if ob.type == 'ARMATURE':
|
||||
for bone in ob.pose.bones:
|
||||
if bone.custom_shape:
|
||||
widgetList.append(bone.custom_shape)
|
||||
|
||||
unwantedList = [
|
||||
ob for ob in collection.all_objects if ob not in widgetList]
|
||||
# save the current context mode
|
||||
mode = C.mode
|
||||
# jump into object mode
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
# delete unwanted widgets
|
||||
for ob in unwantedList:
|
||||
bpy.data.objects.remove(bpy.data.objects[ob.name], do_unlink=True)
|
||||
# jump back to current mode
|
||||
bpy.ops.object.mode_set(mode=mode)
|
||||
return
|
||||
|
||||
|
||||
def editWidget(active_bone):
|
||||
C = bpy.context
|
||||
D = bpy.data
|
||||
widget = active_bone.custom_shape
|
||||
|
||||
collection = getViewLayerCollection(C, widget)
|
||||
collection.hide_viewport = False
|
||||
|
||||
# hide all other objects in collection
|
||||
for obj in collection.collection.all_objects:
|
||||
if obj.name != widget.name:
|
||||
obj.hide_set(True)
|
||||
else:
|
||||
obj.hide_set(False) # in case user manually hid it
|
||||
|
||||
armature = active_bone.id_data
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
C.active_object.select_set(False)
|
||||
|
||||
if C.space_data.local_view:
|
||||
bpy.ops.view3d.localview()
|
||||
|
||||
# select object and make it active
|
||||
widget.select_set(True)
|
||||
bpy.context.view_layer.objects.active = widget
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.context.tool_settings.mesh_select_mode = (True, False, False) # enter vertex mode
|
||||
|
||||
|
||||
def returnToArmature(widget):
|
||||
C = bpy.context
|
||||
D = bpy.data
|
||||
|
||||
bone = fromWidgetFindBone(widget)
|
||||
armature = bone.id_data
|
||||
|
||||
if C.active_object.mode == 'EDIT':
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
|
||||
collection = getViewLayerCollection(C, widget)
|
||||
collection.hide_viewport = True
|
||||
|
||||
# unhide all objects in the collection
|
||||
for obj in collection.collection.all_objects:
|
||||
obj.hide_set(False)
|
||||
|
||||
if C.space_data.local_view:
|
||||
bpy.ops.view3d.localview()
|
||||
|
||||
bpy.context.view_layer.objects.active = armature
|
||||
armature.select_set(True)
|
||||
bpy.ops.object.mode_set(mode='POSE')
|
||||
armature.data.bones[bone.name].select = True
|
||||
armature.data.bones.active = armature.data.bones[bone.name]
|
||||
|
||||
|
||||
def findMirrorObject(object):
|
||||
C = bpy.context
|
||||
D = bpy.data
|
||||
|
||||
bw_symmetry_suffix = C.preferences.addons[__package__].preferences.symmetry_suffix
|
||||
bw_symmetry_suffix = bw_symmetry_suffix.split(";")
|
||||
|
||||
suffix_1 = bw_symmetry_suffix[0].replace(" ", "")
|
||||
suffix_2 = bw_symmetry_suffix[1].replace(" ", "")
|
||||
|
||||
if object.name.endswith(suffix_1):
|
||||
suffix = suffix_2
|
||||
suffix_length = len(suffix_1)
|
||||
|
||||
elif object.name.endswith(suffix_2):
|
||||
suffix = suffix_1
|
||||
suffix_length = len(suffix_2)
|
||||
|
||||
elif object.name.endswith(suffix_1.lower()):
|
||||
suffix = suffix_2.lower()
|
||||
suffix_length = len(suffix_1)
|
||||
elif object.name.endswith(suffix_2.lower()):
|
||||
suffix = suffix_1.lower()
|
||||
suffix_length = len(suffix_2)
|
||||
else: # what if the widget ends in .001?
|
||||
print('Object suffix unknown, using blank')
|
||||
suffix = ''
|
||||
|
||||
objectName = list(object.name)
|
||||
objectBaseName = objectName[:-suffix_length]
|
||||
mirroredObjectName = "".join(objectBaseName) + suffix
|
||||
|
||||
if object.id_data.type == 'ARMATURE':
|
||||
return object.id_data.pose.bones.get(mirroredObjectName)
|
||||
else:
|
||||
return bpy.context.scene.objects.get(mirroredObjectName)
|
||||
|
||||
|
||||
def findMatchBones():
|
||||
C = bpy.context
|
||||
D = bpy.data
|
||||
|
||||
bw_symmetry_suffix = C.preferences.addons[__package__].preferences.symmetry_suffix
|
||||
bw_symmetry_suffix = bw_symmetry_suffix.split(";")
|
||||
|
||||
suffix_1 = bw_symmetry_suffix[0].replace(" ", "")
|
||||
suffix_2 = bw_symmetry_suffix[1].replace(" ", "")
|
||||
|
||||
widgetsAndBones = {}
|
||||
|
||||
if bpy.context.object.type == 'ARMATURE':
|
||||
for bone in C.selected_pose_bones:
|
||||
if bone.name.endswith(suffix_1) or bone.name.endswith(suffix_2):
|
||||
widgetsAndBones[bone] = bone.custom_shape
|
||||
mirrorBone = findMirrorObject(bone)
|
||||
if mirrorBone:
|
||||
widgetsAndBones[mirrorBone] = mirrorBone.custom_shape
|
||||
|
||||
armature = bpy.context.object
|
||||
activeObject = C.active_pose_bone
|
||||
else:
|
||||
for shape in C.selected_objects:
|
||||
bone = fromWidgetFindBone(shape)
|
||||
if bone.name.endswith(("L","R")):
|
||||
widgetsAndBones[fromWidgetFindBone(shape)] = shape
|
||||
|
||||
mirrorShape = findMirrorObject(shape)
|
||||
if mirrorShape:
|
||||
widgetsAndBones[mirrorShape] = mirrorShape
|
||||
|
||||
activeObject = fromWidgetFindBone(C.object)
|
||||
armature = activeObject.id_data
|
||||
return (widgetsAndBones, activeObject, armature)
|
||||
|
||||
|
||||
def resyncWidgetNames():
|
||||
C = bpy.context
|
||||
D = bpy.data
|
||||
|
||||
if not C.preferences.addons[__package__].preferences.use_rigify_defaults:
|
||||
bw_collection_name = C.preferences.addons[__package__].preferences.bonewidget_collection_name
|
||||
bw_widget_prefix = C.preferences.addons[__package__].preferences.widget_prefix
|
||||
else:
|
||||
bw_collection_name = 'WGTS_' + C.active_object.name
|
||||
bw_widget_prefix = 'WGT-' + C.active_object.name + '_'
|
||||
|
||||
widgetsAndBones = {}
|
||||
|
||||
if bpy.context.object.type == 'ARMATURE':
|
||||
for bone in C.active_object.pose.bones:
|
||||
if bone.custom_shape:
|
||||
widgetsAndBones[bone] = bone.custom_shape
|
||||
|
||||
for k, v in widgetsAndBones.items():
|
||||
if k.name != (bw_widget_prefix + k.name):
|
||||
D.objects[v.name].name = str(bw_widget_prefix + k.name)
|
||||
|
||||
|
||||
def clearBoneWidgets():
|
||||
C = bpy.context
|
||||
D = bpy.data
|
||||
|
||||
if bpy.context.object.type == 'ARMATURE':
|
||||
for bone in C.selected_pose_bones:
|
||||
if bone.custom_shape:
|
||||
bone.custom_shape = None
|
||||
bone.custom_shape_transform = None
|
||||
|
||||
|
||||
def addObjectAsWidget(context, collection):
|
||||
selected_objects = bpy.context.selected_objects
|
||||
|
||||
if len(selected_objects) != 2:
|
||||
print('Only a widget object and the pose bone(s)')
|
||||
return{'FINISHED'}
|
||||
|
||||
allowed_object_types = ['MESH','CURVE']
|
||||
|
||||
widget_object = None
|
||||
|
||||
for ob in selected_objects:
|
||||
if ob.type in allowed_object_types:
|
||||
widget_object = ob
|
||||
|
||||
if widget_object:
|
||||
active_bone = context.active_pose_bone
|
||||
|
||||
# deal with any existing shape
|
||||
if active_bone.custom_shape:
|
||||
bpy.data.objects.remove(bpy.data.objects[active_bone.custom_shape.name], do_unlink=True)
|
||||
|
||||
#duplicate shape
|
||||
widget = widget_object.copy()
|
||||
widget.data = widget.data.copy()
|
||||
# reamame it
|
||||
bw_widget_prefix = context.preferences.addons[__package__].preferences.widget_prefix
|
||||
widget_name = bw_widget_prefix + active_bone.name
|
||||
widget.name = widget_name
|
||||
widget.data.name = widget_name
|
||||
# link it
|
||||
collection.objects.link(widget)
|
||||
|
||||
# match transforms
|
||||
widget.matrix_world = bpy.context.active_object.matrix_world @ active_bone.bone.matrix_local
|
||||
widget.scale = [active_bone.bone.length, active_bone.bone.length, active_bone.bone.length]
|
||||
layer = bpy.context.view_layer
|
||||
layer.update()
|
||||
|
||||
active_bone.custom_shape = widget
|
||||
active_bone.bone.show_wire = True
|
||||
|
||||
#deselect original object
|
||||
widget_object.select_set(False)
|
||||
|
||||
|
||||
def advanced_options_toggled(self, context):
|
||||
if self.advanced_options:
|
||||
self.global_size_advanced = (self.global_size_simple,) * 3
|
||||
self.slide_advanced[1] = self.slide_simple
|
||||
else:
|
||||
self.global_size_simple = self.global_size_advanced[1]
|
||||
self.slide_simple = self.slide_advanced[1]
|
||||
@@ -0,0 +1,108 @@
|
||||
import bpy
|
||||
import bpy.utils.previews
|
||||
from .jsonFunctions import readWidgets, JSON_USER_WIDGETS
|
||||
import os
|
||||
from .. import __package__
|
||||
|
||||
preview_collections = {}
|
||||
|
||||
|
||||
def createPreviewCollection():
|
||||
if preview_collections:
|
||||
del bpy.types.WindowManager.widget_list
|
||||
for pcoll in preview_collections.values():
|
||||
bpy.utils.previews.remove(pcoll)
|
||||
preview_collections.clear()
|
||||
|
||||
pcoll = bpy.utils.previews.new()
|
||||
pcoll.widget_list = ()
|
||||
preview_collections["widgets"] = pcoll
|
||||
|
||||
bpy.types.WindowManager.widget_list = bpy.props.EnumProperty(
|
||||
items=generate_previews(), name="Shape", description="Shape", update=preview_update
|
||||
)
|
||||
|
||||
|
||||
def generate_previews():
|
||||
enum_items = []
|
||||
|
||||
pcoll = preview_collections["widgets"]
|
||||
if pcoll.widget_list:
|
||||
return pcoll.widget_list
|
||||
|
||||
#directory = os.path.join(os.path.dirname(__file__), "thumbnails")
|
||||
directory = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'thumbnails'))
|
||||
custom_directory = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'custom_thumbnails'))
|
||||
|
||||
if directory and os.path.exists(directory):
|
||||
widget_data = {item[0]: item[1].get("image", "missing_image.png") for item in readWidgets().items()}
|
||||
widget_names = sorted(widget_data.keys())
|
||||
|
||||
for i, name in enumerate(widget_names):
|
||||
image = widget_data.get(name, "")
|
||||
filepath = os.path.join(directory, image)
|
||||
|
||||
# try in custom_thumbnails if above failed
|
||||
if not os.path.exists(filepath):
|
||||
filepath = os.path.join(custom_directory, image)
|
||||
|
||||
# if image still not found, let the user know
|
||||
if not os.path.exists(filepath):
|
||||
filepath = os.path.join(directory, "missing_image.png")
|
||||
|
||||
icon = pcoll.get(name)
|
||||
if not icon:
|
||||
thumb = pcoll.load(name, filepath, 'IMAGE')
|
||||
else:
|
||||
thumb = pcoll[name]
|
||||
enum_items.append((name, name, "", thumb.icon_id, i))
|
||||
|
||||
pcoll.widget_list = enum_items
|
||||
return enum_items
|
||||
|
||||
|
||||
def preview_update(self, context):
|
||||
generate_previews()
|
||||
|
||||
|
||||
def get_preview_default():
|
||||
return bpy.context.preferences.addons[__package__].preferences.preview_default
|
||||
|
||||
|
||||
def copyCustomImage(filepath, filename):
|
||||
if os.path.exists(filepath):
|
||||
image_directory = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'custom_thumbnails'))
|
||||
destination_path = os.path.join(image_directory, filename)
|
||||
|
||||
try:
|
||||
# create custom thumbnail folder if not existing
|
||||
if not os.path.exists(image_directory):
|
||||
os.makedirs(image_directory)
|
||||
|
||||
import shutil
|
||||
shutil.copyfile(filepath, destination_path)
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def removeCustomImage(filename):
|
||||
image_directory = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'custom_thumbnails'))
|
||||
destination_path = os.path.join(image_directory, filename)
|
||||
|
||||
if os.path.isfile(destination_path):
|
||||
# make sure the image is only used once - else stop
|
||||
count = 0
|
||||
for v in readWidgets(JSON_USER_WIDGETS).values():
|
||||
if v.get("image") == filename:
|
||||
count += 1
|
||||
if count > 1:
|
||||
return False
|
||||
|
||||
try:
|
||||
os.remove(destination_path)
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
Reference in New Issue
Block a user