-
Notifications
You must be signed in to change notification settings - Fork 10
/
RigFlex.py
489 lines (389 loc) · 18.2 KB
/
RigFlex.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
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
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# ##### BEGIN GPL LICENSE BLOCK #####
#
# RigFlex.py -- a script
# by Ian Huish (nerk)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# version comment: V0.3.4 main branch - Blender 2.80 version - Call initialize and set range on first bake
import bpy
import mathutils, math, os
from bpy.props import FloatProperty, FloatVectorProperty, IntProperty, BoolProperty, EnumProperty, StringProperty
from random import random
#Misc Rountines
def PrintMatrix(mat, desc):
ret = mat.decompose()
print(desc)
print("Trans: ", ret[0])
print("Rot: (%.2f, %.2f, %.2f), %.2f" % (ret[1].axis[:] + (math.degrees(ret[1].angle), )))
def PrintQuat(quat, desc):
print(desc)
print("Rot: (%.2f, %.2f, %.2f), %.2f" % (quat.axis[:] + (math.degrees(quat.angle), )))
#Remove keyframes from selected bones
def RemoveKeyframes2(armature, bones):
dispose_paths = []
for bone in bones:
if bone.name[-5:] == "_flex":
dispose_paths.append('pose.bones["{}"].rotation_quaternion'.format(bone.name))
dispose_paths.append('pose.bones["{}"].scale'.format(bone.name))
try:
dispose_curves = [fcurve for fcurve in armature.animation_data.action.fcurves if fcurve.data_path in dispose_paths]
for fcurve in dispose_curves:
armature.animation_data.action.fcurves.remove(fcurve)
except AttributeError:
pass
class ARMATURE_OT_SBSimulate(bpy.types.Operator):
"""Bake the soft body simulation"""
bl_idname = "armature.sbsimulate"
bl_label = "Bake"
bl_options = {'REGISTER', 'UNDO'}
sTargetRig = None
sSourceRig = None
sTree = []
# sBase = None
# sTestTarg = None
# sTestSource = None
sOldTail = {}
sPrevTail = {}
sBone = None
sNode = []
#check for any flex bones
def InitDone(self, targetRig):
found = False
for b in targetRig.pose.bones:
if b.name[-5:] == "_flex":
found = True
return found
def Redirect(self, targetRig, Dirn, context):
for b in targetRig.pose.bones:
if b.name[-5:] == "_flex" and "Freeze" not in b:
for c in b.constraints:
b.constraints.remove(c)
if b.parent == None:
crc = b.constraints.new('COPY_LOCATION')
crc.target = targetRig
crc.subtarget = b.name[:-5]
def addBranch(self, bones, Branch, CurrentBone):
children = []
for b in bones:
if b.parent == CurrentBone:
children.append(b)
if len(children) > 1:
for c in children:
newBranch = [c]
self.sTree.append(newBranch)
self.addBranch(bones, newBranch, c)
# print("Added Subbranch: ", c.name)
elif len(children) == 1:
Branch.append(children[0])
self.addBranch(bones, Branch, children[0])
# print("Added Leaf: ", children[0].name)
def BuildTree(self, TargetRig):
# print("BuildTree")
self.sTree = []
for b in TargetRig.pose.bones:
if b.name[-5:] == "_flex" and b.parent == None:
Branch = [b]
self.sTree.append(Branch)
self.addBranch(TargetRig.pose.bones, Branch, b)
# print("Added Branch: ", b.name)
def SetInitialKeyframe(self, TargetRig, nFrame):
TargetRig.keyframe_insert(data_path='location', frame=(nFrame))
TargetRig.keyframe_insert(data_path='rotation_euler', frame=(nFrame))
def RemoveKeyframes(self, armature, bones):
dispose_paths = []
for bone in bones:
if bone.name[-5:] == "_flex" and "Freeze" not in bone:
dispose_paths.append('pose.bones["{}"].rotation_quaternion'.format(bone.name))
dispose_paths.append('pose.bones["{}"].scale'.format(bone.name))
dispose_curves = [fcurve for fcurve in armature.animation_data.action.fcurves if fcurve.data_path in dispose_paths]
for fcurve in dispose_curves:
armature.animation_data.action.fcurves.remove(fcurve)
#Set up the parameter for the iteration in ModalMove
def BoneMovement(self, context):
# print("BoneMovement")
scene = context.scene
pFSM = scene.SBSimMainProps
startFrame = pFSM.sbsim_start_frame
endFrame = pFSM.sbsim_end_frame
TargetRig = self.sTargetRig
self.BuildTree(TargetRig)
#Go back to the start before removing keyframes to remember starting point
context.scene.frame_set(startFrame)
#Delete existing keyframes
try:
self.RemoveKeyframes(TargetRig, TargetRig.pose.bones)
except AttributeError:
pass
#record to previous tail position
context.scene.frame_set(startFrame)
layer = context.view_layer
layer.update()
# self.SetInitialKeyframe(TargetRig, startFrame)
def ModalMove(self, context):
scene = context.scene
pFSM = scene.SBSimMainProps
startFrame = pFSM.sbsim_start_frame
endFrame = pFSM.sbsim_end_frame
layer = context.view_layer
layer.update()
nFrame = scene.frame_current
# print("Frame: ", nFrame)
#Get the conditions for the whole armature
WT_Mat = self.sTargetRig.matrix_world
WT_Mat_Inv = WT_Mat.inverted_safe()
#Handle each bone
for branch in self.sTree:
#For each bone in the branch
for BranchBone in branch:
if nFrame == startFrame:
self.sOldTail[BranchBone.name] = WT_Mat @ BranchBone.tail
SourceBone = self.sTargetRig.pose.bones.get(BranchBone.name[:-5])
if SourceBone is None:
print("Null Source Bone: ", BranchBone.name)
else:
BranchBone.matrix = SourceBone.matrix
else:
layer = context.view_layer
layer.update()
BranchBone.rotation_mode = 'QUATERNION'
#Do initial calcs in world co-ords
TailLoc = WT_Mat @ BranchBone.tail
if BranchBone.parent is None:# or nFrame == startFrame:
HeadLoc = WT_Mat @ BranchBone.head
else:
HeadLoc = self.sOldTail[BranchBone.parent.name]
Movement = TailLoc - self.sOldTail[BranchBone.name]
VecBeforeMove = TailLoc - HeadLoc
VecAfterMove = self.sOldTail[BranchBone.name] - HeadLoc
RotMove = VecBeforeMove.rotation_difference(VecAfterMove)
# Now convert rotation to the local bone co-ords
bm = BranchBone.matrix.to_3x3()
bm.invert()
NewRotAxis = bm @ RotMove.axis
RotMoveLocal = mathutils.Quaternion(NewRotAxis, RotMove.angle)
# Add spring function
SourceBone = self.sTargetRig.pose.bones.get(BranchBone.name[:-5])
if SourceBone is None:
print("Null Source Bone: ", BranchBone.name)
NewAngle = BranchBone.rotation_quaternion.copy()
NewAngle.rotate(RotMoveLocal)
#Work out Source Bone rotation after constraints (rotation_quaternion doesn't seem to work)
if BranchBone.parent is not None and SourceBone is not None and SourceBone.parent is not None:
#Rest position inverse relationship
edit2parent = (BranchBone.parent.bone.matrix_local.inverted() @ BranchBone.bone.matrix_local)
#Test extra for when SimRig bones have added parents
edit2parent_i = (BranchBone.parent.bone.matrix_local.inverted() @ BranchBone.bone.matrix_local).inverted()
edit2source_i = (SourceBone.parent.bone.matrix_local.inverted() @ SourceBone.bone.matrix_local).inverted()
editAdjust = edit2source_i @ edit2parent @ edit2parent_i
#Armature Pose relationship
final2parent = SourceBone.parent.matrix.inverted() @ SourceBone.matrix
#Desired move in pose space
pspacemove = editAdjust @ final2parent
SourceQuat = pspacemove.to_quaternion()
elif SourceBone is not None:
SourceQuat = (SourceBone.bone.matrix_local.inverted() @ SourceBone.matrix).to_quaternion()
if "Stiffness" in BranchBone:
NewAngle = NewAngle.slerp(SourceQuat, BranchBone["Stiffness"])
else:
NewAngle = NewAngle.slerp(SourceQuat, pFSM.sbsim_stiffness)
# if nFrame > startFrame:
BranchBone.rotation_quaternion = NewAngle
if "Freeze" not in BranchBone:
BranchBone.keyframe_insert(data_path='rotation_quaternion', frame=(nFrame))
layer = context.view_layer
layer.update()
# if "DEF-FeelerT.002.R" in BranchBone.name:
# PrintQuat(BranchBone.rotation_quaternion, "BoneAngle")
self.sPrevTail[BranchBone.name] = BranchBone.tail
self.sOldTail[BranchBone.name] = WT_Mat @ BranchBone.tail
#Diagnostics
# print("TailLoc: (SimRig Tail)")
# print(TailLoc)
# print("HeadLoc: (SimRig Head)")
# print(HeadLoc)
# if "001" in SourceBone.name or "004" in SourceBone.name:
# print("SourceBone", SourceBone.name)
# PrintQuat(SourceBone.rotation_quaternion, "SourceQuat")
# NewQuat = SourceBone.matrix.to_quaternion()
# PrintQuat(NewQuat, "NewQuat")
#Go to next frame, or finish
wm = context.window_manager
if nFrame == endFrame:
# print("Finished")
return 0
else:
wm.progress_update(nFrame*99.0/endFrame)
context.scene.frame_set(nFrame + 1)
# print("Increment Frame")
return 1
def modal(self, context, event):
if event.type in {'RIGHTMOUSE', 'ESC'}:
self.cancel(context)
return {'CANCELLED'}
if event.type == 'TIMER':
modal_rtn = self.ModalMove(context)
if modal_rtn == 0:
context.scene.frame_set(context.scene.SBSimMainProps.sbsim_start_frame)
# print("Cancelled")
wm = context.window_manager
wm.progress_end()
return {'CANCELLED'}
return {'PASS_THROUGH'}
def execute(self, context):
sFPM = context.scene.SBSimMainProps
self.sTargetRig = context.object
#Initialize and set frame range on first bake
if not self.InitDone(self.sTargetRig):
bpy.ops.armature.sbsim_copy()
sFPM.sbsim_start_frame = context.scene.frame_start
sFPM.sbsim_end_frame = context.scene.frame_end
#Convert dependent objects
context.scene.frame_set(sFPM.sbsim_start_frame)
self.Redirect(self.sTargetRig, True, context)
scene = context.scene
#Progress bar
wm = context.window_manager
wm.progress_begin(0.0,100.0)
scene.frame_set(sFPM.sbsim_start_frame)
self.BoneMovement(context)
wm = context.window_manager
self._timer = wm.event_timer_add(0.001, window=context.window)
wm.modal_handler_add(self)
return {'RUNNING_MODAL'}
# return {'FINISHED'}
def cancel(self, context):
wm = context.window_manager
wm.event_timer_remove(self._timer)
class ARMATURE_OT_SBSim_Unbake(bpy.types.Operator):
"""Revert to the original animation"""
bl_label = "Free Bake"
bl_idname = "armature.sbsim_unbake"
bl_options = {'REGISTER', 'UNDO'}
#revert to the original amature
def execute(self, context):
sFPM = context.scene.SBSimMainProps
TargetRig = context.object
if TargetRig.type != "ARMATURE":
print("Not an Armature", context.object.type)
return {'FINISHED'}
for b in TargetRig.pose.bones:
if b.name[-5:] == "_flex" and "Freeze" not in b:
crc = b.constraints.new('COPY_TRANSFORMS')
crc.target = TargetRig
crc.subtarget = b.name[:-5]
return {'FINISHED'}
class ARMATURE_OT_SBSim_Revert(bpy.types.Operator):
"""Revert to the original armature and vertex groups"""
bl_label = "Revert"
bl_idname = "armature.sbsim_revert"
bl_options = {'REGISTER', 'UNDO'}
#update the vertex groups on any associated object
def RevertVertexGroups(self, context, targetRig):
for o in context.scene.objects:
ArmMod = False
for mod in o.modifiers:
if mod.type == 'ARMATURE' and mod.object is not None:
if mod.object.name == targetRig.name:
# print("RevertVG Object found: ", o.name)
ArmMod = True
if ArmMod:
for vg in o.vertex_groups:
# print("Looking at VG: ", vg.name)
if vg.name[-5:] == "_flex":
vg.name = vg.name[:-5]
#revert to the original amature
def execute(self, context):
sFPM = context.scene.SBSimMainProps
TargetRig = context.object
#Remove animation data from flex bones
FlexBones = []
for b in TargetRig.pose.bones:
if b.name[-5:] == "_flex":
FlexBones.append(b)
RemoveKeyframes2(TargetRig, FlexBones)
#Delete each sim bone in Edit mode
OrigMode = context.mode
bpy.ops.object.mode_set(mode='EDIT')
for b in TargetRig.data.edit_bones:
# print("Delete EditBone: ", b.name)
if b.name[-5:] == "_flex":
TargetRig.data.edit_bones.remove(b)
# Remove RigFlex Bone Collection
RigFlexCollection = TargetRig.data.collections["RigFlex"]
# if len(RigFlexCollection.bones) < 1:
TargetRig.data.collections.remove(RigFlexCollection)
#Return from Edit mode
bpy.ops.object.mode_set(mode=OrigMode)
self.RevertVertexGroups(context, TargetRig)
return {'FINISHED'}
# class ARMATURE_OT_SBSim_Test(bpy.types.Operator):
# """Test"""
# bl_label = "Test"
# bl_idname = "armature.sbsim_test"
# bl_options = {'REGISTER', 'UNDO'}
# def execute(self, context):
# sim = bpy.data.objects["Armature"]
# sb = sim.pose.bones["Bone.001"]
# test = bpy.data.objects["test"]
# tb = test.pose.bones["Bone"]
# tb1 = test.pose.bones["Bone.001"]
# tbr = tb.bone.matrix_local
# tb1r = tb1.bone.matrix_local
# tbri = tb.bone.matrix_local.inverted()
# tb1ri = tb1.bone.matrix_local.inverted()
# sbm = sb.matrix
# sbmi = sbm.inverted()
# sbpm = sb.parent.matrix
# sbpmi = sbpm.inverted()
# tbmi = tb.matrix.inverted()
# tbm = tb.matrix
# tb1mi = tb1.matrix.inverted()
# tb1m = tb1.matrix
# #Test Edit inverse relationship
# edit2parent = (tbri * tb1r)
# edit2parent_i = (tbri * tb1r).inverted()
# print("EditRot", edit2parent_i.to_euler())
# #Armature Pose relationship
# final2parent = sbpmi * sbm
# print("PoseRot", final2parent.to_euler())
# #Desired move in pose space
# pspacemove = edit2parent_i * final2parent
# print("pspacemove", pspacemove.to_euler())
# tb1.rotation_quaternion = pspacemove.to_quaternion()
# loc = tbri * sb.parent.matrix.translation
# tb.location = loc
# return {'FINISHED'}
def registerTypes():
classes = (ARMATURE_OT_SBSimulate,ARMATURE_OT_SBSim_Revert,ARMATURE_OT_SBSim_Unbake)
from bpy.utils import register_class
for cls in classes:
register_class(cls)
# bpy.utils.register_class(ARMATURE_OT_SBSimulate)
# bpy.utils.register_class(ARMATURE_OT_SBSim_Revert)
# bpy.utils.register_class(ARMATURE_OT_SBSim_Unbake)
# bpy.utils.register_class(ARMATURE_OT_SBSim_Test)
def unregisterTypes():
classes = (ARMATURE_OT_SBSimulate,ARMATURE_OT_SBSim_Revert,ARMATURE_OT_SBSim_Unbake)
from bpy.utils import unregister_class
for cls in reversed(classes):
unregister_class(cls)
# bpy.utils.unregister_class(ARMATURE_OT_SBSimulate)
# bpy.utils.unregister_class(ARMATURE_OT_SBSim_Revert)
# bpy.utils.unregister_class(ARMATURE_OT_SBSim_Unbake)
# bpy.utils.unregister_class(ARMATURE_OT_SBSim_Test)
# if __name__ == "__main__":
# register()