Merge branch '218-new-ui-ux-implementation' into 'develop'

New UI/UX implementation

See merge request slumber/multi-user!140
This commit is contained in:
Swann Martinez 2021-07-26 15:52:19 +00:00
commit 4c0d4cb1c7
13 changed files with 803 additions and 438 deletions

View File

@ -61,6 +61,7 @@ def register():
from . import operators from . import operators
from . import handlers from . import handlers
from . import ui from . import ui
from . import icons
from . import preferences from . import preferences
from . import addon_updater_ops from . import addon_updater_ops
@ -70,6 +71,7 @@ def register():
operators.register() operators.register()
handlers.register() handlers.register()
ui.register() ui.register()
icons.register()
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
raise Exception(module_error_msg) raise Exception(module_error_msg)
logging.error(module_error_msg) logging.error(module_error_msg)
@ -83,7 +85,9 @@ def register():
type=preferences.SessionUser type=preferences.SessionUser
) )
bpy.types.WindowManager.user_index = bpy.props.IntProperty() bpy.types.WindowManager.user_index = bpy.props.IntProperty()
bpy.types.WindowManager.server_index = bpy.props.IntProperty()
bpy.types.TOPBAR_MT_file_import.append(operators.menu_func_import) bpy.types.TOPBAR_MT_file_import.append(operators.menu_func_import)
bpy.types.TOPBAR_MT_file_export.append(operators.menu_func_export)
def unregister(): def unregister():
@ -91,14 +95,17 @@ def unregister():
from . import operators from . import operators
from . import handlers from . import handlers
from . import ui from . import ui
from . import icons
from . import preferences from . import preferences
from . import addon_updater_ops from . import addon_updater_ops
bpy.types.TOPBAR_MT_file_import.remove(operators.menu_func_import) bpy.types.TOPBAR_MT_file_import.remove(operators.menu_func_import)
bpy.types.TOPBAR_MT_file_export.remove(operators.menu_func_export)
presence.unregister() presence.unregister()
addon_updater_ops.unregister() addon_updater_ops.unregister()
ui.unregister() ui.unregister()
icons.unregister()
handlers.unregister() handlers.unregister()
operators.unregister() operators.unregister()
preferences.unregister() preferences.unregister()
@ -107,5 +114,6 @@ def unregister():
del bpy.types.ID.uuid del bpy.types.ID.uuid
del bpy.types.WindowManager.online_users del bpy.types.WindowManager.online_users
del bpy.types.WindowManager.user_index del bpy.types.WindowManager.user_index
del bpy.types.WindowManager.server_index
environment.unregister() environment.unregister()

View File

@ -0,0 +1,45 @@
# ##### 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 bpy
import os
from pathlib import Path
import bpy.utils.previews
def register():
global icons_col
pcoll = bpy.utils.previews.new()
icons_dir = os.path.join(os.path.dirname(__file__), ".")
for png in Path(icons_dir).rglob("*.png"):
pcoll.load(png.stem, str(png), "IMAGE")
icons_col = pcoll
def unregister():
global icons_col
try:
bpy.utils.previews.remove(icons_col)
except Exception:
pass
icons_col = None

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -1 +1 @@
Subproject commit 15dc5c62c07a3e0025a82d87022f5073c019e7e9 Subproject commit d283d39e9cac6a5a8dcedf049220c764063ac8a9

View File

@ -20,6 +20,7 @@ import asyncio
import copy import copy
import gzip import gzip
import logging import logging
from multi_user.preferences import ServerPreset
import os import os
import queue import queue
import random import random
@ -28,6 +29,7 @@ import string
import sys import sys
import time import time
import traceback import traceback
from uuid import uuid4
from datetime import datetime from datetime import datetime
from operator import itemgetter from operator import itemgetter
from pathlib import Path from pathlib import Path
@ -56,7 +58,7 @@ from replication.repository import Repository
from . import bl_types, environment, shared_data, timers, ui, utils from . import bl_types, environment, shared_data, timers, ui, utils
from .handlers import on_scene_update, sanitize_deps_graph from .handlers import on_scene_update, sanitize_deps_graph
from .presence import SessionStatusWidget, renderer, view3d_find from .presence import SessionStatusWidget, renderer, view3d_find, refresh_sidebar_view
from .timers import registry from .timers import registry
background_execution_queue = Queue() background_execution_queue = Queue()
@ -81,7 +83,6 @@ def session_callback(name):
def initialize_session(): def initialize_session():
"""Session connection init hander """Session connection init hander
""" """
settings = utils.get_preferences()
runtime_settings = bpy.context.window_manager.session runtime_settings = bpy.context.window_manager.session
if not runtime_settings.is_host: if not runtime_settings.is_host:
@ -108,7 +109,6 @@ def initialize_session():
for d in deleyables: for d in deleyables:
d.register() d.register()
# Step 5: Clearing history # Step 5: Clearing history
utils.flush_history() utils.flush_history()
@ -142,16 +142,143 @@ def on_connection_end(reason="none"):
if isinstance(handler, logging.FileHandler): if isinstance(handler, logging.FileHandler):
logger.removeHandler(handler) logger.removeHandler(handler)
if reason != "user": if reason != "user":
bpy.ops.session.notify('INVOKE_DEFAULT', message=f"Disconnected from session. Reason: {reason}. ") bpy.ops.session.notify('INVOKE_DEFAULT', message=f"Disconnected from session. Reason: {reason}. ") #TODO: change op session.notify to add ui + change reason (in replication->interface)
def setup_logging():
""" Session setup logging (host/connect)
"""
settings = utils.get_preferences()
logger = logging.getLogger()
if len(logger.handlers) == 1:
formatter = logging.Formatter(
fmt='%(asctime)s CLIENT %(levelname)-8s %(message)s',
datefmt='%H:%M:%S'
)
start_time = datetime.now().strftime('%Y_%m_%d_%H-%M-%S')
log_directory = os.path.join(
settings.cache_directory,
f"multiuser_{start_time}.log")
os.makedirs(settings.cache_directory, exist_ok=True)
handler = logging.FileHandler(log_directory, mode='w')
logger.addHandler(handler)
for handler in logger.handlers:
if isinstance(handler, logging.NullHandler):
continue
handler.setFormatter(formatter)
def setup_timer():
""" Session setup timer (host/connect)
"""
settings = utils.get_preferences()
deleyables.append(timers.ClientUpdate())
deleyables.append(timers.DynamicRightSelectTimer())
deleyables.append(timers.ApplyTimer(timeout=settings.depsgraph_update_rate))
session_update = timers.SessionStatusUpdate()
session_user_sync = timers.SessionUserSync()
session_background_executor = timers.MainThreadExecutor(execution_queue=background_execution_queue)
session_listen = timers.SessionListenTimer(timeout=0.001)
session_listen.register()
session_update.register()
session_user_sync.register()
session_background_executor.register()
deleyables.append(session_background_executor)
deleyables.append(session_update)
deleyables.append(session_user_sync)
deleyables.append(session_listen)
deleyables.append(timers.AnnotationUpdates())
def get_active_server_preset(context):
active_index = context.window_manager.server_index
server_presets = utils.get_preferences().server_preset
active_index = active_index if active_index <= len(server_presets)-1 else 0
return server_presets[active_index]
# OPERATORS # OPERATORS
class SessionStartOperator(bpy.types.Operator): class SessionConnectOperator(bpy.types.Operator):
bl_idname = "session.start" bl_idname = "session.connect"
bl_label = "start" bl_label = "connect"
bl_description = "connect to a net server" bl_description = "connect to a net server"
host: bpy.props.BoolProperty(default=False) @classmethod
def poll(cls, context):
return True
def execute(self, context):
global deleyables
settings = utils.get_preferences()
users = bpy.data.window_managers['WinMan'].online_users
active_server = get_active_server_preset()
admin_pass = active_server.admin_password if active_server.use_admin_password else None
server_pass = active_server.server_password if active_server.use_server_password else None
users.clear()
deleyables.clear()
setup_logging()
bpy_protocol = bl_types.get_data_translation_protocol()
# Check if supported_datablocks are up to date before starting the
# the session
for dcc_type_id in bpy_protocol.implementations.keys():
if dcc_type_id not in settings.supported_datablocks:
logging.info(f"{dcc_type_id} not found, \
regenerate type settings...")
settings.generate_supported_types()
if bpy.app.version[1] >= 91:
python_binary_path = sys.executable
else:
python_binary_path = bpy.app.binary_path_python
repo = Repository(
rdp=bpy_protocol,
username=settings.username)
# Join a session
if not active_server.use_admin_password:
utils.clean_scene()
try:
porcelain.remote_add(
repo,
'origin',
active_server.ip,
active_server.port,
server_password=server_pass,
admin_password=admin_pass)
session.connect(
repository= repo,
timeout=settings.connection_timeout,
server_password=server_pass,
admin_password=admin_pass
)
except Exception as e:
self.report({'ERROR'}, str(e))
logging.error(str(e))
# Background client updates service
setup_timer()
return {"FINISHED"}
class SessionHostOperator(bpy.types.Operator):
bl_idname = "session.host"
bl_label = "host"
bl_description = "host server"
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -163,33 +290,13 @@ class SessionStartOperator(bpy.types.Operator):
settings = utils.get_preferences() settings = utils.get_preferences()
runtime_settings = context.window_manager.session runtime_settings = context.window_manager.session
users = bpy.data.window_managers['WinMan'].online_users users = bpy.data.window_managers['WinMan'].online_users
admin_pass = settings.password admin_pass = settings.host_admin_password if settings.host_use_admin_password else None
server_pass = settings.host_server_password if settings.host_use_server_password else None
users.clear() users.clear()
deleyables.clear() deleyables.clear()
logger = logging.getLogger() setup_logging()
if len(logger.handlers) == 1:
formatter = logging.Formatter(
fmt='%(asctime)s CLIENT %(levelname)-8s %(message)s',
datefmt='%H:%M:%S'
)
start_time = datetime.now().strftime('%Y_%m_%d_%H-%M-%S')
log_directory = os.path.join(
settings.cache_directory,
f"multiuser_{start_time}.log")
os.makedirs(settings.cache_directory, exist_ok=True)
handler = logging.FileHandler(log_directory, mode='w')
logger.addHandler(handler)
for handler in logger.handlers:
if isinstance(handler, logging.NullHandler):
continue
handler.setFormatter(formatter)
bpy_protocol = bl_types.get_data_translation_protocol() bpy_protocol = bl_types.get_data_translation_protocol()
@ -212,80 +319,38 @@ class SessionStartOperator(bpy.types.Operator):
username=settings.username) username=settings.username)
# Host a session # Host a session
if self.host: if settings.init_method == 'EMPTY':
if settings.init_method == 'EMPTY': utils.clean_scene()
utils.clean_scene()
runtime_settings.is_host = True try:
runtime_settings.internet_ip = environment.get_ip() # Init repository
for scene in bpy.data.scenes:
porcelain.add(repo, scene)
try: porcelain.remote_add(
# Init repository repo,
for scene in bpy.data.scenes: 'origin',
porcelain.add(repo, scene) '127.0.0.1',
settings.host_port,
porcelain.remote_add( server_password=server_pass,
repo, admin_password=admin_pass)
'origin', session.host(
'127.0.0.1', repository= repo,
settings.port, remote='origin',
admin_password=admin_pass) timeout=settings.connection_timeout,
session.host( server_password=server_pass,
repository= repo, admin_password=admin_pass,
remote='origin', cache_directory=settings.cache_directory,
timeout=settings.connection_timeout, server_log_level=logging.getLevelName(
password=admin_pass, logging.getLogger().level),
cache_directory=settings.cache_directory, )
server_log_level=logging.getLevelName( except Exception as e:
logging.getLogger().level), self.report({'ERROR'}, repr(e))
) logging.error(f"Error: {e}")
except Exception as e: traceback.print_exc()
self.report({'ERROR'}, repr(e))
logging.error(f"Error: {e}")
traceback.print_exc()
# Join a session
else:
if not runtime_settings.admin:
utils.clean_scene()
# regular session, no password needed
admin_pass = None
try:
porcelain.remote_add(
repo,
'origin',
settings.ip,
settings.port,
admin_password=admin_pass)
session.connect(
repository= repo,
timeout=settings.connection_timeout,
password=admin_pass
)
except Exception as e:
self.report({'ERROR'}, str(e))
logging.error(str(e))
# Background client updates service # Background client updates service
deleyables.append(timers.ClientUpdate()) setup_timer()
deleyables.append(timers.DynamicRightSelectTimer())
deleyables.append(timers.ApplyTimer(timeout=settings.depsgraph_update_rate))
session_update = timers.SessionStatusUpdate()
session_user_sync = timers.SessionUserSync()
session_background_executor = timers.MainThreadExecutor(execution_queue=background_execution_queue)
session_listen = timers.SessionListenTimer(timeout=0.001)
session_listen.register()
session_update.register()
session_user_sync.register()
session_background_executor.register()
deleyables.append(session_background_executor)
deleyables.append(session_update)
deleyables.append(session_user_sync)
deleyables.append(session_listen)
deleyables.append(timers.AnnotationUpdates())
return {"FINISHED"} return {"FINISHED"}
@ -783,7 +848,6 @@ class SessionStopAutoSaveOperator(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper): class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
bl_idname = "session.load" bl_idname = "session.load"
bl_label = "Load session save" bl_label = "Load session save"
@ -831,12 +895,90 @@ class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
class SessionPresetServerAdd(bpy.types.Operator): class SessionPresetServerAdd(bpy.types.Operator):
"""Add a server to the server list preset""" """Add a server to the server list preset"""
bl_idname = "session.preset_server_add" bl_idname = "session.preset_server_add"
bl_label = "add server preset" bl_label = "Add server preset"
bl_description = "add the current server to the server preset list" bl_description = "add a server to the server preset list"
bl_options = {"REGISTER"} bl_options = {"REGISTER"}
name : bpy.props.StringProperty(default="server_preset") server_name: bpy.props.StringProperty(default="")
ip: bpy.props.StringProperty(default="127.0.0.1")
port: bpy.props.IntProperty(default=5555)
use_server_password: bpy.props.BoolProperty(default=False)
server_password: bpy.props.StringProperty(default="", subtype = "PASSWORD")
use_admin_password: bpy.props.BoolProperty(default=False)
admin_password: bpy.props.StringProperty(default="", subtype = "PASSWORD")
@classmethod
def poll(cls, context):
return True
def invoke(self, context, event):
self.server_name = ""
self.ip = "127.0.0.1"
self.port = 5555
self.use_server_password = False
self.server_password = ""
self.use_admin_password = False
self.admin_password = ""
assert(context)
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, "server_name", text="Server name")
row = layout.row(align = True)
row.prop(self, "ip", text="IP+port")
row.prop(self, "port", text="")
row = layout.row()
col = row.column()
col.prop(self, "use_server_password", text="Server password:")
col = row.column()
col.enabled = True if self.use_server_password else False
col.prop(self, "server_password", text="")
row = layout.row()
col = row.column()
col.prop(self, "use_admin_password", text="Admin password:")
col = row.column()
col.enabled = True if self.use_admin_password else False
col.prop(self, "admin_password", text="")
def execute(self, context):
assert(context)
settings = utils.get_preferences()
existing_preset = settings.get_server_preset(self.server_name)
new_server = existing_preset if existing_preset else settings.server_preset.add()
new_server.name = str(uuid4())
new_server.server_name = self.server_name
new_server.ip = self.ip
new_server.port = self.port
new_server.use_server_password = self.use_server_password
new_server.server_password = self.server_password
new_server.use_admin_password = self.use_admin_password
new_server.admin_password = self.admin_password
refresh_sidebar_view()
if new_server == existing_preset :
self.report({'INFO'}, "Server '" + self.server_name + "' edited")
else :
self.report({'INFO'}, "New '" + self.server_name + "' server preset")
return {'FINISHED'}
class SessionPresetServerEdit(bpy.types.Operator): # TODO : use preset, not settings
"""Edit a server to the server list preset"""
bl_idname = "session.preset_server_edit"
bl_label = "Edit server preset"
bl_description = "Edit a server from the server preset list"
bl_options = {"REGISTER"}
target_server_name: bpy.props.StringProperty(default="None")
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return True return True
@ -847,35 +989,39 @@ class SessionPresetServerAdd(bpy.types.Operator):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
col = layout.column()
settings = utils.get_preferences() settings = utils.get_preferences()
settings_active_server = settings.server_preset.get(self.target_server_name)
col.prop(settings, "server_name", text="server name") row = layout.row()
row.prop(settings_active_server, "server_name", text="Server name")
row = layout.row(align = True)
row.prop(settings_active_server, "ip", text="IP+port")
row.prop(settings_active_server, "port", text="")
row = layout.row()
col = row.column()
col.prop(settings_active_server, "use_server_password", text="Server password:")
col = row.column()
col.enabled = True if settings_active_server.use_server_password else False
col.prop(settings_active_server, "server_password", text="")
row = layout.row()
col = row.column()
col.prop(settings_active_server, "use_admin_password", text="Admin password:")
col = row.column()
col.enabled = True if settings_active_server.use_admin_password else False
col.prop(settings_active_server, "admin_password", text="")
def execute(self, context): def execute(self, context):
assert(context) assert(context)
settings = utils.get_preferences() settings = utils.get_preferences()
settings_active_server = settings.server_preset.get(self.target_server_name)
existing_preset = settings.server_preset.get(settings.server_name) refresh_sidebar_view()
new_server = existing_preset if existing_preset else settings.server_preset.add() self.report({'INFO'}, "Server '" + settings_active_server.server_name + "' edited")
new_server.name = settings.server_name
new_server.server_ip = settings.ip
new_server.server_port = settings.port
new_server.server_password = settings.password
settings.server_preset_interface = settings.server_name
if new_server == existing_preset :
self.report({'INFO'}, "Server '" + settings.server_name + "' override")
else :
self.report({'INFO'}, "New '" + settings.server_name + "' server preset")
return {'FINISHED'} return {'FINISHED'}
class SessionPresetServerRemove(bpy.types.Operator): class SessionPresetServerRemove(bpy.types.Operator):
"""Remove a server to the server list preset""" """Remove a server to the server list preset"""
bl_idname = "session.preset_server_remove" bl_idname = "session.preset_server_remove"
@ -883,6 +1029,8 @@ class SessionPresetServerRemove(bpy.types.Operator):
bl_description = "remove the current server from the server preset list" bl_description = "remove the current server from the server preset list"
bl_options = {"REGISTER"} bl_options = {"REGISTER"}
target_server_name: bpy.props.StringProperty(default="None")
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return True return True
@ -891,19 +1039,77 @@ class SessionPresetServerRemove(bpy.types.Operator):
assert(context) assert(context)
settings = utils.get_preferences() settings = utils.get_preferences()
settings.server_preset.remove(settings.server_preset.find(self.target_server_name))
settings.server_preset.remove(settings.server_preset.find(settings.server_preset_interface))
return {'FINISHED'} return {'FINISHED'}
class RefreshServerStatus(bpy.types.Operator):
bl_idname = "session.get_info"
bl_label = "Get session info"
bl_description = "Get session info"
target_server: bpy.props.StringProperty(default="127.0.0.1:5555")
@classmethod
def poll(cls, context):
return (session.state != STATE_ACTIVE)
def execute(self, context):
settings = utils.get_preferences()
for server in settings.server_preset:
infos = porcelain.request_session_info(f"{server.ip}:{server.port}", timeout=settings.ping_timeout)
server.is_online = True if infos else False
if server.is_online:
server.is_private = infos.get("private")
return {'FINISHED'}
class GetDoc(bpy.types.Operator):
"""Get the documentation of the addon"""
bl_idname = "doc.get"
bl_label = "Multi-user's doc"
bl_description = "Go to the doc of the addon"
@classmethod
def poll(cls, context):
return True
def execute(self, context):
assert(context)
bpy.ops.wm.url_open(url="https://slumber.gitlab.io/multi-user/index.html")
return {'FINISHED'}
class FirstLaunch(bpy.types.Operator):
"""First time lauching the addon"""
bl_idname = "firstlaunch.verify"
bl_label = "First launch"
bl_description = "First time lauching the addon"
@classmethod
def poll(cls, context):
return True
def execute(self, context):
assert(context)
settings = utils.get_preferences()
settings.is_first_launch = False
settings.server_preset.clear()
prefs = bpy.context.preferences.addons[__package__].preferences
prefs.generate_default_presets()
return {'FINISHED'}
def menu_func_import(self, context): def menu_func_import(self, context):
self.layout.operator(SessionLoadSaveOperator.bl_idname, text='Multi-user session snapshot (.db)') self.layout.operator(SessionLoadSaveOperator.bl_idname, text='Multi-user session snapshot (.db)')
def menu_func_export(self, context):
self.layout.operator(SessionSaveBackupOperator.bl_idname, text='Multi-user session snapshot (.db)')
classes = ( classes = (
SessionStartOperator, SessionConnectOperator,
SessionHostOperator,
SessionStopOperator, SessionStopOperator,
SessionPropertyRemoveOperator, SessionPropertyRemoveOperator,
SessionSnapUserOperator, SessionSnapUserOperator,
@ -920,7 +1126,11 @@ classes = (
SessionStopAutoSaveOperator, SessionStopAutoSaveOperator,
SessionPurgeOperator, SessionPurgeOperator,
SessionPresetServerAdd, SessionPresetServerAdd,
SessionPresetServerEdit,
SessionPresetServerRemove, SessionPresetServerRemove,
RefreshServerStatus,
GetDoc,
FirstLaunch,
) )

View File

@ -17,6 +17,7 @@
import random import random
import logging import logging
from uuid import uuid4
import bpy import bpy
import string import string
import re import re
@ -25,7 +26,7 @@ import os
from pathlib import Path from pathlib import Path
from . import bl_types, environment, addon_updater_ops, presence, ui from . import bl_types, environment, addon_updater_ops, presence, ui
from .utils import get_preferences, get_expanded_icon from .utils import get_preferences, get_expanded_icon, get_folder_size
from replication.constants import RP_COMMON from replication.constants import RP_COMMON
from replication.interface import session from replication.interface import session
@ -33,15 +34,21 @@ from replication.interface import session
IP_REGEX = re.compile("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$") IP_REGEX = re.compile("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")
HOSTNAME_REGEX = re.compile("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$") HOSTNAME_REGEX = re.compile("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$")
#SERVER PRESETS AT LAUNCH
DEFAULT_PRESETS = { DEFAULT_PRESETS = {
"localhost" : { "localhost" : {
"server_ip": "localhost", "server_name": "localhost",
"server_port": 5555, "ip": "localhost",
"server_password": "admin" "port": 5555,
"use_admin_password": True,
"admin_password": "admin",
"server_password": ""
}, },
"public session" : { "public session" : {
"server_ip": "51.75.71.183", "server_name": "public session",
"server_port": 5555, "ip": "51.75.71.183",
"port": 5555,
"admin_password": "",
"server_password": "" "server_password": ""
}, },
} }
@ -78,11 +85,6 @@ def update_ip(self, context):
logging.error("Wrong IP format") logging.error("Wrong IP format")
self['ip'] = "127.0.0.1" self['ip'] = "127.0.0.1"
def update_server_preset_interface(self, context):
self.server_name = self.server_preset.get(self.server_preset_interface).name
self.ip = self.server_preset.get(self.server_preset_interface).server_ip
self.port = self.server_preset.get(self.server_preset_interface).server_port
self.password = self.server_preset.get(self.server_preset_interface).server_password
def update_directory(self, context): def update_directory(self, context):
new_dir = Path(self.cache_directory) new_dir = Path(self.cache_directory)
@ -110,9 +112,15 @@ class ReplicatedDatablock(bpy.types.PropertyGroup):
icon: bpy.props.StringProperty() icon: bpy.props.StringProperty()
class ServerPreset(bpy.types.PropertyGroup): class ServerPreset(bpy.types.PropertyGroup):
server_ip: bpy.props.StringProperty() server_name: bpy.props.StringProperty(default="")
server_port: bpy.props.IntProperty(default=5555) ip: bpy.props.StringProperty(default="127.0.0.1", update=update_ip)
server_password: bpy.props.StringProperty(default="admin", subtype = "PASSWORD") port: bpy.props.IntProperty(default=5555)
use_server_password: bpy.props.BoolProperty(default=False)
server_password: bpy.props.StringProperty(default="", subtype = "PASSWORD")
use_admin_password: bpy.props.BoolProperty(default=False)
admin_password: bpy.props.StringProperty(default="", subtype = "PASSWORD")
is_online: bpy.props.BoolProperty(default=False)
is_private: bpy.props.BoolProperty(default=False)
def set_sync_render_settings(self, value): def set_sync_render_settings(self, value):
self['sync_render_settings'] = value self['sync_render_settings'] = value
@ -162,35 +170,60 @@ class ReplicationFlags(bpy.types.PropertyGroup):
class SessionPrefs(bpy.types.AddonPreferences): class SessionPrefs(bpy.types.AddonPreferences):
bl_idname = __package__ bl_idname = __package__
ip: bpy.props.StringProperty( # User settings
name="ip",
description='Distant host ip',
default="localhost",
update=update_ip)
username: bpy.props.StringProperty( username: bpy.props.StringProperty(
name="Username", name="Username",
default=f"user_{random_string_digits()}" default=f"user_{random_string_digits()}"
) )
client_color: bpy.props.FloatVectorProperty( client_color: bpy.props.FloatVectorProperty(
name="client_instance_color", name="client_instance_color",
description='User color',
subtype='COLOR', subtype='COLOR',
default=randomColor()) default=randomColor()
port: bpy.props.IntProperty(
name="port",
description='Distant host port',
default=5555
) )
# Current server settings
server_name: bpy.props.StringProperty( server_name: bpy.props.StringProperty(
name="server_name", name="server_name",
description="Custom name of the server", description="Custom name of the server",
default='localhost', default='localhost',
) )
password: bpy.props.StringProperty( server_index: bpy.props.IntProperty(
name="password", name="server_index",
default=random_string_digits(), description="index of the server",
)
# User host session settings
host_port: bpy.props.IntProperty(
name="host_port",
description='Distant host port',
default=5555
)
host_use_server_password: bpy.props.BoolProperty(
name="use_server_password",
description='Use session password',
default=False
)
host_server_password: bpy.props.StringProperty(
name="server_password",
description='Session password', description='Session password',
subtype='PASSWORD' subtype='PASSWORD'
) )
host_use_admin_password: bpy.props.BoolProperty(
name="use_admin_password",
description='Use admin password',
default=True
)
host_admin_password: bpy.props.StringProperty(
name="admin_password",
description='Admin password',
subtype='PASSWORD',
default='admin'
)
# Other
is_first_launch: bpy.props.BoolProperty(
name="is_fnirst_launch",
description="First time lauching the addon",
default=True
)
sync_flags: bpy.props.PointerProperty( sync_flags: bpy.props.PointerProperty(
type=ReplicationFlags type=ReplicationFlags
) )
@ -214,6 +247,11 @@ class SessionPrefs(bpy.types.AddonPreferences):
description='connection timeout before disconnection', description='connection timeout before disconnection',
default=5000 default=5000
) )
ping_timeout: bpy.props.IntProperty(
name='ping timeout',
description='check if servers are online',
default=500
)
# Replication update settings # Replication update settings
depsgraph_update_rate: bpy.props.FloatProperty( depsgraph_update_rate: bpy.props.FloatProperty(
name='depsgraph update rate (s)', name='depsgraph update rate (s)',
@ -225,11 +263,12 @@ class SessionPrefs(bpy.types.AddonPreferences):
description="Remove filecache from memory", description="Remove filecache from memory",
default=False default=False
) )
# for UI # For UI
category: bpy.props.EnumProperty( category: bpy.props.EnumProperty(
name="Category", name="Category",
description="Preferences Category", description="Preferences Category",
items=[ items=[
('PREF', "Preferences", "Preferences of this add-on"),
('CONFIG', "Configuration", "Configuration of this add-on"), ('CONFIG', "Configuration", "Configuration of this add-on"),
('UPDATE', "Update", "Update this add-on"), ('UPDATE', "Update", "Update this add-on"),
], ],
@ -273,38 +312,58 @@ class SessionPrefs(bpy.types.AddonPreferences):
step=1, step=1,
subtype='PERCENTAGE', subtype='PERCENTAGE',
) )
presence_mode_distance: bpy.props.FloatProperty( presence_text_distance: bpy.props.FloatProperty(
name="Distance mode visibilty", name="Distance text visibilty",
description="Adjust the distance visibilty of user's mode", description="Adjust the distance visibilty of user's mode/name",
min=0.1, min=0.1,
max=1000, max=10000,
default=100, default=100,
) )
conf_session_identity_expanded: bpy.props.BoolProperty( conf_session_identity_expanded: bpy.props.BoolProperty(
name="Identity", name="Identity",
description="Identity", description="Identity",
default=True default=False
) )
conf_session_net_expanded: bpy.props.BoolProperty( conf_session_net_expanded: bpy.props.BoolProperty(
name="Net", name="Net",
description="net", description="net",
default=True default=False
) )
conf_session_hosting_expanded: bpy.props.BoolProperty( conf_session_hosting_expanded: bpy.props.BoolProperty(
name="Rights", name="Rights",
description="Rights", description="Rights",
default=False default=False
) )
conf_session_rep_expanded: bpy.props.BoolProperty(
name="Replication",
description="Replication",
default=False
)
conf_session_cache_expanded: bpy.props.BoolProperty( conf_session_cache_expanded: bpy.props.BoolProperty(
name="Cache", name="Cache",
description="cache", description="cache",
default=False default=False
) )
conf_session_log_expanded: bpy.props.BoolProperty(
name="conf_session_log_expanded",
description="conf_session_log_expanded",
default=False
)
conf_session_ui_expanded: bpy.props.BoolProperty( conf_session_ui_expanded: bpy.props.BoolProperty(
name="Interface", name="Interface",
description="Interface", description="Interface",
default=False default=False
) )
sidebar_repository_shown: bpy.props.BoolProperty(
name="sidebar_repository_shown",
description="sidebar_repository_shown",
default=False
)
sidebar_advanced_shown: bpy.props.BoolProperty(
name="sidebar_advanced_shown",
description="sidebar_advanced_shown",
default=False
)
sidebar_advanced_rep_expanded: bpy.props.BoolProperty( sidebar_advanced_rep_expanded: bpy.props.BoolProperty(
name="sidebar_advanced_rep_expanded", name="sidebar_advanced_rep_expanded",
description="sidebar_advanced_rep_expanded", description="sidebar_advanced_rep_expanded",
@ -315,6 +374,11 @@ class SessionPrefs(bpy.types.AddonPreferences):
description="sidebar_advanced_log_expanded", description="sidebar_advanced_log_expanded",
default=False default=False
) )
sidebar_advanced_hosting_expanded: bpy.props.BoolProperty(
name="sidebar_advanced_hosting_expanded",
description="sidebar_advanced_hosting_expanded",
default=False
)
sidebar_advanced_net_expanded: bpy.props.BoolProperty( sidebar_advanced_net_expanded: bpy.props.BoolProperty(
name="sidebar_advanced_net_expanded", name="sidebar_advanced_net_expanded",
description="sidebar_advanced_net_expanded", description="sidebar_advanced_net_expanded",
@ -371,12 +435,6 @@ class SessionPrefs(bpy.types.AddonPreferences):
name="server preset", name="server preset",
type=ServerPreset, type=ServerPreset,
) )
server_preset_interface: bpy.props.EnumProperty(
name="servers",
description="servers enum",
items=server_list_callback,
update=update_server_preset_interface,
)
# Custom panel # Custom panel
panel_category: bpy.props.StringProperty( panel_category: bpy.props.StringProperty(
@ -386,38 +444,28 @@ class SessionPrefs(bpy.types.AddonPreferences):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.row().prop(self, "category", expand=True) layout.row().prop(self, "category", expand=True)
if self.category == 'PREF':
grid = layout.column()
box = grid.box()
row = box.row()
# USER SETTINGS
split = row.split(factor=0.7, align=True)
split.prop(self, "username", text="User")
split.prop(self, "client_color", text="")
row = box.row()
row.label(text="Hide settings:")
row = box.row()
row.prop(self, "sidebar_advanced_shown", text="Hide “Advanced” settings in side pannel (Not in session)")
row = box.row()
row.prop(self, "sidebar_repository_shown", text="Hide “Repository” settings in side pannel (In session)")
if self.category == 'CONFIG': if self.category == 'CONFIG':
grid = layout.column() grid = layout.column()
# USER INFORMATIONS
box = grid.box()
box.prop(
self, "conf_session_identity_expanded", text="User information",
icon=get_expanded_icon(self.conf_session_identity_expanded),
emboss=False)
if self.conf_session_identity_expanded:
box.row().prop(self, "username", text="name")
box.row().prop(self, "client_color", text="color")
# NETWORK SETTINGS
box = grid.box()
box.prop(
self, "conf_session_net_expanded", text="Networking",
icon=get_expanded_icon(self.conf_session_net_expanded),
emboss=False)
if self.conf_session_net_expanded:
box.row().prop(self, "ip", text="Address")
row = box.row()
row.label(text="Port:")
row.prop(self, "port", text="")
row = box.row()
row.label(text="Init the session from:")
row.prop(self, "init_method", text="")
# HOST SETTINGS # HOST SETTINGS
box = grid.box() box = grid.box()
box.prop( box.prop(
@ -425,9 +473,57 @@ class SessionPrefs(bpy.types.AddonPreferences):
icon=get_expanded_icon(self.conf_session_hosting_expanded), icon=get_expanded_icon(self.conf_session_hosting_expanded),
emboss=False) emboss=False)
if self.conf_session_hosting_expanded: if self.conf_session_hosting_expanded:
row = box.row()
row.prop(self, "host_port", text="Port: ")
row = box.row() row = box.row()
row.label(text="Init the session from:") row.label(text="Init the session from:")
row.prop(self, "init_method", text="") row.prop(self, "init_method", text="")
row = box.row()
col = row.column()
col.prop(self, "host_use_server_password", text="Server password:")
col = row.column()
col.enabled = True if self.host_use_server_password else False
col.prop(self, "host_server_password", text="")
row = box.row()
col = row.column()
col.prop(self, "host_use_admin_password", text="Admin password:")
col = row.column()
col.enabled = True if self.host_use_admin_password else False
col.prop(self, "host_admin_password", text="")
# NETWORKING
box = grid.box()
box.prop(
self, "conf_session_net_expanded", text="Network",
icon=get_expanded_icon(self.conf_session_net_expanded),
emboss=False)
if self.conf_session_net_expanded:
row = box.row()
row.label(text="Timeout (ms):")
row.prop(self, "connection_timeout", text="")
row = box.row()
row.label(text="Server ping (ms):")
row.prop(self, "ping_timeout", text="")
# REPLICATION
box = grid.box()
box.prop(
self, "conf_session_rep_expanded", text="Replication",
icon=get_expanded_icon(self.conf_session_rep_expanded),
emboss=False)
if self.conf_session_rep_expanded:
row = box.row()
row.prop(self.sync_flags, "sync_render_settings")
row = box.row()
row.prop(self.sync_flags, "sync_active_camera")
row = box.row()
row.prop(self.sync_flags, "sync_during_editmode")
row = box.row()
if self.sync_flags.sync_during_editmode:
warning = row.box()
warning.label(text="Don't use this with heavy meshes !", icon='ERROR')
row = box.row()
row.prop(self, "depsgraph_update_rate", text="Apply delay")
# CACHE SETTINGS # CACHE SETTINGS
box = grid.box() box = grid.box()
@ -438,25 +534,18 @@ class SessionPrefs(bpy.types.AddonPreferences):
if self.conf_session_cache_expanded: if self.conf_session_cache_expanded:
box.row().prop(self, "cache_directory", text="Cache directory") box.row().prop(self, "cache_directory", text="Cache directory")
box.row().prop(self, "clear_memory_filecache", text="Clear memory filecache") box.row().prop(self, "clear_memory_filecache", text="Clear memory filecache")
box.row().operator('session.clear_cache', text=f"Clear cache ({get_folder_size(self.cache_directory)})")
# INTERFACE SETTINGS
# LOGGING
box = grid.box() box = grid.box()
box.prop( box.prop(
self, "conf_session_ui_expanded", text="Interface", self, "conf_session_log_expanded", text="Logging",
icon=get_expanded_icon(self.conf_session_ui_expanded), icon=get_expanded_icon(self.conf_session_log_expanded),
emboss=False) emboss=False)
if self.conf_session_ui_expanded: if self.conf_session_log_expanded:
box.row().prop(self, "panel_category", text="Panel category", expand=True)
row = box.row() row = box.row()
row.label(text="Session widget:") row.label(text="Log level:")
row.prop(self, 'logging_level', text="")
col = box.column(align=True)
col.prop(self, "presence_hud_scale", expand=True)
col.prop(self, "presence_hud_hpos", expand=True)
col.prop(self, "presence_hud_vpos", expand=True)
col.prop(self, "presence_mode_distance", expand=True)
if self.category == 'UPDATE': if self.category == 'UPDATE':
from . import addon_updater_ops from . import addon_updater_ops
@ -477,18 +566,31 @@ class SessionPrefs(bpy.types.AddonPreferences):
new_db.icon = impl.bl_icon new_db.icon = impl.bl_icon
new_db.bl_name = impl.bl_id new_db.bl_name = impl.bl_id
# Get a server preset through its name
def get_server_preset(self, name):
existing_preset = None
# custom at launch server preset for server_preset in self.server_preset :
if server_preset.server_name == name :
existing_preset = server_preset
return existing_preset
# Custom at launch server preset
def generate_default_presets(self): def generate_default_presets(self):
for preset_name, preset_data in DEFAULT_PRESETS.items(): for preset_name, preset_data in DEFAULT_PRESETS.items():
existing_preset = self.server_preset.get(preset_name) existing_preset = self.get_server_preset(preset_name)
if existing_preset : if existing_preset :
continue continue
new_server = self.server_preset.add() new_server = self.server_preset.add()
new_server.name = preset_name new_server.name = str(uuid4())
new_server.server_ip = preset_data.get('server_ip') new_server.server_name = preset_data.get('server_name')
new_server.server_port = preset_data.get('server_port') new_server.ip = preset_data.get('ip')
new_server.port = preset_data.get('port')
new_server.use_server_password = preset_data.get('use_server_password',False)
new_server.server_password = preset_data.get('server_password',None) new_server.server_password = preset_data.get('server_password',None)
new_server.use_admin_password = preset_data.get('use_admin_password',False)
new_server.admin_password = preset_data.get('admin_password',None)
def client_list_callback(scene, context): def client_list_callback(scene, context):
@ -576,11 +678,6 @@ class SessionProps(bpy.types.PropertyGroup):
description='Connect as admin', description='Connect as admin',
default=False default=False
) )
internet_ip: bpy.props.StringProperty(
name="internet ip",
default="no found",
description='Internet interface ip',
)
user_snap_running: bpy.props.BoolProperty( user_snap_running: bpy.props.BoolProperty(
default=False default=False
) )

View File

@ -16,7 +16,9 @@
# ##### END GPL LICENSE BLOCK ##### # ##### END GPL LICENSE BLOCK #####
from logging import log
import bpy import bpy
import bpy.utils.previews
from .utils import get_preferences, get_expanded_icon, get_folder_size, get_state_str from .utils import get_preferences, get_expanded_icon, get_folder_size, get_state_str
from replication.constants import (ADDED, ERROR, FETCHED, from replication.constants import (ADDED, ERROR, FETCHED,
@ -71,157 +73,124 @@ class SESSION_PT_settings(bpy.types.Panel):
def draw_header(self, context): def draw_header(self, context):
layout = self.layout layout = self.layout
settings = get_preferences()
from multi_user import icons
offline_icon = icons.icons_col["session_status_offline"]
waiting_icon = icons.icons_col["session_status_waiting"]
online_icon = icons.icons_col["session_status_online"]
if session and session.state != STATE_INITIAL: if session and session.state != STATE_INITIAL:
cli_state = session.state cli_state = session.state
state = session.state state = session.state
connection_icon = "KEYTYPE_MOVING_HOLD_VEC" connection_icon = offline_icon
if state == STATE_ACTIVE: if state == STATE_ACTIVE:
connection_icon = 'PROP_ON' connection_icon = online_icon
else: else:
connection_icon = 'PROP_CON' connection_icon = waiting_icon
layout.label(text=f"Session - {get_state_str(cli_state)}", icon=connection_icon) layout.label(text=f"{str(settings.server_name)} - {get_state_str(cli_state)}", icon_value=connection_icon.icon_id)
else: else:
layout.label(text=f"Session - v{__version__}",icon="PROP_OFF") layout.label(text=f"Multi-user - v{__version__}", icon="ANTIALIASED")
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
row = layout.row()
runtime_settings = context.window_manager.session runtime_settings = context.window_manager.session
settings = get_preferences() settings = get_preferences()
if hasattr(context.window_manager, 'session'): if settings.is_first_launch:
# STATE INITIAL # USER SETTINGS
if not session \ row = layout.row()
or (session and session.state == STATE_INITIAL): row.label(text="1. Enter your username and color:")
pass row = layout.row()
else: split = row.split(factor=0.7, align=True)
progress = session.state_progress split.prop(settings, "username", text="")
row = layout.row() split.prop(settings, "client_color", text="")
current_state = session.state # DOC
info_msg = None row = layout.row()
row.label(text="2. New here ? See the doc:")
row = layout.row()
row.operator("doc.get", text="Documentation", icon="HELP")
# START
row = layout.row()
row.label(text="3: Start the Multi-user:")
row = layout.row()
row.scale_y = 2
row.operator("firstlaunch.verify", text="Continue")
if not settings.is_first_launch:
if hasattr(context.window_manager, 'session'):
# STATE INITIAL
if not session \
or (session and session.state == STATE_INITIAL):
layout = self.layout
settings = get_preferences()
server_preset = settings.server_preset
selected_server = context.window_manager.server_index if context.window_manager.server_index<=len(server_preset)-1 else 0
active_server_name = server_preset[selected_server].name if len(server_preset)>=1 else ""
is_server_selected = True if active_server_name else False
if current_state in [STATE_ACTIVE]: # SERVER LIST
row = row.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True) row = layout.row()
row.prop(settings.sync_flags, "sync_render_settings",text="",icon_only=True, icon='SCENE') box = row.box()
row.prop(settings.sync_flags, "sync_during_editmode", text="",icon_only=True, icon='EDITMODE_HLT') box.scale_y = 0.7
row.prop(settings.sync_flags, "sync_active_camera", text="",icon_only=True, icon='VIEW_CAMERA') split = box.split(factor=0.7)
split.label(text="Server")
split.label(text="Online")
row= layout.row() col = row.column(align=True)
col.operator("session.get_info", icon="FILE_REFRESH", text="")
if current_state in [STATE_ACTIVE] and runtime_settings.is_host: row = layout.row()
info_msg = f"LAN: {runtime_settings.internet_ip}" col = row.column(align=True)
if current_state == STATE_LOBBY: col.template_list("SESSION_UL_network", "", settings, "server_preset", context.window_manager, "server_index")
info_msg = "Waiting for the session to start." col.separator()
connectOp = col.row()
connectOp.operator("session.host", text="Host")
connectopcol = connectOp.column()
connectopcol.enabled =is_server_selected
connectopcol.operator("session.connect", text="Connect")
if info_msg: col = row.column(align=True)
info_box = row.box() col.operator("session.preset_server_add", icon="ADD", text="") # TODO : add conditions (need a name, etc..)
info_box.row().label(text=info_msg,icon='INFO') row_visible = col.row(align=True)
col_visible = row_visible.column(align=True)
col_visible.enabled = is_server_selected
col_visible.operator("session.preset_server_remove", icon="REMOVE", text="").target_server_name = active_server_name
col_visible.separator()
col_visible.operator("session.preset_server_edit", icon="GREASEPENCIL", text="").target_server_name = active_server_name
# Progress bar else:
if current_state in [STATE_SYNCING, STATE_SRV_SYNC, STATE_WAITING]: exitbutton = layout.row()
info_box = row.box() exitbutton.scale_y = 1.5
info_box.row().label(text=printProgressBar( exitbutton.operator("session.stop", icon='QUIT', text="Disconnect")
progress['current'],
progress['total'],
length=16
))
layout.row().operator("session.stop", icon='QUIT', text="Exit") progress = session.state_progress
current_state = session.state
info_msg = None
if current_state == STATE_LOBBY:
row= layout.row()
info_msg = "Waiting for the session to start."
class SESSION_PT_settings_network(bpy.types.Panel): if info_msg:
bl_idname = "MULTIUSER_SETTINGS_NETWORK_PT_panel" info_box = row.box()
bl_label = "Network" info_box.row().label(text=info_msg,icon='INFO')
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_parent_id = 'MULTIUSER_SETTINGS_PT_panel'
@classmethod # PROGRESS BAR
def poll(cls, context): if current_state in [STATE_SYNCING, STATE_SRV_SYNC, STATE_WAITING]:
return not session \ row= layout.row()
or (session and session.state == 0) row.label(text=f"Status: {get_state_str(current_state)}")
row= layout.row()
def draw_header(self, context): info_box = row.box()
self.layout.label(text="", icon='URL') info_box.label(text=printProgressBar(
progress['current'],
def draw(self, context): progress['total'],
layout = self.layout length=16
))
runtime_settings = context.window_manager.session
settings = get_preferences()
# USER SETTINGS
row = layout.row()
row.prop(runtime_settings, "session_mode", expand=True)
row = layout.row()
col = row.row(align=True)
col.prop(settings, "server_preset_interface", text="")
col.operator("session.preset_server_add", icon='ADD', text="")
col.operator("session.preset_server_remove", icon='REMOVE', text="")
row = layout.row()
box = row.box()
if runtime_settings.session_mode == 'HOST':
row = box.row()
row.label(text="Port:")
row.prop(settings, "port", text="")
row = box.row()
row.label(text="Start from:")
row.prop(settings, "init_method", text="")
row = box.row()
row.label(text="Admin password:")
row.prop(settings, "password", text="")
row = box.row()
row.operator("session.start", text="HOST").host = True
else:
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.prop(runtime_settings, "admin", text='Connect as admin', icon='DISCLOSURE_TRI_DOWN' if runtime_settings.admin
else 'DISCLOSURE_TRI_RIGHT')
if runtime_settings.admin:
row = box.row()
row.label(text="Password:")
row.prop(settings, "password", text="")
row = box.row()
row.operator("session.start", text="CONNECT").host = False
class SESSION_PT_settings_user(bpy.types.Panel):
bl_idname = "MULTIUSER_SETTINGS_USER_PT_panel"
bl_label = "User info"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_parent_id = 'MULTIUSER_SETTINGS_PT_panel'
@classmethod
def poll(cls, context):
return not session \
or (session and session.state == 0)
def draw_header(self, context):
self.layout.label(text="", icon='USER')
def draw(self, context):
layout = self.layout
runtime_settings = context.window_manager.session
settings = get_preferences()
row = layout.row()
# USER SETTINGS
row.prop(settings, "username", text="name")
row = layout.row()
row.prop(settings, "client_color", text="color")
row = layout.row()
class SESSION_PT_advanced_settings(bpy.types.Panel): class SESSION_PT_advanced_settings(bpy.types.Panel):
@ -234,19 +203,45 @@ class SESSION_PT_advanced_settings(bpy.types.Panel):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
settings = get_preferences()
return not session \ return not session \
or (session and session.state == 0) or (session and session.state == 0) \
and not settings.sidebar_advanced_shown \
and not settings.is_first_launch
def draw_header(self, context): def draw_header(self, context):
self.layout.label(text="", icon='PREFERENCES') self.layout.label(text="", icon='PREFERENCES')
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
runtime_settings = context.window_manager.session
settings = get_preferences() settings = get_preferences()
#ADVANCED HOST
host_selection = layout.row().box()
host_selection.prop(
settings, "sidebar_advanced_hosting_expanded", text="Hosting",
icon=get_expanded_icon(settings.sidebar_advanced_hosting_expanded),
emboss=False)
if settings.sidebar_advanced_hosting_expanded:
host_selection_row = host_selection.row()
host_selection_row.prop(settings, "host_port", text="Port:")
host_selection_row = host_selection.row()
host_selection_row.label(text="Init the session from:")
host_selection_row.prop(settings, "init_method", text="")
host_selection_row = host_selection.row()
host_selection_col = host_selection_row.column()
host_selection_col.prop(settings, "host_use_server_password", text="Server password:")
host_selection_col = host_selection_row.column()
host_selection_col.enabled = True if settings.host_use_server_password else False
host_selection_col.prop(settings, "host_server_password", text="")
host_selection_row = host_selection.row()
host_selection_col = host_selection_row.column()
host_selection_col.prop(settings, "host_use_admin_password", text="Admin password:")
host_selection_col = host_selection_row.column()
host_selection_col.enabled = True if settings.host_use_admin_password else False
host_selection_col.prop(settings, "host_admin_password", text="")
#ADVANCED NET
net_section = layout.row().box() net_section = layout.row().box()
net_section.prop( net_section.prop(
settings, settings,
@ -254,12 +249,15 @@ class SESSION_PT_advanced_settings(bpy.types.Panel):
text="Network", text="Network",
icon=get_expanded_icon(settings.sidebar_advanced_net_expanded), icon=get_expanded_icon(settings.sidebar_advanced_net_expanded),
emboss=False) emboss=False)
if settings.sidebar_advanced_net_expanded: if settings.sidebar_advanced_net_expanded:
net_section_row = net_section.row() net_section_row = net_section.row()
net_section_row.label(text="Timeout (ms):") net_section_row.label(text="Timeout (ms):")
net_section_row.prop(settings, "connection_timeout", text="") net_section_row.prop(settings, "connection_timeout", text="")
net_section_row = net_section.row()
net_section_row.label(text="Server ping (ms):")
net_section_row.prop(settings, "ping_timeout", text="")
#ADVANCED REPLICATION
replication_section = layout.row().box() replication_section = layout.row().box()
replication_section.prop( replication_section.prop(
settings, settings,
@ -267,16 +265,12 @@ class SESSION_PT_advanced_settings(bpy.types.Panel):
text="Replication", text="Replication",
icon=get_expanded_icon(settings.sidebar_advanced_rep_expanded), icon=get_expanded_icon(settings.sidebar_advanced_rep_expanded),
emboss=False) emboss=False)
if settings.sidebar_advanced_rep_expanded: if settings.sidebar_advanced_rep_expanded:
replication_section_row = replication_section.row()
replication_section_row = replication_section.row() replication_section_row = replication_section.row()
replication_section_row.prop(settings.sync_flags, "sync_render_settings") replication_section_row.prop(settings.sync_flags, "sync_render_settings")
replication_section_row = replication_section.row() replication_section_row = replication_section.row()
replication_section_row.prop(settings.sync_flags, "sync_active_camera") replication_section_row.prop(settings.sync_flags, "sync_active_camera")
replication_section_row = replication_section.row() replication_section_row = replication_section.row()
replication_section_row.prop(settings.sync_flags, "sync_during_editmode") replication_section_row.prop(settings.sync_flags, "sync_during_editmode")
replication_section_row = replication_section.row() replication_section_row = replication_section.row()
if settings.sync_flags.sync_during_editmode: if settings.sync_flags.sync_during_editmode:
@ -285,7 +279,7 @@ class SESSION_PT_advanced_settings(bpy.types.Panel):
replication_section_row = replication_section.row() replication_section_row = replication_section.row()
replication_section_row.prop(settings, "depsgraph_update_rate", text="Apply delay") replication_section_row.prop(settings, "depsgraph_update_rate", text="Apply delay")
#ADVANCED CACHE
cache_section = layout.row().box() cache_section = layout.row().box()
cache_section.prop( cache_section.prop(
settings, settings,
@ -303,6 +297,8 @@ class SESSION_PT_advanced_settings(bpy.types.Panel):
cache_section_row.prop(settings, "clear_memory_filecache", text="") cache_section_row.prop(settings, "clear_memory_filecache", text="")
cache_section_row = cache_section.row() cache_section_row = cache_section.row()
cache_section_row.operator('session.clear_cache', text=f"Clear cache ({get_folder_size(settings.cache_directory)})") cache_section_row.operator('session.clear_cache', text=f"Clear cache ({get_folder_size(settings.cache_directory)})")
#ADVANCED LOG
log_section = layout.row().box() log_section = layout.row().box()
log_section.prop( log_section.prop(
settings, settings,
@ -310,11 +306,11 @@ class SESSION_PT_advanced_settings(bpy.types.Panel):
text="Logging", text="Logging",
icon=get_expanded_icon(settings.sidebar_advanced_log_expanded), icon=get_expanded_icon(settings.sidebar_advanced_log_expanded),
emboss=False) emboss=False)
if settings.sidebar_advanced_log_expanded: if settings.sidebar_advanced_log_expanded:
log_section_row = log_section.row() log_section_row = log_section.row()
log_section_row.label(text="Log level:") log_section_row.label(text="Log level:")
log_section_row.prop(settings, 'logging_level', text="") log_section_row.prop(settings, 'logging_level', text="")
class SESSION_PT_user(bpy.types.Panel): class SESSION_PT_user(bpy.types.Panel):
bl_idname = "MULTIUSER_USER_PT_panel" bl_idname = "MULTIUSER_USER_PT_panel"
bl_label = "Online users" bl_label = "Online users"
@ -324,7 +320,8 @@ class SESSION_PT_user(bpy.types.Panel):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return session and session.state in [STATE_ACTIVE, STATE_LOBBY] return session \
and session.state in [STATE_ACTIVE, STATE_LOBBY]
def draw_header(self, context): def draw_header(self, context):
self.layout.label(text="", icon='USER') self.layout.label(text="", icon='USER')
@ -336,9 +333,8 @@ class SESSION_PT_user(bpy.types.Panel):
settings = get_preferences() settings = get_preferences()
active_user = online_users[selected_user] if len( active_user = online_users[selected_user] if len(
online_users)-1 >= selected_user else 0 online_users)-1 >= selected_user else 0
runtime_settings = context.window_manager.session
# Create a simple row. #USER LIST
row = layout.row() row = layout.row()
box = row.box() box = row.box()
split = box.split(factor=0.35) split = box.split(factor=0.35)
@ -353,6 +349,7 @@ class SESSION_PT_user(bpy.types.Panel):
layout.template_list("SESSION_UL_users", "", context.window_manager, layout.template_list("SESSION_UL_users", "", context.window_manager,
"online_users", context.window_manager, "user_index") "online_users", context.window_manager, "user_index")
#OPERATOR ON USER
if active_user != 0 and active_user.username != settings.username: if active_user != 0 and active_user.username != settings.username:
row = layout.row() row = layout.row()
user_operations = row.split() user_operations = row.split()
@ -436,57 +433,8 @@ class SESSION_UL_users(bpy.types.UIList):
split.label(text=scene_current) split.label(text=scene_current)
split.label(text=ping) split.label(text=ping)
class SESSION_PT_presence(bpy.types.Panel):
bl_idname = "MULTIUSER_MODULE_PT_panel"
bl_label = "Presence overlay"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_parent_id = 'MULTIUSER_SETTINGS_PT_panel'
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
return not session \
or (session and session.state in [STATE_INITIAL, STATE_ACTIVE])
def draw_header(self, context):
self.layout.prop(context.window_manager.session,
"enable_presence", text="",icon='OVERLAY')
def draw(self, context):
layout = self.layout
settings = context.window_manager.session
pref = get_preferences()
layout.active = settings.enable_presence
row = layout.row()
row = row.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
row.prop(settings, "presence_show_selected",text="",icon_only=True, icon='CUBE')
row.prop(settings, "presence_show_user", text="",icon_only=True, icon='CAMERA_DATA')
row.prop(settings, "presence_show_mode", text="",icon_only=True, icon='OBJECT_DATAMODE')
row.prop(settings, "presence_show_far_user", text="",icon_only=True, icon='SCENE_DATA')
col = layout.column()
if settings.presence_show_mode :
row = col.column()
row.prop(pref, "presence_mode_distance", expand=True)
col.prop(settings, "presence_show_session_status")
if settings.presence_show_session_status :
row = col.column()
row.active = settings.presence_show_session_status
row.prop(pref, "presence_hud_scale", expand=True)
row = col.column(align=True)
row.active = settings.presence_show_session_status
row.prop(pref, "presence_hud_hpos", expand=True)
row.prop(pref, "presence_hud_vpos", expand=True)
def draw_property(context, parent, property_uuid, level=0): def draw_property(context, parent, property_uuid, level=0):
settings = get_preferences() settings = get_preferences()
runtime_settings = context.window_manager.session
item = session.repository.graph.get(property_uuid) item = session.repository.graph.get(property_uuid)
type_id = item.data.get('type_id') type_id = item.data.get('type_id')
area_msg = parent.row(align=True) area_msg = parent.row(align=True)
@ -506,15 +454,18 @@ def draw_property(context, parent, property_uuid, level=0):
detail_item_box.label(text=f"{name}") detail_item_box.label(text=f"{name}")
# Operations # Operations
have_right_to_modify = (item.owner == settings.username or \ have_right_to_modify = (item.owner == settings.username or \
item.owner == RP_COMMON) and item.state != ERROR item.owner == RP_COMMON) and item.state != ERROR
from multi_user import icons
sync_status = icons.icons_col["repository_push"] #TODO: Link all icons to the right sync (push/merge/issue). For issue use "UNLINKED" for icon
# sync_status = icons.icons_col["repository_merge"]
if have_right_to_modify: if have_right_to_modify:
detail_item_box.operator( detail_item_box.operator(
"session.commit", "session.commit",
text="", text="",
icon='TRIA_UP').target = item.uuid icon_value=sync_status.icon_id).target = item.uuid
detail_item_box.separator() detail_item_box.separator()
if item.state in [FETCHED, UP]: if item.state in [FETCHED, UP]:
@ -546,12 +497,40 @@ def draw_property(context, parent, property_uuid, level=0):
else: else:
detail_item_box.label(text="", icon="DECORATE_LOCKED") detail_item_box.label(text="", icon="DECORATE_LOCKED")
class SESSION_PT_sync(bpy.types.Panel):
bl_idname = "MULTIUSER_SYNC_PT_panel"
bl_label = "Synchronize"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_parent_id = 'MULTIUSER_SETTINGS_PT_panel'
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
return session \
and session.state in [STATE_ACTIVE]
def draw_header(self, context):
self.layout.label(text="", icon='UV_SYNC_SELECT')
def draw(self, context):
layout = self.layout
settings = get_preferences()
row= layout.row()
row = row.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True)
row.prop(settings.sync_flags, "sync_render_settings",text="",icon_only=True, icon='SCENE')
row.prop(settings.sync_flags, "sync_during_editmode", text="",icon_only=True, icon='EDITMODE_HLT')
row.prop(settings.sync_flags, "sync_active_camera", text="",icon_only=True, icon='VIEW_CAMERA')
class SESSION_PT_repository(bpy.types.Panel): class SESSION_PT_repository(bpy.types.Panel):
bl_idname = "MULTIUSER_PROPERTIES_PT_panel" bl_idname = "MULTIUSER_PROPERTIES_PT_panel"
bl_label = "Repository" bl_label = "Repository"
bl_space_type = 'VIEW_3D' bl_space_type = 'VIEW_3D'
bl_region_type = 'UI' bl_region_type = 'UI'
bl_parent_id = 'MULTIUSER_SETTINGS_PT_panel' bl_parent_id = 'MULTIUSER_SETTINGS_PT_panel'
bl_options = {'DEFAULT_CLOSED'}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -565,7 +544,8 @@ class SESSION_PT_repository(bpy.types.Panel):
return hasattr(context.window_manager, 'session') and \ return hasattr(context.window_manager, 'session') and \
session and \ session and \
(session.state == STATE_ACTIVE or \ (session.state == STATE_ACTIVE or \
session.state == STATE_LOBBY and admin) session.state == STATE_LOBBY and admin) and \
not settings.sidebar_repository_shown
def draw_header(self, context): def draw_header(self, context):
self.layout.label(text="", icon='OUTLINER_OB_GROUP_INSTANCE') self.layout.label(text="", icon='OUTLINER_OB_GROUP_INSTANCE')
@ -579,19 +559,18 @@ class SESSION_PT_repository(bpy.types.Panel):
usr = session.online_users.get(settings.username) usr = session.online_users.get(settings.username)
row = layout.row()
if session.state == STATE_ACTIVE: if session.state == STATE_ACTIVE:
if 'SessionBackupTimer' in registry: if 'SessionBackupTimer' in registry:
row = layout.row()
row.alert = True row.alert = True
row.operator('session.cancel_autosave', icon="CANCEL") row.operator('session.cancel_autosave', icon="CANCEL")
row.alert = False row.alert = False
else: # else:
row.operator('session.save', icon="FILE_TICK") # row.operator('session.save', icon="FILE_TICK")
box = layout.box() box = layout.box()
row = box.row() row = box.row()
row.prop(runtime_settings, "filter_owned", text="Show only owned Nodes", icon_only=True, icon="DECORATE_UNLOCKED") row.prop(runtime_settings, "filter_owned", text="Only show owned data blocks", icon_only=True, icon="DECORATE_UNLOCKED")
row = box.row() row = box.row()
row.prop(runtime_settings, "filter_name", text="Filter") row.prop(runtime_settings, "filter_name", text="Filter")
row = box.row() row = box.row()
@ -612,8 +591,10 @@ class SESSION_PT_repository(bpy.types.Panel):
layout.row().label(text="Empty") layout.row().label(text="Empty")
elif session.state == STATE_LOBBY and usr and usr['admin']: elif session.state == STATE_LOBBY and usr and usr['admin']:
row = layout.row()
row.operator("session.init", icon='TOOL_SETTINGS', text="Init") row.operator("session.init", icon='TOOL_SETTINGS', text="Init")
else: else:
row = layout.row()
row.label(text="Waiting to start") row.label(text="Waiting to start")
class VIEW3D_PT_overlay_session(bpy.types.Panel): class VIEW3D_PT_overlay_session(bpy.types.Panel):
@ -634,41 +615,65 @@ class VIEW3D_PT_overlay_session(bpy.types.Panel):
layout.active = settings.enable_presence layout.active = settings.enable_presence
row = layout.row() row = layout.row()
row = row.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True) row.prop(settings, "presence_show_selected",text="Selected Objects")
row.prop(settings, "presence_show_selected",text="",icon_only=True, icon='CUBE')
row.prop(settings, "presence_show_user", text="",icon_only=True, icon='CAMERA_DATA') row = layout.row(align=True)
row.prop(settings, "presence_show_mode", text="",icon_only=True, icon='OBJECT_DATAMODE') row.prop(settings, "presence_show_user", text="Users camera")
row.prop(settings, "presence_show_far_user", text="",icon_only=True, icon='SCENE_DATA') row.prop(settings, "presence_show_mode", text="Users mode")
col = layout.column() col = layout.column()
if settings.presence_show_mode : if settings.presence_show_mode or settings.presence_show_user:
row = col.column() row = col.column()
row.prop(pref, "presence_mode_distance", expand=True) row.prop(pref, "presence_text_distance", expand=True)
row = col.column()
row.prop(settings, "presence_show_far_user", text="Users on different scenes")
col.prop(settings, "presence_show_session_status") col.prop(settings, "presence_show_session_status")
if settings.presence_show_session_status : if settings.presence_show_session_status :
row = col.column() split = layout.split()
row.active = settings.presence_show_session_status text_pos = split.column(align=True)
row.prop(pref, "presence_hud_scale", expand=True) text_pos.active = settings.presence_show_session_status
row = col.column(align=True) text_pos.prop(pref, "presence_hud_hpos", expand=True)
row.active = settings.presence_show_session_status text_pos.prop(pref, "presence_hud_vpos", expand=True)
row.prop(pref, "presence_hud_hpos", expand=True) text_scale = split.column()
row.prop(pref, "presence_hud_vpos", expand=True) text_scale.active = settings.presence_show_session_status
text_scale.prop(pref, "presence_hud_scale", expand=True)
class SESSION_UL_network(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag):
settings = get_preferences()
server_name = '-'
server_status = 'BLANK1'
server_private = 'BLANK1'
server_name = item.server_name
split = layout.split(factor=0.7)
if item.is_private:
server_private = 'LOCKED'
split.label(text=server_name, icon=server_private)
else:
split.label(text=server_name)
from multi_user import icons
server_status = icons.icons_col["server_offline"]
if item.is_online:
server_status = icons.icons_col["server_online"]
split.label(icon_value=server_status.icon_id)
classes = ( classes = (
SESSION_UL_users, SESSION_UL_users,
SESSION_UL_network,
SESSION_PT_settings, SESSION_PT_settings,
SESSION_PT_settings_user,
SESSION_PT_settings_network,
SESSION_PT_presence,
SESSION_PT_advanced_settings, SESSION_PT_advanced_settings,
SESSION_PT_user, SESSION_PT_user,
SESSION_PT_sync,
SESSION_PT_repository, SESSION_PT_repository,
VIEW3D_PT_overlay_session, VIEW3D_PT_overlay_session,
) )
register, unregister = bpy.utils.register_classes_factory(classes) register, unregister = bpy.utils.register_classes_factory(classes)
if __name__ == "__main__": if __name__ == "__main__":