From 1b614f4fb69d20703e15a35cb8c1d4ef1b4f9b59 Mon Sep 17 00:00:00 2001 From: Swann Martinez Date: Sun, 27 Mar 2022 17:33:13 +0200 Subject: [PATCH] refactor: initial step rewrite user frustum drawing code to debug xr camera position --- multi_user/handlers.py | 31 ++++++++++++++---- multi_user/operators.py | 1 + multi_user/presence.py | 71 +++++++++++++---------------------------- multi_user/timers.py | 50 +++++++++++++++++++++-------- 4 files changed, 85 insertions(+), 68 deletions(-) diff --git a/multi_user/handlers.py b/multi_user/handlers.py index d6f936e..c6fb25b 100644 --- a/multi_user/handlers.py +++ b/multi_user/handlers.py @@ -23,6 +23,7 @@ from replication import porcelain from replication.constants import RP_COMMON, STATE_ACTIVE, STATE_SYNCING, UP from replication.exception import ContextError, NonAuthorizedOperationError from replication.interface import session +from .timers import XrUserUpdate from . import shared_data, utils @@ -46,14 +47,16 @@ def sanitize_deps_graph(remove_nodes: bool = False): rm_cpt += 1 except NonAuthorizedOperationError: continue - logging.info(f"Sanitize took { utils.current_milli_time()-start} ms, removed {rm_cpt} nodes") + logging.info( + f"Sanitize took { utils.current_milli_time()-start} ms, removed {rm_cpt} nodes") def update_external_dependencies(): """Force external dependencies(files such as images) evaluation """ external_types = ['WindowsPath', 'PosixPath', 'Image'] - nodes_ids = [n.uuid for n in session.repository.graph.values() if n.data['type_id'] in external_types] + nodes_ids = [n.uuid for n in session.repository.graph.values() + if n.data['type_id'] in external_types] for node_id in nodes_ids: node = session.repository.graph.get(node_id) if node and node.owner in [session.repository.username, RP_COMMON]: @@ -72,11 +75,13 @@ def on_scene_update(scene): settings = utils.get_preferences() incoming_updates = shared_data.session.applied_updates - distant_update = [getattr(u.id, 'uuid', None) for u in dependency_updates if getattr(u.id, 'uuid', None) in incoming_updates] + distant_update = [getattr(u.id, 'uuid', None) for u in dependency_updates if getattr( + u.id, 'uuid', None) in incoming_updates] if distant_update: for u in distant_update: shared_data.session.applied_updates.remove(u) - logging.debug(f"Ignoring distant update of {dependency_updates[0].id.name}") + logging.debug( + f"Ignoring distant update of {dependency_updates[0].id.name}") return # NOTE: maybe we don't need to check each update but only the first @@ -84,7 +89,8 @@ def on_scene_update(scene): update_uuid = getattr(update.id, 'uuid', None) if update_uuid: node = session.repository.graph.get(update.id.uuid) - check_common = session.repository.rdp.get_implementation(update.id).bl_check_common + check_common = session.repository.rdp.get_implementation( + update.id).bl_check_common if node and (node.owner == session.repository.username or check_common): logging.debug(f"Evaluate {update.id.name}") @@ -107,12 +113,14 @@ def on_scene_update(scene): porcelain.commit(session.repository, scn_uuid) porcelain.push(session.repository, 'origin', scn_uuid) - scene_graph_changed = [u for u in reversed(dependency_updates) if getattr(u.id, 'uuid', None) and isinstance(u.id,(bpy.types.Scene,bpy.types.Collection))] + scene_graph_changed = [u for u in reversed(dependency_updates) if getattr( + u.id, 'uuid', None) and isinstance(u.id, (bpy.types.Scene, bpy.types.Collection))] if scene_graph_changed: porcelain.purge_orphan_nodes(session.repository) update_external_dependencies() + @persistent def resolve_deps_graph(dummy): """Resolve deps graph @@ -138,6 +146,13 @@ def update_client_frame(scene): 'frame_current': scene.frame_current }) +@persistent +def xr_user_update(scene): + if session and session.state == STATE_ACTIVE: + xr_timer = XrUserUpdate() + xr_timer.register() + logging.info("XR Session timer started") + def register(): bpy.app.handlers.undo_post.append(resolve_deps_graph) @@ -145,6 +160,8 @@ def register(): bpy.app.handlers.load_pre.append(load_pre_handler) bpy.app.handlers.frame_change_pre.append(update_client_frame) + + bpy.app.handlers.xr_session_start_pre.append(xr_user_update) def unregister(): @@ -153,3 +170,5 @@ def unregister(): bpy.app.handlers.load_pre.remove(load_pre_handler) bpy.app.handlers.frame_change_pre.remove(update_client_frame) + + bpy.app.handlers.xr_session_start_pre.remove(xr_user_update) diff --git a/multi_user/operators.py b/multi_user/operators.py index 9dee28a..951ccf6 100644 --- a/multi_user/operators.py +++ b/multi_user/operators.py @@ -68,6 +68,7 @@ stop_modal_executor = False def draw_user(username, metadata, radius=0.01, intensity=10.0): + # TODO: Draw camera model from viewmatrix view_corners = metadata.get('view_corners') color = metadata.get('color', (1,1,1,0)) objects = metadata.get('selected_objects', None) diff --git a/multi_user/presence.py b/multi_user/presence.py index 2da5096..bd789ca 100644 --- a/multi_user/presence.py +++ b/multi_user/presence.py @@ -26,7 +26,7 @@ import bgl import blf import bpy import gpu -import mathutils +from mathutils import Vector, Matrix from bpy_extras import view3d_utils from gpu_extras.batch import batch_for_shader from replication.constants import (STATE_ACTIVE, STATE_AUTH, STATE_CONFIG, @@ -136,7 +136,7 @@ def bbox_from_obj(obj: bpy.types.Object, index: int = 1) -> list: (-radius, +radius, +radius), (+radius, +radius, +radius)] base = obj.matrix_world - bbox_corners = [base @ mathutils.Vector(corner) for corner in coords] + bbox_corners = [base @ Vector(corner) for corner in coords] vertex_pos = [(point.x, point.y, point.z) for point in bbox_corners] @@ -159,39 +159,12 @@ def bbox_from_instance_collection(ic: bpy.types.Object, index: int = 0) -> list: vertex_pos += vertex_pos_temp vertex_indices += vertex_indices_temp - bbox_corners = [ic.matrix_world @ mathutils.Vector(vertex) for vertex in vertex_pos] + bbox_corners = [ic.matrix_world @ Vector(vertex) for vertex in vertex_pos] vertex_pos = [(point.x, point.y, point.z) for point in bbox_corners] return vertex_pos, vertex_indices -def generate_user_camera() -> list: - """ Generate a basic camera represention of the user point of view - - :return: list of 7 points - """ - area, region, rv3d = view3d_find() - - v1 = v2 = v3 = v4 = v5 = v6 = v7 = [0, 0, 0] - - if area and region and rv3d: - width = region.width - height = region.height - - v1 = project_to_viewport(region, rv3d, (0, 0)) - v3 = project_to_viewport(region, rv3d, (0, height)) - v2 = project_to_viewport(region, rv3d, (width, height)) - v4 = project_to_viewport(region, rv3d, (width, 0)) - - v5 = project_to_viewport(region, rv3d, (width/2, height/2)) - v6 = list(rv3d.view_location) - v7 = project_to_viewport( - region, rv3d, (width/2, height/2), distance=-.8) - - coords = [v1, v2, v3, v4, v5, v6, v7] - - return coords - def project_to_screen(coords: list) -> list: """ Project 3D coordinate to 2D screen coordinates @@ -219,10 +192,10 @@ def get_bb_coords_from_obj(object: bpy.types.Object, instance: bpy.types.Object base = object.matrix_world if instance: - scale = mathutils.Matrix.Diagonal(object.matrix_world.to_scale()) + scale = Matrix.Diagonal(object.matrix_world.to_scale()) base = instance.matrix_world @ scale.to_4x4() - bbox_corners = [base @ mathutils.Vector( + bbox_corners = [base @ Vector( corner) for corner in object.bound_box] @@ -267,9 +240,14 @@ class Widget(object): class UserFrustumWidget(Widget): # Camera widget indices - indices = ((1, 3), (2, 1), (3, 0), - (2, 0), (4, 5), (1, 6), - (2, 6), (3, 6), (0, 6)) + camera_vertex = ((0, 0, 1), + (-1, -0.5, -1), (1, -0.5, -1), (1, 0.5, -1), (-1, 0.5, -1), + (0, 1, -1), + (-0.5, 0.6, -1), (0.5, 0.6, -1)) + + camera_indices = ((0, 1), (0, 2), (0, 3), (0, 4), + (1, 2), (2, 3), (3, 4), (4, 1), + (5, 6), (6, 7), (7, 5)) def __init__( self, @@ -290,27 +268,25 @@ class UserFrustumWidget(Widget): return False scene_current = self.data.get('scene_current') - view_corners = self.data.get('view_corners') + view_matrix = self.data.get('view_matrix') return (scene_current == bpy.context.scene.name or self.settings.presence_show_far_user) and \ - view_corners and \ + view_matrix and \ self.settings.presence_show_user and \ self.settings.enable_presence def draw(self): - location = self.data.get('view_corners') shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR') - positions = [tuple(coord) for coord in location] - if len(positions) != 7: - return + view_matrix = Matrix(self.data.get('view_matrix')).inverted() + coords = [view_matrix @ Vector(vertex) for vertex in self.camera_vertex] batch = batch_for_shader( shader, 'LINES', - {"pos": positions}, - indices=self.indices) + {"pos": coords}, + indices=self.camera_indices) shader.bind() shader.uniform_float("color", self.data.get('color')) @@ -405,19 +381,18 @@ class UserNameWidget(Widget): return False scene_current = self.data.get('scene_current') - view_corners = self.data.get('view_corners') + view_matrix = self.data.get('view_matrix') return (scene_current == bpy.context.scene.name or self.settings.presence_show_far_user) and \ - view_corners and \ + view_matrix and \ self.settings.presence_show_user and \ self.settings.enable_presence def draw(self): - view_corners = self.data.get('view_corners') + position = Matrix(self.data.get('view_matrix')).inverted().to_translation() color = self.data.get('color') - position = [tuple(coord) for coord in view_corners] - coords = project_to_screen(position[1]) + coords = project_to_screen(position) if coords: blf.position(0, coords[0], coords[1]+10, 0) diff --git a/multi_user/timers.py b/multi_user/timers.py index 4456515..3ff6e1c 100644 --- a/multi_user/timers.py +++ b/multi_user/timers.py @@ -28,8 +28,7 @@ from replication import porcelain from . import operators, utils from .presence import (UserFrustumWidget, UserNameWidget, UserModeWidget, UserSelectionWidget, - generate_user_camera, get_view_matrix, refresh_3d_view, - refresh_sidebar_view, renderer) + get_view_matrix, refresh_3d_view, refresh_sidebar_view, renderer) from . import shared_data @@ -276,22 +275,23 @@ class ClientUpdate(Timer): if session and renderer: if session.state in [STATE_ACTIVE, STATE_LOBBY]: - local_user = session.online_users.get( - settings.username) - + local_user = session.online_users.get(settings.username) + xr_session_state = bpy.context.window_manager.xr_session_state if not local_user: return else: for username, user_data in session.online_users.items(): if username != settings.username: - cached_user_data = self.users_metadata.get( - username) + cached_user_data = self.users_metadata.get(username) new_user_data = session.online_users[username]['metadata'] if cached_user_data is None: self.users_metadata[username] = user_data['metadata'] elif 'view_matrix' in cached_user_data and 'view_matrix' in new_user_data and cached_user_data['view_matrix'] != new_user_data['view_matrix']: refresh_3d_view() + viewer = new_user_data.get('xr') + if viewer: + logging.info(viewer) self.users_metadata[username] = user_data['metadata'] break else: @@ -300,13 +300,12 @@ class ClientUpdate(Timer): local_user_metadata = local_user.get('metadata') scene_current = bpy.context.scene.name local_user = session.online_users.get(settings.username) - current_view_corners = generate_user_camera() + current_view_matrix = get_view_matrix() # Init client metadata if not local_user_metadata or 'color' not in local_user_metadata.keys(): metadata = { - 'view_corners': get_view_matrix(), - 'view_matrix': get_view_matrix(), + 'view_matrix': current_view_matrix, 'color': (settings.client_color.r, settings.client_color.g, settings.client_color.b, @@ -322,10 +321,8 @@ class ClientUpdate(Timer): elif scene_current != local_user_metadata['scene_current']: local_user_metadata['scene_current'] = scene_current porcelain.update_user_metadata(session.repository, local_user_metadata) - elif 'view_corners' in local_user_metadata and current_view_corners != local_user_metadata['view_corners']: - local_user_metadata['view_corners'] = current_view_corners - local_user_metadata['view_matrix'] = get_view_matrix( - ) + elif 'view_matrix' in local_user_metadata and current_view_matrix != local_user_metadata['view_matrix']: + local_user_metadata['view_matrix'] = current_view_matrix porcelain.update_user_metadata(session.repository, local_user_metadata) elif bpy.context.mode != local_user_metadata['mode_current']: local_user_metadata['mode_current'] = bpy.context.mode @@ -387,3 +384,28 @@ class MainThreadExecutor(Timer): function, kwargs = self.execution_queue.get() logging.debug(f"Executing {function.__name__}") function(**kwargs) + +class XrUserUpdate(Timer): + def __init__(self, timeout=1): + # TODO: Add user refresh rate settings + super().__init__(timeout) + + def execute(self): + xr_session_state = bpy.context.window_manager.xr_session_state + + if xr_session_state and xr_session_state.is_running: + # Update user state + + porcelain.update_user_metadata( + session.repository, + {'xr': { + 'viewer_pose_location': list(xr_session_state.viewer_pose_location), + 'viewer_pose_rotation': list(xr_session_state.viewer_pose_rotation), + 'controller_0_location': list(xr_session_state.controller_grip_location_get(bpy.context, 0)), + 'controller_0_rotation': list(xr_session_state.controller_grip_rotation_get(bpy.context, 0)), + 'controller_1_location': list(xr_session_state.controller_grip_location_get(bpy.context, 1)), + 'controller_1_rotation': list(xr_session_state.controller_grip_rotation_get(bpy.context, 1))} + }) + else: + logging.info("XR Session ended, stopping user update") + self.unregister() \ No newline at end of file