Merge branch 'animation-support' into 'develop'
Animation support See merge request slumber/multi-user!6
This commit is contained in:
@ -31,45 +31,45 @@ DEPENDENCIES = {
|
|||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.setLevel(logging.ERROR)
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
#TODO: refactor config
|
#TODO: refactor config
|
||||||
# UTILITY FUNCTIONS
|
# UTILITY FUNCTIONS
|
||||||
def generate_supported_types():
|
def generate_supported_types():
|
||||||
stype_dict = {'supported_types':{}}
|
stype_dict = {'supported_types':{}}
|
||||||
for type in bl_types.types_to_register():
|
for type in bl_types.types_to_register():
|
||||||
_type = getattr(bl_types, type)
|
type_module = getattr(bl_types, type)
|
||||||
props = {}
|
type_impl_name = "Bl{}".format(type.split('_')[1].capitalize())
|
||||||
props['bl_delay_refresh']=_type.bl_delay_refresh
|
type_module_class = getattr(type_module, type_impl_name)
|
||||||
props['bl_delay_apply']=_type.bl_delay_apply
|
|
||||||
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['supported_types'][_type.bl_rep_class.__name__] = props
|
props = {}
|
||||||
|
props['bl_delay_refresh']=type_module_class.bl_delay_refresh
|
||||||
|
props['bl_delay_apply']=type_module_class.bl_delay_apply
|
||||||
|
props['use_as_filter'] = False
|
||||||
|
props['icon'] = type_module_class.bl_icon
|
||||||
|
props['auto_push']=type_module_class.bl_automatic_push
|
||||||
|
props['bl_name']=type_module_class.bl_id
|
||||||
|
|
||||||
|
stype_dict['supported_types'][type_impl_name] = props
|
||||||
|
|
||||||
return stype_dict
|
return stype_dict
|
||||||
|
|
||||||
|
|
||||||
def client_list_callback(scene, context):
|
def client_list_callback(scene, context):
|
||||||
from . import operators
|
from . import operators
|
||||||
from .bl_types.bl_user import BlUser
|
|
||||||
|
|
||||||
items = [(RP_COMMON, RP_COMMON, "")]
|
items = [(RP_COMMON, RP_COMMON, "")]
|
||||||
|
|
||||||
username = bpy.context.window_manager.session.username
|
username = bpy.context.window_manager.session.username
|
||||||
cli = operators.client
|
cli = operators.client
|
||||||
if cli:
|
if cli:
|
||||||
client_keys = cli.list(filter=BlUser)
|
client_ids = cli.online_users.keys()
|
||||||
for k in client_keys:
|
for id in client_ids:
|
||||||
name = cli.get(uuid=k).data["name"]
|
name_desc = id
|
||||||
|
if id == username:
|
||||||
name_desc = name
|
|
||||||
if name == username:
|
|
||||||
name_desc += " (self)"
|
name_desc += " (self)"
|
||||||
|
|
||||||
items.append((name, name_desc, ""))
|
items.append((id, name_desc, ""))
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
@ -90,6 +90,15 @@ class ReplicatedDatablock(bpy.types.PropertyGroup):
|
|||||||
auto_push: bpy.props.BoolProperty(default=True)
|
auto_push: bpy.props.BoolProperty(default=True)
|
||||||
icon: bpy.props.StringProperty()
|
icon: bpy.props.StringProperty()
|
||||||
|
|
||||||
|
class SessionUser(bpy.types.PropertyGroup):
|
||||||
|
"""Session User
|
||||||
|
|
||||||
|
Blender user information property
|
||||||
|
"""
|
||||||
|
username: bpy.props.StringProperty(name="username")
|
||||||
|
current_frame: bpy.props.IntProperty(name="current_frame")
|
||||||
|
|
||||||
|
|
||||||
class SessionProps(bpy.types.PropertyGroup):
|
class SessionProps(bpy.types.PropertyGroup):
|
||||||
username: bpy.props.StringProperty(
|
username: bpy.props.StringProperty(
|
||||||
name="Username",
|
name="Username",
|
||||||
@ -109,25 +118,14 @@ class SessionProps(bpy.types.PropertyGroup):
|
|||||||
description='Distant host port',
|
description='Distant host port',
|
||||||
default=5555
|
default=5555
|
||||||
)
|
)
|
||||||
add_property_depth: bpy.props.IntProperty(
|
|
||||||
name="add_property_depth",
|
|
||||||
default=1
|
|
||||||
)
|
|
||||||
outliner_filter: bpy.props.StringProperty(name="None")
|
|
||||||
is_admin: bpy.props.BoolProperty(
|
is_admin: bpy.props.BoolProperty(
|
||||||
name="is_admin",
|
name="is_admin",
|
||||||
default=False
|
default=False
|
||||||
)
|
)
|
||||||
init_scene: bpy.props.BoolProperty(
|
|
||||||
name="init_scene",
|
|
||||||
default=True
|
|
||||||
)
|
|
||||||
start_empty: bpy.props.BoolProperty(
|
start_empty: bpy.props.BoolProperty(
|
||||||
name="start_empty",
|
name="start_empty",
|
||||||
default=True
|
default=True
|
||||||
)
|
)
|
||||||
active_object: bpy.props.PointerProperty(
|
|
||||||
name="active_object", type=bpy.types.Object)
|
|
||||||
session_mode: bpy.props.EnumProperty(
|
session_mode: bpy.props.EnumProperty(
|
||||||
name='session_mode',
|
name='session_mode',
|
||||||
description='session mode',
|
description='session mode',
|
||||||
@ -179,10 +177,11 @@ class SessionProps(bpy.types.PropertyGroup):
|
|||||||
description='Show only owned datablocks',
|
description='Show only owned datablocks',
|
||||||
default=True
|
default=True
|
||||||
)
|
)
|
||||||
use_select_right: bpy.props.BoolProperty(
|
user_snap_running: bpy.props.BoolProperty(
|
||||||
name="Selection right",
|
default=False
|
||||||
description='Change right on selection',
|
)
|
||||||
default=True
|
time_snap_running: bpy.props.BoolProperty(
|
||||||
|
default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
@ -239,6 +238,7 @@ class SessionProps(bpy.types.PropertyGroup):
|
|||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
|
SessionUser,
|
||||||
ReplicatedDatablock,
|
ReplicatedDatablock,
|
||||||
SessionProps,
|
SessionProps,
|
||||||
|
|
||||||
@ -267,7 +267,10 @@ def register():
|
|||||||
bpy.types.WindowManager.session = bpy.props.PointerProperty(
|
bpy.types.WindowManager.session = bpy.props.PointerProperty(
|
||||||
type=SessionProps)
|
type=SessionProps)
|
||||||
bpy.types.ID.uuid = bpy.props.StringProperty(default="")
|
bpy.types.ID.uuid = bpy.props.StringProperty(default="")
|
||||||
|
bpy.types.WindowManager.online_users = bpy.props.CollectionProperty(
|
||||||
|
type=SessionUser
|
||||||
|
)
|
||||||
|
bpy.types.WindowManager.user_index = bpy.props.IntProperty()
|
||||||
bpy.context.window_manager.session.load()
|
bpy.context.window_manager.session.load()
|
||||||
|
|
||||||
presence.register()
|
presence.register()
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
'bl_user',
|
|
||||||
'bl_object',
|
'bl_object',
|
||||||
'bl_mesh',
|
'bl_mesh',
|
||||||
'bl_camera',
|
'bl_camera',
|
||||||
|
@ -7,22 +7,77 @@ from .bl_datablock import BlDatablock
|
|||||||
# WIP
|
# WIP
|
||||||
|
|
||||||
class BlAction(BlDatablock):
|
class BlAction(BlDatablock):
|
||||||
def load(self, data, target):
|
bl_id = "actions"
|
||||||
utils.dump_anything.load(target, data)
|
bl_class = bpy.types.Action
|
||||||
|
bl_delay_refresh = 1
|
||||||
|
bl_delay_apply = 1
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'ACTION_TWEAK'
|
||||||
|
|
||||||
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 +85,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:
|
||||||
@ -49,20 +115,7 @@ class BlAction(BlDatablock):
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
assert(self.data)
|
|
||||||
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'])
|
||||||
|
|
||||||
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_TWEAK'
|
|
||||||
|
@ -3,104 +3,129 @@ import mathutils
|
|||||||
|
|
||||||
from ..libs.overrider import Overrider
|
from ..libs.overrider import Overrider
|
||||||
from .. import utils
|
from .. import utils
|
||||||
from .. import presence
|
from .. import presence, operators
|
||||||
from .bl_datablock import BlDatablock
|
from .bl_datablock import BlDatablock
|
||||||
|
|
||||||
# WIP
|
# WIP
|
||||||
|
|
||||||
|
|
||||||
class BlArmature(BlDatablock):
|
class BlArmature(BlDatablock):
|
||||||
|
bl_id = "armatures"
|
||||||
|
bl_class = bpy.types.Armature
|
||||||
|
bl_delay_refresh = 1
|
||||||
|
bl_delay_apply = 0
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'ARMATURE_DATA'
|
||||||
|
|
||||||
def construct(self, data):
|
def construct(self, data):
|
||||||
return bpy.data.armatures.new(data["name"])
|
return bpy.data.armatures.new(data["name"])
|
||||||
|
|
||||||
def load(self, data, target):
|
def load_implementation(self, data, target):
|
||||||
# Load parent object
|
# Load parent object
|
||||||
if data['user'] not in bpy.data.objects.keys():
|
parent_object = utils.find_from_attr(
|
||||||
parent_object = bpy.data.objects.new(data['user'], self.pointer)
|
'uuid',
|
||||||
else:
|
data['user'],
|
||||||
parent_object = bpy.data.objects[data['user']]
|
bpy.data.objects
|
||||||
|
)
|
||||||
|
|
||||||
is_object_in_master = (data['user_collection'][0] == "Master Collection")
|
if parent_object is None:
|
||||||
#TODO: recursive parent collection loading
|
parent_object = bpy.data.objects.new(
|
||||||
|
data['user_name'], self.pointer)
|
||||||
|
parent_object.uuid = data['user']
|
||||||
|
|
||||||
|
is_object_in_master = (
|
||||||
|
data['user_collection'][0] == "Master Collection")
|
||||||
|
# TODO: recursive parent collection loading
|
||||||
# Link parent object to the collection
|
# Link parent object to the collection
|
||||||
if is_object_in_master:
|
if is_object_in_master:
|
||||||
parent_collection = bpy.data.scenes[data['user_scene'][0]].collection
|
parent_collection = bpy.data.scenes[data['user_scene']
|
||||||
elif data['user_collection'][0] not in bpy.data.collections.keys():
|
[0]].collection
|
||||||
parent_collection = bpy.data.collections.new(data['user_collection'][0])
|
elif data['user_collection'][0] not in bpy.data.collections.keys():
|
||||||
|
parent_collection = bpy.data.collections.new(
|
||||||
|
data['user_collection'][0])
|
||||||
else:
|
else:
|
||||||
parent_collection = bpy.data.collections[data['user_collection'][0]]
|
parent_collection = bpy.data.collections[data['user_collection'][0]]
|
||||||
|
|
||||||
if parent_object.name not in parent_collection.objects:
|
if parent_object.name not in parent_collection.objects:
|
||||||
parent_collection.objects.link(parent_object)
|
parent_collection.objects.link(parent_object)
|
||||||
|
|
||||||
# Link parent collection to the scene master collection
|
# Link parent collection to the scene master collection
|
||||||
if not is_object_in_master and parent_collection.name not in bpy.data.scenes[data['user_scene'][0]].collection.children:
|
if not is_object_in_master and parent_collection.name not in bpy.data.scenes[data['user_scene'][0]].collection.children:
|
||||||
bpy.data.scenes[data['user_scene'][0]].collection. children.link(parent_collection)
|
bpy.data.scenes[data['user_scene'][0]
|
||||||
|
].collection. children.link(parent_collection)
|
||||||
|
|
||||||
# utils.dump_anything.load(target, data)
|
|
||||||
# with Overrider(name="bpy_",parent=bpy.context) as bpy_:
|
|
||||||
area, region, rv3d = presence.view3d_find()
|
|
||||||
|
|
||||||
|
|
||||||
|
current_mode = bpy.context.mode
|
||||||
|
current_active_object = bpy.context.view_layer.objects.active
|
||||||
|
|
||||||
|
# LOAD ARMATURE BONES
|
||||||
|
if bpy.context.mode != 'OBJECT':
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
bpy.context.view_layer.objects.active = parent_object
|
bpy.context.view_layer.objects.active = parent_object
|
||||||
# override = bpy.context.copy()
|
|
||||||
# override['window'] = bpy.data.window_managers[0].windows[0]
|
|
||||||
# override['mode'] = 'EDIT_ARMATURE'
|
|
||||||
# override['window_manager'] = bpy.data.window_managers[0]
|
|
||||||
# override['area'] = area
|
|
||||||
# override['region'] = region
|
|
||||||
# override['screen'] = bpy.data.window_managers[0].windows[0].screen
|
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
|
||||||
for bone in data['bones']:
|
for bone in data['bones']:
|
||||||
if bone not in self.pointer.edit_bones:
|
if bone not in self.pointer.edit_bones:
|
||||||
new_bone = self.pointer.edit_bones.new(bone)
|
new_bone = self.pointer.edit_bones.new(bone)
|
||||||
else:
|
else:
|
||||||
new_bone = self.pointer.edit_bones[bone]
|
new_bone = self.pointer.edit_bones[bone]
|
||||||
|
|
||||||
new_bone.tail = data['bones'][bone]['tail_local']
|
bone_data = data['bones'].get(bone)
|
||||||
new_bone.head = data['bones'][bone]['head_local']
|
|
||||||
new_bone.tail_radius = data['bones'][bone]['tail_radius']
|
|
||||||
new_bone.head_radius = data['bones'][bone]['head_radius']
|
|
||||||
|
|
||||||
if 'parent' in data['bones'][bone]:
|
new_bone.tail = bone_data['tail_local']
|
||||||
new_bone.parent = self.pointer.edit_bones[data['bones'][bone]['parent']['name']]
|
new_bone.head = bone_data['head_local']
|
||||||
new_bone.use_connect = data['bones'][bone]['use_connect']
|
new_bone.tail_radius = bone_data['tail_radius']
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
new_bone.head_radius = bone_data['head_radius']
|
||||||
|
|
||||||
# bpy_.mode = 'EDIT_ARMATURE'
|
if 'parent' in bone_data:
|
||||||
|
new_bone.parent = self.pointer.edit_bones[data['bones']
|
||||||
|
[bone]['parent']]
|
||||||
|
new_bone.use_connect = bone_data['use_connect']
|
||||||
|
|
||||||
# bpy_.active_object = armature
|
utils.dump_anything.load(new_bone, bone_data)
|
||||||
# bpy_.selected_objects = [armature]
|
|
||||||
|
|
||||||
def dump(self, pointer=None):
|
if bpy.context.mode != 'OBJECT':
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
bpy.context.view_layer.objects.active = current_active_object
|
||||||
|
|
||||||
|
# TODO: clean way to restore previous context
|
||||||
|
if 'EDIT' in current_mode:
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
|
||||||
|
def dump_implementation(self, data, pointer=None):
|
||||||
assert(pointer)
|
assert(pointer)
|
||||||
data = utils.dump_datablock(pointer, 4)
|
|
||||||
|
|
||||||
#get the parent Object
|
dumper = utils.dump_anything.Dumper()
|
||||||
|
dumper.depth = 4
|
||||||
|
dumper.include_filter = [
|
||||||
|
'bones',
|
||||||
|
'tail_local',
|
||||||
|
'head_local',
|
||||||
|
'tail_radius',
|
||||||
|
'head_radius',
|
||||||
|
'use_connect',
|
||||||
|
'parent',
|
||||||
|
'name',
|
||||||
|
'layers'
|
||||||
|
|
||||||
|
]
|
||||||
|
data = dumper.dump(pointer)
|
||||||
|
|
||||||
|
for bone in pointer.bones:
|
||||||
|
if bone.parent:
|
||||||
|
data['bones'][bone.name]['parent'] = bone.parent.name
|
||||||
|
# get the parent Object
|
||||||
object_users = utils.get_datablock_users(pointer)[0]
|
object_users = utils.get_datablock_users(pointer)[0]
|
||||||
data['user'] = object_users.name
|
data['user'] = object_users.uuid
|
||||||
|
data['user_name'] = object_users.name
|
||||||
|
|
||||||
#get parent collection
|
# get parent collection
|
||||||
container_users = utils.get_datablock_users(object_users)
|
container_users = utils.get_datablock_users(object_users)
|
||||||
data['user_collection'] = [item.name for item in container_users if isinstance(item,bpy.types.Collection)]
|
data['user_collection'] = [
|
||||||
data['user_scene'] = [item.name for item in container_users if isinstance(item,bpy.types.Scene)]
|
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
|
return data
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
assert(self.data)
|
|
||||||
self.pointer = bpy.data.armatures.get(self.data['name'])
|
|
||||||
|
|
||||||
def diff(self):
|
|
||||||
False
|
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.armatures.get(self.data['name'])
|
return bpy.data.armatures.get(self.data['name'])
|
||||||
|
|
||||||
bl_id = "armatures"
|
|
||||||
bl_class = bpy.types.Armature
|
|
||||||
bl_rep_class = BlArmature
|
|
||||||
bl_delay_refresh = 1
|
|
||||||
bl_delay_apply = 0
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'ARMATURE_DATA'
|
|
@ -6,6 +6,13 @@ from .bl_datablock import BlDatablock
|
|||||||
|
|
||||||
|
|
||||||
class BlCamera(BlDatablock):
|
class BlCamera(BlDatablock):
|
||||||
|
bl_id = "cameras"
|
||||||
|
bl_class = bpy.types.Camera
|
||||||
|
bl_delay_refresh = 1
|
||||||
|
bl_delay_apply = 1
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'CAMERA_DATA'
|
||||||
|
|
||||||
def load(self, data, target):
|
def load(self, data, target):
|
||||||
utils.dump_anything.load(target, data)
|
utils.dump_anything.load(target, data)
|
||||||
|
|
||||||
@ -18,7 +25,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()
|
||||||
@ -45,17 +52,5 @@ class BlCamera(BlDatablock):
|
|||||||
]
|
]
|
||||||
return dumper.dump(pointer)
|
return dumper.dump(pointer)
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.cameras)
|
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.cameras.get(self.data['name'])
|
return bpy.data.cameras.get(self.data['name'])
|
||||||
|
|
||||||
|
|
||||||
bl_id = "cameras"
|
|
||||||
bl_class = bpy.types.Camera
|
|
||||||
bl_rep_class = BlCamera
|
|
||||||
bl_delay_refresh = 1
|
|
||||||
bl_delay_apply = 1
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'CAMERA_DATA'
|
|
||||||
|
@ -6,6 +6,13 @@ from .bl_datablock import BlDatablock
|
|||||||
|
|
||||||
|
|
||||||
class BlCollection(BlDatablock):
|
class BlCollection(BlDatablock):
|
||||||
|
bl_id = "collections"
|
||||||
|
bl_icon = 'FILE_FOLDER'
|
||||||
|
bl_class = bpy.types.Collection
|
||||||
|
bl_delay_refresh = 1
|
||||||
|
bl_delay_apply = 1
|
||||||
|
bl_automatic_push = True
|
||||||
|
|
||||||
def construct(self, data):
|
def construct(self, data):
|
||||||
if self.is_library:
|
if self.is_library:
|
||||||
with bpy.data.libraries.load(filepath=bpy.data.libraries[self.data['library']].filepath, link=True) as (sourceData, targetData):
|
with bpy.data.libraries.load(filepath=bpy.data.libraries[self.data['library']].filepath, link=True) as (sourceData, targetData):
|
||||||
@ -47,7 +54,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
|
||||||
@ -68,19 +75,8 @@ class BlCollection(BlDatablock):
|
|||||||
|
|
||||||
data['children'] = collection_children
|
data['children'] = collection_children
|
||||||
|
|
||||||
# dumper = utils.dump_anything.Dumper()
|
|
||||||
# dumper.depth = 2
|
|
||||||
# dumper.include_filter = ['name','objects', 'children']
|
|
||||||
|
|
||||||
# return dumper.dump(pointer)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
self.pointer = utils.find_from_attr(
|
|
||||||
'uuid',
|
|
||||||
self.uuid,
|
|
||||||
bpy.data.collections)
|
|
||||||
|
|
||||||
def resolve_dependencies(self):
|
def resolve_dependencies(self):
|
||||||
deps = []
|
deps = []
|
||||||
|
|
||||||
@ -94,11 +90,3 @@ class BlCollection(BlDatablock):
|
|||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.collections.get(self.data['name'])
|
return bpy.data.collections.get(self.data['name'])
|
||||||
|
|
||||||
|
|
||||||
bl_id = "collections"
|
|
||||||
bl_icon = 'FILE_FOLDER'
|
|
||||||
bl_class = bpy.types.Collection
|
|
||||||
bl_rep_class = BlCollection
|
|
||||||
bl_delay_refresh = 1
|
|
||||||
bl_delay_apply = 1
|
|
||||||
bl_automatic_push = True
|
|
||||||
|
@ -5,6 +5,13 @@ from .. import utils
|
|||||||
from .bl_datablock import BlDatablock
|
from .bl_datablock import BlDatablock
|
||||||
|
|
||||||
class BlCurve(BlDatablock):
|
class BlCurve(BlDatablock):
|
||||||
|
bl_id = "curves"
|
||||||
|
bl_class = bpy.types.Curve
|
||||||
|
bl_delay_refresh = 1
|
||||||
|
bl_delay_apply = 1
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'CURVE_DATA'
|
||||||
|
|
||||||
def construct(self, data):
|
def construct(self, data):
|
||||||
return bpy.data.curves.new(data["name"], 'CURVE')
|
return bpy.data.curves.new(data["name"], 'CURVE')
|
||||||
|
|
||||||
@ -29,7 +36,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'] = {}
|
||||||
@ -52,15 +59,5 @@ class BlCurve(BlDatablock):
|
|||||||
data['type'] = 'CURVE'
|
data['type'] = 'CURVE'
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.curves)
|
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.curves.get(self.data['name'])
|
return bpy.data.curves.get(self.data['name'])
|
||||||
bl_id = "curves"
|
|
||||||
bl_class = bpy.types.Curve
|
|
||||||
bl_rep_class = BlCurve
|
|
||||||
bl_delay_refresh = 1
|
|
||||||
bl_delay_apply = 1
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'CURVE_DATA'
|
|
@ -6,7 +6,63 @@ from ..libs.replication.replication.data import ReplicatedDatablock
|
|||||||
from ..libs.replication.replication.constants import UP
|
from ..libs.replication.replication.constants import UP
|
||||||
|
|
||||||
|
|
||||||
|
def dump_driver(driver):
|
||||||
|
dumper = utils.dump_anything.Dumper()
|
||||||
|
dumper.depth = 6
|
||||||
|
data = dumper.dump(driver)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def load_driver(target_datablock, src_driver):
|
||||||
|
drivers = target_datablock.animation_data.drivers
|
||||||
|
src_driver_data = src_driver['driver']
|
||||||
|
new_driver = drivers.new(src_driver['data_path'])
|
||||||
|
|
||||||
|
# Settings
|
||||||
|
new_driver.driver.type = src_driver_data['type']
|
||||||
|
new_driver.driver.expression = src_driver_data['expression']
|
||||||
|
utils.dump_anything.load(new_driver, src_driver)
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
for src_variable in src_driver_data['variables']:
|
||||||
|
src_var_data = src_driver_data['variables'][src_variable]
|
||||||
|
new_var = new_driver.driver.variables.new()
|
||||||
|
new_var.name = src_var_data['name']
|
||||||
|
new_var.type = src_var_data['type']
|
||||||
|
|
||||||
|
for src_target in src_var_data['targets']:
|
||||||
|
src_target_data = src_var_data['targets'][src_target]
|
||||||
|
new_var.targets[src_target].id = utils.resolve_from_id(
|
||||||
|
src_target_data['id'], src_target_data['id_type'])
|
||||||
|
utils.dump_anything.load(
|
||||||
|
new_var.targets[src_target], src_target_data)
|
||||||
|
|
||||||
|
# Fcurve
|
||||||
|
new_fcurve = new_driver.keyframe_points
|
||||||
|
for p in reversed(new_fcurve):
|
||||||
|
new_fcurve.remove(p, fast=True)
|
||||||
|
|
||||||
|
new_fcurve.add(len(src_driver['keyframe_points']))
|
||||||
|
|
||||||
|
for index, src_point in enumerate(src_driver['keyframe_points']):
|
||||||
|
new_point = new_fcurve[index]
|
||||||
|
utils.dump_anything.load(
|
||||||
|
new_point, src_driver['keyframe_points'][src_point])
|
||||||
|
|
||||||
|
|
||||||
class BlDatablock(ReplicatedDatablock):
|
class BlDatablock(ReplicatedDatablock):
|
||||||
|
"""BlDatablock
|
||||||
|
|
||||||
|
bl_id : blender internal storage identifier
|
||||||
|
bl_class : blender internal type
|
||||||
|
bl_delay_refresh : refresh rate in second for observers
|
||||||
|
bl_delay_apply : refresh rate in sec for apply
|
||||||
|
bl_automatic_push : boolean
|
||||||
|
bl_icon : type icon (blender icon name)
|
||||||
|
"""
|
||||||
|
bl_id = "scenes"
|
||||||
|
|
||||||
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)
|
||||||
@ -14,7 +70,7 @@ class BlDatablock(ReplicatedDatablock):
|
|||||||
# 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
|
||||||
@ -50,10 +106,68 @@ class BlDatablock(ReplicatedDatablock):
|
|||||||
def resolve_dependencies_library(self):
|
def resolve_dependencies_library(self):
|
||||||
return [self.pointer.library]
|
return [self.pointer.library]
|
||||||
|
|
||||||
|
def resolve(self):
|
||||||
|
datablock_ref = None
|
||||||
|
datablock_root = getattr(bpy.data, self.bl_id)
|
||||||
|
datablock_ref = utils.find_from_attr('uuid', self.uuid, datablock_root)
|
||||||
|
|
||||||
|
# In case of lost uuid (ex: undo), resolve by name and reassign it
|
||||||
|
# TODO: avoid reference storing
|
||||||
|
if not datablock_ref:
|
||||||
|
datablock_ref = getattr(
|
||||||
|
bpy.data, self.bl_id).get(self.data['name'])
|
||||||
|
|
||||||
|
if datablock_ref:
|
||||||
|
setattr(datablock_ref, 'uuid', self.uuid)
|
||||||
|
|
||||||
|
self.pointer = datablock_ref
|
||||||
|
|
||||||
|
def dump(self, pointer=None):
|
||||||
|
data = {}
|
||||||
|
if utils.has_action(pointer):
|
||||||
|
dumper = utils.dump_anything.Dumper()
|
||||||
|
dumper.include_filter = ['action']
|
||||||
|
data['animation_data'] = dumper.dump(pointer.animation_data)
|
||||||
|
|
||||||
|
if utils.has_driver(pointer):
|
||||||
|
dumped_drivers = {'animation_data': {'drivers': []}}
|
||||||
|
for driver in pointer.animation_data.drivers:
|
||||||
|
dumped_drivers['animation_data']['drivers'].append(
|
||||||
|
dump_driver(driver))
|
||||||
|
|
||||||
|
data.update(dumped_drivers)
|
||||||
|
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()
|
||||||
|
|
||||||
|
for d in target.animation_data.drivers:
|
||||||
|
target.animation_data.drivers.remove(d)
|
||||||
|
|
||||||
|
if 'drivers' in data['animation_data']:
|
||||||
|
for driver in data['animation_data']['drivers']:
|
||||||
|
load_driver(target, driver)
|
||||||
|
|
||||||
|
if 'action' in data['animation_data']:
|
||||||
|
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 utils.has_action(self.pointer):
|
||||||
dependencies.append(self.pointer.animation_data.action)
|
dependencies.append(self.pointer.animation_data.action)
|
||||||
|
|
||||||
return dependencies
|
return dependencies
|
||||||
|
@ -34,6 +34,13 @@ def load_gpencil_layer(target=None, data=None, create=False):
|
|||||||
|
|
||||||
|
|
||||||
class BlGpencil(BlDatablock):
|
class BlGpencil(BlDatablock):
|
||||||
|
bl_id = "grease_pencils"
|
||||||
|
bl_class = bpy.types.GreasePencil
|
||||||
|
bl_delay_refresh = 5
|
||||||
|
bl_delay_apply = 5
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'GREASEPENCIL'
|
||||||
|
|
||||||
def construct(self, data):
|
def construct(self, data):
|
||||||
return bpy.data.grease_pencils.new(data["name"])
|
return bpy.data.grease_pencils.new(data["name"])
|
||||||
|
|
||||||
@ -57,16 +64,13 @@ 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(
|
||||||
pointer, ['layers'], 9, data)
|
pointer, ['layers'], 9, data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.grease_pencils)
|
|
||||||
|
|
||||||
def resolve_dependencies(self):
|
def resolve_dependencies(self):
|
||||||
deps = []
|
deps = []
|
||||||
|
|
||||||
@ -77,11 +81,3 @@ class BlGpencil(BlDatablock):
|
|||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.grease_pencils.get(self.data['name'])
|
return bpy.data.grease_pencils.get(self.data['name'])
|
||||||
|
|
||||||
bl_id = "grease_pencils"
|
|
||||||
bl_class = bpy.types.GreasePencil
|
|
||||||
bl_rep_class = BlGpencil
|
|
||||||
bl_delay_refresh = 5
|
|
||||||
bl_delay_apply = 5
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'GREASEPENCIL'
|
|
@ -24,6 +24,13 @@ def dump_image(image):
|
|||||||
return pixels
|
return pixels
|
||||||
|
|
||||||
class BlImage(BlDatablock):
|
class BlImage(BlDatablock):
|
||||||
|
bl_id = "images"
|
||||||
|
bl_class = bpy.types.Image
|
||||||
|
bl_delay_refresh = 0
|
||||||
|
bl_delay_apply = 0
|
||||||
|
bl_automatic_push = False
|
||||||
|
bl_icon = 'IMAGE_DATA'
|
||||||
|
|
||||||
def construct(self, data):
|
def construct(self, data):
|
||||||
return bpy.data.images.new(
|
return bpy.data.images.new(
|
||||||
name=data['name'],
|
name=data['name'],
|
||||||
@ -46,7 +53,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)
|
||||||
@ -58,18 +65,8 @@ class BlImage(BlDatablock):
|
|||||||
data)
|
data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.images)
|
|
||||||
|
|
||||||
def diff(self):
|
def diff(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.images.get(self.data['name'])
|
return bpy.data.images.get(self.data['name'])
|
||||||
bl_id = "images"
|
|
||||||
bl_class = bpy.types.Image
|
|
||||||
bl_rep_class = BlImage
|
|
||||||
bl_delay_refresh = 0
|
|
||||||
bl_delay_apply = 0
|
|
||||||
bl_automatic_push = False
|
|
||||||
bl_icon = 'IMAGE_DATA'
|
|
@ -6,6 +6,13 @@ from .bl_datablock import BlDatablock
|
|||||||
|
|
||||||
|
|
||||||
class BlLattice(BlDatablock):
|
class BlLattice(BlDatablock):
|
||||||
|
bl_id = "lattices"
|
||||||
|
bl_class = bpy.types.Lattice
|
||||||
|
bl_delay_refresh = 1
|
||||||
|
bl_delay_apply = 1
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'LATTICE_DATA'
|
||||||
|
|
||||||
def load(self, data, target):
|
def load(self, data, target):
|
||||||
utils.dump_anything.load(target, data)
|
utils.dump_anything.load(target, data)
|
||||||
|
|
||||||
@ -38,17 +45,8 @@ class BlLattice(BlDatablock):
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.lattices)
|
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.lattices.get(self.data['name'])
|
return bpy.data.lattices.get(self.data['name'])
|
||||||
|
|
||||||
|
|
||||||
bl_id = "lattices"
|
|
||||||
bl_class = bpy.types.Lattice
|
|
||||||
bl_rep_class = BlLattice
|
|
||||||
bl_delay_refresh = 1
|
|
||||||
bl_delay_apply = 1
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'LATTICE_DATA'
|
|
||||||
|
@ -6,6 +6,13 @@ from .bl_datablock import BlDatablock
|
|||||||
|
|
||||||
|
|
||||||
class BlLibrary(BlDatablock):
|
class BlLibrary(BlDatablock):
|
||||||
|
bl_id = "libraries"
|
||||||
|
bl_class = bpy.types.Library
|
||||||
|
bl_delay_refresh = 1
|
||||||
|
bl_delay_apply = 1
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'LIBRARY_DATA_DIRECT'
|
||||||
|
|
||||||
def construct(self, data):
|
def construct(self, data):
|
||||||
with bpy.data.libraries.load(filepath=data["filepath"], link=True) as (sourceData, targetData):
|
with bpy.data.libraries.load(filepath=data["filepath"], link=True) as (sourceData, targetData):
|
||||||
targetData = sourceData
|
targetData = sourceData
|
||||||
@ -17,16 +24,5 @@ class BlLibrary(BlDatablock):
|
|||||||
assert(pointer)
|
assert(pointer)
|
||||||
return utils.dump_datablock(pointer, 1)
|
return utils.dump_datablock(pointer, 1)
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.libraries)
|
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.libraries.get(self.data['name'])
|
return bpy.data.libraries.get(self.data['name'])
|
||||||
|
|
||||||
bl_id = "libraries"
|
|
||||||
bl_class = bpy.types.Library
|
|
||||||
bl_rep_class = BlLibrary
|
|
||||||
bl_delay_refresh = 1
|
|
||||||
bl_delay_apply = 1
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'LIBRARY_DATA_DIRECT'
|
|
@ -6,13 +6,20 @@ from .bl_datablock import BlDatablock
|
|||||||
|
|
||||||
|
|
||||||
class BlLight(BlDatablock):
|
class BlLight(BlDatablock):
|
||||||
|
bl_id = "lights"
|
||||||
|
bl_class = bpy.types.Light
|
||||||
|
bl_delay_refresh = 1
|
||||||
|
bl_delay_apply = 1
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'LIGHT_DATA'
|
||||||
|
|
||||||
def construct(self, data):
|
def construct(self, data):
|
||||||
return bpy.data.lights.new(data["name"], data["type"])
|
return bpy.data.lights.new(data["name"], data["type"])
|
||||||
|
|
||||||
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
|
||||||
@ -39,16 +46,6 @@ class BlLight(BlDatablock):
|
|||||||
data = dumper.dump(pointer)
|
data = dumper.dump(pointer)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.lights)
|
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.lights.get(self.data['name'])
|
return bpy.data.lights.get(self.data['name'])
|
||||||
|
|
||||||
bl_id = "lights"
|
|
||||||
bl_class = bpy.types.Light
|
|
||||||
bl_rep_class = BlLight
|
|
||||||
bl_delay_refresh = 1
|
|
||||||
bl_delay_apply = 1
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'LIGHT_DATA'
|
|
@ -5,12 +5,20 @@ from .. import utils
|
|||||||
from .bl_datablock import BlDatablock
|
from .bl_datablock import BlDatablock
|
||||||
|
|
||||||
|
|
||||||
class BlLightProbe(BlDatablock):
|
class BlLightprobe(BlDatablock):
|
||||||
|
bl_id = "lightprobes"
|
||||||
|
bl_class = bpy.types.LightProbe
|
||||||
|
bl_delay_refresh = 1
|
||||||
|
bl_delay_apply = 1
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'LIGHTPROBE_GRID'
|
||||||
|
|
||||||
def load(self, data, target):
|
def load(self, data, target):
|
||||||
utils.dump_anything.load(target, data)
|
utils.dump_anything.load(target, data)
|
||||||
|
|
||||||
def construct(self, data):
|
def construct(self, data):
|
||||||
return bpy.data.lightprobes.new(data["name"])
|
type = 'CUBE' if data['type'] == 'CUBEMAP' else data['type']
|
||||||
|
return bpy.data.lightprobes.new(data["name"], type)
|
||||||
|
|
||||||
def dump(self, pointer=None):
|
def dump(self, pointer=None):
|
||||||
assert(pointer)
|
assert(pointer)
|
||||||
@ -19,6 +27,7 @@ class BlLightProbe(BlDatablock):
|
|||||||
dumper.depth = 1
|
dumper.depth = 1
|
||||||
dumper.include_filter = [
|
dumper.include_filter = [
|
||||||
"name",
|
"name",
|
||||||
|
'type',
|
||||||
'influence_type',
|
'influence_type',
|
||||||
'influence_distance',
|
'influence_distance',
|
||||||
'falloff',
|
'falloff',
|
||||||
@ -29,21 +38,15 @@ class BlLightProbe(BlDatablock):
|
|||||||
'use_custom_parallax',
|
'use_custom_parallax',
|
||||||
'parallax_type',
|
'parallax_type',
|
||||||
'parallax_distance',
|
'parallax_distance',
|
||||||
|
'grid_resolution_x',
|
||||||
|
'grid_resolution_y',
|
||||||
|
'grid_resolution_z',
|
||||||
|
'visibility_buffer_bias',
|
||||||
|
'visibility_bleed_bias',
|
||||||
|
'visibility_blur'
|
||||||
]
|
]
|
||||||
|
|
||||||
return dumper.dump(pointer)
|
return dumper.dump(pointer)
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.lattices)
|
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.lattices.get(self.data['name'])
|
return bpy.data.lattices.get(self.data['name'])
|
||||||
|
|
||||||
|
|
||||||
bl_id = "lightprobes"
|
|
||||||
bl_class = bpy.types.LightProbe
|
|
||||||
bl_rep_class = BlLightProbe
|
|
||||||
bl_delay_refresh = 1
|
|
||||||
bl_delay_apply = 1
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'LIGHTPROBE_GRID'
|
|
||||||
|
@ -68,6 +68,13 @@ def load_link(target_node_tree, source):
|
|||||||
|
|
||||||
|
|
||||||
class BlMaterial(BlDatablock):
|
class BlMaterial(BlDatablock):
|
||||||
|
bl_id = "materials"
|
||||||
|
bl_class = bpy.types.Material
|
||||||
|
bl_delay_refresh = 10
|
||||||
|
bl_delay_apply = 10
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'MATERIAL_DATA'
|
||||||
|
|
||||||
def construct(self, data):
|
def construct(self, data):
|
||||||
return bpy.data.materials.new(data["name"])
|
return bpy.data.materials.new(data["name"])
|
||||||
|
|
||||||
@ -98,7 +105,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
|
||||||
@ -175,9 +182,6 @@ class BlMaterial(BlDatablock):
|
|||||||
utils.dump_datablock_attibutes(pointer, ["grease_pencil"], 3, data)
|
utils.dump_datablock_attibutes(pointer, ["grease_pencil"], 3, data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.materials)
|
|
||||||
|
|
||||||
def resolve_dependencies(self):
|
def resolve_dependencies(self):
|
||||||
# TODO: resolve node group deps
|
# TODO: resolve node group deps
|
||||||
deps = []
|
deps = []
|
||||||
@ -194,11 +198,3 @@ class BlMaterial(BlDatablock):
|
|||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.materials.get(self.data['name'])
|
return bpy.data.materials.get(self.data['name'])
|
||||||
|
|
||||||
|
|
||||||
bl_id = "materials"
|
|
||||||
bl_class = bpy.types.Material
|
|
||||||
bl_rep_class = BlMaterial
|
|
||||||
bl_delay_refresh = 10
|
|
||||||
bl_delay_apply = 10
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'MATERIAL_DATA'
|
|
||||||
|
@ -75,6 +75,13 @@ def dump_mesh(mesh, data={}):
|
|||||||
return mesh_data
|
return mesh_data
|
||||||
|
|
||||||
class BlMesh(BlDatablock):
|
class BlMesh(BlDatablock):
|
||||||
|
bl_id = "meshes"
|
||||||
|
bl_class = bpy.types.Mesh
|
||||||
|
bl_delay_refresh = 10
|
||||||
|
bl_delay_apply = 10
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'MESH_DATA'
|
||||||
|
|
||||||
def construct(self, data):
|
def construct(self, data):
|
||||||
instance = bpy.data.meshes.new(data["name"])
|
instance = bpy.data.meshes.new(data["name"])
|
||||||
instance.uuid = self.uuid
|
instance.uuid = self.uuid
|
||||||
@ -139,7 +146,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)
|
||||||
@ -154,9 +161,6 @@ class BlMesh(BlDatablock):
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.meshes)
|
|
||||||
|
|
||||||
def resolve_dependencies(self):
|
def resolve_dependencies(self):
|
||||||
deps = []
|
deps = []
|
||||||
|
|
||||||
@ -169,10 +173,3 @@ class BlMesh(BlDatablock):
|
|||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.meshes.get(self.data['name'])
|
return bpy.data.meshes.get(self.data['name'])
|
||||||
|
|
||||||
bl_id = "meshes"
|
|
||||||
bl_class = bpy.types.Mesh
|
|
||||||
bl_rep_class = BlMesh
|
|
||||||
bl_delay_refresh = 10
|
|
||||||
bl_delay_apply = 10
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'MESH_DATA'
|
|
||||||
|
@ -6,6 +6,13 @@ from .bl_datablock import BlDatablock
|
|||||||
|
|
||||||
|
|
||||||
class BlMetaball(BlDatablock):
|
class BlMetaball(BlDatablock):
|
||||||
|
bl_id = "metaballs"
|
||||||
|
bl_class = bpy.types.MetaBall
|
||||||
|
bl_delay_refresh = 1
|
||||||
|
bl_delay_apply = 1
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'META_BALL'
|
||||||
|
|
||||||
def construct(self, data):
|
def construct(self, data):
|
||||||
return bpy.data.metaballs.new(data["name"])
|
return bpy.data.metaballs.new(data["name"])
|
||||||
|
|
||||||
@ -17,7 +24,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
|
||||||
@ -26,16 +33,5 @@ class BlMetaball(BlDatablock):
|
|||||||
data = dumper.dump(pointer)
|
data = dumper.dump(pointer)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.metaballs)
|
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.metaballs.get(self.data['name'])
|
return bpy.data.metaballs.get(self.data['name'])
|
||||||
|
|
||||||
bl_id = "metaballs"
|
|
||||||
bl_class = bpy.types.MetaBall
|
|
||||||
bl_rep_class = BlMetaball
|
|
||||||
bl_delay_refresh = 1
|
|
||||||
bl_delay_apply = 1
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'META_BALL'
|
|
@ -1,11 +1,40 @@
|
|||||||
import bpy
|
import bpy
|
||||||
import mathutils
|
import mathutils
|
||||||
|
import logging
|
||||||
|
|
||||||
from .. import utils
|
from .. import utils
|
||||||
from .bl_datablock import BlDatablock
|
from .bl_datablock import BlDatablock
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def load_constraints(target, data):
|
||||||
|
for local_constraint in target.constraints:
|
||||||
|
if local_constraint.name not in data:
|
||||||
|
target.constraints.remove(local_constraint)
|
||||||
|
|
||||||
|
for constraint in data:
|
||||||
|
target_constraint = target.constraints.get(constraint)
|
||||||
|
|
||||||
|
if not target_constraint:
|
||||||
|
target_constraint = target.constraints.new(
|
||||||
|
data[constraint]['type'])
|
||||||
|
|
||||||
|
utils.dump_anything.load(
|
||||||
|
target_constraint, data[constraint])
|
||||||
|
|
||||||
|
def load_pose(target_bone, data):
|
||||||
|
target_bone.rotation_mode = data['rotation_mode']
|
||||||
|
|
||||||
|
utils.dump_anything.load(target_bone, data)
|
||||||
|
|
||||||
class BlObject(BlDatablock):
|
class BlObject(BlDatablock):
|
||||||
|
bl_id = "objects"
|
||||||
|
bl_class = bpy.types.Object
|
||||||
|
bl_delay_refresh = 1
|
||||||
|
bl_delay_apply = 1
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'OBJECT_DATA'
|
||||||
|
|
||||||
def construct(self, data):
|
def construct(self, data):
|
||||||
pointer = None
|
pointer = None
|
||||||
|
|
||||||
@ -42,24 +71,26 @@ class BlObject(BlDatablock):
|
|||||||
elif data["data"] in bpy.data.speakers.keys():
|
elif data["data"] in bpy.data.speakers.keys():
|
||||||
pointer = bpy.data.speakers[data["data"]]
|
pointer = bpy.data.speakers[data["data"]]
|
||||||
elif data["data"] in bpy.data.lightprobes.keys():
|
elif data["data"] in bpy.data.lightprobes.keys():
|
||||||
pass
|
# Only supported since 2.83
|
||||||
# bpy need to support correct lightprobe creation from python
|
if bpy.app.version[1] >= 83:
|
||||||
# pointer = bpy.data.lightprobes[data["data"]]
|
pointer = bpy.data.lightprobes[data["data"]]
|
||||||
|
else:
|
||||||
instance = bpy.data.objects.new(data["name"], pointer)
|
logger.error("Lightprobe replication only supported since 2.83")
|
||||||
|
instance = bpy.data.objects.new(data["name"], pointer)
|
||||||
instance.uuid = self.uuid
|
instance.uuid = self.uuid
|
||||||
|
|
||||||
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
|
||||||
if hasattr(target, 'modifiers'):
|
if hasattr(target, 'modifiers'):
|
||||||
for local_modifier in target.modifiers:
|
# TODO: smarter selective update
|
||||||
if local_modifier.name not in data['modifiers']:
|
target.modifiers.clear()
|
||||||
target.modifiers.remove(local_modifier)
|
|
||||||
for modifier in data['modifiers']:
|
for modifier in data['modifiers']:
|
||||||
target_modifier = target.modifiers.get(modifier)
|
target_modifier = target.modifiers.get(modifier)
|
||||||
|
|
||||||
@ -70,6 +101,42 @@ class BlObject(BlDatablock):
|
|||||||
utils.dump_anything.load(
|
utils.dump_anything.load(
|
||||||
target_modifier, data['modifiers'][modifier])
|
target_modifier, data['modifiers'][modifier])
|
||||||
|
|
||||||
|
# Load constraints
|
||||||
|
# Object
|
||||||
|
if hasattr(target, 'constraints') and 'constraints' in data:
|
||||||
|
load_constraints(target, data['constraints'])
|
||||||
|
|
||||||
|
|
||||||
|
# Pose
|
||||||
|
if 'pose' in data:
|
||||||
|
if not target.pose:
|
||||||
|
raise Exception('No pose data yet (Fixed in a near futur)')
|
||||||
|
# Bone groups
|
||||||
|
for bg_name in data['pose']['bone_groups']:
|
||||||
|
bg_data = data['pose']['bone_groups'].get(bg_name)
|
||||||
|
bg_target = target.pose.bone_groups.get(bg_name)
|
||||||
|
|
||||||
|
if not bg_target:
|
||||||
|
bg_target = target.pose.bone_groups.new(name=bg_name)
|
||||||
|
|
||||||
|
utils.dump_anything.load(bg_target, bg_data)
|
||||||
|
# target.pose.bone_groups.get
|
||||||
|
|
||||||
|
# Bones
|
||||||
|
for bone in data['pose']['bones']:
|
||||||
|
target_bone = target.pose.bones.get(bone)
|
||||||
|
bone_data = data['pose']['bones'].get(bone)
|
||||||
|
|
||||||
|
if 'constraints' in bone_data.keys():
|
||||||
|
load_constraints(
|
||||||
|
target_bone, bone_data['constraints'])
|
||||||
|
|
||||||
|
load_pose(target_bone,bone_data)
|
||||||
|
|
||||||
|
if 'bone_index' in bone_data.keys():
|
||||||
|
target_bone.bone_group = target.pose.bone_group[bone_data['bone_group_index']]
|
||||||
|
|
||||||
|
|
||||||
# Load relations
|
# Load relations
|
||||||
if 'children' in data.keys():
|
if 'children' in data.keys():
|
||||||
for child in data['children']:
|
for child in data['children']:
|
||||||
@ -80,17 +147,46 @@ class BlObject(BlDatablock):
|
|||||||
target.empty_display_type = data['empty_display_type']
|
target.empty_display_type = data['empty_display_type']
|
||||||
|
|
||||||
# Instancing
|
# Instancing
|
||||||
target.instance_type = data['instance_type']
|
target.instance_type = data['instance_type']
|
||||||
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):
|
# vertex groups
|
||||||
|
if 'vertex_groups' in data:
|
||||||
|
target.vertex_groups.clear()
|
||||||
|
for vg in data['vertex_groups']:
|
||||||
|
vertex_group = target.vertex_groups.new(name=vg['name'])
|
||||||
|
for vert in vg['vertices']:
|
||||||
|
vertex_group.add(
|
||||||
|
[vert['index']], vert['weight'], 'REPLACE')
|
||||||
|
|
||||||
|
# SHAPE KEYS
|
||||||
|
if 'shape_keys' in data:
|
||||||
|
target.shape_key_clear()
|
||||||
|
|
||||||
|
object_data = target.data
|
||||||
|
|
||||||
|
# Create keys and load vertices coords
|
||||||
|
for key_block in data['shape_keys']['key_blocks']:
|
||||||
|
key_data = data['shape_keys']['key_blocks'][key_block]
|
||||||
|
target.shape_key_add(name=key_block)
|
||||||
|
|
||||||
|
utils.dump_anything.load(target.data.shape_keys.key_blocks[key_block],key_data)
|
||||||
|
for vert in key_data['data']:
|
||||||
|
target.data.shape_keys.key_blocks[key_block].data[vert].co = key_data['data'][vert]['co']
|
||||||
|
|
||||||
|
# Load relative key after all
|
||||||
|
for key_block in data['shape_keys']['key_blocks']:
|
||||||
|
reference = data['shape_keys']['key_blocks'][key_block]['relative_key']
|
||||||
|
|
||||||
|
target.data.shape_keys.key_blocks[key_block].relative_key = target.data.shape_keys.key_blocks[reference]
|
||||||
|
|
||||||
|
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",
|
||||||
@ -101,6 +197,9 @@ class BlObject(BlDatablock):
|
|||||||
"instance_collection",
|
"instance_collection",
|
||||||
"instance_type"
|
"instance_type"
|
||||||
]
|
]
|
||||||
|
# if not utils.has_action(pointer):
|
||||||
|
dumper.include_filter.append('matrix_world')
|
||||||
|
|
||||||
data = dumper.dump(pointer)
|
data = dumper.dump(pointer)
|
||||||
|
|
||||||
if self.is_library:
|
if self.is_library:
|
||||||
@ -109,8 +208,55 @@ class BlObject(BlDatablock):
|
|||||||
# MODIFIERS
|
# MODIFIERS
|
||||||
if hasattr(pointer, 'modifiers'):
|
if hasattr(pointer, 'modifiers'):
|
||||||
dumper.include_filter = None
|
dumper.include_filter = None
|
||||||
|
dumper.depth = 2
|
||||||
|
data["modifiers"] = {}
|
||||||
|
for index, modifier in enumerate(pointer.modifiers):
|
||||||
|
data["modifiers"][modifier.name] = dumper.dump(modifier)
|
||||||
|
data["modifiers"][modifier.name]['m_index'] = index
|
||||||
|
|
||||||
|
# CONSTRAINTS
|
||||||
|
# OBJECT
|
||||||
|
if hasattr(pointer, 'constraints'):
|
||||||
dumper.depth = 3
|
dumper.depth = 3
|
||||||
data["modifiers"] = dumper.dump(pointer.modifiers)
|
data["constraints"] = dumper.dump(pointer.constraints)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# POSE
|
||||||
|
if hasattr(pointer, 'pose') and pointer.pose:
|
||||||
|
# BONES
|
||||||
|
bones = {}
|
||||||
|
for bone in pointer.pose.bones:
|
||||||
|
bones[bone.name] = {}
|
||||||
|
dumper.depth = 1
|
||||||
|
rotation = 'rotation_quaternion' if bone.rotation_mode == 'QUATERNION' else 'rotation_euler'
|
||||||
|
group_index = 'bone_group_index' if bone.bone_group else None
|
||||||
|
dumper.include_filter = [
|
||||||
|
'rotation_mode',
|
||||||
|
'location',
|
||||||
|
'scale',
|
||||||
|
group_index,
|
||||||
|
rotation
|
||||||
|
]
|
||||||
|
bones[bone.name] = dumper.dump(bone)
|
||||||
|
|
||||||
|
dumper.include_filter = []
|
||||||
|
dumper.depth = 3
|
||||||
|
bones[bone.name]["constraints"] = dumper.dump(bone.constraints)
|
||||||
|
|
||||||
|
data['pose'] = {'bones': bones}
|
||||||
|
|
||||||
|
# GROUPS
|
||||||
|
bone_groups = {}
|
||||||
|
for group in pointer.pose.bone_groups:
|
||||||
|
dumper.depth = 3
|
||||||
|
dumper.include_filter = [
|
||||||
|
'name',
|
||||||
|
'color_set'
|
||||||
|
]
|
||||||
|
bone_groups[group.name] = dumper.dump(group)
|
||||||
|
data['pose']['bone_groups'] = bone_groups
|
||||||
|
|
||||||
|
|
||||||
# CHILDS
|
# CHILDS
|
||||||
if len(pointer.children) > 0:
|
if len(pointer.children) > 0:
|
||||||
@ -120,13 +266,64 @@ class BlObject(BlDatablock):
|
|||||||
|
|
||||||
data["children"] = childs
|
data["children"] = childs
|
||||||
|
|
||||||
|
# VERTEx GROUP
|
||||||
|
if len(pointer.vertex_groups) > 0:
|
||||||
|
vg_data = []
|
||||||
|
for vg in pointer.vertex_groups:
|
||||||
|
vg_idx = vg.index
|
||||||
|
dumped_vg = {}
|
||||||
|
dumped_vg['name'] = vg.name
|
||||||
|
|
||||||
|
vertices = []
|
||||||
|
|
||||||
|
for v in pointer.data.vertices:
|
||||||
|
for vg in v.groups:
|
||||||
|
if vg.group == vg_idx:
|
||||||
|
# logger.error("VG {} : Adding vertex {} to group {}".format(vg_idx, v.index, vg_idx))
|
||||||
|
|
||||||
|
vertices.append({
|
||||||
|
'index': v.index,
|
||||||
|
'weight': vg.weight
|
||||||
|
})
|
||||||
|
|
||||||
|
dumped_vg['vertices'] = vertices
|
||||||
|
|
||||||
|
vg_data.append(dumped_vg)
|
||||||
|
|
||||||
|
data['vertex_groups'] = vg_data
|
||||||
|
|
||||||
|
# SHAPE KEYS
|
||||||
|
pointer_data = pointer.data
|
||||||
|
if hasattr(pointer_data, 'shape_keys') and pointer_data.shape_keys:
|
||||||
|
dumper = utils.dump_anything.Dumper()
|
||||||
|
dumper.depth = 2
|
||||||
|
dumper.include_filter = [
|
||||||
|
'reference_key',
|
||||||
|
'use_relative'
|
||||||
|
]
|
||||||
|
data['shape_keys'] = dumper.dump(pointer_data.shape_keys)
|
||||||
|
data['shape_keys']['reference_key'] = pointer_data.shape_keys.reference_key.name
|
||||||
|
key_blocks = {}
|
||||||
|
for key in pointer_data.shape_keys.key_blocks:
|
||||||
|
dumper.depth = 3
|
||||||
|
dumper.include_filter = [
|
||||||
|
'name',
|
||||||
|
'data',
|
||||||
|
'mute',
|
||||||
|
'value',
|
||||||
|
'slider_min',
|
||||||
|
'slider_max',
|
||||||
|
'data',
|
||||||
|
'co'
|
||||||
|
]
|
||||||
|
key_blocks[key.name] = dumper.dump(key)
|
||||||
|
key_blocks[key.name]['relative_key'] = key.relative_key.name
|
||||||
|
data['shape_keys']['key_blocks'] = key_blocks
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
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:
|
||||||
@ -137,8 +334,9 @@ class BlObject(BlDatablock):
|
|||||||
if self.is_library:
|
if self.is_library:
|
||||||
deps.append(self.pointer.library)
|
deps.append(self.pointer.library)
|
||||||
|
|
||||||
|
|
||||||
if self.pointer.instance_type == 'COLLECTION':
|
if self.pointer.instance_type == 'COLLECTION':
|
||||||
#TODO: uuid based
|
# TODO: uuid based
|
||||||
deps.append(self.pointer.instance_collection)
|
deps.append(self.pointer.instance_collection)
|
||||||
|
|
||||||
return deps
|
return deps
|
||||||
@ -147,10 +345,3 @@ class BlObject(BlDatablock):
|
|||||||
return bpy.data.objects.get(self.data['name'])
|
return bpy.data.objects.get(self.data['name'])
|
||||||
|
|
||||||
|
|
||||||
bl_id = "objects"
|
|
||||||
bl_class = bpy.types.Object
|
|
||||||
bl_rep_class = BlObject
|
|
||||||
bl_delay_refresh = 1
|
|
||||||
bl_delay_apply = 1
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'OBJECT_DATA'
|
|
||||||
|
@ -5,6 +5,13 @@ from .. import utils
|
|||||||
from .bl_datablock import BlDatablock
|
from .bl_datablock import BlDatablock
|
||||||
|
|
||||||
class BlScene(BlDatablock):
|
class BlScene(BlDatablock):
|
||||||
|
bl_id = "scenes"
|
||||||
|
bl_class = bpy.types.Scene
|
||||||
|
bl_delay_refresh = 1
|
||||||
|
bl_delay_apply = 1
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'SCENE_DATA'
|
||||||
|
|
||||||
def construct(self, data):
|
def construct(self, data):
|
||||||
instance = bpy.data.scenes.new(data["name"])
|
instance = bpy.data.scenes.new(data["name"])
|
||||||
instance.uuid = self.uuid
|
instance.uuid = self.uuid
|
||||||
@ -42,7 +49,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 = {}
|
||||||
|
|
||||||
@ -58,12 +65,6 @@ class BlScene(BlDatablock):
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
scene_name = self.data['name']
|
|
||||||
|
|
||||||
self.pointer = bpy.data.scenes.get(scene_name)
|
|
||||||
# self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.objects)
|
|
||||||
|
|
||||||
def resolve_dependencies(self):
|
def resolve_dependencies(self):
|
||||||
deps = []
|
deps = []
|
||||||
|
|
||||||
@ -87,10 +88,3 @@ class BlScene(BlDatablock):
|
|||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.scenes.get(self.data['name'])
|
return bpy.data.scenes.get(self.data['name'])
|
||||||
bl_id = "scenes"
|
|
||||||
bl_class = bpy.types.Scene
|
|
||||||
bl_rep_class = BlScene
|
|
||||||
bl_delay_refresh = 1
|
|
||||||
bl_delay_apply = 1
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'SCENE_DATA'
|
|
@ -6,6 +6,13 @@ from .bl_datablock import BlDatablock
|
|||||||
|
|
||||||
|
|
||||||
class BlSpeaker(BlDatablock):
|
class BlSpeaker(BlDatablock):
|
||||||
|
bl_id = "speakers"
|
||||||
|
bl_class = bpy.types.Speaker
|
||||||
|
bl_delay_refresh = 1
|
||||||
|
bl_delay_apply = 1
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'SPEAKER'
|
||||||
|
|
||||||
def load(self, data, target):
|
def load(self, data, target):
|
||||||
utils.dump_anything.load(target, data)
|
utils.dump_anything.load(target, data)
|
||||||
|
|
||||||
@ -34,17 +41,6 @@ class BlSpeaker(BlDatablock):
|
|||||||
|
|
||||||
return dumper.dump(pointer)
|
return dumper.dump(pointer)
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.lattices)
|
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.lattices.get(self.data['name'])
|
return bpy.data.lattices.get(self.data['name'])
|
||||||
|
|
||||||
|
|
||||||
bl_id = "speakers"
|
|
||||||
bl_class = bpy.types.Speaker
|
|
||||||
bl_rep_class = BlSpeaker
|
|
||||||
bl_delay_refresh = 1
|
|
||||||
bl_delay_apply = 1
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'SPEAKER'
|
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
import bpy
|
|
||||||
import mathutils
|
|
||||||
import jsondiff
|
|
||||||
|
|
||||||
from .. import utils
|
|
||||||
from .. import presence
|
|
||||||
from .bl_datablock import BlDatablock
|
|
||||||
from ..libs.replication.replication.constants import UP
|
|
||||||
|
|
||||||
class BlUser(BlDatablock):
|
|
||||||
def construct(self, name):
|
|
||||||
return presence.User()
|
|
||||||
|
|
||||||
def load(self, data, target):
|
|
||||||
target.name = data['name']
|
|
||||||
target.location = data['location']
|
|
||||||
target.selected_objects = data['selected_objects']
|
|
||||||
|
|
||||||
utils.dump_anything.load(target, data)
|
|
||||||
|
|
||||||
def apply(self):
|
|
||||||
|
|
||||||
if self.pointer:
|
|
||||||
self.load(data=self.data, target=self.pointer)
|
|
||||||
|
|
||||||
presence.refresh_3d_view()
|
|
||||||
|
|
||||||
self.state = UP
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def dump(self,pointer=None):
|
|
||||||
data = utils.dump_anything.dump(pointer)
|
|
||||||
data['location'] = pointer.location
|
|
||||||
data['color'] = pointer.color
|
|
||||||
data['selected_objects'] = pointer.selected_objects
|
|
||||||
data['view_matrix'] = pointer.view_matrix
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
self.pointer.is_dirty = True
|
|
||||||
|
|
||||||
# def diff(self):
|
|
||||||
# if not self.pointer:
|
|
||||||
# return False
|
|
||||||
# if self.pointer.is_dirty:
|
|
||||||
# self.pointer.is_dirty = False
|
|
||||||
# return True
|
|
||||||
|
|
||||||
# for i,coord in enumerate(self.pointer.location):
|
|
||||||
# if coord != self.data['location'][i]:
|
|
||||||
# return True
|
|
||||||
# return False
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
return True
|
|
||||||
bl_id = "users"
|
|
||||||
bl_class = presence.User
|
|
||||||
bl_rep_class = BlUser
|
|
||||||
bl_delay_refresh = .1
|
|
||||||
bl_delay_apply = .1
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'CON_ARMATURE'
|
|
@ -7,6 +7,13 @@ from .bl_material import load_link, load_node
|
|||||||
|
|
||||||
|
|
||||||
class BlWorld(BlDatablock):
|
class BlWorld(BlDatablock):
|
||||||
|
bl_id = "worlds"
|
||||||
|
bl_class = bpy.types.World
|
||||||
|
bl_delay_refresh = 4
|
||||||
|
bl_delay_apply = 4
|
||||||
|
bl_automatic_push = True
|
||||||
|
bl_icon = 'WORLD_DATA'
|
||||||
|
|
||||||
def construct(self, data):
|
def construct(self, data):
|
||||||
return bpy.data.worlds.new(data["name"])
|
return bpy.data.worlds.new(data["name"])
|
||||||
|
|
||||||
@ -26,7 +33,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()
|
||||||
@ -83,9 +90,6 @@ class BlWorld(BlDatablock):
|
|||||||
pointer.node_tree, ["links"], 3, data['node_tree'])
|
pointer.node_tree, ["links"], 3, data['node_tree'])
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def resolve(self):
|
|
||||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.worlds)
|
|
||||||
|
|
||||||
def resolve_dependencies(self):
|
def resolve_dependencies(self):
|
||||||
deps = []
|
deps = []
|
||||||
|
|
||||||
@ -100,11 +104,3 @@ class BlWorld(BlDatablock):
|
|||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return bpy.data.worlds.get(self.data['name'])
|
return bpy.data.worlds.get(self.data['name'])
|
||||||
|
|
||||||
|
|
||||||
bl_id = "worlds"
|
|
||||||
bl_class = bpy.types.World
|
|
||||||
bl_rep_class = BlWorld
|
|
||||||
bl_delay_refresh = 4
|
|
||||||
bl_delay_apply = 4
|
|
||||||
bl_automatic_push = True
|
|
||||||
bl_icon = 'WORLD_DATA'
|
|
||||||
|
@ -3,10 +3,10 @@ import logging
|
|||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from . import operators, presence, utils
|
from . import operators, presence, utils
|
||||||
from .bl_types.bl_user import BlUser
|
|
||||||
from .libs.replication.replication.constants import FETCHED, RP_COMMON
|
from .libs.replication.replication.constants import FETCHED, RP_COMMON
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
class Delayable():
|
class Delayable():
|
||||||
@ -74,74 +74,101 @@ class ApplyTimer(Timer):
|
|||||||
try:
|
try:
|
||||||
operators.client.apply(node)
|
operators.client.apply(node)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("fail to apply {}: {}".format(node_ref.uuid,e))
|
logger.error(
|
||||||
|
"fail to apply {}: {}".format(node_ref.uuid, e))
|
||||||
|
|
||||||
|
|
||||||
class DynamicRightSelectTimer(Timer):
|
class DynamicRightSelectTimer(Timer):
|
||||||
def __init__(self, timout=.1):
|
def __init__(self, timout=.1):
|
||||||
super().__init__(timout)
|
super().__init__(timout)
|
||||||
self.last_selection = []
|
self._last_selection = []
|
||||||
|
self._user = None
|
||||||
|
self._right_strategy = RP_COMMON
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
if operators.client:
|
session = operators.client
|
||||||
users = operators.client.list(filter=BlUser)
|
settings = bpy.context.window_manager.session
|
||||||
|
|
||||||
for user in users:
|
if session:
|
||||||
user_ref = operators.client.get(uuid=user)
|
# Find user
|
||||||
settings = bpy.context.window_manager.session
|
if self._user is None:
|
||||||
|
self._user = session.online_users.get(settings.username)
|
||||||
|
|
||||||
# Local user
|
if self._right_strategy is None:
|
||||||
if user_ref.pointer:
|
self._right_strategy = session.get_config()[
|
||||||
current_selection = utils.get_selected_objects(
|
'right_strategy']
|
||||||
bpy.context.scene)
|
|
||||||
if current_selection != self.last_selection:
|
|
||||||
right_strategy = operators.client.get_config()[
|
|
||||||
'right_strategy']
|
|
||||||
if right_strategy == RP_COMMON:
|
|
||||||
obj_common = [
|
|
||||||
o for o in self.last_selection if o not in current_selection]
|
|
||||||
obj_ours = [
|
|
||||||
o for o in current_selection if o not in self.last_selection]
|
|
||||||
|
|
||||||
# change new selection to our
|
if self._user:
|
||||||
for obj in obj_ours:
|
current_selection = utils.get_selected_objects(
|
||||||
node = operators.client.get(uuid=obj)
|
bpy.context.scene)
|
||||||
|
if current_selection != self._last_selection:
|
||||||
|
if self._right_strategy == RP_COMMON:
|
||||||
|
obj_common = [
|
||||||
|
o for o in self._last_selection if o not in current_selection]
|
||||||
|
obj_ours = [
|
||||||
|
o for o in current_selection if o not in self._last_selection]
|
||||||
|
|
||||||
if node and node.owner == RP_COMMON:
|
# change old selection right to common
|
||||||
recursive = True
|
for obj in obj_common:
|
||||||
if node.data and 'instance_type' in node.data.keys():
|
node = session.get(uuid=obj)
|
||||||
recursive = node.data['instance_type'] != 'COLLECTION'
|
|
||||||
|
|
||||||
operators.client.change_owner(
|
if node and (node.owner == settings.username or node.owner == RP_COMMON):
|
||||||
node.uuid,
|
recursive = True
|
||||||
settings.username,
|
if node.data and 'instance_type' in node.data.keys():
|
||||||
recursive=recursive)
|
recursive = node.data['instance_type'] != 'COLLECTION'
|
||||||
else:
|
session.change_owner(
|
||||||
return
|
node.uuid,
|
||||||
|
RP_COMMON,
|
||||||
|
recursive=recursive)
|
||||||
|
|
||||||
self.last_selection = current_selection
|
# change new selection to our
|
||||||
user_ref.pointer.update_selected_objects(
|
for obj in obj_ours:
|
||||||
bpy.context)
|
node = session.get(uuid=obj)
|
||||||
user_ref.update()
|
|
||||||
|
|
||||||
# change old selection right to common
|
if node and node.owner == RP_COMMON:
|
||||||
for obj in obj_common:
|
recursive = True
|
||||||
node = operators.client.get(uuid=obj)
|
if node.data and 'instance_type' in node.data.keys():
|
||||||
|
recursive = node.data['instance_type'] != 'COLLECTION'
|
||||||
|
|
||||||
if node and (node.owner == settings.username or node.owner == RP_COMMON):
|
session.change_owner(
|
||||||
recursive = True
|
node.uuid,
|
||||||
if node.data and 'instance_type' in node.data.keys():
|
settings.username,
|
||||||
recursive = node.data['instance_type'] != 'COLLECTION'
|
recursive=recursive)
|
||||||
operators.client.change_owner(
|
else:
|
||||||
node.uuid,
|
return
|
||||||
RP_COMMON,
|
|
||||||
recursive=recursive)
|
self._last_selection = current_selection
|
||||||
else:
|
|
||||||
for obj in bpy.data.objects:
|
user_metadata = {
|
||||||
if obj.hide_select and obj.uuid not in user_ref.data['selected_objects']:
|
'selected_objects': current_selection
|
||||||
obj.hide_select = False
|
}
|
||||||
elif not obj.hide_select and obj.uuid in user_ref.data['selected_objects']:
|
|
||||||
obj.hide_select = True
|
session.update_user_metadata(user_metadata)
|
||||||
|
logger.info("Update selection")
|
||||||
|
|
||||||
|
# Fix deselection until right managment refactoring (with Roles concepts)
|
||||||
|
if len(current_selection) == 0 and self._right_strategy == RP_COMMON:
|
||||||
|
owned_keys = session.list(
|
||||||
|
filter_owner=settings.username)
|
||||||
|
for key in owned_keys:
|
||||||
|
node = session.get(uuid=key)
|
||||||
|
|
||||||
|
session.change_owner(
|
||||||
|
key,
|
||||||
|
RP_COMMON,
|
||||||
|
recursive=recursive)
|
||||||
|
|
||||||
|
for user, user_info in session.online_users.items():
|
||||||
|
if user != settings.username:
|
||||||
|
metadata = user_info.get('metadata')
|
||||||
|
|
||||||
|
if 'selected_objects' in metadata:
|
||||||
|
# Update selectionnable objects
|
||||||
|
for obj in bpy.data.objects:
|
||||||
|
if obj.hide_select and obj.uuid not in metadata['selected_objects']:
|
||||||
|
obj.hide_select = False
|
||||||
|
elif not obj.hide_select and obj.uuid in metadata['selected_objects']:
|
||||||
|
obj.hide_select = True
|
||||||
|
|
||||||
|
|
||||||
class Draw(Delayable):
|
class Draw(Delayable):
|
||||||
@ -160,37 +187,89 @@ class Draw(Delayable):
|
|||||||
bpy.types.SpaceView3D.draw_handler_remove(
|
bpy.types.SpaceView3D.draw_handler_remove(
|
||||||
self._handler, "WINDOW")
|
self._handler, "WINDOW")
|
||||||
except:
|
except:
|
||||||
logger.error("draw already unregistered")
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DrawClient(Draw):
|
class DrawClient(Draw):
|
||||||
def execute(self):
|
def execute(self):
|
||||||
repo = operators.client
|
session = getattr(operators, 'client', None)
|
||||||
if repo and presence.renderer:
|
renderer = getattr(presence, 'renderer', None)
|
||||||
settings = bpy.context.window_manager.session
|
|
||||||
client_list = [key for key in repo.list(filter=BlUser) if
|
|
||||||
key != settings.user_uuid]
|
|
||||||
|
|
||||||
for cli in client_list:
|
if session and renderer:
|
||||||
cli_ref = repo.get(uuid=cli)
|
settings = bpy.context.window_manager.session
|
||||||
if cli_ref.data.get('name'):
|
users = session.online_users
|
||||||
if settings.presence_show_selected:
|
|
||||||
presence.renderer.draw_client_selection(
|
for user in users.values():
|
||||||
cli_ref.data['name'], cli_ref.data['color'], cli_ref.data['selected_objects'])
|
metadata = user.get('metadata')
|
||||||
if settings.presence_show_user:
|
|
||||||
presence.renderer.draw_client_camera(
|
if settings.presence_show_selected and 'selected_objects' in metadata.keys():
|
||||||
cli_ref.data['name'], cli_ref.data['location'], cli_ref.data['color'])
|
renderer.draw_client_selection(
|
||||||
|
user['id'], metadata['color'], metadata['selected_objects'])
|
||||||
|
if settings.presence_show_user and 'view_corners' in metadata:
|
||||||
|
renderer.draw_client_camera(
|
||||||
|
user['id'], metadata['view_corners'], metadata['color'])
|
||||||
|
|
||||||
|
|
||||||
class ClientUpdate(Timer):
|
class ClientUpdate(Timer):
|
||||||
def __init__(self, timout=1, client_uuid=None):
|
def __init__(self, timout=1):
|
||||||
assert(client_uuid)
|
|
||||||
self._client_uuid = client_uuid
|
|
||||||
super().__init__(timout)
|
super().__init__(timout)
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
if self._client_uuid and operators.client:
|
settings = bpy.context.window_manager.session
|
||||||
client = operators.client.get(uuid=self._client_uuid)
|
session_info = bpy.context.window_manager.session
|
||||||
|
session = getattr(operators, 'client', None)
|
||||||
|
renderer = getattr(presence, 'renderer', None)
|
||||||
|
|
||||||
if client:
|
if session and renderer:
|
||||||
client.pointer.update_location()
|
# Check if session has been closes prematurely
|
||||||
|
if session.state == 0:
|
||||||
|
bpy.ops.session.stop()
|
||||||
|
|
||||||
|
local_user = operators.client.online_users.get(
|
||||||
|
session_info.username)
|
||||||
|
if not local_user:
|
||||||
|
return
|
||||||
|
|
||||||
|
local_user_metadata = local_user.get('metadata')
|
||||||
|
current_view_corners = presence.get_view_corners()
|
||||||
|
|
||||||
|
if not local_user_metadata:
|
||||||
|
logger.info("init user metadata")
|
||||||
|
metadata = {
|
||||||
|
'view_corners': current_view_corners,
|
||||||
|
'view_matrix': presence.get_view_matrix(),
|
||||||
|
'color': (settings.client_color.r,
|
||||||
|
settings.client_color.g,
|
||||||
|
settings.client_color.b,
|
||||||
|
1),
|
||||||
|
'frame_current':bpy.context.scene.frame_current
|
||||||
|
}
|
||||||
|
session.update_user_metadata(metadata)
|
||||||
|
elif current_view_corners != local_user_metadata['view_corners']:
|
||||||
|
logger.info('update user metadata')
|
||||||
|
local_user_metadata['view_corners'] = current_view_corners
|
||||||
|
local_user_metadata['view_matrix'] = presence.get_view_matrix()
|
||||||
|
session.update_user_metadata(local_user_metadata)
|
||||||
|
|
||||||
|
# sync online users
|
||||||
|
session_users = operators.client.online_users
|
||||||
|
ui_users = bpy.context.window_manager.online_users
|
||||||
|
|
||||||
|
for index, user in enumerate(ui_users):
|
||||||
|
if user.username not in session_users.keys():
|
||||||
|
ui_users.remove(index)
|
||||||
|
|
||||||
|
renderer.flush_selection()
|
||||||
|
renderer.flush_users()
|
||||||
|
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
for user in session_users:
|
||||||
|
if user not in ui_users:
|
||||||
|
new_key = ui_users.add()
|
||||||
|
new_key.name = user
|
||||||
|
new_key.username = user
|
||||||
|
|
||||||
|
# TODO: event drivent 3d view refresh
|
||||||
|
presence.refresh_3d_view()
|
||||||
|
@ -6,7 +6,7 @@ import sys
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.setLevel(logging.ERROR)
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
CONFIG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config")
|
CONFIG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config")
|
||||||
CONFIG = os.path.join(CONFIG_DIR, "app.yaml")
|
CONFIG = os.path.join(CONFIG_DIR, "app.yaml")
|
||||||
|
@ -92,11 +92,13 @@ class Dumper:
|
|||||||
|
|
||||||
def _build_inline_dump_functions(self):
|
def _build_inline_dump_functions(self):
|
||||||
self._dump_identity = (lambda x, depth: x, lambda x, depth: x)
|
self._dump_identity = (lambda x, depth: x, lambda x, depth: x)
|
||||||
|
self._dump_ref = (lambda x, depth: x.name, self._dump_object_as_branch)
|
||||||
self._dump_ID = (lambda x, depth: x.name, self._dump_default_as_branch)
|
self._dump_ID = (lambda x, depth: x.name, self._dump_default_as_branch)
|
||||||
self._dump_collection = (self._dump_default_as_leaf, self._dump_collection_as_branch)
|
self._dump_collection = (self._dump_default_as_leaf, self._dump_collection_as_branch)
|
||||||
self._dump_array = (self._dump_default_as_leaf, self._dump_array_as_branch)
|
self._dump_array = (self._dump_default_as_leaf, self._dump_array_as_branch)
|
||||||
self._dump_matrix = (self._dump_matrix_as_leaf, self._dump_matrix_as_leaf)
|
self._dump_matrix = (self._dump_matrix_as_leaf, self._dump_matrix_as_leaf)
|
||||||
self._dump_vector = (self._dump_vector_as_leaf, self._dump_vector_as_leaf)
|
self._dump_vector = (self._dump_vector_as_leaf, self._dump_vector_as_leaf)
|
||||||
|
self._dump_quaternion = (self._dump_quaternion_as_leaf, self._dump_quaternion_as_leaf)
|
||||||
self._dump_default = (self._dump_default_as_leaf, self._dump_default_as_branch)
|
self._dump_default = (self._dump_default_as_leaf, self._dump_default_as_branch)
|
||||||
self._dump_color = (self._dump_color_as_leaf, self._dump_color_as_leaf)
|
self._dump_color = (self._dump_color_as_leaf, self._dump_color_as_leaf)
|
||||||
|
|
||||||
@ -105,11 +107,13 @@ class Dumper:
|
|||||||
self._match_type_int = (_dump_filter_type(int), self._dump_identity)
|
self._match_type_int = (_dump_filter_type(int), self._dump_identity)
|
||||||
self._match_type_float = (_dump_filter_type(float), self._dump_identity)
|
self._match_type_float = (_dump_filter_type(float), self._dump_identity)
|
||||||
self._match_type_string = (_dump_filter_type(str), self._dump_identity)
|
self._match_type_string = (_dump_filter_type(str), self._dump_identity)
|
||||||
|
self._match_type_ref = (_dump_filter_type(T.Object), self._dump_ref)
|
||||||
self._match_type_ID = (_dump_filter_type(T.ID), self._dump_ID)
|
self._match_type_ID = (_dump_filter_type(T.ID), self._dump_ID)
|
||||||
self._match_type_bpy_prop_collection = (_dump_filter_type(T.bpy_prop_collection), self._dump_collection)
|
self._match_type_bpy_prop_collection = (_dump_filter_type(T.bpy_prop_collection), self._dump_collection)
|
||||||
self._match_type_array = (_dump_filter_array, self._dump_array)
|
self._match_type_array = (_dump_filter_array, self._dump_array)
|
||||||
self._match_type_matrix = (_dump_filter_type(mathutils.Matrix), self._dump_matrix)
|
self._match_type_matrix = (_dump_filter_type(mathutils.Matrix), self._dump_matrix)
|
||||||
self._match_type_vector = (_dump_filter_type(mathutils.Vector), self._dump_vector)
|
self._match_type_vector = (_dump_filter_type(mathutils.Vector), self._dump_vector)
|
||||||
|
self._match_type_quaternion = (_dump_filter_type(mathutils.Quaternion), self._dump_quaternion)
|
||||||
self._match_type_color = (_dump_filter_type_by_name("Color"), self._dump_color)
|
self._match_type_color = (_dump_filter_type_by_name("Color"), self._dump_color)
|
||||||
self._match_default = (_dump_filter_default, self._dump_default)
|
self._match_default = (_dump_filter_default, self._dump_default)
|
||||||
|
|
||||||
@ -136,9 +140,18 @@ class Dumper:
|
|||||||
def _dump_vector_as_leaf(self, vector, depth):
|
def _dump_vector_as_leaf(self, vector, depth):
|
||||||
return list(vector)
|
return list(vector)
|
||||||
|
|
||||||
|
def _dump_quaternion_as_leaf(self, quaternion, depth):
|
||||||
|
return list(quaternion)
|
||||||
|
|
||||||
def _dump_color_as_leaf(self, color, depth):
|
def _dump_color_as_leaf(self, color, depth):
|
||||||
return list(color)
|
return list(color)
|
||||||
|
|
||||||
|
def _dump_object_as_branch(self, default, depth):
|
||||||
|
if depth == 1:
|
||||||
|
return self._dump_default_as_branch(default, depth)
|
||||||
|
else:
|
||||||
|
return default.name
|
||||||
|
|
||||||
def _dump_default_as_branch(self, default, depth):
|
def _dump_default_as_branch(self, default, depth):
|
||||||
def is_valid_property(p):
|
def is_valid_property(p):
|
||||||
try:
|
try:
|
||||||
@ -173,12 +186,13 @@ class Dumper:
|
|||||||
self._match_type_int,
|
self._match_type_int,
|
||||||
self._match_type_float,
|
self._match_type_float,
|
||||||
self._match_type_string,
|
self._match_type_string,
|
||||||
|
self._match_type_ref,
|
||||||
self._match_type_ID,
|
self._match_type_ID,
|
||||||
self._match_type_bpy_prop_collection,
|
self._match_type_bpy_prop_collection,
|
||||||
self._match_type_array,
|
self._match_type_array,
|
||||||
self._match_type_matrix,
|
self._match_type_matrix,
|
||||||
self._match_type_vector,
|
self._match_type_vector,
|
||||||
self._match_type_color,
|
self._match_type_quaternion,
|
||||||
self._match_type_color,
|
self._match_type_color,
|
||||||
self._match_default
|
self._match_default
|
||||||
]
|
]
|
||||||
@ -307,6 +321,9 @@ class Loader:
|
|||||||
def _load_vector(self, vector, dump):
|
def _load_vector(self, vector, dump):
|
||||||
vector.write(mathutils.Vector(dump))
|
vector.write(mathutils.Vector(dump))
|
||||||
|
|
||||||
|
def _load_quaternion(self, quaternion, dump):
|
||||||
|
quaternion.write(mathutils.Quaternion(dump))
|
||||||
|
|
||||||
def _ordered_keys(self, keys):
|
def _ordered_keys(self, keys):
|
||||||
ordered_keys = []
|
ordered_keys = []
|
||||||
for order_element in self.order:
|
for order_element in self.order:
|
||||||
@ -336,6 +353,7 @@ class Loader:
|
|||||||
(_load_filter_type(T.IntProperty), self._load_identity),
|
(_load_filter_type(T.IntProperty), self._load_identity),
|
||||||
(_load_filter_type(mathutils.Matrix, use_bl_rna=False), self._load_matrix), # before float because bl_rna type of matrix if FloatProperty
|
(_load_filter_type(mathutils.Matrix, use_bl_rna=False), self._load_matrix), # before float because bl_rna type of matrix if FloatProperty
|
||||||
(_load_filter_type(mathutils.Vector, use_bl_rna=False), self._load_vector), # before float because bl_rna type of vector if FloatProperty
|
(_load_filter_type(mathutils.Vector, use_bl_rna=False), self._load_vector), # before float because bl_rna type of vector if FloatProperty
|
||||||
|
(_load_filter_type(mathutils.Quaternion, use_bl_rna=False), self._load_quaternion),
|
||||||
(_load_filter_type(T.FloatProperty), self._load_identity),
|
(_load_filter_type(T.FloatProperty), self._load_identity),
|
||||||
(_load_filter_type(T.StringProperty), self._load_identity),
|
(_load_filter_type(T.StringProperty), self._load_identity),
|
||||||
(_load_filter_type(T.EnumProperty), self._load_identity),
|
(_load_filter_type(T.EnumProperty), self._load_identity),
|
||||||
|
Submodule multi_user/libs/replication updated: de24d35afe...c31917941c
@ -8,7 +8,6 @@ import subprocess
|
|||||||
import time
|
import time
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import msgpack
|
import msgpack
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
@ -23,34 +22,25 @@ from .libs.replication.replication.interface import Session
|
|||||||
from .libs.replication.replication.constants import (
|
from .libs.replication.replication.constants import (
|
||||||
STATE_ACTIVE,
|
STATE_ACTIVE,
|
||||||
STATE_INITIAL,
|
STATE_INITIAL,
|
||||||
STATE_SYNCING)
|
STATE_SYNCING,
|
||||||
|
FETCHED)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.setLevel(logging.ERROR)
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
client = None
|
client = None
|
||||||
delayables = []
|
delayables = []
|
||||||
ui_context = None
|
ui_context = None
|
||||||
|
stop_modal_executor = False
|
||||||
|
modal_executor_queue = None
|
||||||
def init_supported_datablocks(supported_types_id):
|
|
||||||
global client
|
|
||||||
|
|
||||||
for type_id in supported_types_id:
|
|
||||||
if hasattr(bpy.data, type_id):
|
|
||||||
for item in getattr(bpy.data, type_id):
|
|
||||||
if client.exist(uuid=item.uuid):
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
client.add(item)
|
|
||||||
|
|
||||||
|
|
||||||
# OPERATORS
|
# OPERATORS
|
||||||
|
|
||||||
|
|
||||||
class SessionStartOperator(bpy.types.Operator):
|
class SessionStartOperator(bpy.types.Operator):
|
||||||
bl_idname = "session.start"
|
bl_idname = "session.start"
|
||||||
bl_label = "start"
|
bl_label = "start"
|
||||||
bl_description = "connect to a net server"
|
bl_description = "connect to a net server"
|
||||||
bl_options = {"REGISTER"}
|
|
||||||
|
|
||||||
host: bpy.props.BoolProperty(default=False)
|
host: bpy.props.BoolProperty(default=False)
|
||||||
|
|
||||||
@ -59,32 +49,41 @@ class SessionStartOperator(bpy.types.Operator):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
global client, delayables
|
global client, delayables, ui_context
|
||||||
settings = context.window_manager.session
|
settings = context.window_manager.session
|
||||||
|
users = bpy.data.window_managers['WinMan'].online_users
|
||||||
|
|
||||||
|
# TODO: Sync server clients
|
||||||
|
users.clear()
|
||||||
|
|
||||||
# save config
|
# save config
|
||||||
settings.save(context)
|
settings.save(context)
|
||||||
|
|
||||||
bpy_factory = ReplicatedDataFactory()
|
bpy_factory = ReplicatedDataFactory()
|
||||||
supported_bl_types = []
|
supported_bl_types = []
|
||||||
|
ui_context = context.copy()
|
||||||
|
|
||||||
# init the factory with supported types
|
# init the factory with supported types
|
||||||
for type in bl_types.types_to_register():
|
for type in bl_types.types_to_register():
|
||||||
_type = getattr(bl_types, type)
|
type_module = getattr(bl_types, type)
|
||||||
supported_bl_types.append(_type.bl_id)
|
type_impl_name = "Bl{}".format(type.split('_')[1].capitalize())
|
||||||
|
type_module_class = getattr(type_module, type_impl_name)
|
||||||
|
|
||||||
|
supported_bl_types.append(type_module_class.bl_id)
|
||||||
|
|
||||||
# Retreive local replicated types settings
|
# Retreive local replicated types settings
|
||||||
type_local_config = settings.supported_datablock[_type.bl_rep_class.__name__]
|
type_local_config = settings.supported_datablock[type_impl_name]
|
||||||
|
|
||||||
bpy_factory.register_type(
|
bpy_factory.register_type(
|
||||||
_type.bl_class,
|
type_module_class.bl_class,
|
||||||
_type.bl_rep_class,
|
type_module_class,
|
||||||
timer=type_local_config.bl_delay_refresh,
|
timer=type_local_config.bl_delay_refresh,
|
||||||
automatic=type_local_config.auto_push)
|
automatic=type_local_config.auto_push)
|
||||||
|
|
||||||
if type_local_config.bl_delay_apply > 0:
|
if type_local_config.bl_delay_apply > 0:
|
||||||
delayables.append(delayable.ApplyTimer(
|
delayables.append(delayable.ApplyTimer(
|
||||||
timout=type_local_config.bl_delay_apply,
|
timout=type_local_config.bl_delay_apply,
|
||||||
target_type=_type.bl_rep_class))
|
target_type=type_module_class))
|
||||||
|
|
||||||
client = Session(factory=bpy_factory)
|
client = Session(factory=bpy_factory)
|
||||||
|
|
||||||
@ -118,30 +117,15 @@ class SessionStartOperator(bpy.types.Operator):
|
|||||||
"A session is already hosted on this address")
|
"A session is already hosted on this address")
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
if self.host:
|
||||||
|
|
||||||
# Init user settings
|
|
||||||
usr = presence.User(
|
|
||||||
username=settings.username,
|
|
||||||
color=(settings.client_color.r,
|
|
||||||
settings.client_color.g,
|
|
||||||
settings.client_color.b,
|
|
||||||
1),
|
|
||||||
)
|
|
||||||
|
|
||||||
settings.user_uuid = client.add(usr,owner=settings.username)
|
|
||||||
client.commit(settings.user_uuid)
|
|
||||||
|
|
||||||
if settings.init_scene and self.host:
|
|
||||||
for scene in bpy.data.scenes:
|
for scene in bpy.data.scenes:
|
||||||
scene_uuid = client.add(scene)
|
scene_uuid = client.add(scene)
|
||||||
|
|
||||||
# for node in client.list():
|
# for node in client.list():
|
||||||
client.commit(scene_uuid)
|
client.commit(scene_uuid)
|
||||||
delayables.append(delayable.ClientUpdate(
|
|
||||||
client_uuid=settings.user_uuid))
|
|
||||||
delayables.append(delayable.DrawClient())
|
|
||||||
|
|
||||||
|
delayables.append(delayable.ClientUpdate())
|
||||||
|
delayables.append(delayable.DrawClient())
|
||||||
delayables.append(delayable.DynamicRightSelectTimer())
|
delayables.append(delayable.DynamicRightSelectTimer())
|
||||||
|
|
||||||
# Push all added values
|
# Push all added values
|
||||||
@ -155,6 +139,10 @@ class SessionStartOperator(bpy.types.Operator):
|
|||||||
for d in delayables:
|
for d in delayables:
|
||||||
d.register()
|
d.register()
|
||||||
|
|
||||||
|
global modal_executor_queue
|
||||||
|
modal_executor_queue = queue.Queue()
|
||||||
|
bpy.ops.session.apply_armature_operator()
|
||||||
|
|
||||||
self.report(
|
self.report(
|
||||||
{'INFO'},
|
{'INFO'},
|
||||||
"connexion on tcp://{}:{}".format(settings.ip, settings.port))
|
"connexion on tcp://{}:{}".format(settings.ip, settings.port))
|
||||||
@ -172,12 +160,13 @@ class SessionStopOperator(bpy.types.Operator):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
global client, delayables
|
global client, delayables, stop_modal_executor
|
||||||
|
|
||||||
|
stop_modal_executor = True
|
||||||
settings = context.window_manager.session
|
settings = context.window_manager.session
|
||||||
settings.is_admin = False
|
settings.is_admin = False
|
||||||
assert(client)
|
assert(client)
|
||||||
|
|
||||||
client.remove(settings.user_uuid)
|
|
||||||
client.disconnect()
|
client.disconnect()
|
||||||
|
|
||||||
for d in delayables:
|
for d in delayables:
|
||||||
@ -264,6 +253,14 @@ class SessionSnapUserOperator(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
wm = context.window_manager
|
wm = context.window_manager
|
||||||
|
settings = context.window_manager.session
|
||||||
|
|
||||||
|
if settings.time_snap_running:
|
||||||
|
settings.time_snap_running = False
|
||||||
|
return {'CANCELLED'}
|
||||||
|
else:
|
||||||
|
settings.time_snap_running = True
|
||||||
|
|
||||||
self._timer = wm.event_timer_add(0.1, window=context.window)
|
self._timer = wm.event_timer_add(0.1, window=context.window)
|
||||||
wm.modal_handler_add(self)
|
wm.modal_handler_add(self)
|
||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
||||||
@ -273,7 +270,9 @@ class SessionSnapUserOperator(bpy.types.Operator):
|
|||||||
wm.event_timer_remove(self._timer)
|
wm.event_timer_remove(self._timer)
|
||||||
|
|
||||||
def modal(self, context, event):
|
def modal(self, context, event):
|
||||||
if event.type in {'RIGHTMOUSE', 'ESC'}:
|
is_running = context.window_manager.session.time_snap_running
|
||||||
|
|
||||||
|
if event.type in {'RIGHTMOUSE', 'ESC'} or not is_running:
|
||||||
self.cancel(context)
|
self.cancel(context)
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
@ -281,9 +280,64 @@ class SessionSnapUserOperator(bpy.types.Operator):
|
|||||||
area, region, rv3d = presence.view3d_find()
|
area, region, rv3d = presence.view3d_find()
|
||||||
global client
|
global client
|
||||||
|
|
||||||
target_client = client.get(uuid=self.target_client)
|
if client:
|
||||||
if target_client:
|
target_ref = client.online_users.get(self.target_client)
|
||||||
rv3d.view_matrix = mathutils.Matrix(target_client.data['view_matrix'])
|
|
||||||
|
if target_ref:
|
||||||
|
rv3d.view_matrix = mathutils.Matrix(
|
||||||
|
target_ref['metadata']['view_matrix'])
|
||||||
|
else:
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
return {'PASS_THROUGH'}
|
||||||
|
|
||||||
|
|
||||||
|
class SessionSnapTimeOperator(bpy.types.Operator):
|
||||||
|
bl_idname = "session.snaptime"
|
||||||
|
bl_label = "snap to user time"
|
||||||
|
bl_description = "Snap time to selected user time's"
|
||||||
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
|
_timer = None
|
||||||
|
|
||||||
|
target_client: bpy.props.StringProperty(default="None")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
settings = context.window_manager.session
|
||||||
|
|
||||||
|
if settings.user_snap_running:
|
||||||
|
settings.user_snap_running = False
|
||||||
|
return {'CANCELLED'}
|
||||||
|
else:
|
||||||
|
settings.user_snap_running = True
|
||||||
|
|
||||||
|
wm = context.window_manager
|
||||||
|
self._timer = wm.event_timer_add(0.1, window=context.window)
|
||||||
|
wm.modal_handler_add(self)
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
def cancel(self, context):
|
||||||
|
wm = context.window_manager
|
||||||
|
wm.event_timer_remove(self._timer)
|
||||||
|
|
||||||
|
def modal(self, context, event):
|
||||||
|
is_running = context.window_manager.session.user_snap_running
|
||||||
|
if event.type in {'RIGHTMOUSE', 'ESC'} or not is_running:
|
||||||
|
self.cancel(context)
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
if event.type == 'TIMER':
|
||||||
|
global client
|
||||||
|
|
||||||
|
if client:
|
||||||
|
target_ref = client.online_users.get(self.target_client)
|
||||||
|
|
||||||
|
if target_ref:
|
||||||
|
context.scene.frame_current = target_ref['metadata']['frame_current']
|
||||||
else:
|
else:
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
@ -330,15 +384,65 @@ class SessionCommit(bpy.types.Operator):
|
|||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class ApplyArmatureOperator(bpy.types.Operator):
|
||||||
|
"""Operator which runs its self from a timer"""
|
||||||
|
bl_idname = "session.apply_armature_operator"
|
||||||
|
bl_label = "Modal Executor Operator"
|
||||||
|
|
||||||
|
_timer = None
|
||||||
|
|
||||||
|
def modal(self, context, event):
|
||||||
|
global stop_modal_executor, modal_executor_queue
|
||||||
|
if stop_modal_executor:
|
||||||
|
self.cancel(context)
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
if event.type == 'TIMER':
|
||||||
|
global client
|
||||||
|
nodes = client.list(filter=bl_types.bl_armature.BlArmature)
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
node_ref = client.get(uuid=node)
|
||||||
|
|
||||||
|
if node_ref.state == FETCHED:
|
||||||
|
try:
|
||||||
|
client.apply(node)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
"fail to apply {}: {}".format(node_ref.uuid, e))
|
||||||
|
|
||||||
|
return {'PASS_THROUGH'}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
wm = context.window_manager
|
||||||
|
self._timer = wm.event_timer_add(2, window=context.window)
|
||||||
|
wm.modal_handler_add(self)
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
def cancel(self, context):
|
||||||
|
global stop_modal_executor
|
||||||
|
|
||||||
|
wm = context.window_manager
|
||||||
|
wm.event_timer_remove(self._timer)
|
||||||
|
|
||||||
|
stop_modal_executor = False
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
SessionStartOperator,
|
SessionStartOperator,
|
||||||
SessionStopOperator,
|
SessionStopOperator,
|
||||||
SessionPropertyRemoveOperator,
|
SessionPropertyRemoveOperator,
|
||||||
SessionSnapUserOperator,
|
SessionSnapUserOperator,
|
||||||
|
SessionSnapTimeOperator,
|
||||||
SessionPropertyRightOperator,
|
SessionPropertyRightOperator,
|
||||||
SessionApply,
|
SessionApply,
|
||||||
SessionCommit,
|
SessionCommit,
|
||||||
|
ApplyArmatureOperator,
|
||||||
|
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@persistent
|
@persistent
|
||||||
def load_pre_handler(dummy):
|
def load_pre_handler(dummy):
|
||||||
global client
|
global client
|
||||||
@ -347,6 +451,29 @@ def load_pre_handler(dummy):
|
|||||||
bpy.ops.session.stop()
|
bpy.ops.session.stop()
|
||||||
|
|
||||||
|
|
||||||
|
@persistent
|
||||||
|
def sanitize_deps_graph(dummy):
|
||||||
|
"""sanitize deps graph
|
||||||
|
|
||||||
|
Temporary solution to resolve each node pointers after a Undo.
|
||||||
|
A future solution should be to avoid storing dataclock reference...
|
||||||
|
|
||||||
|
"""
|
||||||
|
global client
|
||||||
|
|
||||||
|
if client and client.state in [STATE_ACTIVE]:
|
||||||
|
for node_key in client.list():
|
||||||
|
client.get(node_key).resolve()
|
||||||
|
|
||||||
|
|
||||||
|
@persistent
|
||||||
|
def update_client_frame(scene):
|
||||||
|
if client and client.state == STATE_ACTIVE:
|
||||||
|
client.update_user_metadata({
|
||||||
|
'frame_current': scene.frame_current
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
from bpy.utils import register_class
|
from bpy.utils import register_class
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
@ -354,6 +481,12 @@ def register():
|
|||||||
|
|
||||||
bpy.app.handlers.load_pre.append(load_pre_handler)
|
bpy.app.handlers.load_pre.append(load_pre_handler)
|
||||||
|
|
||||||
|
bpy.app.handlers.undo_post.append(sanitize_deps_graph)
|
||||||
|
bpy.app.handlers.redo_post.append(sanitize_deps_graph)
|
||||||
|
|
||||||
|
bpy.app.handlers.frame_change_pre.append(update_client_frame)
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
global client
|
global client
|
||||||
|
|
||||||
@ -367,5 +500,11 @@ def unregister():
|
|||||||
|
|
||||||
bpy.app.handlers.load_pre.remove(load_pre_handler)
|
bpy.app.handlers.load_pre.remove(load_pre_handler)
|
||||||
|
|
||||||
|
bpy.app.handlers.undo_post.remove(sanitize_deps_graph)
|
||||||
|
bpy.app.handlers.redo_post.remove(sanitize_deps_graph)
|
||||||
|
|
||||||
|
bpy.app.handlers.frame_change_pre.remove(update_client_frame)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
register()
|
register()
|
||||||
|
@ -31,8 +31,8 @@ def view3d_find():
|
|||||||
|
|
||||||
def refresh_3d_view():
|
def refresh_3d_view():
|
||||||
area, region, rv3d = view3d_find()
|
area, region, rv3d = view3d_find()
|
||||||
|
if area and region and rv3d:
|
||||||
area.tag_redraw()
|
area.tag_redraw()
|
||||||
|
|
||||||
|
|
||||||
def get_target(region, rv3d, coord):
|
def get_target(region, rv3d, coord):
|
||||||
@ -69,7 +69,7 @@ def get_default_bbox(obj, radius):
|
|||||||
return [(point.x, point.y, point.z)
|
return [(point.x, point.y, point.z)
|
||||||
for point in bbox_corners]
|
for point in bbox_corners]
|
||||||
|
|
||||||
def get_client_cam_points():
|
def get_view_corners():
|
||||||
area, region, rv3d = view3d_find()
|
area, region, rv3d = view3d_find()
|
||||||
|
|
||||||
v1 = [0, 0, 0]
|
v1 = [0, 0, 0]
|
||||||
@ -78,6 +78,7 @@ def get_client_cam_points():
|
|||||||
v4 = [0, 0, 0]
|
v4 = [0, 0, 0]
|
||||||
v5 = [0, 0, 0]
|
v5 = [0, 0, 0]
|
||||||
v6 = [0, 0, 0]
|
v6 = [0, 0, 0]
|
||||||
|
v7 = [0, 0, 0]
|
||||||
|
|
||||||
if area and region and rv3d:
|
if area and region and rv3d:
|
||||||
width = region.width
|
width = region.width
|
||||||
@ -112,31 +113,14 @@ def get_bb_coords_from_obj(object, parent=None):
|
|||||||
return [(point.x, point.y, point.z)
|
return [(point.x, point.y, point.z)
|
||||||
for point in bbox_corners]
|
for point in bbox_corners]
|
||||||
|
|
||||||
class User():
|
|
||||||
def __init__(self, username=None, color=(0, 0, 0, 1)):
|
|
||||||
self.is_dirty = False
|
|
||||||
self.name = username
|
|
||||||
self.color = color
|
|
||||||
self.location = [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
|
|
||||||
self.selected_objects = []
|
|
||||||
self.last_select_objects = []
|
|
||||||
self.view_matrix = None
|
|
||||||
|
|
||||||
def update_location(self):
|
def get_view_matrix():
|
||||||
current_coords = get_client_cam_points()
|
area, region, rv3d = view3d_find()
|
||||||
area, region, rv3d = view3d_find()
|
|
||||||
|
|
||||||
current_coords = list(get_client_cam_points())
|
|
||||||
if current_coords and self.location != current_coords:
|
|
||||||
self.location = current_coords
|
|
||||||
|
|
||||||
|
if area and region and rv3d:
|
||||||
matrix_dumper = utils.dump_anything.Dumper()
|
matrix_dumper = utils.dump_anything.Dumper()
|
||||||
current_vm = matrix_dumper.dump(rv3d.view_matrix)
|
|
||||||
if self.view_matrix != current_vm:
|
|
||||||
self.view_matrix = current_vm
|
|
||||||
|
|
||||||
def update_selected_objects(self, context):
|
return matrix_dumper.dump(rv3d.view_matrix)
|
||||||
self.selected_objects = utils.get_selected_objects(context.scene)
|
|
||||||
|
|
||||||
def update_presence(self, context):
|
def update_presence(self, context):
|
||||||
global renderer
|
global renderer
|
||||||
@ -171,8 +155,12 @@ class DrawFactory(object):
|
|||||||
self.register_handlers()
|
self.register_handlers()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
self.flush_users()
|
||||||
|
self.flush_selection()
|
||||||
self.unregister_handlers()
|
self.unregister_handlers()
|
||||||
|
|
||||||
|
refresh_3d_view()
|
||||||
|
|
||||||
def register_handlers(self):
|
def register_handlers(self):
|
||||||
self.draw3d_handle = bpy.types.SpaceView3D.draw_handler_add(
|
self.draw3d_handle = bpy.types.SpaceView3D.draw_handler_add(
|
||||||
self.draw3d_callback, (), 'WINDOW', 'POST_VIEW')
|
self.draw3d_callback, (), 'WINDOW', 'POST_VIEW')
|
||||||
@ -215,14 +203,14 @@ class DrawFactory(object):
|
|||||||
|
|
||||||
self.d2d_items.clear()
|
self.d2d_items.clear()
|
||||||
|
|
||||||
def draw_client_selection(self, client_uuid, client_color, client_selection):
|
def draw_client_selection(self, client_id, client_color, client_selection):
|
||||||
local_user = bpy.context.window_manager.session.user_uuid
|
local_user = bpy.context.window_manager.session.username
|
||||||
|
|
||||||
if local_user != client_uuid:
|
if local_user != client_id:
|
||||||
self.flush_selection(client_uuid)
|
self.flush_selection(client_id)
|
||||||
|
|
||||||
for select_ob in client_selection:
|
for select_ob in client_selection:
|
||||||
drawable_key = "{}_select_{}".format(client_uuid, select_ob)
|
drawable_key = "{}_select_{}".format(client_id, select_ob)
|
||||||
|
|
||||||
ob = utils.find_from_attr("uuid", select_ob, bpy.data.objects)
|
ob = utils.find_from_attr("uuid", select_ob, bpy.data.objects)
|
||||||
if not ob:
|
if not ob:
|
||||||
@ -271,11 +259,11 @@ class DrawFactory(object):
|
|||||||
|
|
||||||
self.d3d_items[key] = (shader, batch, color)
|
self.d3d_items[key] = (shader, batch, color)
|
||||||
|
|
||||||
def draw_client_camera(self, client_uuid, client_location, client_color):
|
def draw_client_camera(self, client_id, client_location, client_color):
|
||||||
if client_location:
|
if client_location:
|
||||||
local_user = bpy.context.window_manager.session.user_uuid
|
local_user = bpy.context.window_manager.session.username
|
||||||
|
|
||||||
if local_user != client_uuid:
|
if local_user != client_id:
|
||||||
try:
|
try:
|
||||||
indices = (
|
indices = (
|
||||||
(1, 3), (2, 1), (3, 0),
|
(1, 3), (2, 1), (3, 0),
|
||||||
@ -290,8 +278,8 @@ class DrawFactory(object):
|
|||||||
batch = batch_for_shader(
|
batch = batch_for_shader(
|
||||||
shader, 'LINES', {"pos": position}, indices=indices)
|
shader, 'LINES', {"pos": position}, indices=indices)
|
||||||
|
|
||||||
self.d3d_items[client_uuid] = (shader, batch, color)
|
self.d3d_items[client_id] = (shader, batch, color)
|
||||||
self.d2d_items[client_uuid] = (position[1], client_uuid, color)
|
self.d2d_items[client_id] = (position[1], client_id, color)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Draw client exception {}".format(e))
|
logger.error("Draw client exception {}".format(e))
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from . import operators
|
from . import operators
|
||||||
from .bl_types.bl_user import BlUser
|
|
||||||
from .libs.replication.replication.constants import (ADDED, ERROR, FETCHED,
|
from .libs.replication.replication.constants import (ADDED, ERROR, FETCHED,
|
||||||
MODIFIED, RP_COMMON, UP)
|
MODIFIED, RP_COMMON, UP)
|
||||||
|
|
||||||
@ -167,7 +166,7 @@ class SESSION_PT_settings_replication(bpy.types.Panel):
|
|||||||
|
|
||||||
class SESSION_PT_user(bpy.types.Panel):
|
class SESSION_PT_user(bpy.types.Panel):
|
||||||
bl_idname = "MULTIUSER_USER_PT_panel"
|
bl_idname = "MULTIUSER_USER_PT_panel"
|
||||||
bl_label = "Users"
|
bl_label = "Online users"
|
||||||
bl_space_type = 'VIEW_3D'
|
bl_space_type = 'VIEW_3D'
|
||||||
bl_region_type = 'UI'
|
bl_region_type = 'UI'
|
||||||
bl_category = "Multiuser"
|
bl_category = "Multiuser"
|
||||||
@ -179,44 +178,58 @@ class SESSION_PT_user(bpy.types.Panel):
|
|||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
online_users = context.window_manager.online_users
|
||||||
|
selected_user = context.window_manager.user_index
|
||||||
settings = context.window_manager.session
|
settings = context.window_manager.session
|
||||||
|
active_user = online_users[selected_user] if len(online_users)-1>=selected_user else 0
|
||||||
|
|
||||||
|
|
||||||
# Create a simple row.
|
# Create a simple row.
|
||||||
col = layout.column(align=True)
|
row = layout.row()
|
||||||
|
box = row.box()
|
||||||
client_keys = operators.client.list(filter=BlUser)
|
split = box.split(factor=0.5)
|
||||||
if client_keys and len(client_keys) > 0:
|
split.label(text="user")
|
||||||
for key in client_keys:
|
split.label(text="frame")
|
||||||
area_msg = col.row(align=True)
|
split.label(text="ping")
|
||||||
item_box = area_msg.box()
|
|
||||||
client = operators.client.get(uuid=key).data
|
|
||||||
|
|
||||||
info = ""
|
|
||||||
|
|
||||||
detail_item_row = item_box.row(align=True)
|
|
||||||
|
|
||||||
if client.get('name'):
|
|
||||||
username = client['name']
|
|
||||||
|
|
||||||
is_local_user = username == settings.username
|
|
||||||
|
|
||||||
if is_local_user:
|
|
||||||
info = "(self)"
|
|
||||||
|
|
||||||
detail_item_row.label(
|
|
||||||
text="{} {}".format(username, info))
|
|
||||||
|
|
||||||
if not is_local_user:
|
|
||||||
detail_item_row.operator(
|
|
||||||
"session.snapview",
|
|
||||||
text="",
|
|
||||||
icon='VIEW_CAMERA').target_client = key
|
|
||||||
row = layout.row()
|
|
||||||
else:
|
|
||||||
row.label(text="Empty")
|
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
|
layout.template_list("SESSION_UL_users", "", context.window_manager, "online_users", context.window_manager, "user_index")
|
||||||
|
|
||||||
|
if active_user != 0 and active_user.username != settings.username:
|
||||||
|
row = layout.row()
|
||||||
|
user_operations = row.split()
|
||||||
|
user_operations.alert = context.window_manager.session.time_snap_running
|
||||||
|
user_operations.operator(
|
||||||
|
"session.snapview",
|
||||||
|
text="",
|
||||||
|
icon='VIEW_CAMERA').target_client = active_user.username
|
||||||
|
|
||||||
|
user_operations.alert = context.window_manager.session.user_snap_running
|
||||||
|
user_operations.operator(
|
||||||
|
"session.snaptime",
|
||||||
|
text="",
|
||||||
|
icon='TIME').target_client = active_user.username
|
||||||
|
|
||||||
|
|
||||||
|
class SESSION_UL_users(bpy.types.UIList):
|
||||||
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag):
|
||||||
|
session = operators.client
|
||||||
|
settings = context.window_manager.session
|
||||||
|
is_local_user = item.username == settings.username
|
||||||
|
ping = '-'
|
||||||
|
frame_current = '-'
|
||||||
|
if session:
|
||||||
|
user = session.online_users.get(item.username)
|
||||||
|
if user:
|
||||||
|
ping = str(user['latency'])
|
||||||
|
metadata = user.get('metadata')
|
||||||
|
if metadata:
|
||||||
|
frame_current = str(metadata['frame_current'])
|
||||||
|
|
||||||
|
split = layout.split(factor=0.5)
|
||||||
|
split.label(text=item.username)
|
||||||
|
split.label(text=frame_current)
|
||||||
|
split.label(text=ping)
|
||||||
|
|
||||||
|
|
||||||
class SESSION_PT_presence(bpy.types.Panel):
|
class SESSION_PT_presence(bpy.types.Panel):
|
||||||
@ -250,7 +263,7 @@ def draw_property(context, parent, property_uuid, level=0):
|
|||||||
settings = context.window_manager.session
|
settings = context.window_manager.session
|
||||||
item = operators.client.get(uuid=property_uuid)
|
item = operators.client.get(uuid=property_uuid)
|
||||||
|
|
||||||
if item.str_type == 'BlUser' or item.state == ERROR:
|
if item.state == ERROR:
|
||||||
return
|
return
|
||||||
|
|
||||||
area_msg = parent.row(align=True)
|
area_msg = parent.row(align=True)
|
||||||
@ -365,6 +378,7 @@ class SESSION_PT_outliner(bpy.types.Panel):
|
|||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
|
SESSION_UL_users,
|
||||||
SESSION_PT_settings,
|
SESSION_PT_settings,
|
||||||
SESSION_PT_settings_user,
|
SESSION_PT_settings_user,
|
||||||
SESSION_PT_settings_network,
|
SESSION_PT_settings_network,
|
||||||
|
@ -5,6 +5,7 @@ import random
|
|||||||
import string
|
import string
|
||||||
import sys
|
import sys
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
from collections.abc import Iterable
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import mathutils
|
import mathutils
|
||||||
@ -13,7 +14,18 @@ from . import environment, presence
|
|||||||
from .libs import dump_anything
|
from .libs import dump_anything
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.setLevel(logging.ERROR)
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
def has_action(target):
|
||||||
|
return (hasattr(target, 'animation_data')
|
||||||
|
and target.animation_data
|
||||||
|
and target.animation_data.action)
|
||||||
|
|
||||||
|
|
||||||
|
def has_driver(target):
|
||||||
|
return (hasattr(target, 'animation_data')
|
||||||
|
and target.animation_data
|
||||||
|
and target.animation_data.drivers)
|
||||||
|
|
||||||
|
|
||||||
def find_from_attr(attr_name, attr_value, list):
|
def find_from_attr(attr_name, attr_value, list):
|
||||||
@ -45,7 +57,7 @@ def get_datablock_users(datablock):
|
|||||||
def random_string_digits(stringLength=6):
|
def random_string_digits(stringLength=6):
|
||||||
"""Generate a random string of letters and digits """
|
"""Generate a random string of letters and digits """
|
||||||
lettersAndDigits = string.ascii_letters + string.digits
|
lettersAndDigits = string.ascii_letters + string.digits
|
||||||
return ''.join(random.choice(lettersAndDigits) for i in range(stringLength))
|
return ''.join(random.choices(lettersAndDigits, k=stringLength))
|
||||||
|
|
||||||
|
|
||||||
def clean_scene():
|
def clean_scene():
|
||||||
@ -139,3 +151,13 @@ def dump_datablock_attibutes(datablock=None, attributes=[], depth=1, dickt=None)
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_from_id(id, optionnal_type=None):
|
||||||
|
for category in dir(bpy.data):
|
||||||
|
root = getattr(bpy.data, category)
|
||||||
|
if isinstance(root, Iterable):
|
||||||
|
if id in root and ((optionnal_type is None) or (optionnal_type.lower() in root[id].__class__.__name__.lower())):
|
||||||
|
return root[id]
|
||||||
|
return None
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user