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