feat: animation ground work

work on armature and action support
This commit is contained in:
Swann Martinez 2019-09-16 17:24:48 +02:00
parent 095a44e7b6
commit 6511998360
No known key found for this signature in database
GPG Key ID: 414CCAFD8DA720E1
9 changed files with 388 additions and 12 deletions

View File

@ -43,6 +43,7 @@ def generate_supported_types():
props['use_as_filter'] = False
props['icon'] = _type.bl_icon
props['auto_push']=_type.bl_automatic_push
props['bl_name']=_type.bl_id
# stype_dict[type]['bl_delay_apply']=_type.bl_delay_apply
stype_dict['supported_types'][_type.bl_rep_class.__name__] = props
@ -95,6 +96,8 @@ def save_session_config(self,context):
config["supported_types"][bloc.type_name]['use_as_filter'] = bloc.use_as_filter
config["supported_types"][bloc.type_name]['icon'] = bloc.icon
config["supported_types"][bloc.type_name]['auto_push'] = bloc.auto_push
config["supported_types"][bloc.type_name]['bl_name'] = bloc.bl_name
# Save out the configuration file
@ -104,6 +107,7 @@ def save_session_config(self,context):
class ReplicatedDatablock(bpy.types.PropertyGroup):
'''name = StringProperty() '''
type_name: bpy.props.StringProperty()
bl_name: bpy.props.StringProperty()
bl_delay_refresh: bpy.props.FloatProperty()
bl_delay_apply: bpy.props.FloatProperty()
use_as_filter: bpy.props.BoolProperty(default=True)
@ -199,6 +203,7 @@ class SessionProps(bpy.types.PropertyGroup):
rep_value.bl_delay_apply = config["supported_types"][datablock]['bl_delay_apply']
rep_value.icon = config["supported_types"][datablock]['icon']
rep_value.auto_push = config["supported_types"][datablock]['auto_push']
rep_value.bl_name = config["supported_types"][datablock]['bl_name']
def save(self,context):
config = environment.load_config()
@ -217,6 +222,7 @@ class SessionProps(bpy.types.PropertyGroup):
config["supported_types"][bloc.type_name]['use_as_filter'] = bloc.use_as_filter
config["supported_types"][bloc.type_name]['icon'] = bloc.icon
config["supported_types"][bloc.type_name]['auto_push'] = bloc.auto_push
config["supported_types"][bloc.type_name]['bl_name'] = bloc.bl_name
environment.save_config(config)

View File

@ -11,6 +11,7 @@ __all__ = [
'bl_scene',
'bl_material',
'bl_library',
'bl_armature'
] # Order here defines execution order
from . import *

53
bl_types/bl_action.py Normal file
View File

@ -0,0 +1,53 @@
import bpy
import mathutils
from jsondiff import diff
from .. import utils
from .bl_datablock import BlDatablock
class BlAction(BlDatablock):
def load(self, data, target):
utils.dump_anything.load(target, data)
def construct(self, data):
return bpy.data.actions.new(data["name"])
def dump(self, pointer=None):
assert(pointer)
data = utils.dump_datablock(pointer, 1)
dumper = utils.dump_anything.Dumper()
dumper.depth = 2
data["fcurves"] = []
for fcurve in self.pointer.fcurves:
fc = {
"data_path": fcurve.data_path,
"dumped_array_index": fcurve.array_index,
"keyframe_points": []
}
for k in fcurve.keyframe_points:
fc["keyframe_points"].append(
dumper.dump(k)
)
data["fcurves"].append(fc)
return data
def resolve(self):
assert(self.buffer)
self.pointer = bpy.data.actions.get(self.buffer['name'])
def diff(self):
return False
bl_id = "actions"
bl_class = bpy.types.Action
bl_rep_class = BlAction
bl_delay_refresh = 1
bl_delay_apply = 1
bl_automatic_push = True
bl_icon = 'ACTION_DATA'

77
bl_types/bl_armature.py Normal file
View File

@ -0,0 +1,77 @@
import bpy
import mathutils
from jsondiff import diff
from ..libs.overrider import Overrider
from .. import utils
from .. import presence
from .bl_datablock import BlDatablock
class BlArmature(BlDatablock):
def construct(self, data):
return bpy.data.armatures.new(data["name"])
def load(self, data, target):
# Load parent object
if data['user'] not in bpy.data.objects:
parent_object = bpy.data.objects.new(data['user'],self.pointer)
else:
parent_object = bpy.data.objects['user']
# Link it to the correct context
if data['user_collection'][0] not in bpy.data.collections:
parent_collection = bpy.data.collections.new(data['user_collection'][0])
else:
parent_collection = bpy.data.collection['user_collection'][0]
parent_collection.objects.link(parent_object)
# utils.dump_anything.load(target, data)
# with Overrider(name="bpy_",parent=bpy.context) as bpy_:
area, region, rv3d = presence.view3d_find()
override = bpy.context.copy()
override['window'] = bpy.data.window_managers[0].windows[0]
override['area'] = area
override['region'] = region
override['screen'] = bpy.data.window_managers[0].windows[0].screen
override['active_object'] = parent_object
override['selected_objects'] = [parent_object]
try:
bpy.ops.object.mode_set(override,mode='EDIT')
except Exception as e:
print(e)
# bpy_.mode = 'EDIT_ARMATURE'
# bpy_.active_object = armature
# bpy_.selected_objects = [armature]
def dump(self, pointer=None):
assert(pointer)
data = utils.dump_datablock(pointer, 3)
#get the parent Object
object_users = utils.get_users(pointer)[0]
data['user'] = object_users.name
#get parent collection
container_users = utils.get_users(object_users)
data['user_collection'] = [item.name for item in container_users if isinstance(item,bpy.types.Collection)]
data['user_scene'] = [item.name for item in container_users if isinstance(item,bpy.types.Scene)]
return data
def resolve(self):
assert(self.buffer)
self.pointer = bpy.data.armatures.get(self.buffer['name'])
def diff(self):
False
bl_id = "armatures"
bl_class = bpy.types.Armature
bl_rep_class = BlArmature
bl_delay_refresh = 1
bl_delay_apply = 1
bl_automatic_push = True
bl_icon = 'ARMATURE_DATA'

View File

@ -6,12 +6,6 @@ from .. import utils
from .bl_datablock import BlDatablock
class BlCamera(BlDatablock):
def __init__(self, *args, **kwargs):
self.icon = 'CAMERA_DATA'
super().__init__( *args, **kwargs)
def load(self, data, target):
utils.dump_anything.load(target, data)

View File

@ -16,7 +16,9 @@ class BlObject(BlDatablock):
return targetData.objects[self.buffer['name']]
# Object specific constructor...
if data["data"] in bpy.data.meshes.keys():
if "data" not in data:
pass
elif data["data"] in bpy.data.meshes.keys():
pointer = bpy.data.meshes[data["data"]]
elif data["data"] in bpy.data.lights.keys():
pointer = bpy.data.lights[data["data"]]
@ -78,8 +80,9 @@ class BlObject(BlDatablock):
def resolve_dependencies(self):
deps = []
deps.append(self.pointer.data)
# Avoid Empty case
if self.pointer.data:
deps.append(self.pointer.data)
if self.is_library:
deps.append(self.pointer.library)

219
libs/overrider.py Normal file
View File

@ -0,0 +1,219 @@
"""
Context Manager allowing temporary override of attributes
````python
import bpy
from overrider import Overrider
with Overrider(name='bpy_', parent=bpy) as bpy_:
# set preview render settings
bpy_.context.scene.render.use_file_extension = False
bpy_.context.scene.render.resolution_x = 512
bpy_.context.scene.render.resolution_y = 512
bpy_.context.scene.render.use_file_extension = False
bpy_.context.scene.render.image_settings.file_format = "JPEG"
bpy_.context.scene.layers[10] = False
frame_start = action.frame_range[0]
frame_end = action.frame_range[1]
if begin_frame is not None:
frame_start = begin_frame
if end_frame is not None:
frame_end = end_frame
# render
window = bpy_.data.window_managers[0].windows[0]
screen = bpy_.data.window_managers[0].windows[0].screen
area = next(area for area in screen.areas if area.type == 'VIEW_3D')
space = next(space for space in area.spaces if space.type == 'VIEW_3D')
space.viewport_shade = 'MATERIAL'
space.region_3d.view_perspective = 'CAMERA'
override_context = {
"window": window._real_value_(),
"screen": screen._real_value_()
}
if frame_start == frame_end:
bpy.context.scene.frame_set(int(frame_start))
bpy_.context.scene.render.filepath = os.path.join(directory, "icon.jpg")
bpy.ops.render.opengl(override_context, write_still=True)
else:
for icon_index, frame_number in enumerate(range(int(frame_start), int(frame_end) + 1)):
bpy.context.scene.frame_set(frame_number)
bpy.context.scene.render.filepath = os.path.join(directory, "icon", "{:04d}.jpg".format(icon_index))
bpy.ops.render.opengl(override_context, write_still=True)
````
"""
from collections import OrderedDict
class OverrideIter:
def __init__(self, parent):
self.parent = parent
self.index = -1
def __next__(self):
self.index += 1
try:
return self.parent[self.index]
except IndexError as e:
raise StopIteration
class OverrideBase:
def __init__(self, context_manager, name=None, parent=None):
self._name__ = name
self._context_manager_ = context_manager
self._parent_ = parent
self._changed_attributes_ = OrderedDict()
self._changed_items_ = OrderedDict()
self._children_ = list()
self._original_value_ = self._real_value_()
def __repr__(self):
return "<{}({})>".format(self.__class__.__name__, self._path_)
@property
def _name_(self):
raise NotImplementedError()
@property
def _path_(self):
if isinstance(self._parent_, OverrideBase):
return self._parent_._path_ + self._name_
return self._name_
def _real_value_(self):
raise NotImplementedError()
def _restore_(self):
for attribute, original_value in reversed(self._changed_attributes_.items()):
setattr(self._real_value_(), attribute, original_value)
for item, original_value in reversed(self._changed_items_.items()):
self._real_value_()[item] = original_value
def __getattr__(self, attr):
new_attribute = OverrideAttribute(self._context_manager_, name=attr, parent=self)
self._children_.append(new_attribute)
return new_attribute
def __getitem__(self, item):
new_item = OverrideItem(self._context_manager_, name=item, parent=self)
self._children_.append(new_item)
return new_item
def __iter__(self):
return OverrideIter(self)
def __setattr__(self, attr, value):
if attr in (
'_name__',
'_context_manager_',
'_parent_',
'_children_',
'_original_value_',
'_changed_attributes_',
'_changed_items_'
):
self.__dict__[attr] = value
return
if attr not in self._changed_attributes_.keys():
self._changed_attributes_[attr] = getattr(self._real_value_(), attr)
self._context_manager_.register_as_changed(self)
setattr(self._real_value_(), attr, value)
def __setitem__(self, item, value):
if item not in self._changed_items_.keys():
self._changed_items_[item] = self._real_value_()[item]
self._context_manager_.register_as_changed(self)
self._real_value_()[item] = value
def __eq__(self, other):
return self._real_value_() == other
def __gt__(self, other):
return self._real_value_() > other
def __lt__(self, other):
return self._real_value_() < other
def __ge__(self, other):
return self._real_value_() >= other
def __le__(self, other):
return self._real_value_() <= other
def __call__(self, *args, **kwargs):
# TODO : surround str value with quotes
arguments = list([str(arg) for arg in args]) + ['{}={}'.format(key, value) for key, value in kwargs.items()]
arguments = ', '.join(arguments)
raise RuntimeError('Overrider does not allow call to {}({})'.format(self._path_, arguments))
class OverrideRoot(OverrideBase):
@property
def _name_(self):
return self._name__
def _real_value_(self):
return self._parent_
class OverrideAttribute(OverrideBase):
@property
def _name_(self):
return '.{}'.format(self._name__)
def _real_value_(self):
return getattr(self._parent_._real_value_(), self._name__)
class OverrideItem(OverrideBase):
@property
def _name_(self):
if isinstance(self._name__, str):
return '["{}"]'.format(self._name__)
return '[{}]'.format(self._name__)
def _real_value_(self):
return self._parent_._real_value_()[self._name__]
class Overrider:
def __init__(self, name, parent):
self.name = name
self.parent = parent
self.override = None
self.registered_overrides = list()
def __enter__(self):
self.override = OverrideRoot(
context_manager=self,
parent=self.parent,
name=self.name
)
return self.override
def __exit__(self, exc_type, exc_val, exc_tb):
self.restore()
def register_as_changed(self, override):
self.registered_overrides.append(override)
def restore(self):
for override in reversed(self.registered_overrides):
override._restore_()

2
ui.py
View File

@ -129,7 +129,7 @@ class SESSION_PT_settings_user(bpy.types.Panel):
class SESSION_PT_settings_replication(bpy.types.Panel):
bl_idname = "MULTIUSER_SETTINGS_REPLICATION_PT_panel"
bl_label = "Replication"
bl_label = "Advanced"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Multiuser"

View File

@ -19,12 +19,34 @@ BPY_TYPES = {'Image': 'images', 'Texture': 'textures', 'Material': 'materials',
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
def get_users(datablock):
users = []
supported_types = bpy.context.window_manager.session.supported_datablock
if hasattr(datablock, 'users_collection') and datablock.users_collection:
users.extend(list(datablock.users_collection))
if hasattr(datablock, 'users_scene') and datablock.users_scene:
users.extend(list(datablock.users_scene))
if hasattr(datablock, 'users_group') and datablock.users_scene:
users.extend(list(datablock.users_scene))
for datatype in supported_types:
if datatype.bl_name != 'users':
root = getattr(bpy.data,datatype.bl_name)
for item in root:
if hasattr(item, 'data') and datablock == item.data or \
hasattr(item, 'children') and datablock in item.children:
users.append(item)
return users
# UTILITY FUNCTIONS
def random_string_digits(stringLength=6):
"""Generate a random string of letters and digits """
lettersAndDigits = string.ascii_letters + string.digits
return ''.join(random.choice(lettersAndDigits) for i in range(stringLength))
def clean_scene():
for datablock in BPY_TYPES:
datablock_ref = getattr(bpy.data, BPY_TYPES[datablock])
@ -73,6 +95,7 @@ def get_armature_edition_context(armature):
def get_selected_objects(scene):
return [obj.name for obj in scene.objects if obj.select_get()]
def load_dict(src_dict, target):
try:
for item in src_dict:
@ -83,6 +106,7 @@ def load_dict(src_dict, target):
logger.error(e)
pass
def resolve_bpy_path(path):
"""
Get bpy property value from path
@ -156,6 +180,7 @@ def load_armature(target=None, data=None, create=False):
import os
os.remove(file)
def dump_datablock(datablock, depth):
if datablock:
dumper = dump_anything.Dumper()
@ -191,8 +216,6 @@ def dump_datablock_attibutes(datablock=None, attributes=[], depth=1, dickt=None)
return data
def init_client(key=None):
client_dict = {}