diff --git a/multi_user/__init__.py b/multi_user/__init__.py index 09071c1..41abf8e 100644 --- a/multi_user/__init__.py +++ b/multi_user/__init__.py @@ -57,22 +57,19 @@ def generate_supported_types(): 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 diff --git a/multi_user/bl_types/__init__.py b/multi_user/bl_types/__init__.py index 7f0991a..c3e9605 100644 --- a/multi_user/bl_types/__init__.py +++ b/multi_user/bl_types/__init__.py @@ -1,5 +1,4 @@ __all__ = [ - 'bl_user', 'bl_object', 'bl_mesh', 'bl_camera', diff --git a/multi_user/bl_types/bl_user.py b/multi_user/bl_types/bl_user.py deleted file mode 100644 index 1d0611d..0000000 --- a/multi_user/bl_types/bl_user.py +++ /dev/null @@ -1,64 +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): - bl_id = "users" - bl_class = presence.User - bl_delay_refresh = .1 - bl_delay_apply = .1 - bl_automatic_push = True - bl_icon = 'CON_ARMATURE' - - def construct(self, data): - user = bpy.data.window_managers['WinMan'].online_users.add() - user.name = data['name'] - user.username = data['name'] - user.current_frame = data['current_frame'] - - def apply(self): - if self.pointer is None: - self.set_pointer() - - self.load(self.data,self.pointer) - - for obj in bpy.data.objects: - if obj.hide_select and obj.uuid not in self.data['selected_objects']: - obj.hide_select = False - elif not obj.hide_select and obj.uuid in self.data['selected_objects']: - obj.hide_select = True - - presence.refresh_3d_view() - - self.state = UP - - def load(self, data, target): - target.current_frame = data['current_frame'] - - 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 - data['current_frame'] = bpy.context.scene.frame_current - - return data - - def update(self): - self.pointer.is_dirty = True - - def resolve(self): - self.pointer = bpy.data.window_managers['WinMan'].online_users.get(self.data['name']) - - def is_valid(self): - return True - - - diff --git a/multi_user/delayable.py b/multi_user/delayable.py index e2c94b3..d9ee25e 100644 --- a/multi_user/delayable.py +++ b/multi_user/delayable.py @@ -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(): @@ -83,26 +83,19 @@ class DynamicRightSelectTimer(Timer): super().__init__(timout) self._last_selection = [] self._user = None - self._user_node = None self._right_strategy = RP_COMMON def execute(self): - repo = operators.client - if repo: - settings = bpy.context.window_manager.session + session = operators.client + settings = bpy.context.window_manager.session + if session: # Find user if self._user is None: - users = repo.list(filter=BlUser) - - for user in users: - user_node = repo.get(uuid=user) - if user_node.pointer: - self._user = user_node.pointer - self._user_node = user_node + self._user = session.online_users.get(settings.username) if self._right_strategy is None: - self._right_strategy = repo.get_config()[ + self._right_strategy = session.get_config()[ 'right_strategy'] if self._user: @@ -117,48 +110,65 @@ class DynamicRightSelectTimer(Timer): # change old selection right to common for obj in obj_common: - node = repo.get(uuid=obj) + node = session.get(uuid=obj) 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' - repo.change_owner( + session.change_owner( node.uuid, RP_COMMON, recursive=recursive) # change new selection to our for obj in obj_ours: - node = repo.get(uuid=obj) + node = session.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' - repo.change_owner( + session.change_owner( node.uuid, settings.username, recursive=recursive) else: return - + self._last_selection = current_selection - self._user.update_selected_objects( - bpy.context) - repo.push(self._user_node.uuid) + + 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 = repo.list(filter_owner=settings.username) + owned_keys = session.list( + filter_owner=settings.username) for key in owned_keys: - node = repo.get(uuid=key) - if not isinstance(node, BlUser): - repo.change_owner( - key, - RP_COMMON, - recursive=recursive) + 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): @@ -182,36 +192,59 @@ class Draw(Delayable): class DrawClient(Draw): def execute(self): - repo = operators.client - if repo and presence.renderer: + session = getattr(operators, 'client', None) + renderer = getattr(presence, 'renderer', None) + if session and renderer: settings = bpy.context.window_manager.session - client_list = [key for key in repo.list(filter=BlUser) if - key != settings.user_uuid] + users = session.online_users - 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']) + 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 session and renderer: + 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), + } + 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) - if client: - client.pointer.update_location() - # sync online users session_users = operators.client.online_users ui_users = bpy.context.window_manager.online_users @@ -219,13 +252,18 @@ class ClientUpdate(Timer): for index, user in enumerate(ui_users): if user.username not in session_users.keys(): ui_users.remove(index) - bpy.context.window_manager.session.presence_show_user = False - bpy.context.window_manager.session.presence_show_user = True - presence.refresh_3d_view() + + 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 \ No newline at end of file + new_key.username = user + + # TODO: event drivent 3d view refresh + presence.refresh_3d_view() diff --git a/multi_user/libs/replication b/multi_user/libs/replication index f70b4f6..2060bb6 160000 --- a/multi_user/libs/replication +++ b/multi_user/libs/replication @@ -1 +1 @@ -Subproject commit f70b4f67a6685c4c060ef29036ba3b111590e913 +Subproject commit 2060bb6efb7ff56cad69a8645afe40706dd24682 diff --git a/multi_user/operators.py b/multi_user/operators.py index c7dcfad..25ad4a8 100644 --- a/multi_user/operators.py +++ b/multi_user/operators.py @@ -36,7 +36,6 @@ modal_executor_queue = None # OPERATORS - class SessionStartOperator(bpy.types.Operator): bl_idname = "session.start" bl_label = "start" @@ -54,21 +53,15 @@ class SessionStartOperator(bpy.types.Operator): users = bpy.data.window_managers['WinMan'].online_users # TODO: Sync server clients - users.clear() - # #load our infos into the local user list - # local_user = users.add() - # local_user.name = 'localhost' - # local_user.username = settings.username - # local_user.current_frame = context.scene.frame_current - # 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_module = getattr(bl_types, type) @@ -92,6 +85,8 @@ class SessionStartOperator(bpy.types.Operator): target_type=type_module_class)) client = Session(factory=bpy_factory) + + if self.host: # Scene setup @@ -123,26 +118,14 @@ 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 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.ClientUpdate()) delayables.append(delayable.DrawClient()) delayables.append(delayable.DynamicRightSelectTimer()) @@ -288,10 +271,12 @@ 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"} diff --git a/multi_user/presence.py b/multi_user/presence.py index ffaac44..bc4272c 100644 --- a/multi_user/presence.py +++ b/multi_user/presence.py @@ -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] @@ -113,32 +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() +def get_view_matrix(): + area, region, rv3d = view3d_find() - if area and region and rv3d: - 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): - self.selected_objects = utils.get_selected_objects(context.scene) + return matrix_dumper.dump(rv3d.view_matrix) def update_presence(self, context): global renderer @@ -217,14 +199,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: @@ -273,11 +255,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), @@ -292,8 +274,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)) diff --git a/multi_user/ui.py b/multi_user/ui.py index 3ab637c..f135ceb 100644 --- a/multi_user/ui.py +++ b/multi_user/ui.py @@ -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) @@ -188,7 +187,7 @@ class SESSION_PT_user(bpy.types.Panel): # Create a simple row. row = layout.row() box = row.box() - split = box.split(factor=0.3) + split = box.split(factor=0.5) split.label(text="user") split.label(text="frame") split.label(text="ping") @@ -202,7 +201,7 @@ class SESSION_PT_user(bpy.types.Panel): user_operations.operator( "session.snapview", text="", - icon='VIEW_CAMERA').target_client = online_users[selected_user].name + icon='VIEW_CAMERA').target_client = active_user.username class SESSION_UL_users(bpy.types.UIList): @@ -219,7 +218,7 @@ class SESSION_UL_users(bpy.types.UIList): ping = str(user_stat['latency']) break - split = layout.split(factor=0.3) + split = layout.split(factor=0.5) split.label(text=item.username) split.label(text=str(item.current_frame)) split.label(text=ping) @@ -256,7 +255,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) diff --git a/multi_user/utils.py b/multi_user/utils.py index 2646597..8cd35fb 100644 --- a/multi_user/utils.py +++ b/multi_user/utils.py @@ -27,6 +27,7 @@ def has_driver(target): and target.animation_data and target.animation_data.drivers) + def find_from_attr(attr_name, attr_value, list): for item in list: if getattr(item, attr_name, None) == attr_value: @@ -56,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(): @@ -151,6 +152,7 @@ def dump_datablock_attibutes(datablock=None, attributes=[], depth=1, dickt=None) return data + def resolve_from_id(id, optionnal_type=None): for category in dir(bpy.data): root = getattr(bpy.data, category)