import numpy as np
import io
import tarfile
import pyassimp
from . import transforms
from collections import namedtuple
import yaml
import pickle as pickle
Bone = namedtuple(
"Bone",
("name", "parent_name", "id", "children", "offsetmatrix", "positions", "rotations"),
)
[docs]def get_children(bones, bone):
return [i.name for i in bones if i.parent_name == bone.name]
[docs]def basic_animation(filename):
return {"animation_filename": filename, "animation_index": 0, "looping": True}
[docs]class Animation(object):
def __init__(self):
self.cached_bone_values = {}
[docs] def load_from_file(self, filename, bones):
if filename[-5:] == ".yaml":
self._configuration = yaml.load(open(filename).read())
else:
self._configuration = basic_animation(filename)
if "animation_filename" in self._configuration:
self._animation = pyassimp.load(
self._configuration["animation_filename"]
).animations[
self._configuration["animation_index"]
]
else:
self._animation = False
if "invert" not in self._configuration:
self._configuration["invert"] = False
if "animation_fps" not in self._configuration:
self._configuration["animation_fps"] = self._animation.tickspersecond
if "animation_frames" not in self._configuration:
self._configuration["animation_frames"] = int(self._animation.duration)
if "end_position" not in self._configuration:
self._configuration["end_position"] = [0, 0, 0]
if "end_rotation" not in self._configuration:
self._configuration["end_rotation"] = 0
self._configuration["looping"] = self._configuration["looping"]
self._bones = [
Bone(i, j[1], j[0], [], j[2], None, None) for i, j in list(bones.items())
]
self._bones = [
Bone(
i.name,
i.parent_name,
i.id,
get_children(self._bones, i),
i.offsetmatrix,
None,
None,
)
for i in self._bones
]
self.read_from_assimp(self._animation)
[docs] def read_from_assimp(self, assimp_animation):
for i in range(len(self._bones)):
channel = self.get_channel(self._bones[i].name)
if not channel:
positionkeys = np.zeros((int(self._animation.duration), 3))
rotationkeys = np.zeros((int(self._animation.duration), 3))
else:
positionkeys = np.array([j.value for j in channel.positionkeys])
rotationkeys = np.array([j.value for j in channel.rotationkeys])
self._bones[i] = self._bones[i]._replace(positions=positionkeys)
self._bones[i] = self._bones[i]._replace(rotations=rotationkeys)
[docs] def get_root_offset(self, time):
time = int(time * self._configuration["animation_fps"]) % (
self._configuration["animation_frames"]
)
if self._configuration["invert"]:
time = self._configuration["animation_frames"] - time - 1
if self._bones:
positionkeys = self.get_bone("Hips").positions
return positionkeys[time % len(positionkeys)]
else:
return np.array(self._configuration["end_position"]) * time / float(
self._configuration["animation_frames"]
)
[docs] def get_end_position(self):
"""Returns the position of the animation in the last frame."""
if self._bones:
positionkeys = self.get_bone("Hips").positions
if self._configuration["invert"]:
return -positionkeys[0]
return positionkeys[-1]
else:
return np.array(self._configuration["end_position"])
[docs] def get_end_rotation(self):
"""Returns the rotation of the animation in the last frame."""
if self.bones:
return 0
else:
return self._configuration["end_rotation"]
[docs] def has_bone(self, n):
return n.upper() in [i.name.upper() for i in self._bones]
[docs] def get_bone(self, name):
for i in self._bones:
if i.name.upper() == name.upper():
return i
[docs] def get_channel(self, channelname):
for i in self._animation.channels:
if i.nodename.data.upper() == channelname.upper():
return i
return None
[docs] def get_state(self, time):
if not self._configuration["looping"]:
time = int(time * self._configuration["animation_fps"])
if time > self._configuration["animation_frames"] - 1:
return "finished"
return "running"
@staticmethod
def _dent_asset_load(datastore):
if "data" not in datastore.getnames():
raise IOError()
animation = Animation()
data = pickle.load(datastore.extractfile("data"))
animation._configuration = data["configuration"]
animation._bones = data["bones"]
return animation
def _dent_asset_save(self, datastore):
"""Saves the image in this texture to a dent asset datastore."""
data_buffer = io.StringIO()
pickle.dump(
{"configuration": self._configuration, "bones": self._bones}, data_buffer
)
data_buffer.flush()
data_buffer.seek(0)
data_header = tarfile.TarInfo("data")
data_header.size = data_buffer.len
datastore.addfile(data_header, data_buffer)