feat: animation ground work
work on armature and action support
This commit is contained in:
parent
095a44e7b6
commit
6511998360
@ -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)
|
||||
|
||||
|
||||
|
@ -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
53
bl_types/bl_action.py
Normal 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
77
bl_types/bl_armature.py
Normal 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'
|
@ -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)
|
||||
|
||||
|
@ -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
219
libs/overrider.py
Normal 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
2
ui.py
@ -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"
|
||||
|
27
utils.py
27
utils.py
@ -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 = {}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user