feat: basic action support

Feat action implementation(load and dump action fcurve).
Append action reference management into bl_datablock.
Updated each concerned implementation to use the new action managment from bl_datablock
Related to #27.
This commit is contained in:
Swann Martinez
2019-11-06 14:30:16 +01:00
parent 99649f588c
commit 7bf676020d
15 changed files with 131 additions and 38 deletions

View File

@ -7,22 +7,71 @@ from .bl_datablock import BlDatablock
# WIP # WIP
class BlAction(BlDatablock): class BlAction(BlDatablock):
def load(self, data, target):
utils.dump_anything.load(target, data)
def construct(self, data): def construct(self, data):
return bpy.data.actions.new(data["name"]) return bpy.data.actions.new(data["name"])
def load(self, data, target): def load(self, data, target):
pass begin_frame = 100000
# # find target object end_frame = -100000
# object_ = bpy.context.scene.objects.active
# if object_ is None: for dumped_fcurve in data["fcurves"]:
# raise RuntimeError("Nothing is selected.") begin_frame = min(
# if object_.mode != 'POSE': # object must be in pose mode begin_frame,
# raise RuntimeError("Object must be in pose mode.") min(
# if object_.animation_data.action is None: [begin_frame] + [dkp["co"][0] for dkp in dumped_fcurve["keyframe_points"]]
# raise RuntimeError("Object needs an active action.") )
)
end_frame = max(
end_frame,
max(
[end_frame] + [dkp["co"][0] for dkp in dumped_fcurve["keyframe_points"]]
)
)
begin_frame = 0
loader = utils.dump_anything.Loader()
for dumped_fcurve in data["fcurves"]:
dumped_data_path = dumped_fcurve["data_path"]
dumped_array_index = dumped_fcurve["dumped_array_index"]
# create fcurve if needed
fcurve = target.fcurves.find(dumped_data_path, index=dumped_array_index)
if fcurve is None:
fcurve = target.fcurves.new(dumped_data_path, index=dumped_array_index)
# remove keyframes within dumped_action range
for keyframe in reversed(fcurve.keyframe_points):
if end_frame >= (keyframe.co[0] + begin_frame ) >= begin_frame:
fcurve.keyframe_points.remove(keyframe, fast=True)
# paste dumped keyframes
for dumped_keyframe_point in dumped_fcurve["keyframe_points"]:
new_kf = fcurve.keyframe_points.insert(
dumped_keyframe_point["co"][0] - begin_frame,
dumped_keyframe_point["co"][1],
options={'FAST', 'REPLACE'}
)
loader.load(
new_kf,
utils.dump_anything.remove_items_from_dict(
dumped_keyframe_point,
["co", "handle_left", "handle_right"]
)
)
new_kf.handle_left = [
dumped_keyframe_point["handle_left"][0] - begin_frame,
dumped_keyframe_point["handle_left"][1]
]
new_kf.handle_right = [
dumped_keyframe_point["handle_right"][0] - begin_frame,
dumped_keyframe_point["handle_right"][1]
]
# clearing (needed for blender to update well)
if len(fcurve.keyframe_points) == 0:
target.fcurves.remove(fcurve)
def dump(self, pointer=None): def dump(self, pointer=None):
assert(pointer) assert(pointer)
@ -30,7 +79,18 @@ class BlAction(BlDatablock):
dumper = utils.dump_anything.Dumper() dumper = utils.dump_anything.Dumper()
dumper.depth = 2 dumper.depth = 2
dumper.exclude_filter =[
'name_full',
'original',
'use_fake_user',
'user',
'is_library_indirect',
'id_root',
'select_control_point',
'select_right_handle',
'select_left_handle',
'uuid'
]
data["fcurves"] = [] data["fcurves"] = []
for fcurve in self.pointer.fcurves: for fcurve in self.pointer.fcurves:
@ -52,9 +112,6 @@ class BlAction(BlDatablock):
def resolve(self): def resolve(self):
assert(self.data) assert(self.data)
self.pointer = bpy.data.actions.get(self.data['name']) self.pointer = bpy.data.actions.get(self.data['name'])
def diff(self):
return False
def is_valid(self): def is_valid(self):
return bpy.data.actions.get(self.data['name']) return bpy.data.actions.get(self.data['name'])

View File

@ -76,7 +76,7 @@ class BlArmature(BlDatablock):
else: else:
raise Exception("Wrong context") raise Exception("Wrong context")
def dump(self, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
dumper = utils.dump_anything.Dumper() dumper = utils.dump_anything.Dumper()

View File

@ -18,7 +18,7 @@ class BlCamera(BlDatablock):
def construct(self, data): def construct(self, data):
return bpy.data.cameras.new(data["name"]) return bpy.data.cameras.new(data["name"])
def dump(self, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
dumper = utils.dump_anything.Dumper() dumper = utils.dump_anything.Dumper()

View File

@ -47,7 +47,7 @@ class BlCollection(BlDatablock):
if collection.uuid not in data["children"]: if collection.uuid not in data["children"]:
target.children.unlink(collection) target.children.unlink(collection)
def dump(self, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
data = {} data = {}
data['name'] = pointer.name data['name'] = pointer.name

View File

@ -29,7 +29,7 @@ class BlCurve(BlDatablock):
utils.dump_anything.load( utils.dump_anything.load(
new_spline.points[point_index], data['splines'][spline]["points"][point_index]) new_spline.points[point_index], data['splines'][spline]["points"][point_index])
def dump(self, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
data = utils.dump_datablock(pointer, 1) data = utils.dump_datablock(pointer, 1)
data['splines'] = {} data['splines'] = {}

View File

@ -5,23 +5,27 @@ from .. import utils
from ..libs.replication.replication.data import ReplicatedDatablock from ..libs.replication.replication.data import ReplicatedDatablock
from ..libs.replication.replication.constants import UP from ..libs.replication.replication.constants import UP
def has_animation(target):
return (hasattr(target, 'animation_data') \
and target.animation_data \
and target.animation_data.action)
class BlDatablock(ReplicatedDatablock): class BlDatablock(ReplicatedDatablock):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
pointer = kwargs.get('pointer', None) pointer = kwargs.get('pointer', None)
# TODO: use is_library_indirect # TODO: use is_library_indirect
self.is_library = (pointer and hasattr(pointer, 'library') and self.is_library = (pointer and hasattr(pointer, 'library') and
pointer.library) or \ pointer.library) or \
(self.data and 'library' in self.data) (self.data and 'library' in self.data)
if self.is_library: if self.is_library:
self.load = self.load_library self.load = self.load_library
self.dump = self.dump_library self.dump = self.dump_library
self.diff = self.diff_library self.diff = self.diff_library
self.resolve_dependencies = self.resolve_dependencies_library self.resolve_dependencies = self.resolve_dependencies_library
if self.pointer and hasattr(self.pointer, 'uuid'): if self.pointer and hasattr(self.pointer, 'uuid'):
self.pointer.uuid = self.uuid self.pointer.uuid = self.uuid
@ -50,13 +54,42 @@ class BlDatablock(ReplicatedDatablock):
def resolve_dependencies_library(self): def resolve_dependencies_library(self):
return [self.pointer.library] return [self.pointer.library]
def dump(self, pointer=None):
data = {}
if has_animation(pointer):
dumper = utils.dump_anything.Dumper()
dumper.include_filter = ['action']
data['animation_data'] = dumper.dump(pointer.animation_data)
data.update(self.dump_implementation(data, pointer=pointer))
return data
def dump_implementation(self, data, target):
raise NotImplementedError
def load(self, data, target):
# Load animation data
if 'animation_data' in data.keys():
if target.animation_data is None:
target.animation_data_create()
target.animation_data.action = bpy.data.actions[data['animation_data']['action']]
self.load_implementation(data, target)
def load_implementation(self, data, target):
raise NotImplementedError
def resolve_dependencies(self): def resolve_dependencies(self):
dependencies = [] dependencies = []
if hasattr(self.pointer,'animation_data') and self.pointer.animation_data: if has_animation(self.pointer):
dependencies.append(self.pointer.animation_data.action) dependencies.append(self.pointer.animation_data.action)
return dependencies return dependencies
def is_valid(self): def is_valid(self):
raise NotImplementedError raise NotImplementedError

View File

@ -57,7 +57,7 @@ class BlGpencil(BlDatablock):
for mat in data['materials']: for mat in data['materials']:
target.materials.append(bpy.data.materials[mat]) target.materials.append(bpy.data.materials[mat])
def dump(self, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
data = utils.dump_datablock(pointer, 2) data = utils.dump_datablock(pointer, 2)
utils.dump_datablock_attibutes( utils.dump_datablock_attibutes(

View File

@ -46,7 +46,7 @@ class BlImage(BlDatablock):
image.filepath = img_path image.filepath = img_path
def dump(self, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
data = {} data = {}
data['pixels'] = dump_image(pointer) data['pixels'] = dump_image(pointer)

View File

@ -12,7 +12,7 @@ class BlLight(BlDatablock):
def load(self, data, target): def load(self, data, target):
utils.dump_anything.load(target, data) utils.dump_anything.load(target, data)
def dump(self, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
dumper = utils.dump_anything.Dumper() dumper = utils.dump_anything.Dumper()
dumper.depth = 3 dumper.depth = 3

View File

@ -98,7 +98,7 @@ class BlMaterial(BlDatablock):
for link in data["node_tree"]["links"]: for link in data["node_tree"]["links"]:
load_link(target.node_tree, data["node_tree"]["links"][link]) load_link(target.node_tree, data["node_tree"]["links"][link])
def dump(self, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
mat_dumper = utils.dump_anything.Dumper() mat_dumper = utils.dump_anything.Dumper()
mat_dumper.depth = 2 mat_dumper.depth = 2

View File

@ -139,7 +139,7 @@ class BlMesh(BlDatablock):
def dump(self, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
data = utils.dump_datablock(pointer, 2) data = utils.dump_datablock(pointer, 2)

View File

@ -17,7 +17,7 @@ class BlMetaball(BlDatablock):
new_element = target.elements.new(type=data["elements"][element]['type']) new_element = target.elements.new(type=data["elements"][element]['type'])
utils.dump_anything.load(new_element, data["elements"][element]) utils.dump_anything.load(new_element, data["elements"][element])
def dump(self, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
dumper = utils.dump_anything.Dumper() dumper = utils.dump_anything.Dumper()
dumper.depth = 3 dumper.depth = 3

View File

@ -43,8 +43,9 @@ class BlObject(BlDatablock):
return instance return instance
def load(self, data, target): def load_implementation(self, data, target):
target.matrix_world = mathutils.Matrix(data["matrix_world"]) if "matrix_world" in data:
target.matrix_world = mathutils.Matrix(data["matrix_world"])
target.name = data["name"] target.name = data["name"]
# Load modifiers # Load modifiers
@ -76,13 +77,12 @@ class BlObject(BlDatablock):
if data['instance_type'] == 'COLLECTION': if data['instance_type'] == 'COLLECTION':
target.instance_collection = bpy.data.collections[data['instance_collection']] target.instance_collection = bpy.data.collections[data['instance_collection']]
def dump(self, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
dumper = utils.dump_anything.Dumper() dumper = utils.dump_anything.Dumper()
dumper.depth = 1 dumper.depth = 1
dumper.include_filter = [ dumper.include_filter = [
"name", "name",
"matrix_world",
"rotation_mode", "rotation_mode",
"parent", "parent",
"data", "data",
@ -93,6 +93,9 @@ class BlObject(BlDatablock):
"instance_collection", "instance_collection",
"instance_type" "instance_type"
] ]
if not pointer.animation_data:
dumper.include_filter.append('matrix_world')
data = dumper.dump(pointer) data = dumper.dump(pointer)
if self.is_library: if self.is_library:
@ -118,7 +121,7 @@ class BlObject(BlDatablock):
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.objects) self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.objects)
def resolve_dependencies(self): def resolve_dependencies(self):
deps = [] deps = super().resolve_dependencies()
# Avoid Empty case # Avoid Empty case
if self.pointer.data: if self.pointer.data:

View File

@ -42,7 +42,7 @@ class BlScene(BlDatablock):
if 'grease_pencil' in data.keys(): if 'grease_pencil' in data.keys():
target.grease_pencil = bpy.data.grease_pencils[data['grease_pencil']] target.grease_pencil = bpy.data.grease_pencils[data['grease_pencil']]
def dump(self, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
data = {} data = {}

View File

@ -26,7 +26,7 @@ class BlWorld(BlDatablock):
for link in data["node_tree"]["links"]: for link in data["node_tree"]["links"]:
load_link(target.node_tree, data["node_tree"]["links"][link]) load_link(target.node_tree, data["node_tree"]["links"][link])
def dump(self, pointer=None): def dump_implementation(self, data, pointer=None):
assert(pointer) assert(pointer)
world_dumper = utils.dump_anything.Dumper() world_dumper = utils.dump_anything.Dumper()