Skip to content

Commit

Permalink
Merge pull request #360 from SleepyEngi/master
Browse files Browse the repository at this point in the history
Added "Merge identical nodes" and "Apply armature modifier" options to the UI and the rig edit generator function
  • Loading branch information
HENDRIX-ZT2 authored Nov 13, 2023
2 parents 1da02ac + 18a2524 commit e5cbba3
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 14 deletions.
20 changes: 19 additions & 1 deletion __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,13 @@ def draw(self, context):
icon = preview_collection["frontier.png"].icon_id
row = layout.row(align=True)
sub = row.row()
sub.operator("pose.generate_rig_edit", icon_value=icon)
operator = sub.operator("pose.generate_rig_edit", icon_value=icon)
row = layout.row(align=True)
sub = row.row()
sub.prop(context.scene, "mergenodes", text="Merge Identical Nodes")
row = layout.row(align=True)
sub = row.row()
sub.prop(context.scene, "applyarmature", text="Apply Armature Modifiers")
row = layout.row(align=True)
sub = row.row()
sub.operator("pose.convert_scale_to_loc", icon_value=icon)
Expand Down Expand Up @@ -361,6 +367,16 @@ def register():
bpy.types.Mesh.cobra = bpy.props.PointerProperty(type=CobraMeshSettings)
# bpy.types.RigidBodyObject.cobra = bpy.props.PointerProperty(type=CobraCollisionSettings)
bpy.types.Object.cobra_coll = bpy.props.PointerProperty(type=CobraCollisionSettings)
bpy.types.Scene.mergenodes = bpy.props.BoolProperty(
name = "Merge Idential Nodes",
description = "Merges identical nodes to reduce the amount of duplicates if you moved several bones with the same parent",
default = True
)
bpy.types.Scene.applyarmature = bpy.props.BoolProperty(
name = "Apply Armature Modifiers",
description = "Automatically applies all of the armature's object's armature modifiers and re-adds them",
default = False
)

# Injection of elements in the contextual menu of the File Browser editor
bpy.types.FILEBROWSER_MT_context_menu.append(CT_FileBrowser_Context_Menu)
Expand All @@ -381,6 +397,8 @@ def unregister():

del bpy.types.Scene.cobra
del bpy.types.Mesh.cobra
del bpy.types.Scene.mergenodes
del bpy.types.Scene.applyarmature
global preview_collection
bpy.utils.previews.remove(preview_collection)

Expand Down
4 changes: 2 additions & 2 deletions plugin/utils/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ class GenerateRigEdit(bpy.types.Operator):
bl_options = {'REGISTER', 'UNDO'}

def execute(self, context):
return handle_errors(self, shell.generate_rig_edit, {})
return handle_errors(self, shell.generate_rig_edit, {'mergenodes': context.scene.mergenodes, 'applyarmature': context.scene.applyarmature})


class ConvertScaleToLoc(bpy.types.Operator):
"""Convert pose mode scale transforms into location transforms"""
bl_idname = "pose.convert_scale_to_loc"
bl_label = "Convert scale to location"
bl_label = "Convert Scale to Location"
bl_options = {'REGISTER', 'UNDO'}

def execute(self, context):
Expand Down
47 changes: 36 additions & 11 deletions plugin/utils/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,28 +150,53 @@ def vectorisclose(vector1, vector2, tolerance=0.0001):


# Main rig editing function
def generate_rig_edit():
def generate_rig_edit(**kwargs):
"""Automatic rig edit generator by NDP. Detects posed bones and automatically generates nodes and offsets them."""
# Initiate logging
msgs = []
#logging.info(f"-------------------------------------------------------------")
logging.info(f"generating rig edit from pose")

#Function settings
applyarmature = False
mergenodes = True
# Function settings
mergenodes = kwargs.get('mergenodes', True)
applyarmature = kwargs.get('applyarmature', False)
errortolerance = 0.0001

# Log settings
logging.info(f"function settings:")
logging.info(f"merge identical nodes = {mergenodes}")
logging.info(f"apply armature modifiers = {applyarmature}")
logging.info(f"error tolerance = {errortolerance}")

# Check if the active object is a valid armature
if bpy.context.active_object.type != 'ARMATURE':
# Object is not an armature. Cancelling.
msgs.append(f"No armature selected.")
return msgs

# Get the armature
armature = bpy.context.object
logging.info(f"armature: {armature.name}")

# Apply armature modifiers of children objects
if applyarmature == True:
logging.info(f"Applying armature modifiers of children objects")
armaturechildren = []

# Go over every object in the scene
for ob in bpy.data.objects:
# Check if they are parented to the armature, are a mesh, and have modifiers
if ob.parent == armature and ob.type == 'MESH' and ob.modifiers:
modifier_list = []
#Create a list of current armature modifiers in the object
for modifier in ob.modifiers:
if modifier.type == 'ARMATURE':
modifier_list.append(modifier)
for modifier in modifier_list:
# Apply the armature modifier
bpy.ops.object.modifier_copy({"object": ob}, modifier=modifier.name)
bpy.ops.object.modifier_apply({"object": ob}, modifier=modifier.name)

# Store current mode
original_mode = bpy.context.mode
# For some reason it doesn't recognize edit_armature as a valid mode to switch to so we change it to just edit. Blender moment
Expand All @@ -198,42 +223,42 @@ def generate_rig_edit():
for p_bone in armature.pose.bones:
# We check if vectors have miniscule transforms, and just consider them rounding errors and clear them.
# Check location
if vectorisclose(p_bone.location, Vector((0, 0, 0)), 0.0001) and p_bone.location != Vector((0, 0, 0)):
if vectorisclose(p_bone.location, Vector((0, 0, 0)), errortolerance) and p_bone.location != Vector((0, 0, 0)):
# Warn the user
logging.info(f"{p_bone.name} had miniscule location transforms, assuming it is an error and clearing")
# Clear transforms
p_bone.location = Vector((0, 0, 0))

# Check rotation
if vectorisclose(Vector(p_bone.rotation_quaternion), Vector((1, 0, 0, 0)), 0.0001) and Vector(p_bone.rotation_quaternion) != Vector((1, 0, 0, 0)):
if vectorisclose(Vector(p_bone.rotation_quaternion), Vector((1, 0, 0, 0)), errortolerance) and Vector(p_bone.rotation_quaternion) != Vector((1, 0, 0, 0)):
# Warn the user
logging.info(f"{p_bone.name} had miniscule rotation transforms, assuming it is an error and clearing")
# Clear rotation
p_bone.rotation_quaternion = Quaternion((1, 0, 0, 0))

# Check scale
if vectorisclose(p_bone.scale, Vector((1, 1, 1)), 0.0001) and p_bone.scale != Vector((1, 1, 1)):
if vectorisclose(p_bone.scale, Vector((1, 1, 1)), errortolerance) and p_bone.scale != Vector((1, 1, 1)):
# Warn the user
logging.info(f"{p_bone.name} had miniscule scale transforms, assuming it is an error and clearing")
# Clear scale
p_bone.scale = Vector((1, 1, 1))

# Check if any bones have major scale transform, and warn the user.
if not vectorisclose(p_bone.scale, Vector((1, 1, 1)), 0.0001):
if not vectorisclose(p_bone.scale, Vector((1, 1, 1)), errortolerance):
logging.info(f"{p_bone.name} had scale. Value = {repr(p_bone.scale)}, difference: {(p_bone.scale - Vector((1, 1, 1))).length}")
msgs.append(f"Warning: {str(p_bone.name)} had scale transforms. Reset scale for all bones and try again.")
return msgs

# We check for NODE bones with transforms and skip them.
if (not vectorisclose(p_bone.location, Vector((0, 0, 0)), 0.0001) or not vectorisclose(p_bone.scale,Vector((1, 1, 1)),0.0001) or not vectorisclose(Vector(p_bone.rotation_quaternion), Vector((1, 0, 0, 0)), 0.0001)) and p_bone.name.startswith("NODE_"):
if (not vectorisclose(p_bone.location, Vector((0, 0, 0)), errortolerance) or not vectorisclose(p_bone.scale,Vector((1, 1, 1)), errortolerance) or not vectorisclose(Vector(p_bone.rotation_quaternion), Vector((1, 0, 0, 0)), errortolerance)) and p_bone.name.startswith("NODE_"):
# Ignore posed NODE bones and proceed to the next, their offsets can be applied directly.
editnumber = editnumber + 1
logging.info(f"rig edit number {editnumber}")
logging.info(f"NODE with offsets detected. Applying offsets directly.")
continue

# We append any remaining bones that have been posed.
if (not vectorisclose(p_bone.location, Vector((0, 0, 0)), 0.0001) or not vectorisclose(p_bone.scale,Vector((1, 1, 1)),0.0001) or not vectorisclose(Vector(p_bone.rotation_quaternion), Vector((1, 0, 0, 0)), 0.0001)):
if (not vectorisclose(p_bone.location, Vector((0, 0, 0)), errortolerance) or not vectorisclose(p_bone.scale,Vector((1, 1, 1)), errortolerance) or not vectorisclose(Vector(p_bone.rotation_quaternion), Vector((1, 0, 0, 0)), errortolerance)):
#Check if the bone has no parent and warn the user
if p_bone.parent == None:
logging.info(f"{p_bone.name} has transforms but no parent")
Expand Down

0 comments on commit e5cbba3

Please sign in to comment.