-
Notifications
You must be signed in to change notification settings - Fork 0
/
shape_key_ops.py
197 lines (158 loc) · 7.95 KB
/
shape_key_ops.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
from typing import Optional
from bpy.props import EnumProperty
from bpy.types import UIList, Context, UILayout, Menu, Key
from .context_collection_ops import (
ContextCollectionOperatorBase,
PropCollectionType,
CollectionAddBase,
)
from .extensions import ShapeKeyOp, ObjectBuildSettings, ObjectPropertyGroup, ShapeKeySettings
from .registration import register_module_classes_factory
class ShapeKeyOpsUIList(UIList):
bl_idname = "shapekey_ops_list"
def draw_item(self, context: Context, layout: UILayout, data, item: ShapeKeyOp, icon: int, active_data: ShapeKeyOp,
active_property: str, index: int = 0, flt_flag: int = 0):
self.use_filter_show = False
row = layout.row(align=True)
op_type = item.type
shape_keys = item.id_data.data.shape_keys
if op_type in ShapeKeyOp.DELETE_OPS_DICT:
op = ShapeKeyOp.DELETE_OPS_DICT[op_type]
row.label(text=op.list_label, icon="TRASH")
op.draw_props(row, shape_keys, item, "")
elif op_type in ShapeKeyOp.MERGE_OPS_DICT:
op = ShapeKeyOp.MERGE_OPS_DICT[op_type]
if item.merge_grouping == 'CONSECUTIVE':
mode_icon = ShapeKeyOp.GROUPING_CONSECUTIVE_ICON
elif item.merge_grouping == 'ALL':
mode_icon = ShapeKeyOp.GROUPING_ALL_ICON
else:
mode_icon = "NONE"
row.label(text=op.list_label, icon="FULLSCREEN_EXIT")
op.draw_props(row, shape_keys, item, "")
options = row.operator('wm.context_cycle_enum', text="", icon=mode_icon)
options.wrap = True
options.data_path = 'object.' + item.path_from_id('merge_grouping')
else:
# This shouldn't happen normally
row.label(text="ERROR: Unknown Op Type", icon="QUESTION")
def draw_filter(self, context: Context, layout: UILayout):
# No filter
pass
def filter_items(self, context: Context, data, property: str):
# We always want to show every op in order because they are applied in series. No filtering or sorting is ever
# enabled
return [], []
class ShapeKeyOpsListBase(ContextCollectionOperatorBase):
@staticmethod
def get_shape_key_settings(context: Context) -> Optional[ObjectBuildSettings]:
obj = context.object
group = ObjectPropertyGroup.get_group(obj)
return group.get_displayed_settings(context.scene)
@classmethod
def get_collection(cls, context: Context) -> Optional[PropCollectionType]:
settings = cls.get_shape_key_settings(context)
if settings is not None:
return settings.mesh_settings.shape_key_settings.shape_key_ops.collection
else:
return None
@classmethod
def get_active_index(cls, context: Context) -> Optional[int]:
settings = cls.get_shape_key_settings(context)
if settings is not None:
return settings.mesh_settings.shape_key_settings.shape_key_ops.active_index
else:
return None
@classmethod
def set_active_index(cls, context: Context, value: int):
settings = cls.get_shape_key_settings(context)
if settings is not None:
settings.mesh_settings.shape_key_settings.shape_key_ops.active_index = value
_op_builder = ShapeKeyOpsListBase.op_builder(
class_name_prefix='ShapeKeyOpsList',
bl_idname_prefix='shape_key_ops_list',
element_label="shape key op",
)
ShapeKeyOpsListRemove = _op_builder.remove.build()
ShapeKeyOpsListMove = _op_builder.move.build()
@_op_builder.add.decorate
class ShapeKeyOpsListAdd(ShapeKeyOpsListBase, CollectionAddBase[ShapeKeyOp]):
type: EnumProperty(
items=ShapeKeyOp.TYPE_ITEMS,
name="Type",
description="Type of the added shape key op"
)
def modify_newly_created(self, context: Context, data: PropCollectionType, added: ShapeKeyOp):
super().modify_newly_created(context, data, added)
added.type = self.type
class ShapeKeyOpsListAddDeleteSubMenu(Menu):
"""Add an op that deletes shape keys"""
bl_idname = 'shape_key_ops_list_add_delete_submenu'
bl_label = "Delete"
def draw(self, context: Context):
layout = self.layout
for op in ShapeKeyOp.DELETE_OPS_DICT.values():
layout.operator(ShapeKeyOpsListAdd.bl_idname, text=op.menu_label).type = op.id
class ShapeKeyOpsListAddMergeSubMenu(Menu):
"""Add an op that merges shape keys"""
bl_idname = 'shape_key_ops_list_add_merge_submenu'
bl_label = "Merge"
def draw(self, context: Context):
layout = self.layout
for op in ShapeKeyOp.MERGE_OPS_DICT.values():
layout.operator(ShapeKeyOpsListAdd.bl_idname, text=op.menu_label).type = op.id
class ShapeKeyOpsListAddMenu(Menu):
"""Add a new shape key op to the list"""
bl_idname = 'shape_key_ops_list_add_menu'
bl_label = "Add"
def draw(self, context: Context):
layout = self.layout
layout.menu(ShapeKeyOpsListAddDeleteSubMenu.bl_idname, icon='TRASH')
layout.menu(ShapeKeyOpsListAddMergeSubMenu.bl_idname, icon='FULLSCREEN_EXIT')
def draw_shape_key_ops(shape_keys_box_col: UILayout, settings: ShapeKeySettings, shape_keys: Key):
shape_key_ops = settings.shape_key_ops
operations_title_row = shape_keys_box_col.row()
operations_title_row.label(text="Operations")
vertical_buttons_col = operations_title_row.row(align=True)
vertical_buttons_col.menu(ShapeKeyOpsListAddMenu.bl_idname, text="", icon="ADD")
vertical_buttons_col.operator(ShapeKeyOpsListRemove.bl_idname, text="", icon="REMOVE")
vertical_buttons_col.separator()
vertical_buttons_col.operator(ShapeKeyOpsListMove.bl_idname, text="", icon="TRIA_UP").type = 'UP'
vertical_buttons_col.operator(ShapeKeyOpsListMove.bl_idname, text="", icon="TRIA_DOWN").type = 'DOWN'
shape_keys_box_col.template_list(
ShapeKeyOpsUIList.bl_idname, "",
shape_key_ops, 'collection',
shape_key_ops, 'active_index',
# With the buttons down the side, 4 rows is the minimum we can have, so we put the buttons on top
sort_lock=True, rows=1)
active_op_col = shape_keys_box_col.column(align=True)
active_op = shape_key_ops.active
if active_op:
op_type = active_op.type
if op_type in ShapeKeyOp.DELETE_OPS_DICT:
if op_type == ShapeKeyOp.DELETE_AFTER:
active_op_col.prop_search(active_op, 'delete_after_name', shape_keys, 'key_blocks')
elif op_type == ShapeKeyOp.DELETE_BEFORE:
active_op_col.prop_search(active_op, 'delete_before_name', shape_keys, 'key_blocks')
elif op_type == ShapeKeyOp.DELETE_BETWEEN:
active_op_col.prop_search(active_op, 'delete_after_name', shape_keys, 'key_blocks', text="Key 1")
active_op_col.prop_search(active_op, 'delete_before_name', shape_keys, 'key_blocks', text="Key 2")
elif op_type == ShapeKeyOp.DELETE_SINGLE:
active_op_col.prop_search(active_op, 'pattern', shape_keys, 'key_blocks', text="Name")
elif op_type == ShapeKeyOp.DELETE_REGEX:
active_op_col.prop(active_op, 'pattern')
elif op_type in ShapeKeyOp.MERGE_OPS_DICT:
if op_type == ShapeKeyOp.MERGE_PREFIX:
active_op_col.prop(active_op, 'pattern', text="Prefix")
elif op_type == ShapeKeyOp.MERGE_SUFFIX:
active_op_col.prop(active_op, 'pattern', text="Suffix")
elif op_type == ShapeKeyOp.MERGE_COMMON_BEFORE_DELIMITER or op_type == ShapeKeyOp.MERGE_COMMON_AFTER_DELIMITER:
active_op_col.prop(active_op, 'pattern', text="Delimiter")
elif op_type == ShapeKeyOp.MERGE_REGEX:
active_op_col.prop(active_op, 'pattern')
# Common for all merge ops
active_op_col.prop(active_op, 'merge_grouping')
# Common for all ops
active_op_col.prop(active_op, 'ignore_regex')
del _op_builder
register_module_classes_factory(__name__, globals())