multi-user/multi_user/delayable.py

386 lines
15 KiB
Python
Raw Normal View History

2020-03-20 21:56:50 +08:00
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# ##### END GPL LICENSE BLOCK #####
import logging
2019-08-23 18:28:57 +08:00
2019-09-30 19:35:50 +08:00
import bpy
from . import utils
from .presence import (renderer,
UserFrustumWidget,
UserNameWidget,
UserSelectionWidget,
refresh_3d_view,
generate_user_camera,
get_view_matrix,
refresh_sidebar_view)
from . import operators
2020-07-10 22:50:09 +08:00
from replication.constants import (FETCHED,
2020-09-18 04:47:11 +08:00
UP,
RP_COMMON,
STATE_INITIAL,
STATE_QUITTING,
STATE_ACTIVE,
STATE_SYNCING,
STATE_LOBBY,
STATE_SRV_SYNC)
2019-08-23 18:28:57 +08:00
from replication.interface import session
from replication.exception import NonAuthorizedOperationError
def is_annotating(context: bpy.types.Context):
2020-11-19 02:13:22 +08:00
""" Check if the annotate mode is enabled
"""
return bpy.context.workspace.tools.from_space_view3d_mode('OBJECT', create=False).idname == 'builtin.annotate'
class Delayable():
2019-08-14 21:01:30 +08:00
"""Delayable task interface
"""
def register(self):
raise NotImplementedError
def execute(self):
raise NotImplementedError
def unregister(self):
raise NotImplementedError
2019-08-23 18:28:57 +08:00
class Timer(Delayable):
"""Timer binder interface for blender
Run a bpy.app.Timer in the background looping at the given rate
"""
2019-08-23 18:28:57 +08:00
def __init__(self, duration=1):
super().__init__()
self._timeout = duration
self.is_running = False
def register(self):
"""Register the timer into the blender timer system
"""
if not self.is_running:
bpy.app.timers.register(self.main)
self.is_running = True
logging.debug(f"Register {self.__class__.__name__}")
else:
logging.debug(
f"Timer {self.__class__.__name__} already registered")
def main(self):
try:
self.execute()
except Exception as e:
logging.error(e)
self.unregister()
session.disconnect()
else:
if self.is_running:
return self._timeout
def execute(self):
"""Main timer loop
"""
raise NotImplementedError
def unregister(self):
"""Unnegister the timer of the blender timer system
"""
if bpy.app.timers.is_registered(self.main):
bpy.app.timers.unregister(self.main)
self.is_running = False
2019-08-23 18:28:57 +08:00
2020-12-10 22:50:43 +08:00
class SessionRecordGraphTimer(Timer):
2020-12-12 06:02:20 +08:00
def __init__(self, timout=10, filepath=None):
2020-12-10 22:50:43 +08:00
self._filepath = filepath
super().__init__(timout)
def execute(self):
2020-12-12 06:02:20 +08:00
operators.dump_db(self._filepath)
2019-09-30 19:35:50 +08:00
class ApplyTimer(Timer):
2019-08-23 18:28:57 +08:00
def __init__(self, timout=1, target_type=None):
self._type = target_type
super().__init__(timout)
def execute(self):
if session and session.state['STATE'] == STATE_ACTIVE:
if self._type:
nodes = session.list(filter=self._type)
else:
nodes = session.list()
for node in nodes:
node_ref = session.get(uuid=node)
if node_ref.state == FETCHED:
2019-10-04 00:30:46 +08:00
try:
session.apply(node)
2019-10-14 19:08:31 +08:00
except Exception as e:
2020-04-22 23:04:14 +08:00
logging.error(f"Fail to apply {node_ref.uuid}: {e}")
else:
if self._type.bl_reload_parent:
parents = []
2020-09-18 04:47:11 +08:00
for n in session.list():
deps = session.get(uuid=n).dependencies
if deps and node in deps:
session.apply(n, force=True)
class DynamicRightSelectTimer(Timer):
2019-10-02 20:01:45 +08:00
def __init__(self, timout=.1):
super().__init__(timout)
self._last_selection = []
self._user = None
2020-11-19 02:13:22 +08:00
self._annotating = False
def execute(self):
settings = utils.get_preferences()
2020-02-08 00:56:58 +08:00
if session and session.state['STATE'] == STATE_ACTIVE:
# Find user
if self._user is None:
self._user = session.online_users.get(settings.username)
if self._user:
2020-11-19 02:13:22 +08:00
ctx = bpy.context
annotation_gp = ctx.scene.grease_pencil
2020-11-19 02:13:22 +08:00
# if an annotation exist and is tracked
if annotation_gp and annotation_gp.uuid:
registered_gp = session.get(uuid=annotation_gp.uuid)
if is_annotating(bpy.context):
2020-11-19 02:13:22 +08:00
# try to get the right on it
if registered_gp.owner == RP_COMMON:
self._annotating = True
logging.debug(
"Getting the right on the annotation GP")
2020-11-19 02:13:22 +08:00
session.change_owner(
registered_gp.uuid,
settings.username,
ignore_warnings=True,
affect_dependencies=False)
elif self._annotating:
2020-11-19 02:13:22 +08:00
session.change_owner(
registered_gp.uuid,
RP_COMMON,
ignore_warnings=True,
affect_dependencies=False)
2020-11-19 02:13:22 +08:00
current_selection = utils.get_selected_objects(
bpy.context.scene,
bpy.data.window_managers['WinMan'].windows[0].view_layer
)
if current_selection != self._last_selection:
obj_common = [
o for o in self._last_selection if o not in current_selection]
obj_ours = [
o for o in current_selection if o not in self._last_selection]
# change old selection right to common
for obj in obj_common:
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'
try:
session.change_owner(
node.uuid,
RP_COMMON,
ignore_warnings=True,
affect_dependencies=recursive)
except NonAuthorizedOperationError:
logging.warning(
f"Not authorized to change {node} owner")
# change new selection to our
for obj in obj_ours:
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'
try:
session.change_owner(
node.uuid,
settings.username,
ignore_warnings=True,
affect_dependencies=recursive)
except NonAuthorizedOperationError:
logging.warning(
f"Not authorized to change {node} owner")
else:
return
self._last_selection = current_selection
user_metadata = {
'selected_objects': current_selection
}
session.update_user_metadata(user_metadata)
logging.debug("Update selection")
# Fix deselection until right managment refactoring (with Roles concepts)
if len(current_selection) == 0 :
owned_keys = session.list(
filter_owner=settings.username)
for key in owned_keys:
node = session.get(uuid=key)
try:
session.change_owner(
key,
RP_COMMON,
ignore_warnings=True,
affect_dependencies=recursive)
except NonAuthorizedOperationError:
logging.warning(
f"Not authorized to change {key} owner")
2019-08-23 18:28:57 +08:00
2020-10-13 00:56:42 +08:00
for obj in bpy.data.objects:
object_uuid = getattr(obj, 'uuid', None)
if object_uuid:
is_selectable = not session.is_readonly(object_uuid)
if obj.hide_select != is_selectable:
obj.hide_select = is_selectable
2019-10-14 19:08:31 +08:00
2019-10-03 19:23:59 +08:00
class ClientUpdate(Timer):
def __init__(self, timout=.1):
2019-10-03 19:23:59 +08:00
super().__init__(timout)
self.handle_quit = False
self.users_metadata = {}
def execute(self):
settings = utils.get_preferences()
2020-01-23 01:37:46 +08:00
2020-06-16 23:15:32 +08:00
if session and renderer:
if session.state['STATE'] in [STATE_ACTIVE, STATE_LOBBY]:
local_user = session.online_users.get(
2020-09-18 04:47:11 +08:00
settings.username)
2020-06-16 23:15:32 +08:00
2020-04-15 00:56:20 +08:00
if not local_user:
return
else:
for username, user_data in session.online_users.items():
2020-04-15 00:56:20 +08:00
if username != settings.username:
2020-09-18 04:47:11 +08:00
cached_user_data = self.users_metadata.get(
username)
new_user_data = session.online_users[username]['metadata']
2020-06-16 23:15:32 +08:00
2020-04-15 00:56:20 +08:00
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()
2020-04-15 00:56:20 +08:00
self.users_metadata[username] = user_data['metadata']
break
else:
self.users_metadata[username] = user_data['metadata']
local_user_metadata = local_user.get('metadata')
scene_current = bpy.context.scene.name
2020-09-18 04:47:11 +08:00
local_user = session.online_users.get(settings.username)
current_view_corners = generate_user_camera()
2020-06-16 23:15:32 +08:00
2020-04-15 00:56:20 +08:00
# 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(),
2020-04-15 00:56:20 +08:00
'color': (settings.client_color.r,
2020-09-18 04:47:11 +08:00
settings.client_color.g,
settings.client_color.b,
1),
2020-06-16 23:15:32 +08:00
'frame_current': bpy.context.scene.frame_current,
2020-04-15 00:56:20 +08:00
'scene_current': scene_current
}
session.update_user_metadata(metadata)
# Update client representation
# Update client current scene
elif scene_current != local_user_metadata['scene_current']:
local_user_metadata['scene_current'] = scene_current
2020-06-16 23:15:32 +08:00
session.update_user_metadata(local_user_metadata)
elif 'view_corners' in local_user_metadata and current_view_corners != local_user_metadata['view_corners']:
2020-04-15 00:56:20 +08:00
local_user_metadata['view_corners'] = current_view_corners
local_user_metadata['view_matrix'] = get_view_matrix(
2020-09-18 04:47:11 +08:00
)
2020-04-15 00:56:20 +08:00
session.update_user_metadata(local_user_metadata)
2020-09-18 04:47:11 +08:00
class SessionStatusUpdate(Timer):
def __init__(self, timout=1):
super().__init__(timout)
2020-06-16 23:15:32 +08:00
def execute(self):
refresh_sidebar_view()
2020-06-16 23:15:32 +08:00
2020-09-18 04:47:11 +08:00
class SessionUserSync(Timer):
def __init__(self, timout=1):
super().__init__(timout)
self.settings = utils.get_preferences()
2020-06-16 23:15:32 +08:00
def execute(self):
if session and renderer:
# sync online users
session_users = session.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() and \
user.username != self.settings.username:
renderer.remove_widget(f"{user.username}_cam")
renderer.remove_widget(f"{user.username}_select")
renderer.remove_widget(f"{user.username}_name")
ui_users.remove(index)
break
for user in session_users:
if user not in ui_users:
new_key = ui_users.add()
new_key.name = user
2020-09-18 04:47:11 +08:00
new_key.username = user
if user != self.settings.username:
renderer.add_widget(
f"{user}_cam", UserFrustumWidget(user))
renderer.add_widget(
f"{user}_select", UserSelectionWidget(user))
renderer.add_widget(
f"{user}_name", UserNameWidget(user))
class MainThreadExecutor(Timer):
def __init__(self, timout=1, execution_queue=None):
super().__init__(timout)
self.execution_queue = execution_queue
def execute(self):
while not self.execution_queue.empty():
function, kwargs = self.execution_queue.get()
logging.debug(f"Executing {function.__name__}")
function(**kwargs)