Merge branch '118-optionnal-active-camera-sync-flag' into 'develop'
Resolve "Optionnal active camera sync flag" See merge request slumber/multi-user!49
This commit is contained in:
commit
e0839fe1fb
Binary file not shown.
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 8.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 18 KiB |
BIN
docs/getting_started/img/quickstart_replication.png
Normal file
BIN
docs/getting_started/img/quickstart_replication.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
@ -161,6 +161,19 @@ The collaboration quality directly depend on the communication quality. This sec
|
||||
various tools made in an effort to ease the communication between the different session users.
|
||||
Feel free to suggest any idea for communication tools `here <https://gitlab.com/slumber/multi-user/-/issues/75>`_ .
|
||||
|
||||
---------------------------
|
||||
Change replication behavior
|
||||
---------------------------
|
||||
|
||||
During a session, the multi-user will replicate your modifications to other instances.
|
||||
In order to avoid annoying other users when you are experimenting, some of those modifications can be ignored via
|
||||
various flags present at the top of the panel (see red area in the image bellow). Those flags are explained in the :ref:`replication` section.
|
||||
|
||||
.. figure:: img/quickstart_replication.png
|
||||
:align: center
|
||||
|
||||
Session replication flags
|
||||
|
||||
--------------------
|
||||
Monitor online users
|
||||
--------------------
|
||||
@ -242,6 +255,8 @@ various drawn parts via the following flags:
|
||||
- **Show users**: display users current viewpoint
|
||||
- **Show different scenes**: display users working on other scenes
|
||||
|
||||
|
||||
|
||||
-----------
|
||||
Manage data
|
||||
-----------
|
||||
@ -330,6 +345,8 @@ of the multi-user are using the same IPC port it will create conflict !
|
||||
**Timeout (in milliseconds)** is the maximum ping authorized before auto-disconnecting.
|
||||
You should only increase it if you have a bad connection.
|
||||
|
||||
.. _replication:
|
||||
|
||||
-----------
|
||||
Replication
|
||||
-----------
|
||||
@ -341,6 +358,8 @@ Replication
|
||||
|
||||
**Synchronize render settings** (only host) enable replication of EEVEE and CYCLES render settings to match render between clients.
|
||||
|
||||
**Synchronize active camera** sync the scene active camera.
|
||||
|
||||
**Edit Mode Updates** enable objects update while you are in Edit_Mode.
|
||||
|
||||
.. warning:: Edit Mode Updates kill performances with complex objects (heavy meshes, gpencil, etc...).
|
||||
|
@ -44,10 +44,12 @@ from . import environment, utils
|
||||
|
||||
|
||||
DEPENDENCIES = {
|
||||
("replication", '0.0.21a9'),
|
||||
("replication", '0.0.21a10'),
|
||||
}
|
||||
|
||||
|
||||
module_error_msg = "Insufficient rights to install the multi-user \
|
||||
dependencies, aunch blender with administrator rights."
|
||||
def register():
|
||||
# Setup logging policy
|
||||
logging.basicConfig(
|
||||
@ -57,22 +59,22 @@ def register():
|
||||
|
||||
try:
|
||||
environment.setup(DEPENDENCIES, bpy.app.binary_path_python)
|
||||
except ModuleNotFoundError:
|
||||
logging.fatal("Fail to install multi-user dependencies, try to execute blender with admin rights.")
|
||||
return
|
||||
|
||||
from . import presence
|
||||
from . import operators
|
||||
from . import ui
|
||||
from . import preferences
|
||||
from . import addon_updater_ops
|
||||
|
||||
preferences.register()
|
||||
addon_updater_ops.register(bl_info)
|
||||
presence.register()
|
||||
operators.register()
|
||||
ui.register()
|
||||
from . import presence
|
||||
from . import operators
|
||||
from . import ui
|
||||
from . import preferences
|
||||
from . import addon_updater_ops
|
||||
|
||||
preferences.register()
|
||||
addon_updater_ops.register(bl_info)
|
||||
presence.register()
|
||||
operators.register()
|
||||
ui.register()
|
||||
except ModuleNotFoundError as e:
|
||||
raise Exception(module_error_msg)
|
||||
logging.error(module_error_msg)
|
||||
|
||||
bpy.types.WindowManager.session = bpy.props.PointerProperty(
|
||||
type=preferences.SessionProps)
|
||||
bpy.types.ID.uuid = bpy.props.StringProperty(
|
||||
|
@ -114,7 +114,7 @@ class BlMesh(BlDatablock):
|
||||
def _dump_implementation(self, data, instance=None):
|
||||
assert(instance)
|
||||
|
||||
if instance.is_editmode and not self.preferences.enable_editmode_updates:
|
||||
if instance.is_editmode and not self.preferences.sync_flags.sync_during_editmode:
|
||||
raise ContextError("Mesh is in edit mode")
|
||||
mesh = instance
|
||||
|
||||
|
@ -201,7 +201,7 @@ class BlObject(BlDatablock):
|
||||
assert(instance)
|
||||
|
||||
if _is_editmode(instance):
|
||||
if self.preferences.enable_editmode_updates:
|
||||
if self.preferences.sync_flags.sync_during_editmode:
|
||||
instance.update_from_editmode()
|
||||
else:
|
||||
raise ContextError("Object is in edit-mode.")
|
||||
|
@ -23,6 +23,9 @@ from .dump_anything import Loader, Dumper
|
||||
from .bl_datablock import BlDatablock
|
||||
from .bl_collection import dump_collection_children, dump_collection_objects, load_collection_childrens, load_collection_objects
|
||||
from ..utils import get_preferences
|
||||
from replication.constants import (DIFF_JSON, MODIFIED)
|
||||
from deepdiff import DeepDiff
|
||||
import logging
|
||||
|
||||
class BlScene(BlDatablock):
|
||||
bl_id = "scenes"
|
||||
@ -33,6 +36,11 @@ class BlScene(BlDatablock):
|
||||
bl_check_common = True
|
||||
bl_icon = 'SCENE_DATA'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.diff_method = DIFF_JSON
|
||||
|
||||
def _construct(self, data):
|
||||
instance = bpy.data.scenes.new(data["name"])
|
||||
return instance
|
||||
@ -53,22 +61,23 @@ class BlScene(BlDatablock):
|
||||
if 'grease_pencil' in data.keys():
|
||||
target.grease_pencil = bpy.data.grease_pencils[data['grease_pencil']]
|
||||
|
||||
if 'eevee' in data.keys():
|
||||
loader.load(target.eevee, data['eevee'])
|
||||
|
||||
if 'cycles' in data.keys():
|
||||
loader.load(target.eevee, data['cycles'])
|
||||
if self.preferences.sync_flags.sync_render_settings:
|
||||
if 'eevee' in data.keys():
|
||||
loader.load(target.eevee, data['eevee'])
|
||||
|
||||
if 'render' in data.keys():
|
||||
loader.load(target.render, data['render'])
|
||||
if 'cycles' in data.keys():
|
||||
loader.load(target.eevee, data['cycles'])
|
||||
|
||||
if 'view_settings' in data.keys():
|
||||
loader.load(target.view_settings, data['view_settings'])
|
||||
if target.view_settings.use_curve_mapping:
|
||||
#TODO: change this ugly fix
|
||||
target.view_settings.curve_mapping.white_level = data['view_settings']['curve_mapping']['white_level']
|
||||
target.view_settings.curve_mapping.black_level = data['view_settings']['curve_mapping']['black_level']
|
||||
target.view_settings.curve_mapping.update()
|
||||
if 'render' in data.keys():
|
||||
loader.load(target.render, data['render'])
|
||||
|
||||
if 'view_settings' in data.keys():
|
||||
loader.load(target.view_settings, data['view_settings'])
|
||||
if target.view_settings.use_curve_mapping:
|
||||
#TODO: change this ugly fix
|
||||
target.view_settings.curve_mapping.white_level = data['view_settings']['curve_mapping']['white_level']
|
||||
target.view_settings.curve_mapping.black_level = data['view_settings']['curve_mapping']['black_level']
|
||||
target.view_settings.curve_mapping.update()
|
||||
|
||||
def _dump_implementation(self, data, instance=None):
|
||||
assert(instance)
|
||||
@ -80,12 +89,14 @@ class BlScene(BlDatablock):
|
||||
'name',
|
||||
'world',
|
||||
'id',
|
||||
'camera',
|
||||
'grease_pencil',
|
||||
'frame_start',
|
||||
'frame_end',
|
||||
'frame_step',
|
||||
]
|
||||
if self.preferences.sync_flags.sync_active_camera:
|
||||
scene_dumper.include_filter.append('camera')
|
||||
|
||||
data = scene_dumper.dump(instance)
|
||||
|
||||
scene_dumper.depth = 3
|
||||
@ -97,10 +108,8 @@ class BlScene(BlDatablock):
|
||||
|
||||
scene_dumper.depth = 1
|
||||
scene_dumper.include_filter = None
|
||||
|
||||
pref = get_preferences()
|
||||
|
||||
if pref.sync_flags.sync_render_settings:
|
||||
if self.preferences.sync_flags.sync_render_settings:
|
||||
scene_dumper.exclude_filter = [
|
||||
'gi_cache_info',
|
||||
'feature_set',
|
||||
@ -156,3 +165,17 @@ class BlScene(BlDatablock):
|
||||
deps.append(self.instance.grease_pencil)
|
||||
|
||||
return deps
|
||||
|
||||
def diff(self):
|
||||
exclude_path = []
|
||||
|
||||
if not self.preferences.sync_flags.sync_render_settings:
|
||||
exclude_path.append("root['eevee']")
|
||||
exclude_path.append("root['cycles']")
|
||||
exclude_path.append("root['view_settings']")
|
||||
exclude_path.append("root['render']")
|
||||
|
||||
if not self.preferences.sync_flags.sync_active_camera:
|
||||
exclude_path.append("root['camera']")
|
||||
|
||||
return DeepDiff(self.data, self._dump(instance=self.instance),exclude_paths=exclude_path, cache_size=5000)
|
@ -713,7 +713,7 @@ def depsgraph_evaluation(scene):
|
||||
if node and node.owner in [client.id, RP_COMMON] and node.state == UP:
|
||||
# Avoid slow geometry update
|
||||
if 'EDIT' in context.mode and \
|
||||
not settings.enable_editmode_updates:
|
||||
not settings.sync_during_editmode:
|
||||
break
|
||||
|
||||
client.stash(node.uuid)
|
||||
|
@ -99,11 +99,47 @@ class ReplicatedDatablock(bpy.types.PropertyGroup):
|
||||
icon: bpy.props.StringProperty()
|
||||
|
||||
|
||||
def set_sync_render_settings(self, value):
|
||||
self['sync_render_settings'] = value
|
||||
|
||||
from .operators import client
|
||||
if client and bpy.context.scene.uuid and value:
|
||||
bpy.ops.session.apply('INVOKE_DEFAULT', target=bpy.context.scene.uuid)
|
||||
|
||||
|
||||
def set_sync_active_camera(self, value):
|
||||
self['sync_active_camera'] = value
|
||||
|
||||
from .operators import client
|
||||
if client and bpy.context.scene.uuid and value:
|
||||
bpy.ops.session.apply('INVOKE_DEFAULT', target=bpy.context.scene.uuid)
|
||||
|
||||
|
||||
class ReplicationFlags(bpy.types.PropertyGroup):
|
||||
def get_sync_render_settings(self):
|
||||
return self.get('sync_render_settings', True)
|
||||
|
||||
def get_sync_active_camera(self):
|
||||
return self.get('sync_active_camera', True)
|
||||
|
||||
sync_render_settings: bpy.props.BoolProperty(
|
||||
name="Synchronize render settings",
|
||||
description="Synchronize render settings (eevee and cycles only)",
|
||||
default=True)
|
||||
default=True,
|
||||
set=set_sync_render_settings,
|
||||
get=get_sync_render_settings)
|
||||
sync_during_editmode: bpy.props.BoolProperty(
|
||||
name="Edit mode updates",
|
||||
description="Enable objects update in edit mode (! Impact performances !)",
|
||||
default=False
|
||||
)
|
||||
sync_active_camera: bpy.props.BoolProperty(
|
||||
name="Synchronize active camera",
|
||||
description="Synchronize the active camera",
|
||||
default=True,
|
||||
get=get_sync_active_camera,
|
||||
set=set_sync_active_camera
|
||||
)
|
||||
|
||||
|
||||
class SessionPrefs(bpy.types.AddonPreferences):
|
||||
@ -136,8 +172,8 @@ class SessionPrefs(bpy.types.AddonPreferences):
|
||||
ipc_port: bpy.props.IntProperty(
|
||||
name="ipc_port",
|
||||
description='internal ttl port(only usefull for multiple local instances)',
|
||||
default=5561,
|
||||
update=update_port
|
||||
default=random.randrange(5570, 70000),
|
||||
update=update_port,
|
||||
)
|
||||
init_method: bpy.props.EnumProperty(
|
||||
name='init_method',
|
||||
@ -171,11 +207,6 @@ class SessionPrefs(bpy.types.AddonPreferences):
|
||||
description='Dependency graph uppdate rate (milliseconds)',
|
||||
default=100
|
||||
)
|
||||
enable_editmode_updates: bpy.props.BoolProperty(
|
||||
name="Edit mode updates",
|
||||
description="Enable objects update in edit mode (! Impact performances !)",
|
||||
default=False
|
||||
)
|
||||
clear_memory_filecache: bpy.props.BoolProperty(
|
||||
name="Clear memory filecache",
|
||||
description="Remove filecache from memory",
|
||||
|
106
multi_user/ui.py
106
multi_user/ui.py
@ -122,61 +122,39 @@ class SESSION_PT_settings(bpy.types.Panel):
|
||||
or (operators.client and operators.client.state['STATE'] == STATE_INITIAL):
|
||||
pass
|
||||
else:
|
||||
cli_state = operators.client.state
|
||||
|
||||
|
||||
cli_state = operators.client.state
|
||||
row = layout.row()
|
||||
|
||||
current_state = cli_state['STATE']
|
||||
info_msg = None
|
||||
|
||||
# STATE ACTIVE
|
||||
if current_state in [STATE_ACTIVE]:
|
||||
row.operator("session.stop", icon='QUIT', text="Exit")
|
||||
row = layout.row()
|
||||
if runtime_settings.is_host:
|
||||
row = row.box()
|
||||
row.label(text=f"LAN: {runtime_settings.internet_ip}", icon='INFO')
|
||||
row = layout.row()
|
||||
row = row.split(factor=0.3)
|
||||
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='OBJECT_DATAMODE')
|
||||
|
||||
row= layout.row()
|
||||
|
||||
if current_state in [STATE_ACTIVE] and runtime_settings.is_host:
|
||||
info_msg = f"LAN: {runtime_settings.internet_ip}"
|
||||
if current_state == STATE_LOBBY:
|
||||
row = row.box()
|
||||
row.label(text=f"Waiting the session to start", icon='INFO')
|
||||
row = layout.row()
|
||||
row.operator("session.stop", icon='QUIT', text="Exit")
|
||||
# CONNECTION STATE
|
||||
elif current_state in [STATE_SRV_SYNC,
|
||||
STATE_SYNCING,
|
||||
STATE_AUTH,
|
||||
STATE_CONFIG,
|
||||
STATE_WAITING]:
|
||||
info_msg = "Waiting the session to start."
|
||||
|
||||
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
|
||||
))
|
||||
if info_msg:
|
||||
info_box = row.box()
|
||||
info_box.row().label(text=info_msg,icon='INFO')
|
||||
|
||||
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,
|
||||
# Progress bar
|
||||
if current_state in [STATE_SYNCING, STATE_SRV_SYNC, STATE_WAITING]:
|
||||
info_box = row.box()
|
||||
info_box.row().label(text=printProgressBar(
|
||||
cli_state['CURRENT'],
|
||||
cli_state['TOTAL'],
|
||||
length=16
|
||||
))
|
||||
|
||||
layout.row().operator("session.stop", icon='QUIT', text="Exit")
|
||||
|
||||
class SESSION_PT_settings_network(bpy.types.Panel):
|
||||
bl_idname = "MULTIUSER_SETTINGS_NETWORK_PT_panel"
|
||||
@ -320,10 +298,12 @@ class SESSION_PT_advanced_settings(bpy.types.Panel):
|
||||
replication_section_row = replication_section.row()
|
||||
replication_section_row.prop(settings.sync_flags, "sync_render_settings")
|
||||
replication_section_row = replication_section.row()
|
||||
|
||||
replication_section_row.prop(settings, "enable_editmode_updates")
|
||||
replication_section_row.prop(settings.sync_flags, "sync_active_camera")
|
||||
replication_section_row = replication_section.row()
|
||||
if settings.enable_editmode_updates:
|
||||
|
||||
replication_section_row.prop(settings.sync_flags, "sync_during_editmode")
|
||||
replication_section_row = replication_section.row()
|
||||
if settings.sync_flags.sync_during_editmode:
|
||||
warning = replication_section_row.box()
|
||||
warning.label(text="Don't use this with heavy meshes !", icon='ERROR')
|
||||
replication_section_row = replication_section.row()
|
||||
@ -502,36 +482,6 @@ class SESSION_PT_presence(bpy.types.Panel):
|
||||
row.active = settings.presence_show_user
|
||||
row.prop(settings, "presence_show_far_user")
|
||||
|
||||
|
||||
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_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_header(self, context):
|
||||
self.layout.label(text="", icon='FILE_CACHE')
|
||||
|
||||
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 = get_preferences()
|
||||
runtime_settings = context.window_manager.session
|
||||
@ -681,9 +631,7 @@ classes = (
|
||||
SESSION_PT_presence,
|
||||
SESSION_PT_advanced_settings,
|
||||
SESSION_PT_user,
|
||||
SESSION_PT_services,
|
||||
SESSION_PT_repository,
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user