├── README.md └── shape-key-extras.py /README.md: -------------------------------------------------------------------------------- 1 | ### Shape Key Extras 2 | 3 | A Blender Add-on to manipulate [Shape Keys](https://docs.blender.org/manual/en/dev/animation/shape_keys). 4 | 5 | ![](https://i.sstatic.net/aIb4N.jpg) 6 | 7 | #### Installation 8 | 9 | 1. Download the [latest release](https://github.com/p2or/blender-shapekeyextras/releases) 10 | 2. In Blender open up *User Preferences > Addons* 11 | 3. Click *Install from File*, select `shape-key-extras.py` and activate the Add-on 12 | -------------------------------------------------------------------------------- /shape-key-extras.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 | bl_info = { 20 | "name": "Shape Key Extras", 21 | "description": "Blender Add-on to manipulate Shape Keys", 22 | "author": "Christian Brinkmann (p2or)", 23 | "version": (0, 2, 0), 24 | "blender": (2, 80, 0), 25 | "location": "Properties > Object Data > Shape Keys", 26 | "tracker_url": "https://github.com/p2or/blender-shapekeyextras/issues", 27 | "category": "Mesh" 28 | } 29 | 30 | import bpy 31 | import random 32 | 33 | from bpy.props import (IntProperty, 34 | BoolProperty, 35 | FloatProperty, 36 | StringProperty, 37 | PointerProperty, 38 | CollectionProperty, 39 | EnumProperty 40 | ) 41 | 42 | from bpy.types import (Operator, 43 | Panel, 44 | UIList, 45 | PropertyGroup 46 | ) 47 | 48 | # ------------------------------------------------------------------- 49 | # Helper 50 | # ------------------------------------------------------------------- 51 | 52 | def search_chars(char_sequence, name): 53 | if char_sequence: 54 | 55 | if char_sequence.endswith(','): 56 | char_sequence = char_sequence[:-1] 57 | if char_sequence.startswith(','): 58 | char_sequence = char_sequence[-1:] 59 | 60 | char_list = [i.strip() for i in char_sequence.split(",")] 61 | if name.startswith(tuple(char_list)) or name.endswith(tuple(char_list)): 62 | return True 63 | else: 64 | return False 65 | else: 66 | return False 67 | 68 | def shape_key_selection(op, context): 69 | scn = context.scene 70 | ske = scn.shape_key_extras 71 | shape_key_names = [] 72 | 73 | if not ske.sk_only: 74 | for shapekey in context.object.data.shape_keys.key_blocks: 75 | exclude_char = search_chars(ske.sk_exclude, shapekey.name) 76 | if exclude_char is not True: 77 | if ske.sk_selection == 'ALL': 78 | shape_key_names.append(shapekey.name) 79 | if ske.sk_selection == 'ENABLED': 80 | if shapekey.mute is False: 81 | shape_key_names.append(shapekey.name) 82 | if ske.sk_selection == 'DISABLED': 83 | if shapekey.mute is True: 84 | shape_key_names.append(shapekey.name) 85 | else: 86 | for shapekey in context.object.data.shape_keys.key_blocks: 87 | only_char = search_chars(ske.sk_only, shapekey.name) 88 | if only_char is True: 89 | if ske.sk_selection == 'ALL': 90 | shape_key_names.append(shapekey.name) 91 | if ske.sk_selection == 'ENABLED': 92 | if shapekey.mute is False: 93 | shape_key_names.append(shapekey.name) 94 | if ske.sk_selection == 'DISABLED': 95 | if shapekey.mute is True: 96 | shape_key_names.append(shapekey.name) 97 | 98 | return shape_key_names 99 | 100 | # ------------------------------------------------------------------- 101 | # Properties 102 | # ------------------------------------------------------------------- 103 | 104 | class SKE_PG_sceneSettings(PropertyGroup): 105 | 106 | sk_value: FloatProperty( 107 | name = "Value", 108 | description = "Set static value", 109 | default = 0, #min = 0, #max =1 110 | ) 111 | 112 | sk_random_min: FloatProperty( 113 | name = "Min", 114 | description = "Set minimum random value", 115 | default = 0, 116 | ) 117 | 118 | sk_random_max: FloatProperty( 119 | name = "Max", 120 | description = "Set maximum random value", 121 | default = 1, 122 | ) 123 | 124 | sk_exclude: StringProperty ( 125 | name = "Exclude", 126 | description = "Exclude by first character", 127 | default = "Basis, #, *" 128 | ) 129 | 130 | sk_only: StringProperty ( 131 | name = "Only", 132 | description = "Include by first character", 133 | default = "" 134 | ) 135 | 136 | sk_selection: EnumProperty( 137 | name="Selection", 138 | description="Shape Key Selection", 139 | items = (('ALL', "All", ""), 140 | ('ENABLED', "Enabled", ""), 141 | ('DISABLED', "Disabled", ""), 142 | ),default='ALL' 143 | ) 144 | 145 | sk_set_attributes: BoolProperty(default=False) 146 | sk_advanced_selection: BoolProperty(default=False) 147 | vg_uilist_index: IntProperty() 148 | vg_merge_vgroups: BoolProperty(default=False) 149 | 150 | 151 | class SKE_PT_indexShapeKeys(PropertyGroup): 152 | collection_id: IntProperty() 153 | 154 | 155 | # ------------------------------------------------------------------- 156 | # Shape Key Operators 157 | # ------------------------------------------------------------------- 158 | 159 | class SKE_OT_enableShapeKeys(Operator): 160 | bl_idname = "shapekeyextras.enable_all" 161 | bl_label = "Enable All" 162 | bl_description = "Enable all Shape Keys in Selection" 163 | bl_options = {'REGISTER', 'UNDO'} 164 | 165 | def execute(self, context): 166 | scn = context.scene 167 | ske = scn.shape_key_extras 168 | 169 | if context.object.data.shape_keys: 170 | shape_keys = (shape_key_selection(self, context)) 171 | for i in shape_keys: 172 | shapekey = context.object.data.shape_keys.key_blocks[i] 173 | shapekey.mute = False 174 | 175 | self.report({'INFO'}, "All Shape Keys enabled") 176 | else: 177 | self.report({'WARNING'}, "No shape keys found.") 178 | 179 | return {'FINISHED'} 180 | 181 | 182 | class SKE_OT_disableShapeKeys(Operator): 183 | bl_idname = "shapekeyextras.disable_all" 184 | bl_label = "Disable All" 185 | bl_description = "Mute all Shape Keys in Selection" 186 | bl_options = {'REGISTER', 'UNDO'} 187 | 188 | def execute(self, context): 189 | scn = context.scene 190 | ske = scn.shape_key_extras 191 | 192 | if context.object.data.shape_keys: 193 | shape_keys = shape_key_selection(self, context) 194 | for i in shape_keys: 195 | shapekey = context.object.data.shape_keys.key_blocks[i] 196 | shapekey.mute = True 197 | 198 | self.report({'INFO'}, "All Shape Keys disabled") 199 | else: 200 | self.report({'WARNING'}, "No shape keys found.") 201 | 202 | return {'FINISHED'} 203 | 204 | 205 | class SKE_OT_toggleShapeKeys(Operator): 206 | bl_idname = "shapekeyextras.toggle_mute" 207 | bl_label = "Toggle Visibility" 208 | bl_description = "Toggle Mute State of all Shape Keys in Selection" 209 | bl_options = {'REGISTER', 'UNDO'} 210 | 211 | def execute(self, context): 212 | scn = context.scene 213 | ske = scn.shape_key_extras 214 | 215 | if context.object.data.shape_keys: 216 | shape_keys = shape_key_selection(self, context) 217 | for i in shape_keys: 218 | shapekey = context.object.data.shape_keys.key_blocks[i] 219 | shapekey.mute = not shapekey.mute 220 | 221 | self.report({'INFO'}, "Enabled Shape Keys disabled and Disabled Shape Keys enabled") 222 | else: 223 | self.report({'WARNING'}, "No shape keys found.") 224 | return {'FINISHED'} 225 | 226 | 227 | class SKE_OT_randomShapeKeyEnable(Operator): 228 | bl_idname = "shapekeyextras.random_visibility" 229 | bl_label = "Randomize Visibility" 230 | bl_description = "Randomize Visibility/Mute State for all Shape Keys in Selection" 231 | bl_options = {'REGISTER', 'UNDO'} 232 | 233 | def execute(self, context): 234 | scn = context.scene 235 | ske = scn.shape_key_extras 236 | 237 | if context.object.data.shape_keys: 238 | shape_keys = shape_key_selection(self, context) 239 | for i in shape_keys: 240 | shapekey = context.object.data.shape_keys.key_blocks[i] 241 | shapekey.mute = bool(random.getrandbits(1)) 242 | 243 | self.report({'INFO'}, "Ramdomized Shape Key Visibility") 244 | else: 245 | self.report({'WARNING'}, "No shape keys found.") 246 | return {'FINISHED'} 247 | 248 | 249 | class SKE_OT_randomShapeKeyValue(Operator): 250 | bl_idname = "shapekeyextras.randomize" 251 | bl_label = "Randomize Shape Key Value" 252 | bl_description = "Randomize Shape Key Value for all Shape Keys in Selection" 253 | bl_options = {'REGISTER', 'UNDO'} 254 | 255 | def execute(self, context): 256 | scn = context.scene 257 | ske = scn.shape_key_extras 258 | 259 | if context.object.data.shape_keys: 260 | shape_keys = shape_key_selection(self, context) 261 | for i in shape_keys: 262 | if i != 'Basis': 263 | shapekey = context.object.data.shape_keys.key_blocks[i] 264 | shapekey.value = random.uniform(ske.sk_random_min, ske.sk_random_max) 265 | 266 | self.report({'INFO'}, "Values for Shape Keys generated") 267 | else: 268 | self.report({'WARNING'}, "No shape keys found.") 269 | return {'FINISHED'} 270 | 271 | 272 | class SKE_OT_setShapeKeyRange(Operator): 273 | bl_idname = "shapekeyextras.set_range" 274 | bl_label = "Set Shape Key Range" 275 | bl_description = "Set Range Values for Shape Keys in Selection" 276 | bl_options = {'REGISTER', 'UNDO'} 277 | 278 | def execute(self, context): 279 | scn = context.scene 280 | ske = scn.shape_key_extras 281 | 282 | if context.object.data.shape_keys: 283 | shape_keys = shape_key_selection(self, context) 284 | for i in shape_keys: 285 | if i != 'Basis': 286 | shapekey = context.object.data.shape_keys.key_blocks[i] 287 | shapekey.slider_min = ske.sk_random_min 288 | shapekey.slider_max = ske.sk_random_max 289 | 290 | self.report({'INFO'}, "Range Values adjusted") 291 | else: 292 | self.report({'WARNING'}, "No shape keys found.") 293 | return {'FINISHED'} 294 | 295 | 296 | class SKE_OT_applyShapeKeyValue(Operator): 297 | bl_idname = "shapekeyextras.set_values" 298 | bl_label = "Set Shape Key Values" 299 | bl_description = "Assign static Values to all Shape Keys in Selection" 300 | bl_options = {'REGISTER', 'UNDO'} 301 | 302 | def execute(self, context): 303 | scn = context.scene 304 | ske = scn.shape_key_extras 305 | 306 | if context.object.data.shape_keys: 307 | shape_keys = shape_key_selection(self, context) 308 | for i in shape_keys: 309 | if i != 'Basis': 310 | shapekey = context.object.data.shape_keys.key_blocks[i] 311 | shapekey.value = ske.sk_value 312 | 313 | self.report({'INFO'}, "Value assigned to Shape Keys") 314 | else: 315 | self.report({'WARNING'}, "No shape keys found.") 316 | return {'FINISHED'} 317 | 318 | 319 | class SKE_OT_removeShapeKeyDriver(Operator): 320 | bl_idname = "shapekeyextras.remove_drivers" 321 | bl_label = "Remove Drivers" 322 | bl_description = "Remove Drivers from Shapekeys in Selection" 323 | bl_options = {'REGISTER', 'UNDO'} 324 | 325 | def execute(self, context): 326 | if context.object.data.shape_keys: 327 | shape_keys = shape_key_selection(self, context) 328 | for i in shape_keys: 329 | if i != 'Basis': 330 | context.object.data.shape_keys.key_blocks[i].driver_remove("value") 331 | 332 | self.report({'INFO'}, "Drivers Removed") 333 | else: 334 | self.report({'WARNING'}, "No shape keys found.") 335 | return {'FINISHED'} 336 | 337 | def invoke(self, context, event): 338 | return context.window_manager.invoke_confirm(self, event) 339 | 340 | 341 | class SKE_OT_addShapeKeyDriver(Operator): 342 | bl_idname = "shapekeyextras.add_drivers" 343 | bl_label = "Add Drivers" 344 | bl_description = "Add Drivers to Shapekeys in Selection" 345 | bl_options = {'REGISTER', 'UNDO'} 346 | 347 | def execute(self, context): 348 | if context.object.data.shape_keys: 349 | shape_keys = shape_key_selection(self, context) 350 | for i in shape_keys: 351 | if i != 'Basis': 352 | context.object.data.shape_keys.key_blocks[i].driver_add("value") 353 | 354 | self.report({'INFO'}, "Drivers added") 355 | else: 356 | self.report({'WARNING'}, "No shape keys found.") 357 | return {'FINISHED'} 358 | 359 | 360 | # http://stackoverflow.com/questions/7977550/how-to-change-the-value-of-the-shape-key-in-blender-script 361 | class SKE_OT_addShapeKeyKeyframe(Operator): 362 | bl_idname = "shapekeyextras.insert_keyframe" 363 | bl_label = "Insert Value Keyframe" 364 | bl_description = "Insert Keyframe (Shape Key Value) for all Shape Keys in Selection" 365 | bl_options = {'REGISTER', 'UNDO'} 366 | 367 | def execute(self, context): 368 | if context.object.data.shape_keys: 369 | shape_keys = shape_key_selection(self, context) 370 | for i in shape_keys: 371 | if i != 'Basis': 372 | context.object.data.shape_keys.key_blocks[i].keyframe_insert(data_path="value") 373 | 374 | self.report({'INFO'}, "Keyframes inserted") 375 | else: 376 | self.report({'WARNING'}, "No shape keys found.") 377 | return {'FINISHED'} 378 | 379 | 380 | class SKE_OT_deleteShapeKeyKeyframe (Operator): 381 | bl_idname = "shapekeyextras.delete_keyframe" 382 | bl_label = "Remove current Keyframe" 383 | bl_description = "Remove current Keyframe for all Shape Keys in Selection" 384 | bl_options = {'REGISTER', 'UNDO'} 385 | 386 | def execute(self, context): 387 | if context.object.data.shape_keys: 388 | shape_keys = shape_key_selection(self, context) 389 | for i in shape_keys: 390 | if i != 'Basis': 391 | try: 392 | context.object.data.shape_keys.key_blocks[i].keyframe_delete(data_path="value") 393 | self.report({'INFO'}, "Keyframes deleted.") 394 | except: 395 | self.report({'WARNING'}, "No Keyframe to remove.") 396 | else: 397 | self.report({'WARNING'}, "No shape keys found.") 398 | return {'FINISHED'} 399 | 400 | def invoke(self, context, event): 401 | return context.window_manager.invoke_confirm(self, event) 402 | 403 | 404 | # http://blender.stackexchange.com/questions/5827/get-shape-key-from-action-or-fcurve-in-python 405 | class SKE_OT_removeAllShapeKeyKeyframes(Operator): 406 | bl_idname = "shapekeyextras.delete_all_keyframes" 407 | bl_label = "Remove Animation" 408 | bl_description = "Remove all Value Keyframes for all Shape Keys in Selection" 409 | bl_options = {'REGISTER', 'UNDO'} 410 | 411 | def execute(self, context): 412 | sce = context.scene 413 | if context.object.data.shape_keys: 414 | shape_keys = shape_key_selection(self, context) 415 | sk_data = context.object.data.shape_keys 416 | for i in shape_keys: 417 | if i != 'Basis' and sk_data.animation_data is not None: 418 | for f in range(sce.frame_start, sce.frame_end+1): 419 | if sk_data.animation_data.action: 420 | sk_data.key_blocks[i].keyframe_delete(data_path="value", frame=f) #returns a bool 421 | 422 | self.report({'INFO'}, "All Keyframes removed.") 423 | else: 424 | self.report({'WARNING'}, "No shape keys found.") 425 | return {'FINISHED'} 426 | 427 | def invoke(self, context, event): 428 | return context.window_manager.invoke_confirm(self, event) 429 | 430 | 431 | class SKE_OT_removeShapeKeysSelected(Operator): 432 | bl_idname = "shapekeyextras.remove_selection" 433 | bl_label = "Remove Shape Keys" 434 | bl_description = "Remove all Shape Keys in Selection" 435 | bl_options = {'REGISTER', 'UNDO'} 436 | 437 | def execute(self, context): 438 | scn = context.scene 439 | ske = scn.shape_key_extras 440 | 441 | if context.object.data.shape_keys: 442 | shape_keys = shape_key_selection(self, context) 443 | if len(shape_keys) > 0: 444 | for i in shape_keys: 445 | shapekey_index = context.object.data.shape_keys.key_blocks.keys().index(i) 446 | context.object.active_shape_key_index = shapekey_index 447 | bpy.ops.object.shape_key_remove() 448 | 449 | self.report({'INFO'}, "Selected Shape Keys removed") 450 | else: 451 | self.report({'INFO'}, "Nothing to remove") 452 | else: 453 | self.report({'WARNING'}, "No shape keys found.") 454 | return {'FINISHED'} 455 | 456 | def invoke(self, context, event): 457 | return context.window_manager.invoke_confirm(self, event) 458 | 459 | 460 | class SKE_OT_printShapeKeySelection(Operator): 461 | bl_idname = "shapekeyextras.print_shape_key_selection" 462 | bl_label = "Print Selection to Console" 463 | bl_description = "Print Shape Key Selection to the Console" 464 | bl_options = {'INTERNAL'} 465 | 466 | def execute(self, context): 467 | if context.object.data.shape_keys: 468 | shape_keys = shape_key_selection(self, context) 469 | self.report({'INFO'}, ('Selection: %s' % (', '.join(shape_keys)))) 470 | print ("Selection:", ', '.join(shape_keys)) 471 | else: 472 | self.report({'WARNING'}, "No shape keys found.") 473 | return {'FINISHED'} 474 | 475 | 476 | class SKE_OT_moveShapeKey(bpy.types.Operator): 477 | bl_idname = "shapekeyextras.move_shapekey" 478 | bl_label = "Move Shape Key" 479 | bl_description = "Move Shape Key up or down by certain amount" 480 | bl_options = {'REGISTER', 'UNDO'} 481 | 482 | steps: IntProperty() 483 | action: EnumProperty( 484 | items=( 485 | ('DOWN','Down','', 'TRIA_DOWN', 1), 486 | ('UP','Up','', 'TRIA_UP', 2) 487 | )) 488 | 489 | @classmethod 490 | def poll(cls, context): 491 | return context.object.active_shape_key 492 | 493 | def invoke(self, context, event): 494 | return context.window_manager.invoke_props_dialog(self) 495 | 496 | def draw(self, context): 497 | row = self.layout 498 | row.prop(self, "steps", text="Steps") 499 | row.prop(self, "action", expand=True) 500 | row.separator() 501 | 502 | def execute(self, context): 503 | if context.object.active_shape_key: 504 | old_id = context.object.active_shape_key_index 505 | for i in range(self.steps): 506 | if self.action == 'DOWN': 507 | bpy.ops.object.shape_key_move(type="DOWN") 508 | else: 509 | bpy.ops.object.shape_key_move(type="UP") 510 | 511 | new_id = context.object.active_shape_key_index 512 | info = 'Shape Key moved from %s to %s' % (old_id + 1, new_id + 1) 513 | self.report({'INFO'}, info) 514 | return {'FINISHED'} 515 | 516 | 517 | # ------------------------------------------------------------------- 518 | # Vertex Group Operators 519 | # ------------------------------------------------------------------- 520 | 521 | class SKE_OT_mergeVertexGroups(Operator): 522 | bl_idname = "shapekeyextras.merge_vg_ui_list" 523 | bl_label = "Merge Groups" 524 | bl_description = "Merge all Groups in List" 525 | bl_options = {'REGISTER', 'UNDO'} 526 | 527 | @classmethod 528 | def poll(cls, context): 529 | return (context.active_object.type == 'MESH' and 530 | context.mode == 'OBJECT' and 531 | len(context.active_object.vertex_groups) > 1) 532 | 533 | def execute(self, context): 534 | group_input = {i.name for i in context.scene.shape_key_extras_collection if i.name} 535 | ob = context.active_object 536 | 537 | group_lookup = {g.index: g.name for g in ob.vertex_groups} 538 | group_candidates = {n for n in group_lookup.values() if n in group_input} 539 | 540 | if len(group_candidates) > 1: 541 | # iterate through the vertices and sum the weights per group 542 | vertex_weights = {} 543 | for vert in ob.data.vertices: 544 | if len(vert.groups): 545 | for item in vert.groups: 546 | vg = ob.vertex_groups[item.group] 547 | if vg.name in group_candidates: 548 | if vert.index in vertex_weights: 549 | vertex_weights[vert.index] += vg.weight(vert.index) 550 | else: 551 | vertex_weights[vert.index] = vg.weight(vert.index) 552 | 553 | # create new vertex group 554 | vgroup = ob.vertex_groups.new(name="+".join(group_candidates)) 555 | 556 | # add the values to the group 557 | for key, value in vertex_weights.items(): 558 | vgroup.add([key], value ,'REPLACE') #'ADD','SUBTRACT' 559 | 560 | self.report({'INFO'}, ('Merged: %s' % (', '.join(group_candidates)))) 561 | return{'FINISHED'} 562 | 563 | else: 564 | self.report({'WARNING'}, "No Groups to merge.") 565 | return{'CANCELLED'} 566 | 567 | 568 | class SKE_OT_printVertexGroups(Operator): 569 | bl_idname = "shapekeyextras.print_vg_ui_list" 570 | bl_label = "Print Selection" 571 | bl_description = "Print Vertex Group Selection to Console" 572 | bl_options = {'INTERNAL'} 573 | 574 | def execute(self, context): 575 | selection = {i.name for i in context.scene.shape_key_extras_collection if i.name} 576 | if selection: 577 | self.report({'INFO'}, ", ".join(selection)) 578 | else: 579 | self.report({'INFO'}, "Nothing in Selection") 580 | return{'FINISHED'} 581 | 582 | 583 | class SKE_OT_addVertexGroups(Operator): 584 | bl_idname = "shapekeyextras.add_all_vg_ui_list" 585 | bl_label = "Add All Groups" 586 | bl_description = "Add all Vertex Groups to the List" 587 | bl_options = {'REGISTER', 'UNDO'} 588 | 589 | def execute(self, context): 590 | scn = context.scene 591 | ske = scn.shape_key_extras 592 | colprop = scn.shape_key_extras_collection 593 | idx = ske.vg_uilist_index 594 | 595 | try: 596 | item = colprop[idx] 597 | except IndexError: 598 | pass 599 | 600 | for idx, item in enumerate(colprop): 601 | if not item.name: 602 | colprop.remove(idx) 603 | 604 | items = 0 605 | for g in context.active_object.vertex_groups: 606 | if g.name not in scn.shape_key_extras_collection: 607 | item = colprop.add() 608 | item.collection_id = len(colprop) 609 | item.name = g.name 610 | ske.vg_uilist_index = (len(colprop)-1) 611 | items += 1 612 | 613 | if items: info = '%s Vertex Groups added to the list' % (items) 614 | else: info = 'Nothing to add' 615 | self.report({'INFO'}, info) 616 | return{'FINISHED'} 617 | 618 | 619 | class SKE_OT_clearVertexGroups(Operator): 620 | bl_idname = "shapekeyextras.clear_vg_ui_list" 621 | bl_label = "Clear List" 622 | bl_description = "Clear all Items in Vertex Group list" 623 | bl_options = {'REGISTER', 'UNDO'} 624 | 625 | def execute(self, context): 626 | scn = context.scene 627 | ske = scn.shape_key_extras 628 | coll = scn.shape_key_extras_collection 629 | 630 | if len(coll) > 0: 631 | # reverse range to remove last item first 632 | for i in range(len(coll)-1,-1,-1): 633 | coll.remove(i) 634 | self.report({'INFO'}, "All items removed") 635 | else: 636 | self.report({'INFO'}, "Nothing to remove") 637 | return{'FINISHED'} 638 | 639 | 640 | # ------------------------------------------------------------------- 641 | # UI 642 | # ------------------------------------------------------------------- 643 | 644 | def shapekey_panel_append(self, context): 645 | if (context.object.data.shape_keys and 646 | context.mode == 'OBJECT'): 647 | 648 | scn = context.scene 649 | ske = scn.shape_key_extras 650 | layout = self.layout 651 | 652 | layout.separator() 653 | box_set_attributes = layout.box() 654 | row = box_set_attributes.row() 655 | row.prop(ske, "sk_set_attributes", 656 | icon="TRIA_DOWN" if ske.sk_set_attributes else "TRIA_RIGHT", 657 | icon_only=True, emboss=False) 658 | 659 | row.label(text="Set Shape Key Properties") 660 | if ske.sk_set_attributes: 661 | 662 | row = box_set_attributes.row() 663 | col = row.column(align=True) 664 | rowsub = col.row(align=True) 665 | rowsub.operator("shapekeyextras.enable_all", icon="RESTRICT_VIEW_OFF") 666 | rowsub.operator("shapekeyextras.disable_all", icon="RESTRICT_VIEW_ON") 667 | col.operator("shapekeyextras.toggle_mute", icon="FILE_REFRESH") 668 | col.operator("shapekeyextras.random_visibility", icon="GROUP_VERTEX") 669 | col.separator() 670 | 671 | #row = box_set_attributes.row() 672 | col = box_set_attributes.column(align=True) 673 | rowsub = col.row(align=True) 674 | rowsub.prop(ske, "sk_selection", expand=True) 675 | 676 | box_selection = col.box() 677 | row = box_selection.row() 678 | row.prop(ske, "sk_advanced_selection", 679 | icon="TRIA_DOWN" if ske.sk_advanced_selection else "TRIA_RIGHT", 680 | icon_only=True, emboss=False) 681 | 682 | row.label(text="Custom Selection") 683 | if ske.sk_advanced_selection: 684 | row = box_selection.row() 685 | col = row.column(align=True) 686 | rowsub = col.row(align=True) 687 | rowsub = col.row(align=True) 688 | rowsub.prop(ske, "sk_exclude") 689 | rowsub = col.column(align=True) 690 | rowsub.prop(ske, "sk_only") 691 | row = box_selection.row() 692 | col = box_selection.column(align=True) 693 | rowsub = col.row(align=True) 694 | rowsub.operator("shapekeyextras.print_shape_key_selection", icon="CONSOLE") 695 | 696 | col.separator() 697 | row = box_set_attributes.row() 698 | col = row.column(align=True) 699 | col.prop(ske, "sk_value") 700 | col.operator("shapekeyextras.set_values", icon="KEY_HLT") 701 | rowsub = col.row(align=True) 702 | rowsub.prop(ske, "sk_random_min") 703 | rowsub.prop(ske, "sk_random_max") 704 | col.operator("shapekeyextras.randomize", icon="KEYINGSET") 705 | col.operator("shapekeyextras.set_range", icon="SORTSIZE") 706 | col.separator() 707 | 708 | row = box_set_attributes.row() 709 | col = row.column(align=True) 710 | rowsub = col.row(align=True) 711 | rowsub.operator("shapekeyextras.insert_keyframe", icon="ACTION") 712 | rowsub = col.row(align=True) 713 | rowsub.operator("shapekeyextras.delete_keyframe", icon="PANEL_CLOSE") 714 | rowsub.operator("shapekeyextras.delete_all_keyframes", icon="PANEL_CLOSE") 715 | col.separator() 716 | 717 | row = box_set_attributes.row() 718 | col = row.column(align=True) 719 | rowsub = col.row(align=True) 720 | rowsub.operator("shapekeyextras.add_drivers", icon="DRIVER") 721 | rowsub.operator("shapekeyextras.remove_drivers", icon="PANEL_CLOSE") 722 | rowsub = col.row(align=True) 723 | rowsub.operator("shapekeyextras.remove_selection", icon="CANCEL") 724 | 725 | layout.separator() 726 | 727 | 728 | class SKE_OT_vertexGroupActions(Operator): 729 | bl_idname = "shapekeyextras.action_vg_ui_list" 730 | bl_label = "Vertex Group List Actions" 731 | bl_options = {'REGISTER', 'UNDO'} 732 | 733 | action: EnumProperty( 734 | items=( 735 | ('UP', "Up", ""), 736 | ('DOWN', "Down", ""), 737 | ('REMOVE', "Remove", ""), 738 | ('ADD', "Add", ""), 739 | ) 740 | ) 741 | 742 | def invoke(self, context, event): 743 | scn = context.scene 744 | ske = scn.shape_key_extras 745 | colprop = scn.shape_key_extras_collection 746 | idx = ske.vg_uilist_index 747 | 748 | try: 749 | item = colprop[idx] 750 | except IndexError: 751 | pass 752 | 753 | else: 754 | if self.action == 'DOWN' and idx < len(colprop) - 1: 755 | item_next = colprop[idx+1].name 756 | ske.vg_uilist_index += 1 757 | info = 'Item %d selected' % (ske.vg_uilist_index + 1) 758 | self.report({'INFO'}, info) 759 | 760 | elif self.action == 'UP' and idx >= 1: 761 | item_prev = colprop[idx-1].name 762 | ske.vg_uilist_index -= 1 763 | info = 'Item %d selected' % (ske.vg_uilist_index + 1) 764 | self.report({'INFO'}, info) 765 | 766 | elif self.action == 'REMOVE': 767 | 768 | first_item = True if ske.vg_uilist_index == 0 else False 769 | 770 | if colprop[ske.vg_uilist_index].name: 771 | info = '%s removed from list' % (colprop[ske.vg_uilist_index].name) 772 | else: 773 | info = 'Item %s removed from list' % (ske.vg_uilist_index +1) 774 | 775 | if first_item: 776 | ske.vg_uilist_index = 0 777 | else: 778 | ske.vg_uilist_index -= 1 779 | 780 | self.report({'INFO'}, info) 781 | colprop.remove(idx) 782 | 783 | if self.action == 'ADD': 784 | item = colprop.add() 785 | item.collection_id = len(colprop) 786 | #item.collection_name = "" 787 | ske.vg_uilist_index = (len(colprop)-1) 788 | #info = '%s added to list' % ("Empty group item") 789 | #self.report({'INFO'}, info) 790 | 791 | return {"FINISHED"} 792 | 793 | 794 | class SKE_UL_vertexGroups(UIList): 795 | def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): 796 | split = layout.split(factor=0.1) 797 | split.scale_y = 1.1 798 | split.label(text=str(index+1)) 799 | split.prop_search(item, "name", context.active_object, "vertex_groups", text="") 800 | 801 | 802 | def vertexgroup_panel_append(self, context): 803 | if (context.active_object.type == 'MESH' and 804 | len(context.active_object.vertex_groups) > 1 and 805 | context.mode == 'OBJECT'): 806 | 807 | scn = context.scene 808 | ske = scn.shape_key_extras 809 | layout = self.layout 810 | 811 | box_merge_vgroups = layout.box() 812 | row = box_merge_vgroups.row() 813 | row.prop(ske, "vg_merge_vgroups", 814 | icon="TRIA_DOWN" if ske.vg_merge_vgroups else "TRIA_RIGHT", 815 | icon_only=True, emboss=False 816 | ) 817 | row.label(text="Merge Vertex Groups") 818 | if ske.vg_merge_vgroups: 819 | 820 | rows = 4 821 | row = box_merge_vgroups.row() 822 | row.template_list("SKE_UL_vertexGroups", "", scn, "shape_key_extras_collection", ske, "vg_uilist_index", rows=rows) 823 | 824 | col = row.column(align=True) 825 | col.operator("shapekeyextras.action_vg_ui_list", icon='ZOOM_IN', text="").action = 'ADD' 826 | col.operator("shapekeyextras.action_vg_ui_list", icon='ZOOM_OUT', text="").action = 'REMOVE' 827 | col.separator() 828 | col.operator("shapekeyextras.action_vg_ui_list", icon='TRIA_UP', text="").action = 'UP' 829 | col.operator("shapekeyextras.action_vg_ui_list", icon='TRIA_DOWN', text="").action = 'DOWN' 830 | 831 | row = box_merge_vgroups.row() 832 | col = row.column(align=True) 833 | rowsub = col.row(align=True) 834 | #rowsub.operator("shapekeyextras.print_vg_ui_list", icon="WORDWRAP_ON") 835 | rowsub.operator("shapekeyextras.add_all_vg_ui_list", icon="WORDWRAP_ON") 836 | rowsub.operator("shapekeyextras.clear_vg_ui_list", icon="X") 837 | col.operator("shapekeyextras.merge_vg_ui_list", icon="STICKY_UVS_LOC") 838 | 839 | layout.separator() 840 | 841 | 842 | def shapekey_specials_append(self, context): 843 | layout = self.layout 844 | row = layout.row(align=True) 845 | row.operator("shapekeyextras.move_shapekey", icon="PHYSICS") 846 | 847 | 848 | class DrawShapeKeyListItem: 849 | 850 | def draw(self, context, layout, data, item, icon, active_data, active_propname, index): 851 | obj = active_data 852 | key_block = item 853 | if self.layout_type in {'DEFAULT', 'COMPACT'}: 854 | 855 | split = layout.split(factor=0.1) 856 | split.label(text=str(index+1)) 857 | row = split.row(align=True) 858 | row.prop(key_block, "name", text="", emboss=False, icon_value=icon) 859 | 860 | row = split.row(align=True) 861 | if key_block.mute or (obj.mode == 'EDIT' and not (obj.use_shape_key_edit_mode and obj.type == 'MESH')): 862 | row.active = False 863 | if not item.id_data.use_relative: 864 | row.prop(key_block, "frame", text="", emboss=False) 865 | elif index > 0: 866 | row.prop(key_block, "value", text="", emboss=False) 867 | else: 868 | row.label(text="") 869 | row.prop(key_block, "mute", text="", emboss=False) 870 | 871 | elif self.layout_type == 'GRID': 872 | layout.alignment = 'CENTER' 873 | layout.label(text="", icon_value=icon) 874 | 875 | # built-in method ../startup/bl_ui/properties_data_mesh.py 876 | _draw = bpy.types.MESH_UL_shape_keys.draw_item 877 | 878 | 879 | # ------------------------------------------------------------------- 880 | # Register 881 | # ------------------------------------------------------------------- 882 | 883 | classes = ( 884 | SKE_PG_sceneSettings, 885 | SKE_PT_indexShapeKeys, 886 | SKE_OT_enableShapeKeys, 887 | SKE_OT_disableShapeKeys, 888 | SKE_OT_toggleShapeKeys, 889 | SKE_OT_randomShapeKeyEnable, 890 | SKE_OT_randomShapeKeyValue, 891 | SKE_OT_setShapeKeyRange, 892 | SKE_OT_applyShapeKeyValue, 893 | SKE_OT_removeShapeKeyDriver, 894 | SKE_OT_addShapeKeyDriver, 895 | SKE_OT_addShapeKeyKeyframe, 896 | SKE_OT_deleteShapeKeyKeyframe, 897 | SKE_OT_removeAllShapeKeyKeyframes, 898 | SKE_OT_removeShapeKeysSelected, 899 | SKE_OT_printShapeKeySelection, 900 | SKE_OT_moveShapeKey, 901 | SKE_OT_mergeVertexGroups, 902 | SKE_OT_printVertexGroups, 903 | SKE_OT_addVertexGroups, 904 | SKE_OT_clearVertexGroups, 905 | SKE_OT_vertexGroupActions, 906 | SKE_UL_vertexGroups, 907 | # DrawShapeKeyListItem, 908 | ) 909 | 910 | def register(): 911 | from bpy.utils import register_class 912 | for cls in classes: 913 | register_class(cls) 914 | 915 | bpy.types.Scene.shape_key_extras = PointerProperty(type=SKE_PG_sceneSettings) 916 | bpy.types.Scene.shape_key_extras_collection = CollectionProperty(type=SKE_PT_indexShapeKeys) 917 | bpy.types.DATA_PT_shape_keys.append(shapekey_panel_append) 918 | bpy.types.DATA_PT_vertex_groups.append(vertexgroup_panel_append) 919 | bpy.types.MESH_MT_shape_key_context_menu.append(shapekey_specials_append) 920 | bpy.types.MESH_UL_shape_keys.draw_item = DrawShapeKeyListItem.draw 921 | 922 | def unregister(): 923 | bpy.types.DATA_PT_shape_keys.remove(shapekey_panel_append) 924 | bpy.types.DATA_PT_vertex_groups.remove(vertexgroup_panel_append) 925 | bpy.types.MESH_MT_shape_key_context_menu.remove(shapekey_specials_append) 926 | bpy.types.MESH_UL_shape_keys.draw_item = DrawShapeKeyListItem._draw 927 | 928 | from bpy.utils import unregister_class 929 | for cls in reversed(classes): 930 | unregister_class(cls) 931 | 932 | del bpy.types.Scene.shape_key_extras_collection 933 | del bpy.types.Scene.shape_key_extras 934 | 935 | if __name__ == "__main__": 936 | register() 937 | --------------------------------------------------------------------------------