1#====================== BEGIN GPL LICENSE BLOCK ======================
2#
3#  This program is free software; you can redistribute it and/or
4#  modify it under the terms of the GNU General Public License
5#  as published by the Free Software Foundation; either version 2
6#  of the License, or (at your option) any later version.
7#
8#  This program is distributed in the hope that it will be useful,
9#  but WITHOUT ANY WARRANTY; without even the implied warranty of
10#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11#  GNU General Public License for more details.
12#
13#  You should have received a copy of the GNU General Public License
14#  along with this program; if not, write to the Free Software Foundation,
15#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16#
17#======================= END GPL LICENSE BLOCK ========================
18
19# <pep8 compliant>
20
21import re
22
23import bpy
24from mathutils import Vector
25
26from ..utils import MetarigError
27from ..utils import copy_bone
28from ..utils import connected_children_names
29from ..utils import strip_org, make_mechanism_name, make_deformer_name
30from ..utils import create_widget, create_limb_widget
31
32from ...utils.mechanism import make_property
33
34class Rig:
35    """ A finger rig.  It takes a single chain of bones.
36        This is a control and deformation rig.
37    """
38    def __init__(self, obj, bone, params):
39        """ Gather and validate data about the rig.
40        """
41        self.obj = obj
42        self.org_bones = [bone] + connected_children_names(obj, bone)
43        self.params = params
44
45        if len(self.org_bones) <= 1:
46            raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 2 or more bones" % (strip_org(bone)))
47
48        # Get user-specified layers, if they exist
49        if params.separate_extra_layers:
50            self.ex_layers = list(params.extra_layers)
51        else:
52            self.ex_layers = None
53
54        # Get other rig parameters
55        self.primary_rotation_axis = params.primary_rotation_axis
56        self.use_digit_twist = params.use_digit_twist
57
58    def deform(self):
59        """ Generate the deformation rig.
60            Just a copy of the original bones, except the first digit which is a twist bone.
61        """
62        bpy.ops.object.mode_set(mode='EDIT')
63
64        # Create the bones
65        # First bone is a twist bone
66        if self.use_digit_twist:
67            b1a = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".01")))
68            b1b = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".02")))
69            b1tip = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(self.org_bones[0] + ".tip")))
70        else:
71            b1 = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0])))
72
73        # The rest are normal
74        bones = []
75        for bone in self.org_bones[1:]:
76            bones += [copy_bone(self.obj, bone, make_deformer_name(strip_org(bone)))]
77
78        # Position bones
79        eb = self.obj.data.edit_bones
80        if self.use_digit_twist:
81            b1a_e = eb[b1a]
82            b1b_e = eb[b1b]
83            b1tip_e = eb[b1tip]
84
85            b1tip_e.use_connect = False
86            b1tip_e.tail += Vector((0.1, 0, 0))
87            b1tip_e.head = b1b_e.tail
88            b1tip_e.length = b1a_e.length / 4
89
90            center = (b1a_e.head + b1a_e.tail) / 2
91            b1a_e.tail = center
92            b1b_e.use_connect = False
93            b1b_e.head = center
94
95        # Parenting
96        if self.use_digit_twist:
97            b1b_e.parent = eb[self.org_bones[0]]
98            b1tip_e.parent = eb[self.org_bones[0]]
99        else:
100            eb[b1].use_connect = False
101            eb[b1].parent = eb[self.org_bones[0]]
102
103        for (ba, bb) in zip(bones, self.org_bones[1:]):
104            eb[ba].use_connect = False
105            eb[ba].parent = eb[bb]
106
107        # Constraints
108        if self.use_digit_twist:
109            bpy.ops.object.mode_set(mode='OBJECT')
110            pb = self.obj.pose.bones
111
112            b1a_p = pb[b1a]
113
114            con = b1a_p.constraints.new('COPY_LOCATION')
115            con.name = "copy_location"
116            con.target = self.obj
117            con.subtarget = self.org_bones[0]
118
119            con = b1a_p.constraints.new('COPY_SCALE')
120            con.name = "copy_scale"
121            con.target = self.obj
122            con.subtarget = self.org_bones[0]
123
124            con = b1a_p.constraints.new('DAMPED_TRACK')
125            con.name = "track_to"
126            con.target = self.obj
127            con.subtarget = b1tip
128
129    def control(self):
130        """ Generate the control rig.
131        """
132        bpy.ops.object.mode_set(mode='EDIT')
133
134        # Figure out the name for the control bone (remove the last .##)
135        ctrl_name = re.sub("([0-9]+\.)", "", strip_org(self.org_bones[0])[::-1], count=1)[::-1]
136
137        # Create the bones
138        ctrl = copy_bone(self.obj, self.org_bones[0], ctrl_name)
139
140        helpers = []
141        bones = []
142        for bone in self.org_bones:
143            bones += [copy_bone(self.obj, bone, strip_org(bone))]
144            helpers += [copy_bone(self.obj, bone, make_mechanism_name(strip_org(bone)))]
145
146        # Position bones
147        eb = self.obj.data.edit_bones
148
149        length = 0.0
150        for bone in helpers:
151            length += eb[bone].length
152            eb[bone].length /= 2
153
154        eb[ctrl].length = length * 1.5
155
156        # Parent bones
157        prev = eb[self.org_bones[0]].parent
158        for (b, h) in zip(bones, helpers):
159            b_e = eb[b]
160            h_e = eb[h]
161            b_e.use_connect = False
162            h_e.use_connect = False
163
164            b_e.parent = h_e
165            h_e.parent = prev
166
167            prev = b_e
168
169        # Transform locks and rotation mode
170        bpy.ops.object.mode_set(mode='OBJECT')
171        pb = self.obj.pose.bones
172
173        for bone in bones[1:]:
174            pb[bone].lock_location = True, True, True
175
176        if pb[self.org_bones[0]].bone.use_connect is True:
177            pb[bones[0]].lock_location = True, True, True
178
179        pb[ctrl].lock_scale = True, False, True
180
181        for bone in helpers:
182            pb[bone].rotation_mode = 'XYZ'
183
184        # Drivers
185        i = 1
186        val = 1.2 / (len(self.org_bones) - 1)
187        for bone in helpers:
188            # Add custom prop
189            prop_name = "bend_%02d" % i
190            if i == 1:
191                propval = 0.0
192            else:
193                propval = val
194
195            make_property(pb[ctrl], prop_name, propval)
196
197            # Add driver
198            if 'X' in self.primary_rotation_axis:
199                fcurve = pb[bone].driver_add("rotation_euler", 0)
200            elif 'Y' in self.primary_rotation_axis:
201                fcurve = pb[bone].driver_add("rotation_euler", 1)
202            else:
203                fcurve = pb[bone].driver_add("rotation_euler", 2)
204
205            driver = fcurve.driver
206            driver.type = 'SCRIPTED'
207
208            var = driver.variables.new()
209            var.name = "ctrl_y"
210            var.targets[0].id_type = 'OBJECT'
211            var.targets[0].id = self.obj
212            var.targets[0].data_path = pb[ctrl].path_from_id() + '.scale[1]'
213
214            var = driver.variables.new()
215            var.name = "bend"
216            var.targets[0].id_type = 'OBJECT'
217            var.targets[0].id = self.obj
218            var.targets[0].data_path = pb[ctrl].path_from_id() + '["' + prop_name + '"]'
219
220            if '-' in self.primary_rotation_axis:
221                driver.expression = "-(1.0-ctrl_y) * bend * 3.14159 * 2"
222            else:
223                driver.expression = "(1.0-ctrl_y) * bend * 3.14159 * 2"
224
225            i += 1
226
227        # Constraints
228        con = pb[helpers[0]].constraints.new('COPY_LOCATION')
229        con.name = "copy_location"
230        con.target = self.obj
231        con.subtarget = ctrl
232
233        con = pb[helpers[0]].constraints.new('COPY_ROTATION')
234        con.name = "copy_rotation"
235        con.target = self.obj
236        con.subtarget = ctrl
237
238        # Constrain org bones to the control bones
239        for (bone, org) in zip(bones, self.org_bones):
240            con = pb[org].constraints.new('COPY_TRANSFORMS')
241            con.name = "copy_transforms"
242            con.target = self.obj
243            con.subtarget = bone
244
245        # Set layers for extra control bones
246        if self.ex_layers:
247            for bone in bones:
248                pb[bone].bone.layers = self.ex_layers
249
250        # Create control widgets
251        w = create_widget(self.obj, ctrl)
252        if w is not None:
253            mesh = w.data
254            verts = [(0, 0, 0), (0, 1, 0), (0.05, 1, 0), (0.05, 1.1, 0), (-0.05, 1.1, 0), (-0.05, 1, 0)]
255            if 'Z' in self.primary_rotation_axis:
256                # Flip x/z coordinates
257                temp = []
258                for v in verts:
259                    temp += [(v[2], v[1], v[0])]
260                verts = temp
261            edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 1)]
262            mesh.from_pydata(verts, edges, [])
263            mesh.update()
264
265        for bone in bones:
266            create_limb_widget(self.obj, bone)
267
268    def generate(self):
269        """ Generate the rig.
270            Do NOT modify any of the original bones, except for adding constraints.
271            The main armature should be selected and active before this is called.
272        """
273        self.deform()
274        self.control()
275
276
277def add_parameters(params):
278    """ Add the parameters of this rig type to the
279        RigifyParameters PropertyGroup
280    """
281    items = [('X', 'X', ''), ('Y', 'Y', ''), ('Z', 'Z', ''), ('-X', '-X', ''), ('-Y', '-Y', ''), ('-Z', '-Z', '')]
282    params.primary_rotation_axis = bpy.props.EnumProperty(items=items, name="Primary Rotation Axis", default='X')
283
284    params.separate_extra_layers = bpy.props.BoolProperty(name="Separate Secondary Control Layers:", default=False, description="Enable putting the secondary controls on a separate layer from the primary controls")
285    params.extra_layers = bpy.props.BoolVectorProperty(size=32, description="Layers for the secondary controls to be on")
286
287    params.use_digit_twist = bpy.props.BoolProperty(name="Digit Twist", default=True, description="Generate the dual-bone twist setup for the first finger digit")
288
289
290def parameters_ui(layout, params):
291    """ Create the ui for the rig parameters.
292    """
293    r = layout.row()
294    r.prop(params, "separate_extra_layers")
295
296    r = layout.row()
297    r.active = params.separate_extra_layers
298
299    col = r.column(align=True)
300    row = col.row(align=True)
301    row.prop(params, "extra_layers", index=0, toggle=True, text="")
302    row.prop(params, "extra_layers", index=1, toggle=True, text="")
303    row.prop(params, "extra_layers", index=2, toggle=True, text="")
304    row.prop(params, "extra_layers", index=3, toggle=True, text="")
305    row.prop(params, "extra_layers", index=4, toggle=True, text="")
306    row.prop(params, "extra_layers", index=5, toggle=True, text="")
307    row.prop(params, "extra_layers", index=6, toggle=True, text="")
308    row.prop(params, "extra_layers", index=7, toggle=True, text="")
309    row = col.row(align=True)
310    row.prop(params, "extra_layers", index=16, toggle=True, text="")
311    row.prop(params, "extra_layers", index=17, toggle=True, text="")
312    row.prop(params, "extra_layers", index=18, toggle=True, text="")
313    row.prop(params, "extra_layers", index=19, toggle=True, text="")
314    row.prop(params, "extra_layers", index=20, toggle=True, text="")
315    row.prop(params, "extra_layers", index=21, toggle=True, text="")
316    row.prop(params, "extra_layers", index=22, toggle=True, text="")
317    row.prop(params, "extra_layers", index=23, toggle=True, text="")
318
319    col = r.column(align=True)
320    row = col.row(align=True)
321    row.prop(params, "extra_layers", index=8, toggle=True, text="")
322    row.prop(params, "extra_layers", index=9, toggle=True, text="")
323    row.prop(params, "extra_layers", index=10, toggle=True, text="")
324    row.prop(params, "extra_layers", index=11, toggle=True, text="")
325    row.prop(params, "extra_layers", index=12, toggle=True, text="")
326    row.prop(params, "extra_layers", index=13, toggle=True, text="")
327    row.prop(params, "extra_layers", index=14, toggle=True, text="")
328    row.prop(params, "extra_layers", index=15, toggle=True, text="")
329    row = col.row(align=True)
330    row.prop(params, "extra_layers", index=24, toggle=True, text="")
331    row.prop(params, "extra_layers", index=25, toggle=True, text="")
332    row.prop(params, "extra_layers", index=26, toggle=True, text="")
333    row.prop(params, "extra_layers", index=27, toggle=True, text="")
334    row.prop(params, "extra_layers", index=28, toggle=True, text="")
335    row.prop(params, "extra_layers", index=29, toggle=True, text="")
336    row.prop(params, "extra_layers", index=30, toggle=True, text="")
337    row.prop(params, "extra_layers", index=31, toggle=True, text="")
338
339    r = layout.row()
340    r.label(text="Bend rotation axis:")
341    r.prop(params, "primary_rotation_axis", text="")
342
343    col = layout.column()
344    col.prop(params, "use_digit_twist")
345
346
347def create_sample(obj):
348    # generated by rigify.utils.write_metarig
349    bpy.ops.object.mode_set(mode='EDIT')
350    arm = obj.data
351
352    bones = {}
353
354    bone = arm.edit_bones.new('finger.01')
355    bone.head[:] = 0.0000, 0.0000, 0.0000
356    bone.tail[:] = 0.2529, 0.0000, 0.0000
357    bone.roll = 3.1416
358    bone.use_connect = False
359    bones['finger.01'] = bone.name
360    bone = arm.edit_bones.new('finger.02')
361    bone.head[:] = 0.2529, 0.0000, 0.0000
362    bone.tail[:] = 0.4024, 0.0000, -0.0264
363    bone.roll = -2.9671
364    bone.use_connect = True
365    bone.parent = arm.edit_bones[bones['finger.01']]
366    bones['finger.02'] = bone.name
367    bone = arm.edit_bones.new('finger.03')
368    bone.head[:] = 0.4024, 0.0000, -0.0264
369    bone.tail[:] = 0.4975, -0.0000, -0.0610
370    bone.roll = -2.7925
371    bone.use_connect = True
372    bone.parent = arm.edit_bones[bones['finger.02']]
373    bones['finger.03'] = bone.name
374
375    bpy.ops.object.mode_set(mode='OBJECT')
376    pbone = obj.pose.bones[bones['finger.01']]
377    pbone.rigify_type = 'finger'
378    pbone.lock_location = (True, True, True)
379    pbone.lock_rotation = (False, False, False)
380    pbone.lock_rotation_w = False
381    pbone.lock_scale = (False, False, False)
382    pbone.rotation_mode = 'YZX'
383    pbone = obj.pose.bones[bones['finger.02']]
384    pbone.rigify_type = ''
385    pbone.lock_location = (False, False, False)
386    pbone.lock_rotation = (False, False, False)
387    pbone.lock_rotation_w = False
388    pbone.lock_scale = (False, False, False)
389    pbone.rotation_mode = 'YZX'
390    pbone = obj.pose.bones[bones['finger.03']]
391    pbone.rigify_type = ''
392    pbone.lock_location = (False, False, False)
393    pbone.lock_rotation = (False, False, False)
394    pbone.lock_rotation_w = False
395    pbone.lock_scale = (False, False, False)
396    pbone.rotation_mode = 'YZX'
397
398    bpy.ops.object.mode_set(mode='EDIT')
399    for bone in arm.edit_bones:
400        bone.select = False
401        bone.select_head = False
402        bone.select_tail = False
403    for b in bones:
404        bone = arm.edit_bones[bones[b]]
405        bone.select = True
406        bone.select_head = True
407        bone.select_tail = True
408        arm.edit_bones.active = bone
409