Pole angle fix add-on

Hello.

As many who come from other 3D packages, there are some features that start to feel amiss. So I took Jerryno’s calculations for IK’s pole angle and turned it into simple add-on that does this automatically at a button press.

The source code follows at the end of the post.

Still, there are one of two things I’d like to address: To use it, right now you have press the button that will appear at the end of the Properties>Bone Constraints panel for bones that have IK.

I’d like that button to appear inside the IK subpanel. Any tips on how to do that?


bl_info = {
	"name": "Bone Panel extension",
	"author": "Fernando D'Andrea - Rockhead Studios (thanks to Jerryno for the angle calculation code)",
	"version": (0, 1),
	"blender": (2, 78, 0),
	"location": "Properties > Bone Constraints > Inverse Kinematics",
	"description": "Extends IK panel funcionality",
	"warning": "",
	"wiki_url": "",
	"category": "Rigging"
	}
	
import bpy
import mathutils
import bmesh


def dandrea_IKConstraint_panel(self, context):
	
	_bone = context.pose_bone #context.object.pose.bones
	#self.layout.label(str(_bone.name))
	#self.layout.label(str(_bone.constraints))
	#self.layout.label(str(len(_bone.constraints)))
	for cons in _bone.constraints:
		if cons.type=='IK':
			self.layout.operator("constraint.dandrea_adjust_pole", icon = 'OUTLINER_OB_ARMATURE', text='Adjust Pole Angle:    ' + _bone.name + ' ("'+ cons.name +'")').constraint = cons.name




class dandrea_adjust_pole(bpy.types.Operator):
	"""Adds functionality to the IK constraint panel."""
	bl_idname = "constraint.dandrea_adjust_pole"
	bl_label = "Adjust IK Pole Angle"
	bl_icon = 'OUTLINER_OB_ARMATURE'
	constraint = bpy.props.StringProperty(name="IK Name", description="IK constraint name to set", default="")
	
	@classmethod
	def poll(cls, context):
		#implement poll
		return True
			
	def execute(self, context):
	
		def signed_angle(vector_u, vector_v, normal):
			# Normal specifies orientation
			angle = vector_u.angle(vector_v)
			if vector_u.cross(vector_v).angle(normal) < 1:
				angle = -angle
			return angle


		def get_pole_angle(base_bone, ik_bone, pole_location):
			pole_normal = (ik_bone.tail - base_bone.head).cross(pole_location - base_bone.head)
			projected_pole_axis = pole_normal.cross(base_bone.tail - base_bone.head)
			return signed_angle(base_bone.x_axis, projected_pole_axis, base_bone.tail - base_bone.head)


		context_mode = context.mode
		bpy.ops.object.mode_set(mode='POSE')
		ik_bone = context.pose_bone
		print (ik_bone)
		cons = ik_bone.constraints[self.constraint]
		if cons is None:
			return {'CANCELLED'}
		cons_mute = cons.mute
		base_bone = ik_bone
		for i in range(1, cons.chain_count):
			base_bone = base_bone.parent
		if base_bone is None:
			return {'CANCELLED'}
		pole_bone = cons.pole_target
		if pole_bone is None:
			return {'CANCELLED'}
		if pole_bone.type == 'ARMATURE':
			pole_bone = context.object.pose.bones[cons.pole_subtarget]
		if pole_bone is None:
			return {'CANCELLED'}
		print(pole_bone)
		if (base_bone is not None) and (pole_bone is not None):
			cons.mute = False
			bpy.ops.object.mode_set(mode='EDIT')
			pole_angle_in_radians = get_pole_angle(base_bone,ik_bone,pole_bone.matrix.translation)
			pole_angle_in_deg = round(180*pole_angle_in_radians/3.141592, 3)
			print(pole_angle_in_deg)			
			cons.mute = cons_mute
			bpy.ops.object.mode_set(mode=context_mode)
			cons.pole_angle = pole_angle_in_radians


		return {'FINISHED'}


def register():
	bpy.utils.register_class(dandrea_adjust_pole)
	bpy.types.BONE_PT_constraints.append(dandrea_IKConstraint_panel)


def unregister():
	bpy.utils.unregister_class(dandrea_adjust_pole)
	bpy.types.BONE_PT_constraints.remove(dandrea_IKConstraint_panel)


if __name__ == "__main__":
	unregister()
	register()