forked from AlexLemminG/Rigify-To-Unity
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path__init__.py
More file actions
366 lines (286 loc) · 12.9 KB
/
Copy path__init__.py
File metadata and controls
366 lines (286 loc) · 12.9 KB
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
#script to make rigify compatible with unity humanoid
#HOWTO: right after generating rig using rigify
# press armature -> Rigify To Unity Converter -> (Prepare rig for unity) button
bl_info = {
"name": "Rigify to Unity",
"category": "Rigging",
"description": "Change Rigify rig into Mecanim-ready rig for Unity",
"location": "At the bottom of Rigify rig data/armature tab",
"blender":(2,80,0)
}
import bpy
import re
BASE_REQUIRED_BONES = (
'DEF-shoulder.L',
'DEF-shoulder.R',
'DEF-spine.003',
'DEF-upper_arm.L',
'DEF-upper_arm.L.001',
'DEF-upper_arm.R',
'DEF-upper_arm.R.001',
'DEF-forearm.L',
'DEF-forearm.L.001',
'DEF-forearm.R',
'DEF-forearm.R.001',
'DEF-hand.L',
'DEF-hand.R',
'DEF-spine',
'DEF-thigh.L',
'DEF-thigh.L.001',
'DEF-thigh.R',
'DEF-thigh.R.001',
'DEF-shin.L',
'DEF-shin.L.001',
'DEF-shin.R',
'DEF-shin.R.001',
'DEF-foot.L',
'DEF-foot.R',
)
TAIL_BONE_PATTERN = re.compile(r'^DEF-tail(?:\.(\d+))?$')
EAR_BONE_PATTERN = re.compile(r'^DEF-ear\.(L|R)(?:\.(\d+))?$')
RITTY_BODY_BONE_PARENTS = (
('DEF-belly.C', 'DEF-spine.002'),
('DEF-Breast.C', 'DEF-spine.003'),
)
def find_numbered_chain(bones, pattern, suffix_group=1):
"""Return matching bone names with the unnumbered bone first."""
matches = []
for bone in bones:
match = pattern.fullmatch(bone.name)
if match is None:
continue
suffix = match.group(suffix_group)
sort_key = (suffix is not None, int(suffix) if suffix is not None else -1)
matches.append((sort_key, bone.name))
matches.sort(key=lambda item: item[0])
return [name for _, name in matches]
def find_ear_chains(bones):
chains = {'L': [], 'R': []}
for bone in bones:
match = EAR_BONE_PATTERN.fullmatch(bone.name)
if match is None:
continue
side = match.group(1)
suffix = match.group(2)
sort_key = (suffix is not None, int(suffix) if suffix is not None else -1)
chains[side].append((sort_key, bone.name))
for side in chains:
chains[side].sort(key=lambda item: item[0])
chains[side] = [name for _, name in chains[side]]
return chains
def reparent_def_chain(edit_bones, chain_names, root_parent):
if not chain_names:
return
parent = root_parent
for name in chain_names:
bone = edit_bones.get(name)
if bone is None:
continue
# Parenting is all Unity needs. Keeping the bones disconnected prevents
# Blender from snapping their heads to a new parent's tail.
bone.use_connect = False
bone.parent = parent
parent = bone
def get_org_bone_name(def_bone_name):
if def_bone_name.startswith('DEF-'):
return 'ORG-' + def_bone_name[4:]
return 'ORG-' + def_bone_name
def ensure_copy_transforms_constraint(ob, bone_name, target_bone_name):
pose_bone = ob.pose.bones.get(bone_name)
if pose_bone is None:
return False
for constraint in pose_bone.constraints:
if (
constraint.type == 'COPY_TRANSFORMS'
and constraint.target == ob
and constraint.subtarget == target_bone_name
):
return False
constraint = pose_bone.constraints.new('COPY_TRANSFORMS')
constraint.name = 'Ritty Copy Transforms'
constraint.target = ob
constraint.subtarget = target_bone_name
return True
def find_missing_base_bones(ob):
return [name for name in BASE_REQUIRED_BONES if name not in ob.data.bones]
def validate_ritty_anchors(ob):
tail_names = find_numbered_chain(ob.data.bones, TAIL_BONE_PATTERN)
ear_chains = find_ear_chains(ob.data.bones)
if tail_names and 'root' not in ob.data.bones:
return "Tail bones were found, but the required 'root' bone is missing"
has_ears = any(ear_chains.values())
has_head = 'DEF-head' in ob.data.bones or 'DEF-spine.006' in ob.data.bones
if has_ears and not has_head:
return "Ear bones were found, but neither 'DEF-head' nor 'DEF-spine.006' exists"
for bone_name, parent_name in RITTY_BODY_BONE_PARENTS:
if bone_name in ob.data.bones and parent_name not in ob.data.bones:
return "Bone '{}' requires parent bone '{}'".format(bone_name, parent_name)
org_name = get_org_bone_name(bone_name)
if bone_name in ob.data.bones and org_name not in ob.data.bones:
return "Bone '{}' requires constraint target bone '{}'".format(bone_name, org_name)
return None
def convert_base_rig(ob):
"""Run the add-on's original Humanoid conversion."""
bpy.ops.object.mode_set(mode='OBJECT')
if 'DEF-breast.L' in ob.data.bones:
ob.data.bones['DEF-breast.L'].use_deform = False
if 'DEF-breast.R' in ob.data.bones:
ob.data.bones['DEF-breast.R'].use_deform = False
if 'DEF-pelvis.L' in ob.data.bones:
ob.data.bones['DEF-pelvis.L'].use_deform = False
if 'DEF-pelvis.R' in ob.data.bones:
ob.data.bones['DEF-pelvis.R'].use_deform = False
bpy.ops.object.mode_set(mode='EDIT')
ob.data.edit_bones['DEF-shoulder.L'].parent = ob.data.edit_bones['DEF-spine.003']
ob.data.edit_bones['DEF-shoulder.R'].parent = ob.data.edit_bones['DEF-spine.003']
ob.data.edit_bones['DEF-upper_arm.L'].parent = ob.data.edit_bones['DEF-shoulder.L']
ob.data.edit_bones['DEF-upper_arm.R'].parent = ob.data.edit_bones['DEF-shoulder.R']
ob.data.edit_bones['DEF-thigh.L'].parent = ob.data.edit_bones['DEF-spine']
ob.data.edit_bones['DEF-thigh.R'].parent = ob.data.edit_bones['DEF-spine']
ob.data.edit_bones['DEF-upper_arm.L'].tail = ob.data.edit_bones['DEF-upper_arm.L.001'].tail
ob.data.edit_bones['DEF-forearm.L'].tail = ob.data.edit_bones['DEF-forearm.L.001'].tail
ob.data.edit_bones['DEF-forearm.L'].parent = ob.data.edit_bones['DEF-upper_arm.L.001'].parent
ob.data.edit_bones['DEF-hand.L'].parent = ob.data.edit_bones['DEF-forearm.L.001'].parent
ob.data.edit_bones.remove(ob.data.edit_bones['DEF-upper_arm.L.001'])
ob.data.edit_bones.remove(ob.data.edit_bones['DEF-forearm.L.001'])
ob.data.edit_bones['DEF-upper_arm.R'].tail = ob.data.edit_bones['DEF-upper_arm.R.001'].tail
ob.data.edit_bones['DEF-forearm.R'].tail = ob.data.edit_bones['DEF-forearm.R.001'].tail
ob.data.edit_bones['DEF-forearm.R'].parent = ob.data.edit_bones['DEF-upper_arm.R.001'].parent
ob.data.edit_bones['DEF-hand.R'].parent = ob.data.edit_bones['DEF-forearm.R.001'].parent
ob.data.edit_bones.remove(ob.data.edit_bones['DEF-upper_arm.R.001'])
ob.data.edit_bones.remove(ob.data.edit_bones['DEF-forearm.R.001'])
ob.data.edit_bones['DEF-thigh.L'].tail = ob.data.edit_bones['DEF-thigh.L.001'].tail
ob.data.edit_bones['DEF-shin.L'].tail = ob.data.edit_bones['DEF-shin.L.001'].tail
ob.data.edit_bones['DEF-shin.L'].parent = ob.data.edit_bones['DEF-thigh.L.001'].parent
ob.data.edit_bones['DEF-foot.L'].parent = ob.data.edit_bones['DEF-shin.L.001'].parent
ob.data.edit_bones.remove(ob.data.edit_bones['DEF-thigh.L.001'])
ob.data.edit_bones.remove(ob.data.edit_bones['DEF-shin.L.001'])
ob.data.edit_bones['DEF-thigh.R'].tail = ob.data.edit_bones['DEF-thigh.R.001'].tail
ob.data.edit_bones['DEF-shin.R'].tail = ob.data.edit_bones['DEF-shin.R.001'].tail
ob.data.edit_bones['DEF-shin.R'].parent = ob.data.edit_bones['DEF-thigh.R.001'].parent
ob.data.edit_bones['DEF-foot.R'].parent = ob.data.edit_bones['DEF-shin.R.001'].parent
ob.data.edit_bones.remove(ob.data.edit_bones['DEF-thigh.R.001'])
ob.data.edit_bones.remove(ob.data.edit_bones['DEF-shin.R.001'])
if 'DEF-pelvis.L' in ob.data.edit_bones:
ob.data.edit_bones.remove(ob.data.edit_bones['DEF-pelvis.L'])
if 'DEF-pelvis.R' in ob.data.edit_bones:
ob.data.edit_bones.remove(ob.data.edit_bones['DEF-pelvis.R'])
if 'DEF-breast.L' in ob.data.edit_bones:
ob.data.edit_bones.remove(ob.data.edit_bones['DEF-breast.L'])
if 'DEF-breast.R' in ob.data.edit_bones:
ob.data.edit_bones.remove(ob.data.edit_bones['DEF-breast.R'])
bpy.ops.object.mode_set(mode='OBJECT')
namelist = [('DEF-spine.006', 'DEF-head'), ('DEF-spine.005', 'DEF-neck')]
for name, newname in namelist:
pb = ob.pose.bones.get(name)
if pb is None:
continue
pb.name = newname
def convert_ritty_extras(ob):
"""Build Ritty's custom export hierarchy and remove its central pelvis bone."""
bpy.ops.object.mode_set(mode='EDIT')
try:
edit_bones = ob.data.edit_bones
tail_names = find_numbered_chain(edit_bones, TAIL_BONE_PATTERN)
ear_chains = find_ear_chains(edit_bones)
body_bone_names = []
pelvis_removed = False
if tail_names:
reparent_def_chain(edit_bones, tail_names, edit_bones.get('root'))
head = edit_bones.get('DEF-head') or edit_bones.get('DEF-spine.006')
if head is not None:
for chain_names in ear_chains.values():
reparent_def_chain(edit_bones, chain_names, head)
for bone_name, parent_name in RITTY_BODY_BONE_PARENTS:
bone = edit_bones.get(bone_name)
if bone is not None:
bone.use_connect = False
bone.parent = edit_bones.get(parent_name)
body_bone_names.append(bone_name)
pelvis = edit_bones.get('DEF-pelvis.C')
pelvis_found = pelvis is not None
if pelvis_found:
spine = edit_bones.get('DEF-spine')
for child in list(pelvis.children):
child.use_connect = False
child.parent = spine
edit_bones.remove(pelvis)
pelvis_removed = True
finally:
bpy.ops.object.mode_set(mode='OBJECT')
body_constraint_count = 0
for bone_name in body_bone_names:
if ensure_copy_transforms_constraint(ob, bone_name, get_org_bone_name(bone_name)):
body_constraint_count += 1
return {
'tail_count': len(tail_names),
'ear_count': sum(len(chain) for chain in ear_chains.values()),
'body_bone_count': len(body_bone_names),
'body_constraint_count': body_constraint_count,
'pelvis_found': pelvis_found,
'pelvis_removed': pelvis_removed,
}
class UnityMecanim_Panel(bpy.types.Panel):
bl_label = "Rigify to Unity converter"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "data"
@classmethod
def poll(self, context):
return context.object is not None and context.object.type == 'ARMATURE'
def draw(self, context):
self.layout.operator("rig4mec.convert2unity", text="Prepare rig for Unity")
self.layout.operator("rig4mec.convert_ritty2unity", text="Prepare Ritty rig for Unity")
class UnityMecanim_Convert2Unity(bpy.types.Operator):
bl_idname = "rig4mec.convert2unity"
bl_label = "Prepare rig for unity"
def execute(self, context):
ob = context.object
missing = find_missing_base_bones(ob)
if missing:
self.report({'ERROR'}, 'Missing required bones: ' + ', '.join(missing))
return {'CANCELLED'}
convert_base_rig(ob)
self.report({'INFO'}, 'Unity ready rig!')
return {'FINISHED'}
class UnityMecanim_ConvertRitty2Unity(bpy.types.Operator):
bl_idname = "rig4mec.convert_ritty2unity"
bl_label = "Prepare Ritty rig for Unity"
bl_description = "Run the standard conversion and add Ritty custom bone handling"
def execute(self, context):
ob = context.object
missing = find_missing_base_bones(ob)
if missing:
self.report(
{'ERROR'},
'Use an unconverted generated rig. Missing required bones: ' + ', '.join(missing),
)
return {'CANCELLED'}
anchor_error = validate_ritty_anchors(ob)
if anchor_error is not None:
self.report({'ERROR'}, anchor_error)
return {'CANCELLED'}
convert_base_rig(ob)
result = convert_ritty_extras(ob)
pelvis_status = 'not found'
if result['pelvis_removed']:
pelvis_status = 'removed'
self.report(
{'INFO'},
'Ritty rig ready! Tail: {tail}, ears: {ears}, body: {body}, constraints: {constraints}, DEF-pelvis.C: {pelvis}'.format(
tail=result['tail_count'],
ears=result['ear_count'],
body=result['body_bone_count'],
constraints=result['body_constraint_count'],
pelvis=pelvis_status,
),
)
return {'FINISHED'}
def register():
bpy.utils.register_class(UnityMecanim_Convert2Unity)
bpy.utils.register_class(UnityMecanim_ConvertRitty2Unity)
bpy.utils.register_class(UnityMecanim_Panel)
def unregister():
bpy.utils.unregister_class(UnityMecanim_Panel)
bpy.utils.unregister_class(UnityMecanim_ConvertRitty2Unity)
bpy.utils.unregister_class(UnityMecanim_Convert2Unity)