e80f104b3936b49c6b21faa8c0b38020c34012d4
[blender.git] / release / scripts / startup / bl_ui / properties_data_armature.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 import bpy
21 from bpy.types import Panel, Menu
22 from rna_prop_ui import PropertyPanel
23
24 from .properties_animviz import (
25     MotionPathButtonsPanel,
26     MotionPathButtonsPanel_display,
27 )
28
29 class ArmatureButtonsPanel:
30     bl_space_type = 'PROPERTIES'
31     bl_region_type = 'WINDOW'
32     bl_context = "data"
33
34     @classmethod
35     def poll(cls, context):
36         return context.armature
37
38
39 class DATA_PT_context_arm(ArmatureButtonsPanel, Panel):
40     bl_label = ""
41     bl_options = {'HIDE_HEADER'}
42
43     def draw(self, context):
44         layout = self.layout
45
46         ob = context.object
47         arm = context.armature
48         space = context.space_data
49
50         if ob:
51             layout.template_ID(ob, "data")
52         elif arm:
53             layout.template_ID(space, "pin_id")
54
55
56 class DATA_PT_skeleton(ArmatureButtonsPanel, Panel):
57     bl_label = "Skeleton"
58
59     def draw(self, context):
60         layout = self.layout
61
62         arm = context.armature
63
64         layout.row().prop(arm, "pose_position", expand=True)
65
66         col = layout.column()
67         col.label(text="Layers:")
68         col.prop(arm, "layers", text="")
69         col.label(text="Protected Layers:")
70         col.prop(arm, "layers_protected", text="")
71
72
73 class DATA_PT_display(ArmatureButtonsPanel, Panel):
74     bl_label = "Viewport Display"
75     bl_options = {'DEFAULT_CLOSED'}
76
77     def draw(self, context):
78         layout = self.layout
79         layout.use_property_split = True
80
81         ob = context.object
82         arm = context.armature
83
84         layout.prop(arm, "display_type", text="Display As")
85
86         flow = layout.grid_flow(row_major=False, columns=0, even_columns=False, even_rows=False, align=True)
87         col = flow.column()
88         col.prop(arm, "show_names", text="Names")
89         col = flow.column()
90         col.prop(arm, "show_axes", text="Axes")
91         col = flow.column()
92         col.prop(arm, "show_bone_custom_shapes", text="Shapes")
93         col = flow.column()
94         col.prop(arm, "show_group_colors", text="Group Colors")
95         if ob:
96             col = flow.column()
97             col.prop(ob, "show_in_front", text="In Front")
98         col = flow.column()
99         col.prop(arm, "use_deform_delay", text="Delay Refresh")
100
101
102 class DATA_MT_bone_group_context_menu(Menu):
103     bl_label = "Bone Group Specials"
104
105     def draw(self, context):
106         layout = self.layout
107
108         layout.operator("pose.group_sort", icon='SORTALPHA')
109
110
111 class DATA_PT_bone_groups(ArmatureButtonsPanel, Panel):
112     bl_label = "Bone Groups"
113     bl_options = {'DEFAULT_CLOSED'}
114
115     @classmethod
116     def poll(cls, context):
117         return (context.object and context.object.type == 'ARMATURE' and context.object.pose)
118
119     def draw(self, context):
120         layout = self.layout
121
122         ob = context.object
123         pose = ob.pose
124         group = pose.bone_groups.active
125
126         row = layout.row()
127
128         rows = 1
129         if group:
130             rows = 4
131         row.template_list("UI_UL_list", "bone_groups", pose, "bone_groups", pose.bone_groups, "active_index", rows=rows)
132
133         col = row.column(align=True)
134         col.active = (ob.proxy is None)
135         col.operator("pose.group_add", icon='ADD', text="")
136         col.operator("pose.group_remove", icon='REMOVE', text="")
137         col.menu("DATA_MT_bone_group_context_menu", icon='DOWNARROW_HLT', text="")
138         if group:
139             col.separator()
140             col.operator("pose.group_move", icon='TRIA_UP', text="").direction = 'UP'
141             col.operator("pose.group_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
142
143             split = layout.split()
144             split.active = (ob.proxy is None)
145
146             col = split.column()
147             col.prop(group, "color_set")
148             if group.color_set:
149                 col = split.column()
150                 sub = col.row(align=True)
151                 sub.enabled = group.is_custom_color_set  # only custom colors are editable
152                 sub.prop(group.colors, "normal", text="")
153                 sub.prop(group.colors, "select", text="")
154                 sub.prop(group.colors, "active", text="")
155
156         row = layout.row()
157         row.active = (ob.proxy is None)
158
159         sub = row.row(align=True)
160         sub.operator("pose.group_assign", text="Assign")
161         # row.operator("pose.bone_group_remove_from", text="Remove")
162         sub.operator("pose.group_unassign", text="Remove")
163
164         sub = row.row(align=True)
165         sub.operator("pose.group_select", text="Select")
166         sub.operator("pose.group_deselect", text="Deselect")
167
168
169 class DATA_PT_pose_library(ArmatureButtonsPanel, Panel):
170     bl_label = "Pose Library"
171     bl_options = {'DEFAULT_CLOSED'}
172
173     @classmethod
174     def poll(cls, context):
175         return (context.object and context.object.type == 'ARMATURE' and context.object.pose)
176
177     def draw(self, context):
178         layout = self.layout
179
180         ob = context.object
181         poselib = ob.pose_library
182
183         layout.template_ID(ob, "pose_library", new="poselib.new", unlink="poselib.unlink")
184
185         if poselib:
186             # warning about poselib being in an invalid state
187             if poselib.fcurves and not poselib.pose_markers:
188                 layout.label(icon='ERROR', text="Error: Potentially corrupt library, run 'Sanitize' operator to fix")
189
190             # list of poses in pose library
191             row = layout.row()
192             row.template_list("UI_UL_list", "pose_markers", poselib, "pose_markers",
193                               poselib.pose_markers, "active_index", rows=5)
194
195             # column of operators for active pose
196             # - goes beside list
197             col = row.column(align=True)
198
199             # invoke should still be used for 'add', as it is needed to allow
200             # add/replace options to be used properly
201             col.operator("poselib.pose_add", icon='ADD', text="")
202
203             col.operator_context = 'EXEC_DEFAULT'  # exec not invoke, so that menu doesn't need showing
204
205             pose_marker_active = poselib.pose_markers.active
206
207             if pose_marker_active is not None:
208                 col.operator("poselib.pose_remove", icon='REMOVE', text="")
209                 col.operator(
210                     "poselib.apply_pose",
211                     icon='ZOOM_SELECTED',
212                     text="",
213                 ).pose_index = poselib.pose_markers.active_index
214
215             col.operator("poselib.action_sanitize", icon='HELP', text="")  # XXX: put in menu?
216
217             if pose_marker_active is not None:
218                 col.operator("poselib.pose_move", icon='TRIA_UP', text="").direction = 'UP'
219                 col.operator("poselib.pose_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
220
221
222 class DATA_PT_iksolver_itasc(ArmatureButtonsPanel, Panel):
223     bl_label = "Inverse Kinematics"
224     bl_options = {'DEFAULT_CLOSED'}
225
226     @classmethod
227     def poll(cls, context):
228         ob = context.object
229         return (ob and ob.pose)
230
231     def draw(self, context):
232         layout = self.layout
233         layout.use_property_split = True
234
235         ob = context.object
236         itasc = ob.pose.ik_param
237
238         layout.prop(ob.pose, "ik_solver")
239
240         if itasc:
241             layout.prop(itasc, "mode")
242             simulation = (itasc.mode == 'SIMULATION')
243             if simulation:
244                 layout.prop(itasc, "reiteration_method", expand=False)
245
246             col = layout.column()
247             col.active = not simulation or itasc.reiteration_method != 'NEVER'
248             col.prop(itasc, "precision")
249             col.prop(itasc, "iterations")
250
251             if simulation:
252                 col.prop(itasc, "use_auto_step")
253                 sub = layout.column(align=True)
254                 if itasc.use_auto_step:
255                     sub.prop(itasc, "step_min", text="Steps Min")
256                     sub.prop(itasc, "step_max", text="Max")
257                 else:
258                     sub.prop(itasc, "step_count", text="Steps")
259
260             col.prop(itasc, "solver")
261             if simulation:
262                 col.prop(itasc, "feedback")
263                 col.prop(itasc, "velocity_max")
264             if itasc.solver == 'DLS':
265                 col.separator()
266                 col.prop(itasc, "damping_max", text="Damping Max", slider=True)
267                 col.prop(itasc, "damping_epsilon", text="Damping Epsilon", slider=True)
268
269
270 class DATA_PT_motion_paths(MotionPathButtonsPanel, Panel):
271     #bl_label = "Bones Motion Paths"
272     bl_options = {'DEFAULT_CLOSED'}
273     bl_context = "data"
274
275     @classmethod
276     def poll(cls, context):
277         # XXX: include pose-mode check?
278         return (context.object) and (context.armature)
279
280     def draw(self, context):
281         # layout = self.layout
282
283         ob = context.object
284         avs = ob.pose.animation_visualization
285
286         pchan = context.active_pose_bone
287         mpath = pchan.motion_path if pchan else None
288
289         self.draw_settings(context, avs, mpath, bones=True)
290
291
292 class DATA_PT_motion_paths_display(MotionPathButtonsPanel_display, Panel):
293     #bl_label = "Bones Motion Paths"
294     bl_context = "data"
295     bl_parent_id = "DATA_PT_motion_paths"
296     bl_options = {'DEFAULT_CLOSED'}
297
298     @classmethod
299     def poll(cls, context):
300         # XXX: include pose-mode check?
301         return (context.object) and (context.armature)
302
303     def draw(self, context):
304         # layout = self.layout
305
306         ob = context.object
307         avs = ob.pose.animation_visualization
308
309         pchan = context.active_pose_bone
310         mpath = pchan.motion_path if pchan else None
311
312         self.draw_settings(context, avs, mpath, bones=True)
313
314
315 class DATA_PT_custom_props_arm(ArmatureButtonsPanel, PropertyPanel, Panel):
316     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
317     _context_path = "object.data"
318     _property_type = bpy.types.Armature
319
320
321 classes = (
322     DATA_PT_context_arm,
323     DATA_PT_skeleton,
324     DATA_MT_bone_group_context_menu,
325     DATA_PT_bone_groups,
326     DATA_PT_pose_library,
327     DATA_PT_motion_paths,
328     DATA_PT_motion_paths_display,
329     DATA_PT_display,
330     DATA_PT_iksolver_itasc,
331     DATA_PT_custom_props_arm,
332 )
333
334 if __name__ == "__main__":  # only for live edit.
335     from bpy.utils import register_class
336     for cls in classes:
337         register_class(cls)