-
Notifications
You must be signed in to change notification settings - Fork 110
/
Copy pathaction_utils.py
136 lines (114 loc) · 4.74 KB
/
action_utils.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
#====================== BEGIN GPL LICENSE BLOCK ======================
#
# 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 3
# 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 ========================
import math
import bpy
from bpy.types import Action, ActionSlot, Context
# TODO: update this function to work with slotted actions. This is only used
# when exporting actions as new shakes, and is never run for end users, so I've
# left it as-is for now. We can update it when we actually need to use it.
def action_to_python_data_text(act: Action, text_block_name):
channels = {}
act_range = action_frame_range(act)
for curve in act.fcurves:
baked_keys = []
for frame in range(int(act_range[0]), int(act_range[1]) + 1):
baked_keys += [(frame, curve.evaluate(frame))]
channels[(curve.data_path, curve.array_index)] = baked_keys
text = "{\n"
for k in channels:
text += " {}: [".format(k)
for point in channels[k]:
text += "({}, {:.6f}), ".format(point[0], point[1])
text += "],\n"
text += "}\n"
return bpy.data.texts.new(text_block_name).from_string(text)
# Ensure that an Action with the given name exists, and that it has a layer and
# a keyframe strip.
def ensure_action(action_name) -> Action:
# Ensure the action exists.
#
# The song-and-dance here is to make sure we get a *local* action, not a
# library-linked action.
action = None
for act in bpy.data.actions:
if act.name == action_name and act.library == None:
action = act
break
if action == None:
action = bpy.data.actions.new(action_name)
action.use_fake_user = False
# Ensure there's at least one layer.
if len(action.layers) > 0:
layer = action.layers[0]
else:
layer = action.layers.new("Layer")
# Ensure there's a keyframe strip.
if len(layer.strips) > 0:
assert(layer.strips[0].type == 'KEYFRAME')
else:
layer.strips.new(type='KEYFRAME')
return action
# Ensures that a shake with the given name exists as a slot in the given action.
#
# If it doesn't exist, it will be created from the passed `data`.
#
# rot_factor and loc_factor are scaling factors for rotation and location
# values, respectively.
#
# Returns the slot in the action corresponding to the shake.
def ensure_shake_in_action(shake_name, action: Action, data, rot_factor=1.0, loc_factor=1.0) -> ActionSlot:
slot_identifier = "OB" + shake_name
# Ensure a slot for the shake exists.
if slot_identifier in action.slots:
slot = action.slots[slot_identifier]
else:
slot = action.slots.new('OBJECT', shake_name)
assert(slot.identifier == slot_identifier)
# If there's already a channelbag for the slot, we assume it's filled with
# the correct shake animation.
if action.layers[0].strips[0].channelbag(slot) != None:
return slot
# Create channelbag and fill it in with the shake data.
channelbag = action.layers[0].strips[0].channelbags.new(slot)
for k in data:
curve = channelbag.fcurves.new(k[0], index=k[1])
curve.keyframe_points.add(len(data[k]))
for i in range(len(data[k])):
co = [data[k][i][0], data[k][i][1]]
if k[0].startswith("rotation"):
co[1] *= rot_factor
if k[0].startswith("location"):
co[1] *= loc_factor
curve.keyframe_points[i].co = co
curve.keyframe_points[i].handle_left_type = 'AUTO'
curve.keyframe_points[i].handle_right_type = 'AUTO'
curve.keyframe_points[-1].co[1] = curve.keyframe_points[0].co[1] # Ensure looping.
curve.modifiers.new('CYCLES')
curve.update()
return slot
def action_slot_frame_range(action: Action, slot: ActionSlot):
channelbag = action.layers[0].strips[0].channelbag(slot)
r = [9999999999, -9999999999]
for curve in channelbag.fcurves:
cr = curve.range()
r[0] = min(r[0], cr[0])
r[1] = max(r[1], cr[1])
# Ensure integer values.
r[0] = math.floor(r[0])
r[1] = math.ceil(r[1])
return r