137 lines
4.7 KiB
Python
137 lines
4.7 KiB
Python
#====================== 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
|