Welcome to mirror list, hosted at ThFree Co, Russian Federation.

copy_mirror_parameters.py « operators « rigify - git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 171e3be2b7c472eb32e0f2c3f9a2da99fed0785b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# SPDX-License-Identifier: GPL-2.0-or-later

import bpy
import importlib

from ..utils.naming import Side, get_name_base_and_sides, mirror_name
from ..utils.misc import property_to_python

from ..utils.rig import get_rigify_type
from ..rig_lists import get_rig_class


# =============================================
# Single parameter copy button

class POSE_OT_rigify_copy_single_parameter(bpy.types.Operator):
    bl_idname = "pose.rigify_copy_single_parameter"
    bl_label = "Copy Option To Selected Rigs"
    bl_description = "Copy this property value to all selected rigs of the appropriate type"
    bl_options = {'UNDO', 'INTERNAL'}

    property_name: bpy.props.StringProperty(name='Property Name')
    mirror_bone: bpy.props.BoolProperty(name='Mirror As Bone Name')

    module_name: bpy.props.StringProperty(name='Module Name')
    class_name: bpy.props.StringProperty(name='Class Name')

    @classmethod
    def poll(cls, context):
        return (
            context.active_object and context.active_object.type == 'ARMATURE'
            and context.active_pose_bone
            and context.active_object.data.get('rig_id') is None
            and get_rigify_type(context.active_pose_bone)
        )

    def invoke(self, context, event):
        return context.window_manager.invoke_confirm(self, event)

    def execute(self, context):
        try:
            module = importlib.import_module(self.module_name)
            filter_rig_class = getattr(module, self.class_name)
        except (KeyError, AttributeError, ImportError):
            self.report(
                {'ERROR'}, f"Cannot find class {self.class_name} in {self.module_name}")
            return {'CANCELLED'}

        active_pbone = context.active_pose_bone
        active_split = get_name_base_and_sides(active_pbone.name)

        value = getattr(active_pbone.rigify_parameters, self.property_name)
        num_copied = 0

        # Copy to different bones of appropriate rig types
        for sel_pbone in context.selected_pose_bones:
            rig_type = get_rigify_type(sel_pbone)

            if rig_type and sel_pbone != active_pbone:
                rig_class = get_rig_class(rig_type)

                if rig_class and issubclass(rig_class, filter_rig_class):
                    new_value = value

                    # If mirror requested and copying to a different side bone, mirror the value
                    if self.mirror_bone and active_split.side != Side.MIDDLE and value:
                        sel_split = get_name_base_and_sides(sel_pbone.name)

                        if sel_split.side == -active_split.side:
                            new_value = mirror_name(value)

                    # Assign the final value
                    setattr(sel_pbone.rigify_parameters,
                            self.property_name, new_value)
                    num_copied += 1

        if num_copied:
            self.report({'INFO'}, f"Copied the value to {num_copied} bones.")
            return {'FINISHED'}
        else:
            self.report({'WARNING'}, "No suitable selected bones to copy to.")
            return {'CANCELLED'}


def make_copy_parameter_button(layout, property_name, *, base_class, mirror_bone=False):
    """Displays a button that copies the property to selected rig of the specified base type."""
    props = layout.operator(
        POSE_OT_rigify_copy_single_parameter.bl_idname, icon='DUPLICATE', text='')
    props.property_name = property_name
    props.mirror_bone = mirror_bone
    props.module_name = base_class.__module__
    props.class_name = base_class.__name__


def recursive_mirror(value):
    """Mirror strings(.L/.R) in any mixed structure of dictionaries/lists."""

    if isinstance(value, dict):
        return { key: recursive_mirror(val) for key, val in value.items() }

    elif isinstance(value, list):
        return [recursive_mirror(elem) for elem in value]

    elif isinstance(value, str):
        return mirror_name(value)

    else:
        return value


def copy_rigify_params(from_bone: bpy.types.PoseBone, to_bone: bpy.types.PoseBone, *, match_type=False, x_mirror=False) -> bool:
    rig_type = to_bone.rigify_type
    if match_type and to_bone.rigify_type != from_bone.rigify_type:
        return False
    else:
        rig_type = to_bone.rigify_type = get_rigify_type(from_bone)

    from_params = from_bone.get('rigify_parameters')
    if from_params and rig_type:
        param_dict = property_to_python(from_params)
        if x_mirror:
            param_dict = recursive_mirror(param_dict)
        to_bone['rigify_parameters'] = param_dict
    else:
        try:
            del to_bone['rigify_parameters']
        except KeyError:
            pass
    return True


class POSE_OT_rigify_mirror_parameters(bpy.types.Operator):
    """Mirror Rigify type and parameters of selected bones to the opposite side. Names should end in L/R"""

    bl_idname = "pose.rigify_mirror_parameters"
    bl_label = "Mirror Rigify Parameters"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        obj = context.object
        if not obj or obj.type != 'ARMATURE' or obj.mode != 'POSE':
            return False
        sel_bones = context.selected_pose_bones
        if not sel_bones:
            return False
        for pb in sel_bones:
            mirrored_name = mirror_name(pb.name)
            if mirrored_name != pb.name and mirrored_name in obj.pose.bones:
                return True
        return False

    def execute(self, context):
        rig = context.object

        num_mirrored = 0

        # First make sure that all selected bones can be mirrored unambiguously.
        for pb in context.selected_pose_bones:
            flip_bone = rig.pose.bones.get(mirror_name(pb.name))
            if not flip_bone:
                # Bones without an opposite will just be ignored.
                continue
            if flip_bone != pb and flip_bone.bone.select:
                self.report(
                    {'ERROR'}, f"Bone {pb.name} selected on both sides, mirroring would be ambiguous, aborting. Only select the left or right side, not both!")
                return {'CANCELLED'}

        # Then mirror the parameters.
        for pb in context.selected_pose_bones:
            flip_bone = rig.pose.bones.get(mirror_name(pb.name))
            if flip_bone == pb or not flip_bone:
                # Bones without an opposite will just be ignored.
                continue

            num_mirrored += copy_rigify_params(pb, flip_bone, match_type=False, x_mirror=True)

        self.report({'INFO'}, f"Mirrored parameters of {num_mirrored} bones.")

        return {'FINISHED'}


class POSE_OT_rigify_copy_parameters(bpy.types.Operator):
    """Copy Rigify type and parameters from active to selected bones"""

    bl_idname = "pose.rigify_copy_parameters"
    bl_label = "Copy Rigify Parameters to Selected"
    bl_options = {'REGISTER', 'UNDO'}

    match_type: bpy.props.BoolProperty(
        name = "Match Type",
        description = "Only mirror rigify parameters to selected bones which have the same rigify type as the active bone",
        default = False
    )

    @classmethod
    def poll(cls, context):
        obj = context.object
        if not obj or obj.type != 'ARMATURE' or obj.mode != 'POSE':
            return False

        active = context.active_pose_bone
        if not active or not active.rigify_type:
            return False

        select = context.selected_pose_bones
        if len(select) < 2 or active not in select:
            return False

        return True

    def execute(self, context):
        active_bone = context.active_pose_bone

        num_copied = 0
        for pb in context.selected_pose_bones:
            if pb == active_bone:
                continue
            num_copied += copy_rigify_params(active_bone, pb, match_type=self.match_type)

        self.report({'INFO'}, f"Copied {active_bone.rigify_type} parameters to {num_copied} bones.")

        return {'FINISHED'}


def draw_copy_mirror_ops(self, context):
    layout = self.layout
    if context.mode == 'POSE':
        layout.separator()
        op = layout.operator(POSE_OT_rigify_copy_parameters.bl_idname,
                        icon='DUPLICATE', text="Copy Only Parameters")
        op.match_type = True
        op = layout.operator(POSE_OT_rigify_copy_parameters.bl_idname,
                        icon='DUPLICATE', text="Copy Type & Parameters")
        op.match_type = False
        layout.operator(POSE_OT_rigify_mirror_parameters.bl_idname,
                        icon='MOD_MIRROR', text="Mirror Type & Parameters")

# =============================================
# Registration

classes = (
    POSE_OT_rigify_copy_single_parameter,
    POSE_OT_rigify_mirror_parameters,
    POSE_OT_rigify_copy_parameters
)


def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)

    bpy.types.VIEW3D_MT_rigify.append(draw_copy_mirror_ops)

def unregister():
    from bpy.utils import unregister_class
    for cls in classes:
        unregister_class(cls)

    bpy.types.VIEW3D_MT_rigify.remove(draw_copy_mirror_ops)