Add Easy_Weight
to Addons
#47
@ -1,7 +1,7 @@
|
||||
|
||||
import bpy
|
||||
from bpy.props import BoolProperty
|
||||
from . import utils
|
||||
from .utils.naming import flip_name
|
||||
|
||||
# TODO: Should find a way to select the X axis verts before doing Remove Doubles, or don't Remove Doubles at all. Also need to select the Basis shape before doing Remove Doubles.
|
||||
# TODO: Implement our own Remove Doubles algo with kdtree, which would average the vertex weights of the merged verts rather than just picking the weights of one of them at random.
|
||||
@ -17,14 +17,14 @@ def flip_driver_targets(obj):
|
||||
for D in drivers: # Capital D signifies that this is a driver container (known as a driver) rather than a driver(also known as driver) - Yes, the naming convention for drivers in python API is BAD. D=driver, d=driver.driver.
|
||||
print("Data path: " + D.data_path)
|
||||
if(sk.name in D.data_path):
|
||||
sk.vertex_group = utils.flip_name(sk.vertex_group)
|
||||
sk.vertex_group = flip_name(sk.vertex_group)
|
||||
print("Shape key: " + sk.name)
|
||||
for var in D.driver.variables:
|
||||
print("var: " + var.name)
|
||||
for t in var.targets:
|
||||
if(not t.bone_target): continue
|
||||
print("target: " + t.bone_target)
|
||||
t.bone_target = utils.flip_name(t.bone_target)
|
||||
t.bone_target = flip_name(t.bone_target)
|
||||
|
||||
class EASYWEIGHT_OT_force_apply_mirror(bpy.types.Operator):
|
||||
""" Force apply mirror modifier by duplicating the object, flipping it on the X axis, merging into the original """
|
||||
@ -100,7 +100,7 @@ class EASYWEIGHT_OT_force_apply_mirror(bpy.types.Operator):
|
||||
for vg in flipped_o.vertex_groups:
|
||||
if vg in done: continue
|
||||
old_name = vg.name
|
||||
flipped_name = utils.flip_name(vg.name)
|
||||
flipped_name = flip_name(vg.name)
|
||||
if old_name == flipped_name: continue
|
||||
|
||||
opp_vg = flipped_o.vertex_groups.get(flipped_name)
|
||||
@ -122,7 +122,7 @@ class EASYWEIGHT_OT_force_apply_mirror(bpy.types.Operator):
|
||||
for sk in keys:
|
||||
if(sk in done): continue
|
||||
old_name = sk.name
|
||||
flipped_name = utils.flip_name(sk.name)
|
||||
flipped_name = flip_name(sk.name)
|
||||
if(old_name == flipped_name): continue
|
||||
|
||||
opp_sk = keys.get(flipped_name)
|
||||
|
299
utils.py
299
utils.py
@ -1,299 +0,0 @@
|
||||
import bpy
|
||||
|
||||
# Collection of functions that are either used by other parts of the addon, or random code snippets that I wanted to include but aren't actually used.
|
||||
|
||||
class EnsureVisible:
|
||||
""" Class to ensure an object is visible, then reset it to how it was before. """
|
||||
is_visible = False
|
||||
temp_coll = None
|
||||
obj_hide = False
|
||||
obj_hide_viewport = False
|
||||
|
||||
@classmethod
|
||||
def ensure(cls, context, obj):
|
||||
# Make temporary collection so we can ensure visibility.
|
||||
if cls.is_visible:
|
||||
print(f"Could not ensure visibility of object {obj.name}. Can only ensure the visibility of one object at a time. Must Run EnsureVisible.restore()!")
|
||||
return
|
||||
|
||||
coll_name = "temp_coll"
|
||||
temp_coll = bpy.data.collections.get(coll_name)
|
||||
if not temp_coll:
|
||||
temp_coll = bpy.data.collections.new(coll_name)
|
||||
if coll_name not in context.scene.collection.children:
|
||||
context.scene.collection.children.link(temp_coll)
|
||||
|
||||
if obj.name not in temp_coll.objects:
|
||||
temp_coll.objects.link(obj)
|
||||
cls.obj_hide = obj.hide_get()
|
||||
obj.hide_set(False)
|
||||
cls.obj_hide_viewport = obj.hide_viewport
|
||||
obj.hide_viewport = False
|
||||
|
||||
cls.obj = obj
|
||||
cls.temp_coll = temp_coll
|
||||
|
||||
@classmethod
|
||||
def restore(cls, obj):
|
||||
# Delete temp collection
|
||||
if not cls.temp_coll:
|
||||
return
|
||||
cls.temp_coll = None
|
||||
|
||||
obj.hide_set(cls.obj_hide)
|
||||
obj.hide_viewport = cls.obj_hide_viewport
|
||||
|
||||
cls.obj_hide = False
|
||||
cls.obj_hide_viewport = False
|
||||
|
||||
cls.is_visible=False
|
||||
|
||||
def find_invalid_constraints(context, hidden_is_invalid=False):
|
||||
# If hidden=True, disabled constraints are considered invalid.
|
||||
o = context.object
|
||||
present = True
|
||||
|
||||
if(present):
|
||||
o.data.layers = [True] * 32
|
||||
|
||||
for b in o.pose.bones:
|
||||
if len(b.constraints) == 0:
|
||||
b.bone.hide = True
|
||||
for c in b.constraints:
|
||||
if not c.is_valid or (hidden_is_invalid and c.mute):
|
||||
b.bone.hide = False
|
||||
break
|
||||
else:
|
||||
b.bone.hide = True
|
||||
|
||||
def reset_stretch(armature):
|
||||
for b in armature.pose.bones:
|
||||
for c in b.constraints:
|
||||
if(c.type=='STRETCH_TO'):
|
||||
c.rest_length = 0
|
||||
c.use_bulge_min=True
|
||||
c.use_bulge_max=True
|
||||
c.bulge_min=1
|
||||
c.bulge_max=1
|
||||
|
||||
def assign_object_and_material_ids(start=1):
|
||||
counter = start
|
||||
|
||||
for o in bpy.context.selected_objects:
|
||||
if(o.type=='MESH'):
|
||||
o.pass_index = counter
|
||||
counter = counter + 1
|
||||
|
||||
counter = start
|
||||
for m in bpy.data.materials:
|
||||
m.pass_index = counter
|
||||
counter = counter + 1
|
||||
|
||||
def connect_parent_bones():
|
||||
# If the active object is an Armature
|
||||
# For each bone
|
||||
# If there is only one child
|
||||
# Move the tail to the child's head
|
||||
# Set Child's Connected to True
|
||||
|
||||
armature = bpy.context.object
|
||||
if(armature.type != 'ARMATURE'): return
|
||||
else:
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
for b in armature.data.edit_bones:
|
||||
if(len(b.children) == 1):
|
||||
b.tail = b.children[0].head
|
||||
#b.children[0].use_connect = True
|
||||
|
||||
def uniform_scale():
|
||||
for o in bpy.context.selected_objects:
|
||||
o.dimensions = [1, 1, 1]
|
||||
o.scale = [min(o.scale), min(o.scale), min(o.scale)]
|
||||
|
||||
def find_or_create_bone(armature, bonename, select=True):
|
||||
assert armature.mode=='EDIT', "Armature must be in edit mode"
|
||||
|
||||
bone = armature.data.edit_bones.get(bonename)
|
||||
if(not bone):
|
||||
bone = armature.data.edit_bones.new(bonename)
|
||||
bone.select = select
|
||||
return bone
|
||||
|
||||
def find_or_create_constraint(pb, ctype, name=None):
|
||||
""" Create a constraint on a bone if it doesn't exist yet.
|
||||
If a constraint with the given type already exists, just return that.
|
||||
If a name was passed, also make sure the name matches before deeming it a match and returning it.
|
||||
pb: Must be a pose bone.
|
||||
"""
|
||||
for c in pb.constraints:
|
||||
if(c.type==ctype):
|
||||
if(name):
|
||||
if(c.name==name):
|
||||
return c
|
||||
else:
|
||||
return c
|
||||
c = pb.constraints.new(type=ctype)
|
||||
if(name):
|
||||
c.name = name
|
||||
return c
|
||||
|
||||
def bone_search(armature, search=None, start=None, end=None, edit_bone=False, selected=True):
|
||||
""" Convenience function to get iterables for our for loops. """ #TODO: Could use regex.
|
||||
bone_list = []
|
||||
if(edit_bone):
|
||||
bone_list = armature.data.edit_bones
|
||||
else:
|
||||
bone_list = armature.pose.bones
|
||||
|
||||
filtered_list = []
|
||||
if(search):
|
||||
for b in bone_list:
|
||||
if search in b.name:
|
||||
if selected:
|
||||
if edit_bone:
|
||||
if b.select:
|
||||
filtered_list.append(b)
|
||||
else:
|
||||
if b.bone.select:
|
||||
filtered_list.append(b)
|
||||
else:
|
||||
filtered_list.append(b)
|
||||
elif(start):
|
||||
for b in filtered_list:
|
||||
if not b.name.startswith(start):
|
||||
filtered_list.remove(b)
|
||||
elif(end):
|
||||
for b in filtered_list:
|
||||
if not b.name.endswith(end):
|
||||
filtered_list.remove(b)
|
||||
else:
|
||||
assert False, "Nothing passed."
|
||||
|
||||
return filtered_list
|
||||
|
||||
def find_nearby_bones(armature, search_co, dist=0.0005, ebones=None):
|
||||
""" Bruteforce search for bones that are within a given distance of the given coordinates. """
|
||||
""" Active object must be an armature. """ # TODO: Let armature be passed, maybe optionally. Do some assert sanity checks.
|
||||
""" ebones: Only search in these bones. """
|
||||
|
||||
assert armature.mode=='EDIT' # TODO: Could use data.bones instead so we don't have to be in edit mode?
|
||||
ret = []
|
||||
if not ebones:
|
||||
ebones = armature.data.edit_bones
|
||||
|
||||
for eb in ebones:
|
||||
if( (eb.head - search_co).length < dist):
|
||||
ret.append(eb)
|
||||
return ret
|
||||
|
||||
def get_bone_chain(bone, ret=[]):
|
||||
""" Recursively build a list of the first children.
|
||||
bone: Can be pose/data/edit bone, doesn't matter. """
|
||||
ret.append(bone)
|
||||
if(len(bone.children) > 0):
|
||||
return get_bone_chain(bone.children[0], ret)
|
||||
return ret
|
||||
|
||||
def flip_name(from_name, only=True, must_change=False):
|
||||
# based on BLI_string_flip_side_name in https://developer.blender.org/diffusion/B/browse/master/source/blender/blenlib/intern/string_utils.c
|
||||
# If only==True, only replace the first occurrence of a side identifier in the string, eg. "Left_Eyelid.L" would become "Right_Eyelid.L". With only==False, it would instead return "Right_Eyelid.R"
|
||||
# if must_change==True, raise an error if the string couldn't be flipped.
|
||||
|
||||
l = len(from_name) # Number of characters from left to right, that we still care about. At first we care about all of them.
|
||||
|
||||
# Handling .### cases
|
||||
if("." in from_name):
|
||||
# Make sure there are only digits after the last period
|
||||
after_last_period = from_name.split(".")[-1]
|
||||
before_last_period = from_name.replace("."+after_last_period, "")
|
||||
all_digits = True
|
||||
for c in after_last_period:
|
||||
if( c not in "0123456789" ):
|
||||
all_digits = False
|
||||
break
|
||||
# If that is so, then we don't care about the characters after this last period.
|
||||
if(all_digits):
|
||||
l = len(before_last_period)
|
||||
|
||||
new_name = from_name[:l]
|
||||
|
||||
left = ['left', 'Left', 'LEFT', '.l', '.L', '_l', '_L', '-l', '-L', 'l.', 'L.', 'l_', 'L_', 'l-', 'L-']
|
||||
right_placehold = ['*rgt*', '*Rgt*', '*RGT*', '*dotl*', '*dotL*', '*underscorel*', '*underscoreL*', '*dashl*', '*dashL', '*ldot*', '*Ldot', '*lunderscore*', '*Lunderscore*', '*ldash*','*Ldash*']
|
||||
right = ['right', 'Right', 'RIGHT', '.r', '.R', '_r', '_R', '-r', '-R', 'r.', 'R.', 'r_', 'R_', 'r-', 'R-']
|
||||
|
||||
def flip_sides(list_from, list_to, new_name):
|
||||
for side_idx, side in enumerate(list_from):
|
||||
opp_side = list_to[side_idx]
|
||||
if(only):
|
||||
# Only look at prefix/suffix.
|
||||
if(new_name.startswith(side)):
|
||||
new_name = new_name[len(side):]+opp_side
|
||||
break
|
||||
elif(new_name.endswith(side)):
|
||||
new_name = new_name[:-len(side)]+opp_side
|
||||
break
|
||||
else:
|
||||
if("-" not in side and "_" not in side): # When it comes to searching the middle of a string, sides must Strictly a full word or separated with . otherwise we would catch stuff like "_leg" and turn it into "_reg".
|
||||
# Replace all occurences and continue checking for keywords.
|
||||
new_name = new_name.replace(side, opp_side)
|
||||
continue
|
||||
return new_name
|
||||
|
||||
new_name = flip_sides(left, right_placehold, new_name)
|
||||
new_name = flip_sides(right, left, new_name)
|
||||
new_name = flip_sides(right_placehold, right, new_name)
|
||||
|
||||
# Re-add trailing digits (.###)
|
||||
new_name = new_name + from_name[l:]
|
||||
|
||||
if(must_change):
|
||||
assert new_name != from_name, "Failed to flip string: " + from_name
|
||||
|
||||
return new_name
|
||||
|
||||
def copy_attributes(from_thing, to_thing, skip=[""], recursive=False):
|
||||
"""Copy attributes from one thing to another.
|
||||
from_thing: Object to copy values from. (Only if the attribute already exists in to_thing)
|
||||
to_thing: Object to copy attributes into (No new attributes are created, only existing are changed).
|
||||
skip: List of attribute names in from_thing that should not be attempted to be copied.
|
||||
recursive: Copy iterable attributes recursively.
|
||||
"""
|
||||
|
||||
#print("\nCOPYING FROM: " + str(from_thing))
|
||||
#print(".... TO: " + str(to_thing))
|
||||
|
||||
bad_stuff = skip + ['active', 'bl_rna', 'error_location', 'error_rotation']
|
||||
for prop in dir(from_thing):
|
||||
if "__" in prop: continue
|
||||
if(prop in bad_stuff): continue
|
||||
|
||||
if(hasattr(to_thing, prop)):
|
||||
from_value = getattr(from_thing, prop)
|
||||
# Iterables should be copied recursively, except str.
|
||||
if recursive and type(from_value) not in [str]:
|
||||
# NOTE: I think This will infinite loop if a CollectionProperty contains a reference to itself!
|
||||
warn = False
|
||||
try:
|
||||
# Determine if the property is iterable. Otherwise this throws TypeError.
|
||||
iter(from_value)
|
||||
|
||||
to_value = getattr(to_thing, prop)
|
||||
# The thing we are copying to must therefore be an iterable as well. If this fails though, we should throw a warning.
|
||||
warn = True
|
||||
iter(to_value)
|
||||
count = min(len(to_value), len(from_value))
|
||||
for i in range(0, count):
|
||||
copy_attributes(from_value[i], to_value[i], skip, recursive)
|
||||
except TypeError: # Not iterable.
|
||||
if warn:
|
||||
print("WARNING: Could not copy attributes from iterable to non-iterable field: " + prop +
|
||||
"\nFrom object: " + str(from_thing) +
|
||||
"\nTo object: " + str(to_thing)
|
||||
)
|
||||
|
||||
# Copy the attribute.
|
||||
try:
|
||||
setattr(to_thing, prop, from_value)
|
||||
#print(prop + ": " + str(from_value))
|
||||
except AttributeError: # Read-Only properties throw AttributeError. We ignore silently, which is not great.
|
||||
continue
|
Loading…
Reference in New Issue
Block a user