Merge branch '59-timeout-issue-for-scenes-that-are-large-enough' into 'develop'

Resolve "Timeout issue for scenes that are large enough"

See merge request slumber/multi-user!14
This commit is contained in:
Swann Martinez
2020-02-25 14:01:51 +00:00
13 changed files with 306 additions and 139 deletions
-1
View File
@@ -1 +0,0 @@
{"area": null, "blend_data":
+7 -2
View File
@@ -31,7 +31,7 @@ DEPENDENCIES = {
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.setLevel(logging.WARNING)
#TODO: refactor config
# UTILITY FUNCTIONS
@@ -118,6 +118,11 @@ class SessionProps(bpy.types.PropertyGroup):
description='Distant host port',
default=5555
)
ipc_port: bpy.props.IntProperty(
name="ipc_port",
description='internal ttl port(only usefull for multiple local instances)',
default=5561
)
is_admin: bpy.props.BoolProperty(
name="is_admin",
default=False
@@ -244,7 +249,7 @@ classes = (
)
libs = os.path.dirname(os.path.abspath(__file__))+"\\libs\\replication"
libs = os.path.dirname(os.path.abspath(__file__))+"\\libs\\replication\\replication"
@persistent
def load_handler(dummy):
+3 -1
View File
@@ -4,7 +4,7 @@ import mathutils
from .. import utils
from ..libs.replication.replication.data import ReplicatedDatablock
from ..libs.replication.replication.constants import UP
from ..libs.replication.replication.constants import DIFF_BINARY
def dump_driver(driver):
dumper = utils.dump_anything.Dumper()
@@ -80,6 +80,8 @@ class BlDatablock(ReplicatedDatablock):
if self.pointer and hasattr(self.pointer, 'uuid'):
self.pointer.uuid = self.uuid
self.diff_method = DIFF_BINARY
def library_apply(self):
"""Apply stored data
+5 -2
View File
@@ -15,8 +15,11 @@ def dump_image(image):
image.save()
if image.source == "FILE":
image_path = bpy.path.abspath(image.filepath_raw)
image_directory = os.path.dirname(image_path)
os.makedirs(image_directory, exist_ok=True)
image.save()
file = open(image.filepath_raw, "rb")
file = open(image_path, "rb")
pixels = file.read()
file.close()
else:
@@ -27,7 +30,7 @@ class BlImage(BlDatablock):
bl_id = "images"
bl_class = bpy.types.Image
bl_delay_refresh = 0
bl_delay_apply = 0
bl_delay_apply = 1
bl_automatic_push = False
bl_icon = 'IMAGE_DATA'
+12 -1
View File
@@ -1,9 +1,11 @@
import bpy
import mathutils
import logging
from .. import utils
from .bl_datablock import BlDatablock
logger = logging.getLogger(__name__)
class BlLightprobe(BlDatablock):
bl_id = "lightprobes"
@@ -18,10 +20,19 @@ class BlLightprobe(BlDatablock):
def construct(self, data):
type = 'CUBE' if data['type'] == 'CUBEMAP' else data['type']
return bpy.data.lightprobes.new(data["name"], type)
# See https://developer.blender.org/D6396
if bpy.app.version[1] >= 83:
return bpy.data.lightprobes.new(data["name"], type)
else:
logger.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396")
def dump(self, pointer=None):
assert(pointer)
if bpy.app.version[1] < 83:
logger.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396")
dumper = utils.dump_anything.Dumper()
dumper.depth = 1
+24 -22
View File
@@ -3,14 +3,17 @@ import bmesh
import mathutils
from .. import utils
from ..libs.replication.replication.constants import DIFF_BINARY
from .bl_datablock import BlDatablock
def dump_mesh(mesh, data={}):
import bmesh
mesh_data = data
mesh_buffer = bmesh.new()
# https://blog.michelanders.nl/2016/02/copying-vertices-to-numpy-arrays-in_4.html
mesh_buffer.from_mesh(mesh)
uv_layer = mesh_buffer.loops.layers.uv.verify()
@@ -72,7 +75,8 @@ def dump_mesh(mesh, data={}):
uv_layers.append(uv_layer.name)
mesh_data["uv_layers"] = uv_layers
return mesh_data
# return mesh_data
class BlMesh(BlDatablock):
bl_id = "meshes"
@@ -87,15 +91,12 @@ class BlMesh(BlDatablock):
instance.uuid = self.uuid
return instance
def load(self, data, target):
def load_implementation(self, data, target):
if not target or not target.is_editmode:
# 1 - LOAD MATERIAL SLOTS
material_to_load = []
material_to_load = utils.revers(data["materials"])
target.materials.clear()
# SLots
i = 0
for m in data["material_list"]:
target.materials.append(bpy.data.materials[m])
@@ -106,7 +107,7 @@ class BlMesh(BlDatablock):
v = mesh_buffer.verts.new(data["verts"][i]["co"])
v.normal = data["verts"][i]["normal"]
mesh_buffer.verts.ensure_lookup_table()
for i in data["edges"]:
verts = mesh_buffer.verts
v1 = data["edges"][i]["verts"][0]
@@ -117,12 +118,12 @@ class BlMesh(BlDatablock):
verts = []
for v in data["faces"][p]["verts"]:
verts.append(mesh_buffer.verts[v])
if len(verts) > 0:
f = mesh_buffer.faces.new(verts)
uv_layer = mesh_buffer.loops.layers.uv.verify()
f.smooth = data["faces"][p]["smooth"]
f.normal = data["faces"][p]["normal"]
f.index = data["faces"][p]["index"]
@@ -136,21 +137,23 @@ class BlMesh(BlDatablock):
# 3 - LOAD METADATA
# uv's
for uv_layer in data['uv_layers']:
target.uv_layers.new(name=uv_layer)
utils.dump_anything.load(target.uv_layers, data['uv_layers'])
bevel_layer = mesh_buffer.verts.layers.bevel_weight.verify()
skin_layer = mesh_buffer.verts.layers.skin.verify()
utils.dump_anything.load(target, data)
def dump_implementation(self, data, pointer=None):
assert(pointer)
data = utils.dump_datablock(pointer, 2)
data = dump_mesh(pointer, data)
dumper = utils.dump_anything.Dumper()
dumper.depth = 2
dumper.include_filter = [
'name',
]
data = dumper.dump(pointer)
dump_mesh(pointer, data)
# Fix material index
m_list = []
for material in pointer.materials:
@@ -163,13 +166,12 @@ class BlMesh(BlDatablock):
def resolve_dependencies(self):
deps = []
for material in self.pointer.materials:
if material:
deps.append(material)
return deps
def is_valid(self):
return bpy.data.meshes.get(self.data['name'])
+4 -1
View File
@@ -75,7 +75,7 @@ class BlObject(BlDatablock):
if bpy.app.version[1] >= 83:
pointer = bpy.data.lightprobes[data["data"]]
else:
logger.error("Lightprobe replication only supported since 2.83")
logger.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396")
instance = bpy.data.objects.new(data["name"], pointer)
instance.uuid = self.uuid
@@ -235,6 +235,9 @@ class BlObject(BlDatablock):
'rotation_mode',
'location',
'scale',
'custom_shape',
'use_custom_shape_bone_size',
'custom_shape_scale',
group_index,
rotation
]
+24 -20
View File
@@ -3,10 +3,10 @@ import logging
import bpy
from . import operators, presence, utils
from .libs.replication.replication.constants import FETCHED, RP_COMMON
from .libs.replication.replication.constants import FETCHED, RP_COMMON, STATE_ACTIVE, STATE_SYNCING, STATE_SRV_SYNC
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.setLevel(logging.WARNING)
class Delayable():
@@ -64,15 +64,16 @@ class ApplyTimer(Timer):
super().__init__(timout)
def execute(self):
if operators.client:
nodes = operators.client.list(filter=self._type)
client = operators.client
if client and client.state['STATE'] == STATE_ACTIVE:
nodes = client.list(filter=self._type)
for node in nodes:
node_ref = operators.client.get(uuid=node)
node_ref = client.get(uuid=node)
if node_ref.state == FETCHED:
try:
operators.client.apply(node)
client.apply(node)
except Exception as e:
logger.error(
"fail to apply {}: {}".format(node_ref.uuid, e))
@@ -89,13 +90,13 @@ class DynamicRightSelectTimer(Timer):
session = operators.client
settings = bpy.context.window_manager.session
if session:
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._right_strategy is None:
self._right_strategy = session.get_config()[
self._right_strategy = session.config[
'right_strategy']
if self._user:
@@ -195,23 +196,24 @@ class DrawClient(Draw):
session = getattr(operators, 'client', None)
renderer = getattr(presence, 'renderer', None)
if session and renderer:
if session and renderer and session.state['STATE'] == STATE_ACTIVE:
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'])
if 'color' in 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):
def __init__(self, timout=.5):
super().__init__(timout)
def execute(self):
@@ -220,9 +222,9 @@ class ClientUpdate(Timer):
session = getattr(operators, 'client', None)
renderer = getattr(presence, 'renderer', None)
if session and renderer:
if session and renderer and session.state['STATE'] == STATE_ACTIVE:
# Check if session has been closes prematurely
if session.state == 0:
if session.state['STATE'] == 0:
bpy.ops.session.stop()
local_user = operators.client.online_users.get(
@@ -233,8 +235,7 @@ class ClientUpdate(Timer):
local_user_metadata = local_user.get('metadata')
current_view_corners = presence.get_view_corners()
if not local_user_metadata:
logger.info("init user metadata")
if not local_user_metadata or 'color' not in local_user_metadata.keys():
metadata = {
'view_corners': current_view_corners,
'view_matrix': presence.get_view_matrix(),
@@ -273,3 +274,6 @@ class ClientUpdate(Timer):
# TODO: event drivent 3d view refresh
presence.refresh_3d_view()
# ui update
elif session:
presence.refresh_3d_view()
+1 -1
View File
@@ -6,7 +6,7 @@ import sys
from pathlib import Path
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.setLevel(logging.WARNING)
CONFIG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config")
CONFIG = os.path.join(CONFIG_DIR, "app.yaml")
+92 -59
View File
@@ -4,36 +4,32 @@ import os
import queue
import random
import string
import subprocess
import time
from operator import itemgetter
from pathlib import Path
import msgpack
from subprocess import PIPE, Popen, TimeoutExpired
import bpy
import mathutils
from bpy.app.handlers import persistent
from bpy_extras.io_utils import ExportHelper
from . import bl_types, delayable, environment, presence, ui, utils
from .libs.replication.replication.constants import (FETCHED, STATE_ACTIVE,
STATE_INITIAL,
STATE_SYNCING)
from .libs.replication.replication.data import ReplicatedDataFactory
from .libs.replication.replication.exception import NonAuthorizedOperationError
from .libs.replication.replication.interface import Session
from .libs.replication.replication.constants import (
STATE_ACTIVE,
STATE_INITIAL,
STATE_SYNCING,
FETCHED)
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.setLevel(logging.WARNING)
client = None
delayables = []
ui_context = None
stop_modal_executor = False
modal_executor_queue = None
server_process = None
# OPERATORS
@@ -49,7 +45,7 @@ class SessionStartOperator(bpy.types.Operator):
return True
def execute(self, context):
global client, delayables, ui_context
global client, delayables, ui_context, server_process
settings = context.window_manager.session
users = bpy.data.window_managers['WinMan'].online_users
@@ -85,57 +81,54 @@ class SessionStartOperator(bpy.types.Operator):
timout=type_local_config.bl_delay_apply,
target_type=type_module_class))
client = Session(factory=bpy_factory)
client = Session(
factory=bpy_factory,
python_path=bpy.app.binary_path_python,
default_strategy=settings.right_strategy)
# Host a session
if self.host:
# Scene setup
if settings.start_empty:
utils.clean_scene()
try:
for scene in bpy.data.scenes:
scene_uuid = client.add(scene)
client.commit(scene_uuid)
client.host(
id=settings.username,
address=settings.ip,
port=settings.port,
right_strategy=settings.right_strategy
)
ipc_port=settings.ipc_port)
except Exception as e:
self.report({'ERROR'}, repr(e))
logger.error(f"Error: {e}")
settings.is_admin = True
# Join a session
else:
utils.clean_scene()
client.connect(
id=settings.username,
address=settings.ip,
port=settings.port
)
time.sleep(1)
if client.state == 0:
settings.is_admin = False
self.report(
{'ERROR'},
"A session is already hosted on this address")
return {"CANCELLED"}
if self.host:
for scene in bpy.data.scenes:
scene_uuid = client.add(scene)
# for node in client.list():
client.commit(scene_uuid)
try:
client.connect(
id=settings.username,
address=settings.ip,
port=settings.port,
ipc_port=settings.ipc_port
)
except Exception as e:
self.report({'ERROR'}, repr(e))
logger.error(f"Error: {e}")
# Background client updates service
#TODO: Refactoring
delayables.append(delayable.ClientUpdate())
delayables.append(delayable.DrawClient())
delayables.append(delayable.DynamicRightSelectTimer())
# Push all added values
client.push_all()
# Launch drawing module
if settings.enable_presence:
presence.renderer.run()
@@ -165,21 +158,23 @@ class SessionStopOperator(bpy.types.Operator):
return True
def execute(self, context):
global client, delayables, stop_modal_executor
global client, delayables, stop_modal_executor, server_process
assert(client)
stop_modal_executor = True
settings = context.window_manager.session
settings.is_admin = False
assert(client)
client.disconnect()
for d in delayables:
try:
d.unregister()
except:
continue
presence.renderer.stop()
presence.renderer.stop()
try:
client.disconnect()
except Exception as e:
self.report({'ERROR'}, repr(e))
return {"FINISHED"}
@@ -321,7 +316,7 @@ class SessionSnapTimeOperator(bpy.types.Operator):
settings.user_snap_running = True
wm = context.window_manager
self._timer = wm.event_timer_add(0.1, window=context.window)
self._timer = wm.event_timer_add(0.05, window=context.window)
wm.modal_handler_add(self)
return {'RUNNING_MODAL'}
@@ -404,17 +399,18 @@ class ApplyArmatureOperator(bpy.types.Operator):
if event.type == 'TIMER':
global client
nodes = client.list(filter=bl_types.bl_armature.BlArmature)
if client and client.state['STATE'] == STATE_ACTIVE:
nodes = client.list(filter=bl_types.bl_armature.BlArmature)
for node in nodes:
node_ref = client.get(uuid=node)
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))
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'}
@@ -444,7 +440,6 @@ classes = (
SessionCommit,
ApplyArmatureOperator,
)
@@ -452,7 +447,7 @@ classes = (
def load_pre_handler(dummy):
global client
if client and client.state in [STATE_ACTIVE, STATE_SYNCING]:
if client and client.state['STATE'] in [STATE_ACTIVE, STATE_SYNCING]:
bpy.ops.session.stop()
@@ -466,18 +461,52 @@ def sanitize_deps_graph(dummy):
"""
global client
if client and client.state in [STATE_ACTIVE]:
if client and client.state['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:
if client and client.state['STATE'] == STATE_ACTIVE:
client.update_user_metadata({
'frame_current': scene.frame_current
})
@persistent
def depsgraph_evaluation(scene):
if client and client.state['STATE'] == STATE_ACTIVE:
context = bpy.context
blender_depsgraph = bpy.context.view_layer.depsgraph
dependency_updates = [u for u in blender_depsgraph.updates]
session_infos = bpy.context.window_manager.session
# NOTE: maybe we don't need to check each update but only the first
for update in reversed(dependency_updates):
# Is the object tracked ?
if update.id.uuid:
# Retrieve local version
node = client.get(update.id.uuid)
# Check our right on this update:
# - if its ours or ( under common and diff), launch the
# update process
# - if its to someone else, ignore the update (go deeper ?)
if node.owner == session_infos.username:
# Avoid slow geometry update
if 'EDIT' in context.mode:
break
logger.error("UPDATE: MODIFIFY {}".format(type(update.id)))
# client.commit(node.uuid)
# client.push(node.uuid)
else:
# Distant update
continue
# else:
# # New items !
# logger.error("UPDATE: ADD")C.obj
def register():
from bpy.utils import register_class
@@ -491,11 +520,13 @@ def register():
bpy.app.handlers.frame_change_pre.append(update_client_frame)
# bpy.app.handlers.depsgraph_update_post.append(depsgraph_evaluation)
def unregister():
global client
if client and client.state == 2:
if client and client.state['STATE'] == 2:
client.disconnect()
client = None
@@ -510,6 +541,8 @@ def unregister():
bpy.app.handlers.frame_change_pre.remove(update_client_frame)
# bpy.app.handlers.depsgraph_update_post.remove(depsgraph_evaluation)
if __name__ == "__main__":
register()
+132 -27
View File
@@ -2,7 +2,11 @@ import bpy
from . import operators
from .libs.replication.replication.constants import (ADDED, ERROR, FETCHED,
MODIFIED, RP_COMMON, UP)
MODIFIED, RP_COMMON, UP,
STATE_ACTIVE, STATE_AUTH,
STATE_CONFIG, STATE_SYNCING,
STATE_INITIAL, STATE_SRV_SYNC,
STATE_WAITING, STATE_QUITTING)
ICONS_PROP_STATES = ['TRIA_DOWN', # ADDED
'TRIA_UP', # COMMITED
@@ -11,6 +15,44 @@ ICONS_PROP_STATES = ['TRIA_DOWN', # ADDED
'FILE_REFRESH', # UP
'TRIA_UP'] # CHANGED
def printProgressBar (iteration, total, prefix = '', suffix = '', decimals = 1, length = 100, fill = '█', fill_empty=' '):
"""
Call in a loop to create terminal progress bar
@params:
iteration - Required : current iteration (Int)
total - Required : total iterations (Int)
prefix - Optional : prefix string (Str)
suffix - Optional : suffix string (Str)
decimals - Optional : positive number of decimals in percent complete (Int)
length - Optional : character length of bar (Int)
fill - Optional : bar fill character (Str)
From here:
https://gist.github.com/greenstick/b23e475d2bfdc3a82e34eaa1f6781ee4
"""
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
filledLength = int(length * iteration // total)
bar = fill * filledLength + fill_empty * (length - filledLength)
return '{} |{}| {}/{}{}'.format(prefix, bar, iteration,total, suffix)
def get_state_str(state):
state_str = 'None'
if state == STATE_WAITING:
state_str = 'WARMING UP DATA'
elif state == STATE_SYNCING:
state_str = 'FETCHING FROM SERVER'
elif state == STATE_AUTH:
state_str = 'AUTHENTIFICATION'
elif state == STATE_CONFIG:
state_str = 'CONFIGURATION'
elif state == STATE_ACTIVE:
state_str = 'ONLINE'
elif state == STATE_SRV_SYNC:
state_str = 'PUSHING TO SERVER'
elif state == STATE_INITIAL:
state_str = 'INIT'
elif state == STATE_QUITTING:
state_str = 'QUITTING SESSION'
return state_str
class SESSION_PT_settings(bpy.types.Panel):
"""Settings panel"""
@@ -31,22 +73,55 @@ class SESSION_PT_settings(bpy.types.Panel):
if hasattr(context.window_manager, 'session'):
# STATE INITIAL
if not operators.client \
or (operators.client and operators.client.state == 0):
or (operators.client and operators.client.state['STATE'] == STATE_INITIAL):
pass
else:
cli_state = operators.client.state
row.label(text=f"Status : {get_state_str(cli_state['STATE'])}")
row = layout.row()
current_state = cli_state['STATE']
# STATE ACTIVE
if operators.client.state == 2:
row = layout.row()
if current_state == STATE_ACTIVE:
row.operator("session.stop", icon='QUIT', text="Exit")
row = layout.row()
# STATE SYNCING
else:
status = "connecting..."
row.label(text=status)
# CONNECTION STATE
elif current_state in [
STATE_SRV_SYNC,
STATE_SYNCING,
STATE_AUTH,
STATE_CONFIG,
STATE_WAITING]:
if cli_state['STATE'] in [STATE_SYNCING,STATE_SRV_SYNC,STATE_WAITING]:
box = row.box()
box.label(text=printProgressBar(
cli_state['CURRENT'],
cli_state['TOTAL'],
length=16
))
row = layout.row()
row.operator("session.stop", icon='QUIT', text="CANCEL")
elif current_state == STATE_QUITTING:
row = layout.row()
box = row.box()
num_online_services = 0
for name, state in operators.client.services_state.items():
if state == STATE_ACTIVE:
num_online_services += 1
total_online_services = len(operators.client.services_state)
box.label(text=printProgressBar(
total_online_services-num_online_services,
total_online_services,
length=16
))
class SESSION_PT_settings_network(bpy.types.Panel):
bl_idname = "MULTIUSER_SETTINGS_NETWORK_PT_panel"
@@ -59,7 +134,7 @@ class SESSION_PT_settings_network(bpy.types.Panel):
@classmethod
def poll(cls, context):
return not operators.client \
or (operators.client and operators.client.state == 0)
or (operators.client and operators.client.state['STATE'] == 0)
def draw(self, context):
layout = self.layout
@@ -71,24 +146,24 @@ class SESSION_PT_settings_network(bpy.types.Panel):
row.prop(settings, "session_mode", expand=True)
row = layout.row()
box = row.box()
row = box.row()
row.prop(settings, "ip", text="IP")
row = box.row()
row.label(text="Port:")
row.prop(settings, "port", text="")
row = box.row()
row.label(text="IPC Port:")
row.prop(settings, "ipc_port", text="")
if settings.session_mode == 'HOST':
box = row.box()
row = box.row()
row.label(text="Start empty:")
row.prop(settings, "start_empty", text="")
row = box.row()
row.label(text="Port:")
row.prop(settings, "port", text="")
row = box.row()
row.operator("session.start", text="HOST").host = True
else:
box = row.box()
row = box.row()
row.prop(settings, "ip", text="IP")
row = box.row()
row.label(text="Port:")
row.prop(settings, "port", text="")
row = box.row()
row.operator("session.start", text="CONNECT").host = False
@@ -104,7 +179,7 @@ class SESSION_PT_settings_user(bpy.types.Panel):
@classmethod
def poll(cls, context):
return not operators.client \
or (operators.client and operators.client.state == 0)
or (operators.client and operators.client.state['STATE'] == 0)
def draw(self, context):
layout = self.layout
@@ -132,7 +207,7 @@ class SESSION_PT_settings_replication(bpy.types.Panel):
@classmethod
def poll(cls, context):
return not operators.client \
or (operators.client and operators.client.state == 0)
or (operators.client and operators.client.state['STATE'] == 0)
def draw(self, context):
layout = self.layout
@@ -174,7 +249,7 @@ class SESSION_PT_user(bpy.types.Panel):
@classmethod
def poll(cls, context):
return operators.client and operators.client.state == 2
return operators.client and operators.client.state['STATE'] == 2
def draw(self, context):
layout = self.layout
@@ -223,7 +298,7 @@ class SESSION_UL_users(bpy.types.UIList):
if user:
ping = str(user['latency'])
metadata = user.get('metadata')
if metadata:
if metadata and 'frame_current' in metadata:
frame_current = str(metadata['frame_current'])
split = layout.split(factor=0.5)
@@ -243,7 +318,8 @@ class SESSION_PT_presence(bpy.types.Panel):
@classmethod
def poll(cls, context):
return True
return not operators.client \
or (operators.client and operators.client.state['STATE'] in [STATE_INITIAL, STATE_ACTIVE])
def draw_header(self, context):
self.layout.prop(context.window_manager.session, "enable_presence", text="")
@@ -258,6 +334,34 @@ class SESSION_PT_presence(bpy.types.Panel):
col.prop(settings,"presence_show_user")
row = layout.row()
class SESSION_PT_services(bpy.types.Panel):
bl_idname = "MULTIUSER_SERVICE_PT_panel"
bl_label = "Services"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Multiuser"
bl_parent_id = 'MULTIUSER_SETTINGS_PT_panel'
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
return operators.client and operators.client.state['STATE'] == 2
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.
for name, state in operators.client.services_state.items():
row = layout.row()
row.label(text=name)
row.label(text=get_state_str(state))
def draw_property(context, parent, property_uuid, level=0):
settings = context.window_manager.session
@@ -330,7 +434,7 @@ class SESSION_PT_outliner(bpy.types.Panel):
@classmethod
def poll(cls, context):
return operators.client and operators.client.state == 2
return operators.client and operators.client.state['STATE'] == 2
def draw_header(self, context):
self.layout.label(text="", icon='OUTLINER_OB_GROUP_INSTANCE')
@@ -385,7 +489,8 @@ classes = (
SESSION_PT_presence,
SESSION_PT_settings_replication,
SESSION_PT_user,
SESSION_PT_outliner
SESSION_PT_outliner,
SESSION_PT_services
)
+1 -1
View File
@@ -14,7 +14,7 @@ from . import environment, presence
from .libs import dump_anything
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.setLevel(logging.WARNING)
def has_action(target):
return (hasattr(target, 'animation_data')