diff options
Diffstat (limited to 'rigify/rig_ui_template.py')
-rw-r--r-- | rigify/rig_ui_template.py | 565 |
1 files changed, 459 insertions, 106 deletions
diff --git a/rigify/rig_ui_template.py b/rigify/rig_ui_template.py index 6180ad03..8c66b8b3 100644 --- a/rigify/rig_ui_template.py +++ b/rigify/rig_ui_template.py @@ -18,12 +18,28 @@ # <pep8 compliant> +import bpy + +from collections import OrderedDict + +from .utils.animation import SCRIPT_REGISTER_BAKE, SCRIPT_UTILITIES_BAKE +from .utils.layers import get_layers +from .utils.rig import attach_persistent_script + +from . import base_generate + +from rna_prop_ui import rna_idprop_quote_path + + UI_IMPORTS = [ 'import bpy', - 'from bpy.props import StringProperty', 'import math', + 'import json', + 'import collections', 'from math import pi', + 'from bpy.props import StringProperty', 'from mathutils import Euler, Matrix, Quaternion, Vector', + 'from rna_prop_ui import rna_idprop_quote_path', ] UI_BASE_UTILITIES = ''' @@ -63,42 +79,21 @@ def rotation_difference(mat1, mat2): angle = -angle + (2*pi) return angle -def tail_distance(angle,bone_ik,bone_fk): - """ Returns the distance between the tails of two bones - after rotating bone_ik in AXIS_ANGLE mode. - """ - rot_mod=bone_ik.rotation_mode - if rot_mod != 'AXIS_ANGLE': - bone_ik.rotation_mode = 'AXIS_ANGLE' - bone_ik.rotation_axis_angle[0] = angle - bpy.context.view_layer.update() - - dv = (bone_fk.tail - bone_ik.tail).length - - bone_ik.rotation_mode = rot_mod - return dv - -def find_min_range(bone_ik,bone_fk,f=tail_distance,delta=pi/8): +def find_min_range(f,start_angle,delta=pi/8): """ finds the range where lies the minimum of function f applied on bone_ik and bone_fk at a certain angle. """ - rot_mod=bone_ik.rotation_mode - if rot_mod != 'AXIS_ANGLE': - bone_ik.rotation_mode = 'AXIS_ANGLE' - - start_angle = bone_ik.rotation_axis_angle[0] angle = start_angle while (angle > (start_angle - 2*pi)) and (angle < (start_angle + 2*pi)): - l_dist = f(angle-delta,bone_ik,bone_fk) - c_dist = f(angle,bone_ik,bone_fk) - r_dist = f(angle+delta,bone_ik,bone_fk) + l_dist = f(angle-delta) + c_dist = f(angle) + r_dist = f(angle+delta) if min((l_dist,c_dist,r_dist)) == c_dist: - bone_ik.rotation_mode = rot_mod return (angle-delta,angle+delta) else: angle=angle+delta -def ternarySearch(f, left, right, bone_ik, bone_fk, absolutePrecision): +def ternarySearch(f, left, right, absolutePrecision): """ Find minimum of unimodal function f() within [left, right] To find the maximum, revert the if/else statement or revert the comparison. @@ -111,11 +106,13 @@ def ternarySearch(f, left, right, bone_ik, bone_fk, absolutePrecision): leftThird = left + (right - left)/3 rightThird = right - (right - left)/3 - if f(leftThird, bone_ik, bone_fk) > f(rightThird, bone_ik, bone_fk): + if f(leftThird) > f(rightThird): left = leftThird else: right = rightThird +''' +UTILITIES_FUNC_COMMON_IKFK = [''' ######################################### ## "Visual Transform" helper functions ## ######################################### @@ -125,28 +122,8 @@ def get_pose_matrix_in_other_space(mat, pose_bone): transform space. In other words, presuming that mat is in armature space, slapping the returned matrix onto pose_bone should give it the armature-space transforms of mat. - TODO: try to handle cases with axis-scaled parents better. """ - rest = pose_bone.bone.matrix_local.copy() - rest_inv = rest.inverted() - if pose_bone.parent: - par_mat = pose_bone.parent.matrix.copy() - par_inv = par_mat.inverted() - par_rest = pose_bone.parent.bone.matrix_local.copy() - else: - par_mat = Matrix() - par_inv = Matrix() - par_rest = Matrix() - - # Get matrix in bone's current transform space - smat = rest_inv @ (par_rest @ (par_inv @ mat)) - - # Compensate for non-local location - #if not pose_bone.bone.use_local_location: - # loc = smat.to_translation() @ (par_rest.inverted() @ rest).to_quaternion() - # smat.translation = loc - - return smat + return pose_bone.id_data.convert_space(matrix=mat, pose_bone=pose_bone, from_space='POSE', to_space='LOCAL') def get_local_pose_matrix(pose_bone): @@ -159,19 +136,7 @@ def set_pose_translation(pose_bone, mat): """ Sets the pose bone's translation to the same translation as the given matrix. Matrix should be given in bone's local space. """ - if pose_bone.bone.use_local_location == True: - pose_bone.location = mat.to_translation() - else: - loc = mat.to_translation() - - rest = pose_bone.bone.matrix_local.copy() - if pose_bone.bone.parent: - par_rest = pose_bone.bone.parent.matrix_local.copy() - else: - par_rest = Matrix() - - q = (par_rest.inverted() @ rest).to_quaternion() - pose_bone.location = q @ loc + pose_bone.location = mat.to_translation() def set_pose_rotation(pose_bone, mat): @@ -205,8 +170,6 @@ def match_pose_translation(pose_bone, target_bone): """ mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone) set_pose_translation(pose_bone, mat) - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='POSE') def match_pose_rotation(pose_bone, target_bone): @@ -216,8 +179,6 @@ def match_pose_rotation(pose_bone, target_bone): """ mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone) set_pose_rotation(pose_bone, mat) - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='POSE') def match_pose_scale(pose_bone, target_bone): @@ -227,27 +188,55 @@ def match_pose_scale(pose_bone, target_bone): """ mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone) set_pose_scale(pose_bone, mat) - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='POSE') -def correct_rotation(bone_ik, bone_fk): + +############################## +## IK/FK snapping functions ## +############################## + +def correct_rotation(view_layer, bone_ik, target_matrix): """ Corrects the ik rotation in ik2fk snapping functions """ - alfarange = find_min_range(bone_ik,bone_fk) - alfamin = ternarySearch(tail_distance,alfarange[0],alfarange[1],bone_ik,bone_fk,0.1) + axis = target_matrix.to_3x3().col[1].normalized() - rot_mod = bone_ik.rotation_mode - if rot_mod != 'AXIS_ANGLE': - bone_ik.rotation_mode = 'AXIS_ANGLE' - bone_ik.rotation_axis_angle[0] = alfamin - bone_ik.rotation_mode = rot_mod + def distance(angle): + # Rotate the bone and return the actual angle between bones + bone_ik.rotation_euler[1] = angle + view_layer.update() -############################## -## IK/FK snapping functions ## -############################## + return -(bone_ik.vector.normalized().dot(axis)) + + if bone_ik.rotation_mode in {'QUATERNION', 'AXIS_ANGLE'}: + bone_ik.rotation_mode = 'ZXY' + + start_angle = bone_ik.rotation_euler[1] + + alfarange = find_min_range(distance, start_angle) + alfamin = ternarySearch(distance, alfarange[0], alfarange[1], pi / 180) + + bone_ik.rotation_euler[1] = alfamin + view_layer.update() + + +def correct_scale(view_layer, bone_ik, target_matrix): + """ Correct the scale of the base IK bone. """ + input_scale = target_matrix.to_scale() -def match_pole_target(ik_first, ik_last, pole, match_bone, length): + for i in range(3): + cur_scale = bone_ik.matrix.to_scale() + + bone_ik.scale = [ + v * i / c for v, i, c in zip(bone_ik.scale, input_scale, cur_scale) + ] + + view_layer.update() + + if all(abs((c - i)/i) < 0.01 for i, c in zip(input_scale, cur_scale)): + break + + +def match_pole_target(view_layer, ik_first, ik_last, pole, match_bone_matrix, length): """ Places an IK chain's pole target to match ik_first's transforms to match_bone. All bones should be given as pose bones. You need to be in pose mode on the relevant armature object. @@ -278,22 +267,21 @@ def match_pole_target(ik_first, ik_last, pole, match_bone, length): mat = get_pose_matrix_in_other_space(Matrix.Translation(ploc), pole) set_pose_translation(pole, mat) - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='POSE') + view_layer.update() set_pole(pv) # Get the rotation difference between ik_first and match_bone - angle = rotation_difference(ik_first.matrix, match_bone.matrix) + angle = rotation_difference(ik_first.matrix, match_bone_matrix) # Try compensating for the rotation difference in both directions pv1 = Matrix.Rotation(angle, 4, ikv) @ pv set_pole(pv1) - ang1 = rotation_difference(ik_first.matrix, match_bone.matrix) + ang1 = rotation_difference(ik_first.matrix, match_bone_matrix) pv2 = Matrix.Rotation(-angle, 4, ikv) @ pv set_pole(pv2) - ang2 = rotation_difference(ik_first.matrix, match_bone.matrix) + ang2 = rotation_difference(ik_first.matrix, match_bone_matrix) # Do the one with the smaller angle if ang1 < ang2: @@ -309,7 +297,7 @@ def parse_bone_names(names_string): else: return names_string -''' +'''] UTILITIES_FUNC_ARM_FKIK = [''' ###################### @@ -322,6 +310,7 @@ def fk2ik_arm(obj, fk, ik): fk: list of fk bone names ik: list of ik bone names """ + view_layer = bpy.context.view_layer uarm = obj.pose.bones[fk[0]] farm = obj.pose.bones[fk[1]] hand = obj.pose.bones[fk[2]] @@ -341,29 +330,35 @@ def fk2ik_arm(obj, fk, ik): # Upper arm position match_pose_rotation(uarm, uarmi) match_pose_scale(uarm, uarmi) + view_layer.update() # Forearm position match_pose_rotation(farm, farmi) match_pose_scale(farm, farmi) + view_layer.update() # Hand position match_pose_rotation(hand, handi) match_pose_scale(hand, handi) + view_layer.update() else: # Upper arm position match_pose_translation(uarm, uarmi) match_pose_rotation(uarm, uarmi) match_pose_scale(uarm, uarmi) + view_layer.update() # Forearm position #match_pose_translation(hand, handi) match_pose_rotation(farm, farmi) match_pose_scale(farm, farmi) + view_layer.update() # Hand position match_pose_translation(hand, handi) match_pose_rotation(hand, handi) match_pose_scale(hand, handi) + view_layer.update() def ik2fk_arm(obj, fk, ik): @@ -372,6 +367,7 @@ def ik2fk_arm(obj, fk, ik): fk: list of fk bone names ik: list of ik bone names """ + view_layer = bpy.context.view_layer uarm = obj.pose.bones[fk[0]] farm = obj.pose.bones[fk[1]] hand = obj.pose.bones[fk[2]] @@ -395,21 +391,29 @@ def ik2fk_arm(obj, fk, ik): match_pose_translation(handi, hand) match_pose_rotation(handi, hand) match_pose_scale(handi, hand) + view_layer.update() + # Pole target position - match_pole_target(uarmi, farmi, pole, uarm, (uarmi.length + farmi.length)) + match_pole_target(view_layer, uarmi, farmi, pole, uarm.matrix, (uarmi.length + farmi.length)) else: # Hand position match_pose_translation(handi, hand) match_pose_rotation(handi, hand) match_pose_scale(handi, hand) + view_layer.update() # Upper Arm position match_pose_translation(uarmi, uarm) - match_pose_rotation(uarmi, uarm) + #match_pose_rotation(uarmi, uarm) + set_pose_rotation(uarmi, Matrix()) match_pose_scale(uarmi, uarm) + view_layer.update() + # Rotation Correction - correct_rotation(uarmi, uarm) + correct_rotation(view_layer, uarmi, uarm.matrix) + + correct_scale(view_layer, uarmi, uarm.matrix) '''] UTILITIES_FUNC_LEG_FKIK = [''' @@ -423,6 +427,7 @@ def fk2ik_leg(obj, fk, ik): fk: list of fk bone names ik: list of ik bone names """ + view_layer = bpy.context.view_layer thigh = obj.pose.bones[fk[0]] shin = obj.pose.bones[fk[1]] foot = obj.pose.bones[fk[2]] @@ -444,36 +449,38 @@ def fk2ik_leg(obj, fk, ik): # Thigh position match_pose_rotation(thigh, thighi) match_pose_scale(thigh, thighi) + view_layer.update() # Shin position match_pose_rotation(shin, shini) match_pose_scale(shin, shini) + view_layer.update() # Foot position mat = mfoot.bone.matrix_local.inverted() @ foot.bone.matrix_local footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) @ mat set_pose_rotation(foot, footmat) set_pose_scale(foot, footmat) - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='POSE') + view_layer.update() else: # Thigh position match_pose_translation(thigh, thighi) match_pose_rotation(thigh, thighi) match_pose_scale(thigh, thighi) + view_layer.update() # Shin position match_pose_rotation(shin, shini) match_pose_scale(shin, shini) + view_layer.update() # Foot position mat = mfoot.bone.matrix_local.inverted() @ foot.bone.matrix_local footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) @ mat set_pose_rotation(foot, footmat) set_pose_scale(foot, footmat) - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='POSE') + view_layer.update() def ik2fk_leg(obj, fk, ik): @@ -482,6 +489,7 @@ def ik2fk_leg(obj, fk, ik): fk: list of fk bone names ik: list of ik bone names """ + view_layer = bpy.context.view_layer thigh = obj.pose.bones[fk[0]] shin = obj.pose.bones[fk[1]] mfoot = obj.pose.bones[fk[2]] @@ -506,6 +514,7 @@ def ik2fk_leg(obj, fk, ik): # Clear footroll set_pose_rotation(footroll, Matrix()) + view_layer.update() # Foot position mat = mfooti.bone.matrix_local.inverted() @ footi.bone.matrix_local @@ -513,16 +522,17 @@ def ik2fk_leg(obj, fk, ik): set_pose_translation(footi, footmat) set_pose_rotation(footi, footmat) set_pose_scale(footi, footmat) - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='POSE') + view_layer.update() # Thigh position match_pose_translation(thighi, thigh) - match_pose_rotation(thighi, thigh) + #match_pose_rotation(thighi, thigh) + set_pose_rotation(thighi, Matrix()) match_pose_scale(thighi, thigh) + view_layer.update() # Rotation Correction - correct_rotation(thighi,thigh) + correct_rotation(view_layer, thighi, thigh.matrix) else: # Stretch @@ -532,6 +542,7 @@ def ik2fk_leg(obj, fk, ik): # Clear footroll set_pose_rotation(footroll, Matrix()) + view_layer.update() # Foot position mat = mfooti.bone.matrix_local.inverted() @ footi.bone.matrix_local @@ -539,11 +550,12 @@ def ik2fk_leg(obj, fk, ik): set_pose_translation(footi, footmat) set_pose_rotation(footi, footmat) set_pose_scale(footi, footmat) - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='POSE') + view_layer.update() # Pole target position - match_pole_target(thighi, shini, pole, thigh, (thighi.length + shini.length)) + match_pole_target(view_layer, thighi, shini, pole, thigh.matrix, (thighi.length + shini.length)) + + correct_scale(view_layer, thighi, thigh.matrix) '''] UTILITIES_FUNC_POLE = [''' @@ -765,6 +777,7 @@ class Rigify_Rot2PoleSwitch(bpy.types.Operator): REGISTER_RIG_ARM = REGISTER_OP_ARM_FKIK + REGISTER_OP_POLE UTILITIES_RIG_ARM = [ + *UTILITIES_FUNC_COMMON_IKFK, *UTILITIES_FUNC_ARM_FKIK, *UTILITIES_FUNC_POLE, *UTILITIES_OP_ARM_FKIK, @@ -774,6 +787,7 @@ UTILITIES_RIG_ARM = [ REGISTER_RIG_LEG = REGISTER_OP_LEG_FKIK + REGISTER_OP_POLE UTILITIES_RIG_LEG = [ + *UTILITIES_FUNC_COMMON_IKFK, *UTILITIES_FUNC_LEG_FKIK, *UTILITIES_FUNC_POLE, *UTILITIES_OP_LEG_FKIK, @@ -794,6 +808,7 @@ UI_REGISTER = [ # Include arm and leg utilities for now in case somebody wants to use # legacy limb rigs, which expect these to be available by default. UI_UTILITIES = [ + *UTILITIES_FUNC_COMMON_IKFK, *UTILITIES_FUNC_ARM_FKIK, *UTILITIES_FUNC_LEG_FKIK, *UTILITIES_OP_ARM_FKIK, @@ -825,24 +840,44 @@ class RigUI(bpy.types.Panel): layout = self.layout pose_bones = context.active_object.pose.bones try: - selected_bones = [bone.name for bone in context.selected_pose_bones] - selected_bones += [context.active_pose_bone.name] + selected_bones = set(bone.name for bone in context.selected_pose_bones) + selected_bones.add(context.active_pose_bone.name) except (AttributeError, TypeError): return def is_selected(names): # Returns whether any of the named bones are selected. - if type(names) == list: - for name in names: - if name in selected_bones: - return True + if isinstance(names, list) or isinstance(names, set): + return not selected_bones.isdisjoint(names) elif names in selected_bones: return True return False + num_rig_separators = [-1] + def emit_rig_separator(): + if num_rig_separators[0] >= 0: + layout.separator() + num_rig_separators[0] += 1 ''' +UI_REGISTER_BAKE_SETTINGS = ['RigBakeSettings'] + +UI_BAKE_SETTINGS = ''' +class RigBakeSettings(bpy.types.Panel): + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_label = "Rig Bake Settings" + bl_idname = "VIEW3D_PT_rig_bake_settings_" + rig_id + bl_category = 'Item' + + @classmethod + def poll(self, context): + return RigUI.poll(context) and find_action(context.active_object) is not None + + def draw(self, context): + RigifyBakeKeyframesMixin.draw_common_bake_ui(context, self.layout) +''' def layers_ui(layers, layout): """ Turn a list of booleans + a list of names into a layer UI. @@ -896,3 +931,321 @@ class RigLayers(bpy.types.Panel): code += " row.prop(context.active_object.data, 'layers', index=28, toggle=True, text='Root')\n" return code + + +def quote_parameters(positional, named): + """Quote the given positional and named parameters as a code string.""" + positional_list = [ repr(v) for v in positional ] + named_list = [ "%s=%r" % (k, v) for k, v in named.items() ] + return ', '.join(positional_list + named_list) + +def indent_lines(lines, indent=4): + if indent > 0: + prefix = ' ' * indent + return [ prefix + line for line in lines ] + else: + return lines + + +class PanelLayout(object): + """Utility class that builds code for creating a layout.""" + + def __init__(self, parent, index=0): + if isinstance(parent, PanelLayout): + self.parent = parent + self.script = parent.script + else: + self.parent = None + self.script = parent + + self.header = [] + self.items = [] + self.indent = 0 + self.index = index + self.layout = self._get_layout_var(index) + self.is_empty = True + + @staticmethod + def _get_layout_var(index): + return 'layout' if index == 0 else 'group' + str(index) + + def clear_empty(self): + self.is_empty = False + + if self.parent: + self.parent.clear_empty() + + def get_lines(self): + lines = [] + + for item in self.items: + if isinstance(item, PanelLayout): + lines += item.get_lines() + else: + lines.append(item) + + if len(lines) > 0: + return self.wrap_lines(lines) + else: + return [] + + def wrap_lines(self, lines): + return self.header + indent_lines(lines, self.indent) + + def add_line(self, line): + assert isinstance(line, str) + + self.items.append(line) + + if self.is_empty: + self.clear_empty() + + def use_bake_settings(self): + """This panel contains operators that need the common Bake settings.""" + self.parent.use_bake_settings() + + def custom_prop(self, bone_name, prop_name, **params): + """Add a custom property input field to the panel.""" + param_str = quote_parameters([ rna_idprop_quote_path(prop_name) ], params) + self.add_line( + "%s.prop(pose_bones[%r], %s)" % (self.layout, bone_name, param_str) + ) + + def operator(self, operator_name, *, properties=None, **params): + """Add an operator call button to the panel.""" + name = operator_name.format_map(self.script.format_args) + param_str = quote_parameters([ name ], params) + call_str = "%s.operator(%s)" % (self.layout, param_str) + if properties: + self.add_line("props = " + call_str) + for k, v in properties.items(): + self.add_line("props.%s = %r" % (k,v)) + else: + self.add_line(call_str) + + def add_nested_layout(self, name, params): + param_str = quote_parameters([], params) + sub_panel = PanelLayout(self, self.index + 1) + sub_panel.header.append('%s = %s.%s(%s)' % (sub_panel.layout, self.layout, name, param_str)) + self.items.append(sub_panel) + return sub_panel + + def row(self, **params): + """Add a nested row layout to the panel.""" + return self.add_nested_layout('row', params) + + def column(self, **params): + """Add a nested column layout to the panel.""" + return self.add_nested_layout('column', params) + + def split(self, **params): + """Add a split layout to the panel.""" + return self.add_nested_layout('split', params) + + +class BoneSetPanelLayout(PanelLayout): + """Panel restricted to a certain set of bones.""" + + def __init__(self, rig_panel, bones): + assert isinstance(bones, frozenset) + super().__init__(rig_panel) + self.bones = bones + self.show_bake_settings = False + + def clear_empty(self): + self.parent.bones |= self.bones + + super().clear_empty() + + def wrap_lines(self, lines): + if self.bones != self.parent.bones: + header = ["if is_selected(%r):" % (set(self.bones))] + return header + indent_lines(lines) + else: + return lines + + def use_bake_settings(self): + self.show_bake_settings = True + if not self.script.use_bake_settings: + self.script.use_bake_settings = True + self.script.add_utilities(SCRIPT_UTILITIES_BAKE) + self.script.register_classes(SCRIPT_REGISTER_BAKE) + + +class RigPanelLayout(PanelLayout): + """Panel owned by a certain rig.""" + + def __init__(self, script, rig): + super().__init__(script) + self.bones = set() + self.subpanels = OrderedDict() + + def wrap_lines(self, lines): + header = [ "if is_selected(%r):" % (set(self.bones)) ] + prefix = [ "emit_rig_separator()" ] + return header + indent_lines(prefix + lines) + + def panel_with_selected_check(self, control_names): + selected_set = frozenset(control_names) + + if selected_set in self.subpanels: + return self.subpanels[selected_set] + else: + panel = BoneSetPanelLayout(self, selected_set) + self.subpanels[selected_set] = panel + self.items.append(panel) + return panel + + +class ScriptGenerator(base_generate.GeneratorPlugin): + """Generator plugin that builds the python script attached to the rig.""" + + priority = -100 + + def __init__(self, generator): + super().__init__(generator) + + self.ui_scripts = [] + self.ui_imports = UI_IMPORTS.copy() + self.ui_utilities = UI_UTILITIES.copy() + self.ui_register = UI_REGISTER.copy() + self.ui_register_drivers = [] + self.ui_register_props = [] + + self.ui_rig_panels = OrderedDict() + + self.use_bake_settings = False + + # Structured panel code generation + def panel_with_selected_check(self, rig, control_names): + """Add a panel section with restricted selection.""" + rig_key = id(rig) + + if rig_key in self.ui_rig_panels: + panel = self.ui_rig_panels[rig_key] + else: + panel = RigPanelLayout(self, rig) + self.ui_rig_panels[rig_key] = panel + + return panel.panel_with_selected_check(control_names) + + # Raw output + def add_panel_code(self, str_list): + """Add raw code to the panel.""" + self.ui_scripts += str_list + + def add_imports(self, str_list): + self.ui_imports += str_list + + def add_utilities(self, str_list): + self.ui_utilities += str_list + + def register_classes(self, str_list): + self.ui_register += str_list + + def register_driver_functions(self, str_list): + self.ui_register_drivers += str_list + + def register_property(self, name, definition): + self.ui_register_props.append((name, definition)) + + def initialize(self): + self.format_args = { + 'rig_id': self.generator.rig_id, + } + + def finalize(self): + metarig = self.generator.metarig + id_store = self.generator.id_store + rig_id = self.generator.rig_id + + vis_layers = self.obj.data.layers + + # Ensure the collection of layer names exists + for i in range(1 + len(metarig.data.rigify_layers), 29): + metarig.data.rigify_layers.add() + + # Create list of layer name/row pairs + layer_layout = [] + for l in metarig.data.rigify_layers: + layer_layout += [(l.name, l.row)] + + # Generate the UI script + if id_store.rigify_generate_mode == 'overwrite': + rig_ui_name = id_store.rigify_rig_ui or 'rig_ui.py' + else: + rig_ui_name = 'rig_ui.py' + + if id_store.rigify_generate_mode == 'overwrite' and rig_ui_name in bpy.data.texts.keys(): + script = bpy.data.texts[rig_ui_name] + script.clear() + else: + script = bpy.data.texts.new("rig_ui.py") + + rig_ui_old_name = "" + if id_store.rigify_rig_basename: + rig_ui_old_name = script.name + script.name = id_store.rigify_rig_basename + "_rig_ui.py" + + id_store.rigify_rig_ui = script.name + + for s in OrderedDict.fromkeys(self.ui_imports): + script.write(s + "\n") + + script.write(UI_BASE_UTILITIES % rig_id) + + for s in OrderedDict.fromkeys(self.ui_utilities): + script.write(s + "\n") + + script.write(UI_SLIDERS) + + for s in self.ui_scripts: + script.write("\n " + s.replace("\n", "\n ") + "\n") + + if len(self.ui_scripts) > 0: + script.write("\n num_rig_separators[0] = 0\n") + + for panel in self.ui_rig_panels.values(): + lines = panel.get_lines() + if len(lines) > 1: + script.write("\n ".join([''] + lines) + "\n") + + if self.use_bake_settings: + self.ui_register = UI_REGISTER_BAKE_SETTINGS + self.ui_register + script.write(UI_BAKE_SETTINGS) + + script.write(layers_ui(vis_layers, layer_layout)) + + script.write("\ndef register():\n") + + ui_register = OrderedDict.fromkeys(self.ui_register) + for s in ui_register: + script.write(" bpy.utils.register_class("+s+")\n") + + ui_register_drivers = OrderedDict.fromkeys(self.ui_register_drivers) + for s in ui_register_drivers: + script.write(" bpy.app.driver_namespace['"+s+"'] = "+s+"\n") + + ui_register_props = OrderedDict.fromkeys(self.ui_register_props) + for s in ui_register_props: + script.write(" bpy.types.%s = %s\n " % (*s,)) + + script.write("\ndef unregister():\n") + + for s in ui_register_props: + script.write(" del bpy.types.%s\n" % s[0]) + + for s in ui_register: + script.write(" bpy.utils.unregister_class("+s+")\n") + + for s in ui_register_drivers: + script.write(" del bpy.app.driver_namespace['"+s+"']\n") + + script.write("\nregister()\n") + script.use_module = True + + # Run UI script + exec(script.as_string(), {}) + + # Attach the script to the rig + attach_persistent_script(self.obj, script) |