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.setLevel(logging.ERROR)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
#TODO: refactor config
|
||||
# UTILITY FUNCTIONS
|
||||
def generate_supported_types():
|
||||
stype_dict = {'supported_types':{}}
|
||||
for type in bl_types.types_to_register():
|
||||
_type = getattr(bl_types, type)
|
||||
props = {}
|
||||
props['bl_delay_refresh']=_type.bl_delay_refresh
|
||||
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
|
||||
type_module = getattr(bl_types, type)
|
||||
type_impl_name = "Bl{}".format(type.split('_')[1].capitalize())
|
||||
type_module_class = getattr(type_module, type_impl_name)
|
||||
|
||||
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
|
||||
|
||||
|
||||
def client_list_callback(scene, context):
|
||||
from . import operators
|
||||
from .bl_types.bl_user import BlUser
|
||||
|
||||
items = [(RP_COMMON, RP_COMMON, "")]
|
||||
|
||||
username = bpy.context.window_manager.session.username
|
||||
cli = operators.client
|
||||
if cli:
|
||||
client_keys = cli.list(filter=BlUser)
|
||||
for k in client_keys:
|
||||
name = cli.get(uuid=k).data["name"]
|
||||
|
||||
name_desc = name
|
||||
if name == username:
|
||||
client_ids = cli.online_users.keys()
|
||||
for id in client_ids:
|
||||
name_desc = id
|
||||
if id == username:
|
||||
name_desc += " (self)"
|
||||
|
||||
items.append((name, name_desc, ""))
|
||||
items.append((id, name_desc, ""))
|
||||
|
||||
return items
|
||||
|
||||
@ -90,6 +90,15 @@ class ReplicatedDatablock(bpy.types.PropertyGroup):
|
||||
auto_push: bpy.props.BoolProperty(default=True)
|
||||
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):
|
||||
username: bpy.props.StringProperty(
|
||||
name="Username",
|
||||
@ -109,25 +118,14 @@ class SessionProps(bpy.types.PropertyGroup):
|
||||
description='Distant host port',
|
||||
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(
|
||||
name="is_admin",
|
||||
default=False
|
||||
)
|
||||
init_scene: bpy.props.BoolProperty(
|
||||
name="init_scene",
|
||||
default=True
|
||||
)
|
||||
start_empty: bpy.props.BoolProperty(
|
||||
name="start_empty",
|
||||
default=True
|
||||
)
|
||||
active_object: bpy.props.PointerProperty(
|
||||
name="active_object", type=bpy.types.Object)
|
||||
session_mode: bpy.props.EnumProperty(
|
||||
name='session_mode',
|
||||
description='session mode',
|
||||
@ -179,10 +177,11 @@ class SessionProps(bpy.types.PropertyGroup):
|
||||
description='Show only owned datablocks',
|
||||
default=True
|
||||
)
|
||||
use_select_right: bpy.props.BoolProperty(
|
||||
name="Selection right",
|
||||
description='Change right on selection',
|
||||
default=True
|
||||
user_snap_running: bpy.props.BoolProperty(
|
||||
default=False
|
||||
)
|
||||
time_snap_running: bpy.props.BoolProperty(
|
||||
default=False
|
||||
)
|
||||
|
||||
def load(self):
|
||||
@ -239,6 +238,7 @@ class SessionProps(bpy.types.PropertyGroup):
|
||||
|
||||
|
||||
classes = (
|
||||
SessionUser,
|
||||
ReplicatedDatablock,
|
||||
SessionProps,
|
||||
|
||||
@ -267,7 +267,10 @@ def register():
|
||||
bpy.types.WindowManager.session = bpy.props.PointerProperty(
|
||||
type=SessionProps)
|
||||
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()
|
||||
|
||||
presence.register()
|
||||
|
@ -1,5 +1,4 @@
|
||||
__all__ = [
|
||||
'bl_user',
|
||||
'bl_object',
|
||||
'bl_mesh',
|
||||
'bl_camera',
|
||||
|
@ -7,22 +7,77 @@ from .bl_datablock import BlDatablock
|
||||
# WIP
|
||||
|
||||
class BlAction(BlDatablock):
|
||||
def load(self, data, target):
|
||||
utils.dump_anything.load(target, data)
|
||||
bl_id = "actions"
|
||||
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):
|
||||
return bpy.data.actions.new(data["name"])
|
||||
|
||||
def load(self, data, target):
|
||||
pass
|
||||
# # find target object
|
||||
# object_ = bpy.context.scene.objects.active
|
||||
# if object_ is None:
|
||||
# raise RuntimeError("Nothing is selected.")
|
||||
# if object_.mode != 'POSE': # object must be in pose mode
|
||||
# raise RuntimeError("Object must be in pose mode.")
|
||||
# if object_.animation_data.action is None:
|
||||
# raise RuntimeError("Object needs an active action.")
|
||||
begin_frame = 100000
|
||||
end_frame = -100000
|
||||
|
||||
for dumped_fcurve in data["fcurves"]:
|
||||
begin_frame = min(
|
||||
begin_frame,
|
||||
min(
|
||||
[begin_frame] + [dkp["co"][0] for dkp in dumped_fcurve["keyframe_points"]]
|
||||
)
|
||||
)
|
||||
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):
|
||||
assert(pointer)
|
||||
@ -30,7 +85,18 @@ class BlAction(BlDatablock):
|
||||
|
||||
dumper = utils.dump_anything.Dumper()
|
||||
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"] = []
|
||||
for fcurve in self.pointer.fcurves:
|
||||
@ -49,20 +115,7 @@ class BlAction(BlDatablock):
|
||||
|
||||
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):
|
||||
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 .. import utils
|
||||
from .. import presence
|
||||
from .. import presence, operators
|
||||
from .bl_datablock import BlDatablock
|
||||
|
||||
# WIP
|
||||
|
||||
|
||||
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):
|
||||
return bpy.data.armatures.new(data["name"])
|
||||
|
||||
def load(self, data, target):
|
||||
def load_implementation(self, data, target):
|
||||
# Load parent object
|
||||
if data['user'] not in bpy.data.objects.keys():
|
||||
parent_object = bpy.data.objects.new(data['user'], self.pointer)
|
||||
else:
|
||||
parent_object = bpy.data.objects[data['user']]
|
||||
parent_object = utils.find_from_attr(
|
||||
'uuid',
|
||||
data['user'],
|
||||
bpy.data.objects
|
||||
)
|
||||
|
||||
is_object_in_master = (data['user_collection'][0] == "Master Collection")
|
||||
#TODO: recursive parent collection loading
|
||||
if parent_object is None:
|
||||
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
|
||||
if is_object_in_master:
|
||||
parent_collection = bpy.data.scenes[data['user_scene'][0]].collection
|
||||
elif data['user_collection'][0] not in bpy.data.collections.keys():
|
||||
parent_collection = bpy.data.collections.new(data['user_collection'][0])
|
||||
parent_collection = bpy.data.scenes[data['user_scene']
|
||||
[0]].collection
|
||||
elif data['user_collection'][0] not in bpy.data.collections.keys():
|
||||
parent_collection = bpy.data.collections.new(
|
||||
data['user_collection'][0])
|
||||
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:
|
||||
parent_collection.objects.link(parent_object)
|
||||
|
||||
# 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:
|
||||
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()
|
||||
|
||||
bpy.data.scenes[data['user_scene'][0]
|
||||
].collection. children.link(parent_collection)
|
||||
|
||||
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
|
||||
# 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')
|
||||
|
||||
for bone in data['bones']:
|
||||
if bone not in self.pointer.edit_bones:
|
||||
new_bone = self.pointer.edit_bones.new(bone)
|
||||
else:
|
||||
new_bone = self.pointer.edit_bones[bone]
|
||||
|
||||
new_bone.tail = data['bones'][bone]['tail_local']
|
||||
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']
|
||||
bone_data = data['bones'].get(bone)
|
||||
|
||||
if 'parent' in data['bones'][bone]:
|
||||
new_bone.parent = self.pointer.edit_bones[data['bones'][bone]['parent']['name']]
|
||||
new_bone.use_connect = data['bones'][bone]['use_connect']
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
new_bone.tail = bone_data['tail_local']
|
||||
new_bone.head = bone_data['head_local']
|
||||
new_bone.tail_radius = bone_data['tail_radius']
|
||||
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
|
||||
# bpy_.selected_objects = [armature]
|
||||
utils.dump_anything.load(new_bone, bone_data)
|
||||
|
||||
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)
|
||||
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]
|
||||
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)
|
||||
data['user_collection'] = [item.name for item in container_users if isinstance(item,bpy.types.Collection)]
|
||||
data['user_scene'] = [item.name for item in container_users if isinstance(item,bpy.types.Scene)]
|
||||
data['user_collection'] = [
|
||||
item.name for item in container_users if isinstance(item, bpy.types.Collection)]
|
||||
data['user_scene'] = [
|
||||
item.name for item in container_users if isinstance(item, bpy.types.Scene)]
|
||||
return data
|
||||
|
||||
def resolve(self):
|
||||
assert(self.data)
|
||||
self.pointer = bpy.data.armatures.get(self.data['name'])
|
||||
|
||||
def diff(self):
|
||||
False
|
||||
|
||||
def is_valid(self):
|
||||
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):
|
||||
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):
|
||||
utils.dump_anything.load(target, data)
|
||||
|
||||
@ -18,7 +25,7 @@ class BlCamera(BlDatablock):
|
||||
def construct(self, data):
|
||||
return bpy.data.cameras.new(data["name"])
|
||||
|
||||
def dump(self, pointer=None):
|
||||
def dump_implementation(self, data, pointer=None):
|
||||
assert(pointer)
|
||||
|
||||
dumper = utils.dump_anything.Dumper()
|
||||
@ -45,17 +52,5 @@ class BlCamera(BlDatablock):
|
||||
]
|
||||
return dumper.dump(pointer)
|
||||
|
||||
def resolve(self):
|
||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.cameras)
|
||||
|
||||
def is_valid(self):
|
||||
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):
|
||||
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):
|
||||
if self.is_library:
|
||||
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"]:
|
||||
target.children.unlink(collection)
|
||||
|
||||
def dump(self, pointer=None):
|
||||
def dump_implementation(self, data, pointer=None):
|
||||
assert(pointer)
|
||||
data = {}
|
||||
data['name'] = pointer.name
|
||||
@ -68,19 +75,8 @@ class BlCollection(BlDatablock):
|
||||
|
||||
data['children'] = collection_children
|
||||
|
||||
# dumper = utils.dump_anything.Dumper()
|
||||
# dumper.depth = 2
|
||||
# dumper.include_filter = ['name','objects', 'children']
|
||||
|
||||
# return dumper.dump(pointer)
|
||||
return data
|
||||
|
||||
def resolve(self):
|
||||
self.pointer = utils.find_from_attr(
|
||||
'uuid',
|
||||
self.uuid,
|
||||
bpy.data.collections)
|
||||
|
||||
def resolve_dependencies(self):
|
||||
deps = []
|
||||
|
||||
@ -94,11 +90,3 @@ class BlCollection(BlDatablock):
|
||||
def is_valid(self):
|
||||
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
|
||||
|
||||
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):
|
||||
return bpy.data.curves.new(data["name"], 'CURVE')
|
||||
|
||||
@ -29,7 +36,7 @@ class BlCurve(BlDatablock):
|
||||
utils.dump_anything.load(
|
||||
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)
|
||||
data = utils.dump_datablock(pointer, 1)
|
||||
data['splines'] = {}
|
||||
@ -52,15 +59,5 @@ class BlCurve(BlDatablock):
|
||||
data['type'] = 'CURVE'
|
||||
return data
|
||||
|
||||
def resolve(self):
|
||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.curves)
|
||||
|
||||
def is_valid(self):
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
"""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):
|
||||
super().__init__(*args, **kwargs)
|
||||
pointer = kwargs.get('pointer', None)
|
||||
@ -14,7 +70,7 @@ class BlDatablock(ReplicatedDatablock):
|
||||
# TODO: use is_library_indirect
|
||||
self.is_library = (pointer and hasattr(pointer, 'library') and
|
||||
pointer.library) or \
|
||||
(self.data and 'library' in self.data)
|
||||
(self.data and 'library' in self.data)
|
||||
|
||||
if self.is_library:
|
||||
self.load = self.load_library
|
||||
@ -50,10 +106,68 @@ class BlDatablock(ReplicatedDatablock):
|
||||
def resolve_dependencies_library(self):
|
||||
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):
|
||||
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)
|
||||
|
||||
return dependencies
|
||||
|
@ -34,6 +34,13 @@ def load_gpencil_layer(target=None, data=None, create=False):
|
||||
|
||||
|
||||
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):
|
||||
return bpy.data.grease_pencils.new(data["name"])
|
||||
|
||||
@ -57,16 +64,13 @@ class BlGpencil(BlDatablock):
|
||||
for mat in data['materials']:
|
||||
target.materials.append(bpy.data.materials[mat])
|
||||
|
||||
def dump(self, pointer=None):
|
||||
def dump_implementation(self, data, pointer=None):
|
||||
assert(pointer)
|
||||
data = utils.dump_datablock(pointer, 2)
|
||||
utils.dump_datablock_attibutes(
|
||||
pointer, ['layers'], 9, data)
|
||||
return data
|
||||
|
||||
def resolve(self):
|
||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.grease_pencils)
|
||||
|
||||
def resolve_dependencies(self):
|
||||
deps = []
|
||||
|
||||
@ -77,11 +81,3 @@ class BlGpencil(BlDatablock):
|
||||
|
||||
def is_valid(self):
|
||||
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
|
||||
|
||||
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):
|
||||
return bpy.data.images.new(
|
||||
name=data['name'],
|
||||
@ -46,7 +53,7 @@ class BlImage(BlDatablock):
|
||||
image.filepath = img_path
|
||||
|
||||
|
||||
def dump(self, pointer=None):
|
||||
def dump_implementation(self, data, pointer=None):
|
||||
assert(pointer)
|
||||
data = {}
|
||||
data['pixels'] = dump_image(pointer)
|
||||
@ -58,18 +65,8 @@ class BlImage(BlDatablock):
|
||||
data)
|
||||
return data
|
||||
|
||||
def resolve(self):
|
||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.images)
|
||||
|
||||
def diff(self):
|
||||
return False
|
||||
|
||||
def is_valid(self):
|
||||
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):
|
||||
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):
|
||||
utils.dump_anything.load(target, data)
|
||||
|
||||
@ -38,17 +45,8 @@ class BlLattice(BlDatablock):
|
||||
|
||||
return data
|
||||
|
||||
def resolve(self):
|
||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.lattices)
|
||||
|
||||
def is_valid(self):
|
||||
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):
|
||||
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):
|
||||
with bpy.data.libraries.load(filepath=data["filepath"], link=True) as (sourceData, targetData):
|
||||
targetData = sourceData
|
||||
@ -17,16 +24,5 @@ class BlLibrary(BlDatablock):
|
||||
assert(pointer)
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
return bpy.data.lights.new(data["name"], data["type"])
|
||||
|
||||
def load(self, data, target):
|
||||
utils.dump_anything.load(target, data)
|
||||
|
||||
def dump(self, pointer=None):
|
||||
def dump_implementation(self, data, pointer=None):
|
||||
assert(pointer)
|
||||
dumper = utils.dump_anything.Dumper()
|
||||
dumper.depth = 3
|
||||
@ -39,16 +46,6 @@ class BlLight(BlDatablock):
|
||||
data = dumper.dump(pointer)
|
||||
return data
|
||||
|
||||
def resolve(self):
|
||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.lights)
|
||||
|
||||
def is_valid(self):
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
utils.dump_anything.load(target, 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):
|
||||
assert(pointer)
|
||||
@ -19,6 +27,7 @@ class BlLightProbe(BlDatablock):
|
||||
dumper.depth = 1
|
||||
dumper.include_filter = [
|
||||
"name",
|
||||
'type',
|
||||
'influence_type',
|
||||
'influence_distance',
|
||||
'falloff',
|
||||
@ -29,21 +38,15 @@ class BlLightProbe(BlDatablock):
|
||||
'use_custom_parallax',
|
||||
'parallax_type',
|
||||
'parallax_distance',
|
||||
'grid_resolution_x',
|
||||
'grid_resolution_y',
|
||||
'grid_resolution_z',
|
||||
'visibility_buffer_bias',
|
||||
'visibility_bleed_bias',
|
||||
'visibility_blur'
|
||||
]
|
||||
|
||||
return dumper.dump(pointer)
|
||||
|
||||
def resolve(self):
|
||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.lattices)
|
||||
|
||||
def is_valid(self):
|
||||
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):
|
||||
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):
|
||||
return bpy.data.materials.new(data["name"])
|
||||
|
||||
@ -98,7 +105,7 @@ class BlMaterial(BlDatablock):
|
||||
for link in data["node_tree"]["links"]:
|
||||
load_link(target.node_tree, data["node_tree"]["links"][link])
|
||||
|
||||
def dump(self, pointer=None):
|
||||
def dump_implementation(self, data, pointer=None):
|
||||
assert(pointer)
|
||||
mat_dumper = utils.dump_anything.Dumper()
|
||||
mat_dumper.depth = 2
|
||||
@ -175,9 +182,6 @@ class BlMaterial(BlDatablock):
|
||||
utils.dump_datablock_attibutes(pointer, ["grease_pencil"], 3, data)
|
||||
return data
|
||||
|
||||
def resolve(self):
|
||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.materials)
|
||||
|
||||
def resolve_dependencies(self):
|
||||
# TODO: resolve node group deps
|
||||
deps = []
|
||||
@ -194,11 +198,3 @@ class BlMaterial(BlDatablock):
|
||||
def is_valid(self):
|
||||
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
|
||||
|
||||
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):
|
||||
instance = bpy.data.meshes.new(data["name"])
|
||||
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)
|
||||
|
||||
data = utils.dump_datablock(pointer, 2)
|
||||
@ -154,9 +161,6 @@ class BlMesh(BlDatablock):
|
||||
|
||||
return data
|
||||
|
||||
def resolve(self):
|
||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.meshes)
|
||||
|
||||
def resolve_dependencies(self):
|
||||
deps = []
|
||||
|
||||
@ -169,10 +173,3 @@ class BlMesh(BlDatablock):
|
||||
def is_valid(self):
|
||||
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):
|
||||
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):
|
||||
return bpy.data.metaballs.new(data["name"])
|
||||
|
||||
@ -17,7 +24,7 @@ class BlMetaball(BlDatablock):
|
||||
new_element = target.elements.new(type=data["elements"][element]['type'])
|
||||
utils.dump_anything.load(new_element, data["elements"][element])
|
||||
|
||||
def dump(self, pointer=None):
|
||||
def dump_implementation(self, data, pointer=None):
|
||||
assert(pointer)
|
||||
dumper = utils.dump_anything.Dumper()
|
||||
dumper.depth = 3
|
||||
@ -26,16 +33,5 @@ class BlMetaball(BlDatablock):
|
||||
data = dumper.dump(pointer)
|
||||
return data
|
||||
|
||||
def resolve(self):
|
||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.metaballs)
|
||||
|
||||
def is_valid(self):
|
||||
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 mathutils
|
||||
import logging
|
||||
|
||||
from .. import utils
|
||||
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):
|
||||
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):
|
||||
pointer = None
|
||||
|
||||
@ -42,24 +71,26 @@ class BlObject(BlDatablock):
|
||||
elif data["data"] in bpy.data.speakers.keys():
|
||||
pointer = bpy.data.speakers[data["data"]]
|
||||
elif data["data"] in bpy.data.lightprobes.keys():
|
||||
pass
|
||||
# bpy need to support correct lightprobe creation from python
|
||||
# pointer = bpy.data.lightprobes[data["data"]]
|
||||
|
||||
instance = bpy.data.objects.new(data["name"], pointer)
|
||||
# Only supported since 2.83
|
||||
if bpy.app.version[1] >= 83:
|
||||
pointer = bpy.data.lightprobes[data["data"]]
|
||||
else:
|
||||
logger.error("Lightprobe replication only supported since 2.83")
|
||||
instance = bpy.data.objects.new(data["name"], pointer)
|
||||
instance.uuid = self.uuid
|
||||
|
||||
return instance
|
||||
|
||||
def load(self, data, target):
|
||||
target.matrix_world = mathutils.Matrix(data["matrix_world"])
|
||||
def load_implementation(self, data, target):
|
||||
if "matrix_world" in data:
|
||||
target.matrix_world = mathutils.Matrix(data["matrix_world"])
|
||||
|
||||
target.name = data["name"]
|
||||
# Load modifiers
|
||||
if hasattr(target, 'modifiers'):
|
||||
for local_modifier in target.modifiers:
|
||||
if local_modifier.name not in data['modifiers']:
|
||||
target.modifiers.remove(local_modifier)
|
||||
# TODO: smarter selective update
|
||||
target.modifiers.clear()
|
||||
|
||||
for modifier in data['modifiers']:
|
||||
target_modifier = target.modifiers.get(modifier)
|
||||
|
||||
@ -70,6 +101,42 @@ class BlObject(BlDatablock):
|
||||
utils.dump_anything.load(
|
||||
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
|
||||
if 'children' in data.keys():
|
||||
for child in data['children']:
|
||||
@ -80,17 +147,46 @@ class BlObject(BlDatablock):
|
||||
target.empty_display_type = data['empty_display_type']
|
||||
|
||||
# Instancing
|
||||
target.instance_type = data['instance_type']
|
||||
target.instance_type = data['instance_type']
|
||||
if data['instance_type'] == '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)
|
||||
dumper = utils.dump_anything.Dumper()
|
||||
dumper.depth = 1
|
||||
dumper.include_filter = [
|
||||
"name",
|
||||
"matrix_world",
|
||||
"rotation_mode",
|
||||
"parent",
|
||||
"data",
|
||||
@ -101,6 +197,9 @@ class BlObject(BlDatablock):
|
||||
"instance_collection",
|
||||
"instance_type"
|
||||
]
|
||||
# if not utils.has_action(pointer):
|
||||
dumper.include_filter.append('matrix_world')
|
||||
|
||||
data = dumper.dump(pointer)
|
||||
|
||||
if self.is_library:
|
||||
@ -109,8 +208,55 @@ class BlObject(BlDatablock):
|
||||
# MODIFIERS
|
||||
if hasattr(pointer, 'modifiers'):
|
||||
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
|
||||
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
|
||||
if len(pointer.children) > 0:
|
||||
@ -120,13 +266,64 @@ class BlObject(BlDatablock):
|
||||
|
||||
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
|
||||
|
||||
def resolve(self):
|
||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.objects)
|
||||
|
||||
def resolve_dependencies(self):
|
||||
deps = []
|
||||
deps = super().resolve_dependencies()
|
||||
|
||||
# Avoid Empty case
|
||||
if self.pointer.data:
|
||||
@ -137,8 +334,9 @@ class BlObject(BlDatablock):
|
||||
if self.is_library:
|
||||
deps.append(self.pointer.library)
|
||||
|
||||
|
||||
if self.pointer.instance_type == 'COLLECTION':
|
||||
#TODO: uuid based
|
||||
# TODO: uuid based
|
||||
deps.append(self.pointer.instance_collection)
|
||||
|
||||
return deps
|
||||
@ -147,10 +345,3 @@ class BlObject(BlDatablock):
|
||||
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
|
||||
|
||||
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):
|
||||
instance = bpy.data.scenes.new(data["name"])
|
||||
instance.uuid = self.uuid
|
||||
@ -42,7 +49,7 @@ class BlScene(BlDatablock):
|
||||
if 'grease_pencil' in data.keys():
|
||||
target.grease_pencil = bpy.data.grease_pencils[data['grease_pencil']]
|
||||
|
||||
def dump(self, pointer=None):
|
||||
def dump_implementation(self, data, pointer=None):
|
||||
assert(pointer)
|
||||
data = {}
|
||||
|
||||
@ -58,12 +65,6 @@ class BlScene(BlDatablock):
|
||||
|
||||
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):
|
||||
deps = []
|
||||
|
||||
@ -87,10 +88,3 @@ class BlScene(BlDatablock):
|
||||
|
||||
def is_valid(self):
|
||||
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):
|
||||
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):
|
||||
utils.dump_anything.load(target, data)
|
||||
|
||||
@ -34,17 +41,6 @@ class BlSpeaker(BlDatablock):
|
||||
|
||||
return dumper.dump(pointer)
|
||||
|
||||
def resolve(self):
|
||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.lattices)
|
||||
|
||||
def is_valid(self):
|
||||
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):
|
||||
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):
|
||||
return bpy.data.worlds.new(data["name"])
|
||||
|
||||
@ -26,7 +33,7 @@ class BlWorld(BlDatablock):
|
||||
for link in data["node_tree"]["links"]:
|
||||
load_link(target.node_tree, data["node_tree"]["links"][link])
|
||||
|
||||
def dump(self, pointer=None):
|
||||
def dump_implementation(self, data, pointer=None):
|
||||
assert(pointer)
|
||||
|
||||
world_dumper = utils.dump_anything.Dumper()
|
||||
@ -83,9 +90,6 @@ class BlWorld(BlDatablock):
|
||||
pointer.node_tree, ["links"], 3, data['node_tree'])
|
||||
return data
|
||||
|
||||
def resolve(self):
|
||||
self.pointer = utils.find_from_attr('uuid', self.uuid, bpy.data.worlds)
|
||||
|
||||
def resolve_dependencies(self):
|
||||
deps = []
|
||||
|
||||
@ -100,11 +104,3 @@ class BlWorld(BlDatablock):
|
||||
def is_valid(self):
|
||||
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
|
||||
|
||||
from . import operators, presence, utils
|
||||
from .bl_types.bl_user import BlUser
|
||||
from .libs.replication.replication.constants import FETCHED, RP_COMMON
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
class Delayable():
|
||||
@ -74,74 +74,101 @@ class ApplyTimer(Timer):
|
||||
try:
|
||||
operators.client.apply(node)
|
||||
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):
|
||||
def __init__(self, timout=.1):
|
||||
super().__init__(timout)
|
||||
self.last_selection = []
|
||||
self._last_selection = []
|
||||
self._user = None
|
||||
self._right_strategy = RP_COMMON
|
||||
|
||||
def execute(self):
|
||||
if operators.client:
|
||||
users = operators.client.list(filter=BlUser)
|
||||
session = operators.client
|
||||
settings = bpy.context.window_manager.session
|
||||
|
||||
for user in users:
|
||||
user_ref = operators.client.get(uuid=user)
|
||||
settings = bpy.context.window_manager.session
|
||||
if session:
|
||||
# Find user
|
||||
if self._user is None:
|
||||
self._user = session.online_users.get(settings.username)
|
||||
|
||||
# Local user
|
||||
if user_ref.pointer:
|
||||
current_selection = utils.get_selected_objects(
|
||||
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]
|
||||
if self._right_strategy is None:
|
||||
self._right_strategy = session.get_config()[
|
||||
'right_strategy']
|
||||
|
||||
# change new selection to our
|
||||
for obj in obj_ours:
|
||||
node = operators.client.get(uuid=obj)
|
||||
if self._user:
|
||||
current_selection = utils.get_selected_objects(
|
||||
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:
|
||||
recursive = True
|
||||
if node.data and 'instance_type' in node.data.keys():
|
||||
recursive = node.data['instance_type'] != 'COLLECTION'
|
||||
# change old selection right to common
|
||||
for obj in obj_common:
|
||||
node = session.get(uuid=obj)
|
||||
|
||||
operators.client.change_owner(
|
||||
node.uuid,
|
||||
settings.username,
|
||||
recursive=recursive)
|
||||
else:
|
||||
return
|
||||
if node and (node.owner == settings.username or node.owner == RP_COMMON):
|
||||
recursive = True
|
||||
if node.data and 'instance_type' in node.data.keys():
|
||||
recursive = node.data['instance_type'] != 'COLLECTION'
|
||||
session.change_owner(
|
||||
node.uuid,
|
||||
RP_COMMON,
|
||||
recursive=recursive)
|
||||
|
||||
self.last_selection = current_selection
|
||||
user_ref.pointer.update_selected_objects(
|
||||
bpy.context)
|
||||
user_ref.update()
|
||||
# change new selection to our
|
||||
for obj in obj_ours:
|
||||
node = session.get(uuid=obj)
|
||||
|
||||
# change old selection right to common
|
||||
for obj in obj_common:
|
||||
node = operators.client.get(uuid=obj)
|
||||
if node and node.owner == RP_COMMON:
|
||||
recursive = True
|
||||
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):
|
||||
recursive = True
|
||||
if node.data and 'instance_type' in node.data.keys():
|
||||
recursive = node.data['instance_type'] != 'COLLECTION'
|
||||
operators.client.change_owner(
|
||||
node.uuid,
|
||||
RP_COMMON,
|
||||
recursive=recursive)
|
||||
else:
|
||||
for obj in bpy.data.objects:
|
||||
if obj.hide_select and obj.uuid not in user_ref.data['selected_objects']:
|
||||
obj.hide_select = False
|
||||
elif not obj.hide_select and obj.uuid in user_ref.data['selected_objects']:
|
||||
obj.hide_select = True
|
||||
session.change_owner(
|
||||
node.uuid,
|
||||
settings.username,
|
||||
recursive=recursive)
|
||||
else:
|
||||
return
|
||||
|
||||
self._last_selection = current_selection
|
||||
|
||||
user_metadata = {
|
||||
'selected_objects': current_selection
|
||||
}
|
||||
|
||||
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):
|
||||
@ -160,37 +187,89 @@ class Draw(Delayable):
|
||||
bpy.types.SpaceView3D.draw_handler_remove(
|
||||
self._handler, "WINDOW")
|
||||
except:
|
||||
logger.error("draw already unregistered")
|
||||
pass
|
||||
|
||||
|
||||
class DrawClient(Draw):
|
||||
def execute(self):
|
||||
repo = operators.client
|
||||
if repo and presence.renderer:
|
||||
settings = bpy.context.window_manager.session
|
||||
client_list = [key for key in repo.list(filter=BlUser) if
|
||||
key != settings.user_uuid]
|
||||
session = getattr(operators, 'client', None)
|
||||
renderer = getattr(presence, 'renderer', None)
|
||||
|
||||
for cli in client_list:
|
||||
cli_ref = repo.get(uuid=cli)
|
||||
if cli_ref.data.get('name'):
|
||||
if settings.presence_show_selected:
|
||||
presence.renderer.draw_client_selection(
|
||||
cli_ref.data['name'], cli_ref.data['color'], cli_ref.data['selected_objects'])
|
||||
if settings.presence_show_user:
|
||||
presence.renderer.draw_client_camera(
|
||||
cli_ref.data['name'], cli_ref.data['location'], cli_ref.data['color'])
|
||||
if session and renderer:
|
||||
settings = bpy.context.window_manager.session
|
||||
users = session.online_users
|
||||
|
||||
for user in users.values():
|
||||
metadata = user.get('metadata')
|
||||
|
||||
if settings.presence_show_selected and 'selected_objects' in metadata.keys():
|
||||
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):
|
||||
def __init__(self, timout=1, client_uuid=None):
|
||||
assert(client_uuid)
|
||||
self._client_uuid = client_uuid
|
||||
def __init__(self, timout=1):
|
||||
super().__init__(timout)
|
||||
|
||||
def execute(self):
|
||||
if self._client_uuid and operators.client:
|
||||
client = operators.client.get(uuid=self._client_uuid)
|
||||
settings = bpy.context.window_manager.session
|
||||
session_info = bpy.context.window_manager.session
|
||||
session = getattr(operators, 'client', None)
|
||||
renderer = getattr(presence, 'renderer', None)
|
||||
|
||||
if client:
|
||||
client.pointer.update_location()
|
||||
if session and renderer:
|
||||
# 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
|
||||
|
||||
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 = os.path.join(CONFIG_DIR, "app.yaml")
|
||||
|
@ -92,11 +92,13 @@ class Dumper:
|
||||
|
||||
def _build_inline_dump_functions(self):
|
||||
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_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_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_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_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_float = (_dump_filter_type(float), 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_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_matrix = (_dump_filter_type(mathutils.Matrix), self._dump_matrix)
|
||||
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_default = (_dump_filter_default, self._dump_default)
|
||||
|
||||
@ -136,9 +140,18 @@ class Dumper:
|
||||
def _dump_vector_as_leaf(self, vector, depth):
|
||||
return list(vector)
|
||||
|
||||
def _dump_quaternion_as_leaf(self, quaternion, depth):
|
||||
return list(quaternion)
|
||||
|
||||
def _dump_color_as_leaf(self, color, depth):
|
||||
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 is_valid_property(p):
|
||||
try:
|
||||
@ -173,12 +186,13 @@ class Dumper:
|
||||
self._match_type_int,
|
||||
self._match_type_float,
|
||||
self._match_type_string,
|
||||
self._match_type_ref,
|
||||
self._match_type_ID,
|
||||
self._match_type_bpy_prop_collection,
|
||||
self._match_type_array,
|
||||
self._match_type_matrix,
|
||||
self._match_type_vector,
|
||||
self._match_type_color,
|
||||
self._match_type_quaternion,
|
||||
self._match_type_color,
|
||||
self._match_default
|
||||
]
|
||||
@ -307,6 +321,9 @@ class Loader:
|
||||
def _load_vector(self, vector, dump):
|
||||
vector.write(mathutils.Vector(dump))
|
||||
|
||||
def _load_quaternion(self, quaternion, dump):
|
||||
quaternion.write(mathutils.Quaternion(dump))
|
||||
|
||||
def _ordered_keys(self, keys):
|
||||
ordered_keys = []
|
||||
for order_element in self.order:
|
||||
@ -336,6 +353,7 @@ class Loader:
|
||||
(_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.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.StringProperty), 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
|
||||
from operator import itemgetter
|
||||
from pathlib import Path
|
||||
|
||||
import msgpack
|
||||
|
||||
import bpy
|
||||
@ -23,34 +22,25 @@ from .libs.replication.replication.interface import Session
|
||||
from .libs.replication.replication.constants import (
|
||||
STATE_ACTIVE,
|
||||
STATE_INITIAL,
|
||||
STATE_SYNCING)
|
||||
STATE_SYNCING,
|
||||
FETCHED)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.ERROR)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
client = None
|
||||
delayables = []
|
||||
ui_context = 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)
|
||||
|
||||
stop_modal_executor = False
|
||||
modal_executor_queue = None
|
||||
|
||||
# OPERATORS
|
||||
|
||||
|
||||
class SessionStartOperator(bpy.types.Operator):
|
||||
bl_idname = "session.start"
|
||||
bl_label = "start"
|
||||
bl_description = "connect to a net server"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
host: bpy.props.BoolProperty(default=False)
|
||||
|
||||
@ -59,32 +49,41 @@ class SessionStartOperator(bpy.types.Operator):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
global client, delayables
|
||||
global client, delayables, ui_context
|
||||
settings = context.window_manager.session
|
||||
users = bpy.data.window_managers['WinMan'].online_users
|
||||
|
||||
# TODO: Sync server clients
|
||||
users.clear()
|
||||
|
||||
# save config
|
||||
settings.save(context)
|
||||
|
||||
bpy_factory = ReplicatedDataFactory()
|
||||
supported_bl_types = []
|
||||
ui_context = context.copy()
|
||||
|
||||
# init the factory with supported types
|
||||
for type in bl_types.types_to_register():
|
||||
_type = getattr(bl_types, type)
|
||||
supported_bl_types.append(_type.bl_id)
|
||||
type_module = getattr(bl_types, type)
|
||||
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
|
||||
type_local_config = settings.supported_datablock[_type.bl_rep_class.__name__]
|
||||
type_local_config = settings.supported_datablock[type_impl_name]
|
||||
|
||||
bpy_factory.register_type(
|
||||
_type.bl_class,
|
||||
_type.bl_rep_class,
|
||||
type_module_class.bl_class,
|
||||
type_module_class,
|
||||
timer=type_local_config.bl_delay_refresh,
|
||||
automatic=type_local_config.auto_push)
|
||||
|
||||
if type_local_config.bl_delay_apply > 0:
|
||||
delayables.append(delayable.ApplyTimer(
|
||||
timout=type_local_config.bl_delay_apply,
|
||||
target_type=_type.bl_rep_class))
|
||||
target_type=type_module_class))
|
||||
|
||||
client = Session(factory=bpy_factory)
|
||||
|
||||
@ -118,30 +117,15 @@ class SessionStartOperator(bpy.types.Operator):
|
||||
"A session is already hosted on this address")
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
|
||||
# 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:
|
||||
if self.host:
|
||||
for scene in bpy.data.scenes:
|
||||
scene_uuid = client.add(scene)
|
||||
|
||||
# for node in client.list():
|
||||
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())
|
||||
|
||||
# Push all added values
|
||||
@ -155,6 +139,10 @@ class SessionStartOperator(bpy.types.Operator):
|
||||
for d in delayables:
|
||||
d.register()
|
||||
|
||||
global modal_executor_queue
|
||||
modal_executor_queue = queue.Queue()
|
||||
bpy.ops.session.apply_armature_operator()
|
||||
|
||||
self.report(
|
||||
{'INFO'},
|
||||
"connexion on tcp://{}:{}".format(settings.ip, settings.port))
|
||||
@ -172,12 +160,13 @@ class SessionStopOperator(bpy.types.Operator):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
global client, delayables
|
||||
global client, delayables, stop_modal_executor
|
||||
|
||||
stop_modal_executor = True
|
||||
settings = context.window_manager.session
|
||||
settings.is_admin = False
|
||||
assert(client)
|
||||
|
||||
client.remove(settings.user_uuid)
|
||||
client.disconnect()
|
||||
|
||||
for d in delayables:
|
||||
@ -264,6 +253,14 @@ class SessionSnapUserOperator(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
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)
|
||||
wm.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
@ -273,7 +270,9 @@ class SessionSnapUserOperator(bpy.types.Operator):
|
||||
wm.event_timer_remove(self._timer)
|
||||
|
||||
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)
|
||||
return {'CANCELLED'}
|
||||
|
||||
@ -281,9 +280,64 @@ class SessionSnapUserOperator(bpy.types.Operator):
|
||||
area, region, rv3d = presence.view3d_find()
|
||||
global client
|
||||
|
||||
target_client = client.get(uuid=self.target_client)
|
||||
if target_client:
|
||||
rv3d.view_matrix = mathutils.Matrix(target_client.data['view_matrix'])
|
||||
if client:
|
||||
target_ref = client.online_users.get(self.target_client)
|
||||
|
||||
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:
|
||||
return {"CANCELLED"}
|
||||
|
||||
@ -330,15 +384,65 @@ class SessionCommit(bpy.types.Operator):
|
||||
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 = (
|
||||
SessionStartOperator,
|
||||
SessionStopOperator,
|
||||
SessionPropertyRemoveOperator,
|
||||
SessionSnapUserOperator,
|
||||
SessionSnapTimeOperator,
|
||||
SessionPropertyRightOperator,
|
||||
SessionApply,
|
||||
SessionCommit,
|
||||
ApplyArmatureOperator,
|
||||
|
||||
|
||||
)
|
||||
|
||||
|
||||
@persistent
|
||||
def load_pre_handler(dummy):
|
||||
global client
|
||||
@ -347,6 +451,29 @@ def load_pre_handler(dummy):
|
||||
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():
|
||||
from bpy.utils import register_class
|
||||
for cls in classes:
|
||||
@ -354,6 +481,12 @@ def register():
|
||||
|
||||
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():
|
||||
global client
|
||||
|
||||
@ -367,5 +500,11 @@ def unregister():
|
||||
|
||||
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__":
|
||||
register()
|
||||
|
@ -31,8 +31,8 @@ def view3d_find():
|
||||
|
||||
def refresh_3d_view():
|
||||
area, region, rv3d = view3d_find()
|
||||
|
||||
area.tag_redraw()
|
||||
if area and region and rv3d:
|
||||
area.tag_redraw()
|
||||
|
||||
|
||||
def get_target(region, rv3d, coord):
|
||||
@ -69,7 +69,7 @@ def get_default_bbox(obj, radius):
|
||||
return [(point.x, point.y, point.z)
|
||||
for point in bbox_corners]
|
||||
|
||||
def get_client_cam_points():
|
||||
def get_view_corners():
|
||||
area, region, rv3d = view3d_find()
|
||||
|
||||
v1 = [0, 0, 0]
|
||||
@ -78,6 +78,7 @@ def get_client_cam_points():
|
||||
v4 = [0, 0, 0]
|
||||
v5 = [0, 0, 0]
|
||||
v6 = [0, 0, 0]
|
||||
v7 = [0, 0, 0]
|
||||
|
||||
if area and region and rv3d:
|
||||
width = region.width
|
||||
@ -112,31 +113,14 @@ def get_bb_coords_from_obj(object, parent=None):
|
||||
return [(point.x, point.y, point.z)
|
||||
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):
|
||||
current_coords = get_client_cam_points()
|
||||
area, region, rv3d = view3d_find()
|
||||
|
||||
current_coords = list(get_client_cam_points())
|
||||
if current_coords and self.location != current_coords:
|
||||
self.location = current_coords
|
||||
def get_view_matrix():
|
||||
area, region, rv3d = view3d_find()
|
||||
|
||||
if area and region and rv3d:
|
||||
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):
|
||||
self.selected_objects = utils.get_selected_objects(context.scene)
|
||||
return matrix_dumper.dump(rv3d.view_matrix)
|
||||
|
||||
def update_presence(self, context):
|
||||
global renderer
|
||||
@ -171,8 +155,12 @@ class DrawFactory(object):
|
||||
self.register_handlers()
|
||||
|
||||
def stop(self):
|
||||
self.flush_users()
|
||||
self.flush_selection()
|
||||
self.unregister_handlers()
|
||||
|
||||
refresh_3d_view()
|
||||
|
||||
def register_handlers(self):
|
||||
self.draw3d_handle = bpy.types.SpaceView3D.draw_handler_add(
|
||||
self.draw3d_callback, (), 'WINDOW', 'POST_VIEW')
|
||||
@ -215,14 +203,14 @@ class DrawFactory(object):
|
||||
|
||||
self.d2d_items.clear()
|
||||
|
||||
def draw_client_selection(self, client_uuid, client_color, client_selection):
|
||||
local_user = bpy.context.window_manager.session.user_uuid
|
||||
def draw_client_selection(self, client_id, client_color, client_selection):
|
||||
local_user = bpy.context.window_manager.session.username
|
||||
|
||||
if local_user != client_uuid:
|
||||
self.flush_selection(client_uuid)
|
||||
if local_user != client_id:
|
||||
self.flush_selection(client_id)
|
||||
|
||||
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)
|
||||
if not ob:
|
||||
@ -271,11 +259,11 @@ class DrawFactory(object):
|
||||
|
||||
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:
|
||||
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:
|
||||
indices = (
|
||||
(1, 3), (2, 1), (3, 0),
|
||||
@ -290,8 +278,8 @@ class DrawFactory(object):
|
||||
batch = batch_for_shader(
|
||||
shader, 'LINES', {"pos": position}, indices=indices)
|
||||
|
||||
self.d3d_items[client_uuid] = (shader, batch, color)
|
||||
self.d2d_items[client_uuid] = (position[1], client_uuid, color)
|
||||
self.d3d_items[client_id] = (shader, batch, color)
|
||||
self.d2d_items[client_id] = (position[1], client_id, color)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Draw client exception {}".format(e))
|
||||
|
@ -1,7 +1,6 @@
|
||||
import bpy
|
||||
|
||||
from . import operators
|
||||
from .bl_types.bl_user import BlUser
|
||||
from .libs.replication.replication.constants import (ADDED, ERROR, FETCHED,
|
||||
MODIFIED, RP_COMMON, UP)
|
||||
|
||||
@ -167,7 +166,7 @@ class SESSION_PT_settings_replication(bpy.types.Panel):
|
||||
|
||||
class SESSION_PT_user(bpy.types.Panel):
|
||||
bl_idname = "MULTIUSER_USER_PT_panel"
|
||||
bl_label = "Users"
|
||||
bl_label = "Online users"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Multiuser"
|
||||
@ -179,44 +178,58 @@ class SESSION_PT_user(bpy.types.Panel):
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
online_users = context.window_manager.online_users
|
||||
selected_user = context.window_manager.user_index
|
||||
settings = context.window_manager.session
|
||||
active_user = online_users[selected_user] if len(online_users)-1>=selected_user else 0
|
||||
|
||||
|
||||
# Create a simple row.
|
||||
col = layout.column(align=True)
|
||||
|
||||
client_keys = operators.client.list(filter=BlUser)
|
||||
if client_keys and len(client_keys) > 0:
|
||||
for key in client_keys:
|
||||
area_msg = col.row(align=True)
|
||||
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()
|
||||
box = row.box()
|
||||
split = box.split(factor=0.5)
|
||||
split.label(text="user")
|
||||
split.label(text="frame")
|
||||
split.label(text="ping")
|
||||
|
||||
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):
|
||||
@ -250,7 +263,7 @@ def draw_property(context, parent, property_uuid, level=0):
|
||||
settings = context.window_manager.session
|
||||
item = operators.client.get(uuid=property_uuid)
|
||||
|
||||
if item.str_type == 'BlUser' or item.state == ERROR:
|
||||
if item.state == ERROR:
|
||||
return
|
||||
|
||||
area_msg = parent.row(align=True)
|
||||
@ -365,6 +378,7 @@ class SESSION_PT_outliner(bpy.types.Panel):
|
||||
|
||||
|
||||
classes = (
|
||||
SESSION_UL_users,
|
||||
SESSION_PT_settings,
|
||||
SESSION_PT_settings_user,
|
||||
SESSION_PT_settings_network,
|
||||
|
@ -5,6 +5,7 @@ import random
|
||||
import string
|
||||
import sys
|
||||
from uuid import uuid4
|
||||
from collections.abc import Iterable
|
||||
|
||||
import bpy
|
||||
import mathutils
|
||||
@ -13,7 +14,18 @@ from . import environment, presence
|
||||
from .libs import dump_anything
|
||||
|
||||
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):
|
||||
@ -45,7 +57,7 @@ def get_datablock_users(datablock):
|
||||
def random_string_digits(stringLength=6):
|
||||
"""Generate a random string of letters and digits """
|
||||
lettersAndDigits = string.ascii_letters + string.digits
|
||||
return ''.join(random.choice(lettersAndDigits) for i in range(stringLength))
|
||||
return ''.join(random.choices(lettersAndDigits, k=stringLength))
|
||||
|
||||
|
||||
def clean_scene():
|
||||
@ -139,3 +151,13 @@ def dump_datablock_attibutes(datablock=None, attributes=[], depth=1, dickt=None)
|
||||
pass
|
||||
|
||||
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