Allow changing B-Bone custom handle references from Pose Mode.
[blender.git] / release / scripts / startup / bl_ui / properties_data_bone.py
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
21 import bpy
22 from bpy.types import Panel
23 from rna_prop_ui import PropertyPanel
24
25
26 class BoneButtonsPanel:
27     bl_space_type = 'PROPERTIES'
28     bl_region_type = 'WINDOW'
29     bl_context = "bone"
30
31     @classmethod
32     def poll(cls, context):
33         return (context.bone or context.edit_bone)
34
35
36 class BONE_PT_context_bone(BoneButtonsPanel, Panel):
37     bl_label = ""
38     bl_options = {'HIDE_HEADER'}
39
40     def draw(self, context):
41         layout = self.layout
42
43         bone = context.bone
44         if not bone:
45             bone = context.edit_bone
46
47         row = layout.row()
48         row.label(text="", icon='BONE_DATA')
49         row.prop(bone, "name", text="")
50
51
52 class BONE_PT_transform(BoneButtonsPanel, Panel):
53     bl_label = "Transform"
54
55     @classmethod
56     def poll(cls, context):
57         if context.edit_bone:
58             return True
59
60         ob = context.object
61         return ob and ob.mode == 'POSE' and context.bone
62
63     def draw(self, context):
64         layout = self.layout
65         layout.use_property_split = True
66
67         ob = context.object
68         bone = context.bone
69
70         col = layout.column()
71
72         if bone and ob:
73             pchan = ob.pose.bones[bone.name]
74             col.active = not (bone.parent and bone.use_connect)
75
76             sub = col.row(align=True)
77             sub.prop(pchan, "location")
78             sub.prop(pchan, "lock_location", text="")
79
80             col = layout.column()
81             if pchan.rotation_mode == 'QUATERNION':
82                 sub = col.row(align=True)
83                 sub.prop(pchan, "rotation_quaternion", text="Rotation")
84                 subsub = sub.column(align=True)
85                 subsub.prop(pchan, "lock_rotation_w", text="")
86                 subsub.prop(pchan, "lock_rotation", text="")
87             elif pchan.rotation_mode == 'AXIS_ANGLE':
88                 # col.label(text="Rotation")
89                 #col.prop(pchan, "rotation_angle", text="Angle")
90                 #col.prop(pchan, "rotation_axis", text="Axis")
91                 sub = col.row(align=True)
92                 sub.prop(pchan, "rotation_axis_angle", text="Rotation")
93                 subsub = sub.column(align=True)
94                 subsub.prop(pchan, "lock_rotation_w", text="")
95                 subsub.prop(pchan, "lock_rotation", text="")
96             else:
97                 sub = col.row(align=True)
98                 sub.prop(pchan, "rotation_euler", text="Rotation")
99                 sub.prop(pchan, "lock_rotation", text="")
100
101             col = layout.column()
102             sub = col.row(align=True)
103             sub.prop(pchan, "scale")
104             sub.prop(pchan, "lock_scale", text="")
105
106             col = layout.column()
107             col.prop(pchan, "rotation_mode")
108
109         elif context.edit_bone:
110             bone = context.edit_bone
111             col = layout.column()
112             col.prop(bone, "head")
113             col.prop(bone, "tail")
114
115             col = layout.column()
116             col.prop(bone, "roll")
117             col.prop(bone, "lock")
118
119             col = layout.column()
120             col.prop(bone, "tail_radius")
121             col.prop(bone, "envelope_distance")
122
123
124 class BONE_PT_curved(BoneButtonsPanel, Panel):
125     bl_label = "Bendy Bones"
126     bl_options = {'DEFAULT_CLOSED'}
127
128     def draw(self, context):
129         ob = context.object
130         bone = context.bone
131         # arm = context.armature
132         pchan = None
133         bone_list = "bones"
134
135         if ob and bone:
136             pchan = ob.pose.bones[bone.name]
137             bbone = pchan
138         elif bone is None:
139             bone = context.edit_bone
140             bbone = bone
141             bone_list = "edit_bones"
142         else:
143             bbone = bone
144
145         layout = self.layout
146         layout.use_property_split = True
147
148         layout.prop(bone, "bbone_segments", text="Segments")
149
150         topcol = layout.column()
151         topcol.active = bone.bbone_segments > 1
152
153         col = topcol.column(align=True)
154         col.prop(bbone, "bbone_curveinx", text="Curve In X")
155         col.prop(bbone, "bbone_curveiny", text="In Y")
156
157         col = topcol.column(align=True)
158         col.prop(bbone, "bbone_curveoutx", text="Curve Out X")
159         col.prop(bbone, "bbone_curveouty", text="Out Y")
160
161         col = topcol.column(align=True)
162         col.prop(bbone, "bbone_rollin", text="Roll In")
163         col.prop(bbone, "bbone_rollout", text="Out")
164         col.prop(bone, "use_endroll_as_inroll")
165
166         col = topcol.column(align=True)
167         col.prop(bbone, "bbone_scalein", text="Scale In")
168         col.prop(bbone, "bbone_scaleout", text="Out")
169
170         col = topcol.column(align=True)
171         col.prop(bbone, "bbone_easein", text="Ease In")
172         col.prop(bbone, "bbone_easeout", text="Out")
173
174         col = topcol.column(align=True)
175         col.prop(bone, "bbone_handle_type_start", text="Start Handle")
176
177         col = col.column(align=True)
178         col.active = (bone.bbone_handle_type_start != "AUTO")
179         col.prop_search(bone, "bbone_custom_handle_start", ob.data, bone_list, text="Custom")
180
181         col = topcol.column(align=True)
182         col.prop(bone, "bbone_handle_type_end", text="End Handle")
183
184         col = col.column(align=True)
185         col.active = (bone.bbone_handle_type_end != "AUTO")
186         col.prop_search(bone, "bbone_custom_handle_end", ob.data, bone_list, text="Custom")
187
188
189 class BONE_PT_relations(BoneButtonsPanel, Panel):
190     bl_options = {'DEFAULT_CLOSED'}
191     bl_label = "Relations"
192
193     def draw(self, context):
194         layout = self.layout
195         layout.use_property_split = True
196
197         ob = context.object
198         bone = context.bone
199         arm = context.armature
200         pchan = None
201
202         if ob and bone:
203             pchan = ob.pose.bones[bone.name]
204         elif bone is None:
205             bone = context.edit_bone
206
207         col = layout.column()
208         col.use_property_split = False
209         col.prop(bone, "layers", text="")
210         col.use_property_split = True
211         col = layout.column()
212
213         col.separator()
214
215         if context.bone:
216             col.prop(bone, "parent")
217         else:
218             col.prop_search(bone, "parent", arm, "edit_bones")
219
220         if ob and pchan:
221             col.prop(bone, "use_relative_parent")
222             col.prop_search(pchan, "bone_group", ob.pose, "bone_groups", text="Bone Group")
223
224         sub = col.column()
225         sub.active = (bone.parent is not None)
226         sub.prop(bone, "use_connect")
227         sub.prop(bone, "use_inherit_rotation")
228         sub.prop(bone, "use_inherit_scale")
229         sub = col.column()
230         sub.active = (not bone.parent or not bone.use_connect)
231         sub.prop(bone, "use_local_location")
232
233
234 class BONE_PT_display(BoneButtonsPanel, Panel):
235     bl_label = "Display"
236     bl_options = {'DEFAULT_CLOSED'}
237
238     @classmethod
239     def poll(cls, context):
240         return context.bone
241
242     def draw(self, context):
243         # note. this works ok in edit-mode but isn't
244         # all that useful so disabling for now.
245         layout = self.layout
246         layout.use_property_split = True
247
248         ob = context.object
249         bone = context.bone
250         pchan = None
251
252         if ob and bone:
253             pchan = ob.pose.bones[bone.name]
254         elif bone is None:
255             bone = context.edit_bone
256
257         if bone:
258
259             col = layout.column()
260             col.prop(bone, "hide", text="Hide")
261             sub = col.column()
262             sub.active = bool(pchan and pchan.custom_shape)
263             sub.prop(bone, "show_wire", text="Wireframe")
264
265             if pchan:
266                 col = layout.column()
267                 col.prop(pchan, "custom_shape")
268                 if pchan.custom_shape:
269                     col.prop(pchan, "use_custom_shape_bone_size", text="Bone Size")
270                     col.prop(pchan, "custom_shape_scale", text="Scale")
271                     col.prop_search(pchan, "custom_shape_transform", ob.pose, "bones")
272
273
274 class BONE_PT_inverse_kinematics(BoneButtonsPanel, Panel):
275     bl_label = "Inverse Kinematics"
276     bl_options = {'DEFAULT_CLOSED'}
277
278     @classmethod
279     def poll(cls, context):
280         ob = context.object
281         return ob and ob.mode == 'POSE' and context.bone
282
283     def draw(self, context):
284         layout = self.layout
285         layout.use_property_split = True
286
287         ob = context.object
288         bone = context.bone
289         pchan = ob.pose.bones[bone.name]
290
291         active = pchan.is_in_ik_chain
292
293         col = layout.column()
294         col.prop(pchan, "ik_stretch", slider=True)
295         col.active = active
296
297         layout.separator()
298
299         col = layout.column(align=True)
300
301         col.prop(pchan, "lock_ik_x", text="Lock IK X")
302         col.prop(pchan, "lock_ik_y", text="Y")
303         col.prop(pchan, "lock_ik_z", text="Z")
304
305         col = layout.column(align=True)
306
307         sub = col.column(align=True)
308         sub.active = pchan.lock_ik_x is False and active
309         sub.prop(pchan, "ik_stiffness_x", text="Stiffness X", slider=True)
310         sub = col.column(align=True)
311         sub.active = pchan.lock_ik_y is False and active
312         sub.prop(pchan, "ik_stiffness_y", text="Y", slider=True)
313         sub = col.column(align=True)
314         sub.active = pchan.lock_ik_z is False and active
315         sub.prop(pchan, "ik_stiffness_z", text="Z", slider=True)
316
317         col = layout.column(align=True)
318
319         sub = col.column()
320         sub.active = pchan.lock_ik_x is False and active
321         sub.prop(pchan, "use_ik_limit_x", text="Limit X")
322
323         sub = col.column(align=True)
324         sub.active = pchan.lock_ik_x is False and pchan.use_ik_limit_x and active
325         sub.prop(pchan, "ik_min_x", text="Min")
326         sub.prop(pchan, "ik_max_x", text="Max")
327
328         col.separator()
329
330         sub = col.column()
331         sub.active = pchan.lock_ik_y is False and active
332         sub.prop(pchan, "use_ik_limit_y", text="Limit Y")
333
334         sub = col.column(align=True)
335         sub.active = pchan.lock_ik_y is False and pchan.use_ik_limit_y and active
336         sub.prop(pchan, "ik_min_y", text="Min")
337         sub.prop(pchan, "ik_max_y", text="Max")
338
339         col.separator()
340
341         sub = col.column()
342         sub.active = pchan.lock_ik_z is False and active
343         sub.prop(pchan, "use_ik_limit_z", text="Limit Z")
344
345         sub = col.column(align=True)
346         sub.active = pchan.lock_ik_z is False and pchan.use_ik_limit_z and active
347         sub.prop(pchan, "ik_min_z", text="Min")
348         sub.prop(pchan, "ik_max_z", text="Max")
349
350         col.separator()
351
352         if ob.pose.ik_solver == 'ITASC':
353
354             col = layout.column()
355             col.prop(pchan, "use_ik_rotation_control", text="Control Rotation")
356             col.active = active
357
358             col = layout.column()
359
360             col.prop(pchan, "ik_rotation_weight", text="IK Rotation Weight", slider=True)
361             col.active = active
362             # not supported yet
363             #row = layout.row()
364             #row.prop(pchan, "use_ik_linear_control", text="Joint Size")
365             #row.prop(pchan, "ik_linear_weight", text="Weight", slider=True)
366
367
368 class BONE_PT_deform(BoneButtonsPanel, Panel):
369     bl_label = "Deform"
370     bl_options = {'DEFAULT_CLOSED'}
371
372     def draw_header(self, context):
373         bone = context.bone
374
375         if not bone:
376             bone = context.edit_bone
377
378         self.layout.prop(bone, "use_deform", text="")
379
380     def draw(self, context):
381         layout = self.layout
382         layout.use_property_split = True
383
384         bone = context.bone
385
386         if not bone:
387             bone = context.edit_bone
388
389         layout.active = bone.use_deform
390
391         col = layout.column()
392         col.prop(bone, "envelope_distance", text="Envelope Distance")
393         col.prop(bone, "envelope_weight", text="Envelope Weight")
394         col.prop(bone, "use_envelope_multiply", text="Envelope Multiply")
395
396         col.separator()
397
398         col = layout.column(align=True)
399         col.prop(bone, "head_radius", text="Radius Head")
400         col.prop(bone, "tail_radius", text="Tail")
401
402
403 class BONE_PT_custom_props(BoneButtonsPanel, PropertyPanel, Panel):
404     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_OPENGL'}
405     _property_type = bpy.types.Bone, bpy.types.EditBone, bpy.types.PoseBone
406
407     @property
408     def _context_path(self):
409         obj = bpy.context.object
410         if obj and obj.mode == 'POSE':
411             return "active_pose_bone"
412         else:
413             return "active_bone"
414
415
416 classes = (
417     BONE_PT_context_bone,
418     BONE_PT_transform,
419     BONE_PT_curved,
420     BONE_PT_relations,
421     BONE_PT_display,
422     BONE_PT_inverse_kinematics,
423     BONE_PT_deform,
424     BONE_PT_custom_props,
425 )
426
427 if __name__ == "__main__":  # only for live edit.
428     from bpy.utils import register_class
429     for cls in classes:
430         register_class(cls)