Compare commits

..

26 Commits

Author SHA1 Message Date
2910ea654b clean: row factor 2021-12-13 22:29:55 +01:00
5ac61b5348 Merge branch 'develop' into 235-show-color-in-connected-user-pannel 2021-11-17 16:23:03 +01:00
189e5c6cf1 Merge branch 'develop' into 235-show-color-in-connected-user-pannel 2021-11-17 16:19:03 +01:00
80c81dc934 Merge branch '240-adding-music-to-the-sequencer-isn-t-replicating' into 'develop'
Resolve "Adding music to the sequencer isn't replicating"

See merge request slumber/multi-user!159
2021-11-09 09:29:58 +00:00
563fdb693d fix: sound not loading
Related to #240
2021-11-09 10:26:47 +01:00
a64eea3cea Merge branch '239-blender-3-x-compatibility' into 'develop'
Ensure blender 3.x compatibility : Fix geometry node outputs replication

See merge request slumber/multi-user!158
2021-11-09 08:48:30 +00:00
03ad7c0066 fix: geometry nodes input / output 2021-11-08 17:34:02 +01:00
d685573834 Merge branch '239-blender-3-x-compatibility' into 'develop'
Ensure blender 3.x version check

See merge request slumber/multi-user!157
2021-11-05 15:20:35 +00:00
0681b53141 fix: version check 2021-11-05 15:39:46 +01:00
6f02b38b0e fix(replication): missing version update 2021-11-03 16:37:12 +01:00
92c773dae9 Merge branch 'develop' of gitlab.com:slumber/multi-user into develop 2021-11-03 16:34:43 +01:00
f48ade6390 fix python 3.10 compatibility (@NotFood) 2021-11-03 16:32:40 +01:00
63c4501b88 Merge branch '236-crash-with-empty-after-a-reconnection' into 'develop'
Resolve "Crash with empty after a reconnection"

See merge request slumber/multi-user!155
2021-10-29 09:40:04 +00:00
06e21c86ce fix none attribute error 2021-10-21 12:19:46 +02:00
e28d3860da user color property 2021-10-21 12:00:12 +02:00
7b247372fb test: add user color 2021-10-21 12:00:00 +02:00
9d484b00e9 Merge branch '234-user-info-in-side-panel' into 'develop'
User Info in side panel

See merge request slumber/multi-user!153
2021-08-19 16:09:24 +00:00
de9255f71c feat: presence overlay button+UInfo in side panel 2021-08-19 18:04:07 +02:00
99528ea3e0 Merge branch '232-fix-ui-host-and-lobby' into 'develop'
Resolve "fix ui host and lobby"

See merge request slumber/multi-user!152
2021-08-16 14:03:16 +00:00
bb342951a5 fix: lobby init 2021-08-16 15:59:19 +02:00
438a79177b fix: host solo 2021-08-16 12:02:10 +02:00
08fc49c40f fix: session private by default 2021-07-30 14:09:40 +02:00
d7e25b1192 fix: clean docker file 2021-07-30 13:47:31 +02:00
1671422143 Merge branch 'develop' of gitlab.com:slumber/multi-user into develop 2021-07-30 13:17:29 +02:00
a9620c0752 fix: docker server command 2021-07-30 13:16:43 +02:00
583beaf6fe Merge branch '231-server-public-session-private-issue' into 'develop'
Server "public session" private issue

See merge request slumber/multi-user!151
2021-07-28 15:34:24 +00:00
14 changed files with 243 additions and 152 deletions

View File

@ -212,14 +212,14 @@ You can run the dedicated server on any platform by following these steps:
.. code-block:: bash .. code-block:: bash
replication.server replication.serve
.. hint:: .. hint::
You can also specify a custom **port** (-p), **timeout** (-t), **admin password** (-pwd), **log level (ERROR, WARNING, INFO or DEBUG)** (-l) and **log file** (-lf) with the following optional arguments You can also specify a custom **port** (-p), **timeout** (-t), **admin password** (-pwd), **log level (ERROR, WARNING, INFO or DEBUG)** (-l) and **log file** (-lf) with the following optional arguments
.. code-block:: bash .. code-block:: bash
replication.server -p 5555 -pwd admin -t 5000 -l INFO -lf server.log replication.serve -p 5555 -pwd admin -t 5000 -l INFO -lf server.log
Here, for example, a server is instantiated on port 5555, with password 'admin', a 5 second timeout, and logging enabled. Here, for example, a server is instantiated on port 5555, with password 'admin', a 5 second timeout, and logging enabled.
@ -562,7 +562,7 @@ The default Docker image essentially runs the equivalent of:
.. code-block:: bash .. code-block:: bash
replication.server -pwd admin -p 5555 -t 5000 -l DEBUG -lf multiuser_server.log replication.serve -pwd admin -p 5555 -t 5000 -l DEBUG -lf multiuser_server.log
This means the server will be launched with 'admin' as the administrator password, run on ports 5555:5558, use a timeout of 5 seconds, verbose 'DEBUG' log level, and with log files written to 'multiuser_server.log'. See :ref:`cmd-line` for a description of optional parameters. This means the server will be launched with 'admin' as the administrator password, run on ports 5555:5558, use a timeout of 5 seconds, verbose 'DEBUG' log level, and with log files written to 'multiuser_server.log'. See :ref:`cmd-line` for a description of optional parameters.
@ -572,7 +572,7 @@ For example, I would like to launch my server with a different administrator pas
.. code-block:: bash .. code-block:: bash
python3 -m replication.server -pwd supersecretpassword -p 5555 -t 3000 -l DEBUG -lf logname.log replication.serve -pwd supersecretpassword -p 5555 -t 3000 -l DEBUG -lf logname.log
Now, my configuration should look like this: Now, my configuration should look like this:
@ -691,7 +691,7 @@ We're finally ready to launch the server. Simply run:
.. code-block:: bash .. code-block:: bash
python3 -m replication.server -p 5555 -pwd admin -t 5000 -l INFO -lf server.log replication.serve -p 5555 -pwd admin -t 5000 -l INFO -lf server.log
See :ref:`cmd-line` for a description of optional parameters See :ref:`cmd-line` for a description of optional parameters

View File

@ -43,7 +43,7 @@ __all__ = [
"bl_particle", "bl_particle",
] # Order here defines execution order ] # Order here defines execution order
if bpy.app.version[1] >= 91: if bpy.app.version >= (2,91,0):
__all__.append('bl_volume') __all__.append('bl_volume')
from . import * from . import *

View File

@ -53,12 +53,12 @@ STROKE = [
"uv_translation", "uv_translation",
"vertex_color_fill", "vertex_color_fill",
] ]
if bpy.app.version[1] >= 91: if bpy.app.version >= (2,91,0):
STROKE.append('use_cyclic') STROKE.append('use_cyclic')
else: else:
STROKE.append('draw_cyclic') STROKE.append('draw_cyclic')
if bpy.app.version[1] >= 83: if bpy.app.version >= (2,83,0):
STROKE_POINT.append('vertex_color') STROKE_POINT.append('vertex_color')
def dump_stroke(stroke): def dump_stroke(stroke):

View File

@ -37,7 +37,7 @@ class BlLightprobe(ReplicatedDatablock):
def construct(data: dict) -> object: def construct(data: dict) -> object:
type = 'CUBE' if data['type'] == 'CUBEMAP' else data['type'] type = 'CUBE' if data['type'] == 'CUBEMAP' else data['type']
# See https://developer.blender.org/D6396 # See https://developer.blender.org/D6396
if bpy.app.version[1] >= 83: if bpy.app.version >= (2,83,0):
return bpy.data.lightprobes.new(data["name"], type) return bpy.data.lightprobes.new(data["name"], type)
else: else:
logging.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396") logging.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396")
@ -49,7 +49,7 @@ class BlLightprobe(ReplicatedDatablock):
@staticmethod @staticmethod
def dump(datablock: object) -> dict: def dump(datablock: object) -> dict:
if bpy.app.version[1] < 83: if bpy.app.version < (2,83,0):
logging.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396") logging.warning("Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396")
dumper = Dumper() dumper = Dumper()

View File

@ -48,7 +48,7 @@ SHAPEKEY_BLOCK_ATTR = [
] ]
if bpy.app.version[1] >= 93: if bpy.app.version >= (2,93,0):
SUPPORTED_GEOMETRY_NODE_PARAMETERS = (int, str, float) SUPPORTED_GEOMETRY_NODE_PARAMETERS = (int, str, float)
else: else:
SUPPORTED_GEOMETRY_NODE_PARAMETERS = (int, str) SUPPORTED_GEOMETRY_NODE_PARAMETERS = (int, str)
@ -56,14 +56,24 @@ else:
blender 2.92.") blender 2.92.")
def get_node_group_inputs(node_group): def get_node_group_properties_identifiers(node_group):
inputs = [] props_ids = []
# Inputs
for inpt in node_group.inputs: for inpt in node_group.inputs:
if inpt.type in IGNORED_SOCKETS: if inpt.type in IGNORED_SOCKETS:
continue continue
else: else:
inputs.append(inpt) props_ids.append((inpt.identifier, inpt.type))
return inputs
if inpt.type in ['INT', 'VALUE', 'BOOLEAN', 'RGBA', 'VECTOR']:
props_ids.append((f"{inpt.identifier}_attribute_name",'STR'))
props_ids.append((f"{inpt.identifier}_use_attribute", 'BOOL'))
for outpt in node_group.outputs:
if outpt.type not in IGNORED_SOCKETS and outpt.type in ['INT', 'VALUE', 'BOOLEAN', 'RGBA', 'VECTOR']:
props_ids.append((f"{outpt.identifier}_attribute_name", 'STR'))
return props_ids
# return [inpt.identifer for inpt in node_group.inputs if inpt.type not in IGNORED_SOCKETS] # return [inpt.identifer for inpt in node_group.inputs if inpt.type not in IGNORED_SOCKETS]
@ -122,29 +132,35 @@ def load_physics(dumped_settings: dict, target: bpy.types.Object):
bpy.ops.rigidbody.constraint_remove({"object": target}) bpy.ops.rigidbody.constraint_remove({"object": target})
def dump_modifier_geometry_node_inputs(modifier: bpy.types.Modifier) -> list: def dump_modifier_geometry_node_props(modifier: bpy.types.Modifier) -> list:
""" Dump geometry node modifier input properties """ Dump geometry node modifier input properties
:arg modifier: geometry node modifier to dump :arg modifier: geometry node modifier to dump
:type modifier: bpy.type.Modifier :type modifier: bpy.type.Modifier
""" """
dumped_inputs = [] dumped_props = []
for inpt in get_node_group_inputs(modifier.node_group):
input_value = modifier[inpt.identifier]
dumped_input = None for prop_value, prop_type in get_node_group_properties_identifiers(modifier.node_group):
if isinstance(input_value, bpy.types.ID): try:
dumped_input = input_value.uuid prop_value = modifier[prop_value]
elif isinstance(input_value, SUPPORTED_GEOMETRY_NODE_PARAMETERS): except KeyError as e:
dumped_input = input_value logging.error(f"fail to dump geomety node modifier property : {prop_value} ({e})")
elif hasattr(input_value, 'to_list'): else:
dumped_input = input_value.to_list() dump = None
dumped_inputs.append(dumped_input) if isinstance(prop_value, bpy.types.ID):
dump = prop_value.uuid
elif isinstance(prop_value, SUPPORTED_GEOMETRY_NODE_PARAMETERS):
dump = prop_value
elif hasattr(prop_value, 'to_list'):
dump = prop_value.to_list()
return dumped_inputs dumped_props.append((dump, prop_type))
# logging.info(prop_value)
return dumped_props
def load_modifier_geometry_node_inputs(dumped_modifier: dict, target_modifier: bpy.types.Modifier): def load_modifier_geometry_node_props(dumped_modifier: dict, target_modifier: bpy.types.Modifier):
""" Load geometry node modifier inputs """ Load geometry node modifier inputs
:arg dumped_modifier: source dumped modifier to load :arg dumped_modifier: source dumped modifier to load
@ -153,17 +169,17 @@ def load_modifier_geometry_node_inputs(dumped_modifier: dict, target_modifier: b
:type target_modifier: bpy.type.Modifier :type target_modifier: bpy.type.Modifier
""" """
for input_index, inpt in enumerate(get_node_group_inputs(target_modifier.node_group)): for input_index, inpt in enumerate(get_node_group_properties_identifiers(target_modifier.node_group)):
dumped_value = dumped_modifier['inputs'][input_index] dumped_value, dumped_type = dumped_modifier['props'][input_index]
input_value = target_modifier[inpt.identifier] input_value = target_modifier[inpt[0]]
if isinstance(input_value, SUPPORTED_GEOMETRY_NODE_PARAMETERS): if dumped_type in ['INT', 'VALUE', 'STR']:
target_modifier[inpt.identifier] = dumped_value logging.info(f"{inpt[0]}/{dumped_value}")
elif hasattr(input_value, 'to_list'): target_modifier[inpt[0]] = dumped_value
elif dumped_type in ['RGBA', 'VECTOR']:
for index in range(len(input_value)): for index in range(len(input_value)):
input_value[index] = dumped_value[index] input_value[index] = dumped_value[index]
elif inpt.type in ['COLLECTION', 'OBJECT']: elif dumped_type in ['COLLECTION', 'OBJECT', 'IMAGE', 'TEXTURE', 'MATERIAL']:
target_modifier[inpt.identifier] = get_datablock_from_uuid( target_modifier[inpt[0]] = get_datablock_from_uuid(dumped_value, None)
dumped_value, None)
def load_pose(target_bone, data): def load_pose(target_bone, data):
@ -198,12 +214,12 @@ def find_data_from_name(name=None):
instance = bpy.data.speakers[name] instance = bpy.data.speakers[name]
elif name in bpy.data.lightprobes.keys(): elif name in bpy.data.lightprobes.keys():
# Only supported since 2.83 # Only supported since 2.83
if bpy.app.version[1] >= 83: if bpy.app.version >= (2,83,0):
instance = bpy.data.lightprobes[name] instance = bpy.data.lightprobes[name]
else: else:
logging.warning( logging.warning(
"Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396") "Lightprobe replication only supported since 2.83. See https://developer.blender.org/D6396")
elif bpy.app.version[1] >= 91 and name in bpy.data.volumes.keys(): elif bpy.app.version >= (2,91,0) and name in bpy.data.volumes.keys():
# Only supported since 2.91 # Only supported since 2.91
instance = bpy.data.volumes[name] instance = bpy.data.volumes[name]
return instance return instance
@ -250,10 +266,11 @@ def find_geometry_nodes_dependencies(modifiers: bpy.types.bpy_prop_collection) -
for mod in modifiers: for mod in modifiers:
if mod.type == 'NODES' and mod.node_group: if mod.type == 'NODES' and mod.node_group:
dependencies.append(mod.node_group) dependencies.append(mod.node_group)
# for inpt in get_node_group_inputs(mod.node_group): for inpt, inpt_type in get_node_group_properties_identifiers(mod.node_group):
# parameter = mod.get(inpt.identifier) inpt_value = mod.get(inpt)
# if parameter and isinstance(parameter, bpy.types.ID): # Avoid to handle 'COLLECTION', 'OBJECT' to avoid circular dependencies
# dependencies.append(parameter) if inpt_type in ['IMAGE', 'TEXTURE', 'MATERIAL'] and inpt_value:
dependencies.append(inpt_value)
return dependencies return dependencies
@ -387,10 +404,7 @@ def dump_modifiers(modifiers: bpy.types.bpy_prop_collection)->dict:
dumped_modifier = dumper.dump(modifier) dumped_modifier = dumper.dump(modifier)
# hack to dump geometry nodes inputs # hack to dump geometry nodes inputs
if modifier.type == 'NODES': if modifier.type == 'NODES':
dumped_inputs = dump_modifier_geometry_node_inputs( dumped_modifier['props'] = dump_modifier_geometry_node_props(modifier)
modifier)
dumped_modifier['inputs'] = dumped_inputs
elif modifier.type == 'PARTICLE_SYSTEM': elif modifier.type == 'PARTICLE_SYSTEM':
dumper.exclude_filter = [ dumper.exclude_filter = [
"is_edited", "is_edited",
@ -455,7 +469,7 @@ def load_modifiers(dumped_modifiers: list, modifiers: bpy.types.bpy_prop_collect
loader.load(loaded_modifier, dumped_modifier) loader.load(loaded_modifier, dumped_modifier)
if loaded_modifier.type == 'NODES': if loaded_modifier.type == 'NODES':
load_modifier_geometry_node_inputs(dumped_modifier, loaded_modifier) load_modifier_geometry_node_props(dumped_modifier, loaded_modifier)
elif loaded_modifier.type == 'PARTICLE_SYSTEM': elif loaded_modifier.type == 'PARTICLE_SYSTEM':
default = loaded_modifier.particle_system.settings default = loaded_modifier.particle_system.settings
dumped_particles = dumped_modifier['particle_system'] dumped_particles = dumped_modifier['particle_system']

View File

@ -440,7 +440,7 @@ class BlScene(ReplicatedDatablock):
if seq.name not in sequences: if seq.name not in sequences:
vse.sequences.remove(seq) vse.sequences.remove(seq)
# Load existing sequences # Load existing sequences
for seq_data in sequences.value(): for seq_data in sequences.values():
load_sequence(seq_data, vse) load_sequence(seq_data, vse)
# If the sequence is no longer used, clear it # If the sequence is no longer used, clear it
elif datablock.sequence_editor and not sequences: elif datablock.sequence_editor and not sequences:

View File

@ -134,7 +134,7 @@ def install_modules(dependencies: list, python_path: str, install_dir: str):
module_can_be_imported(package_name) module_can_be_imported(package_name)
def register(): def register():
if bpy.app.version[1] >= 91: if bpy.app.version >= (2,91,0):
python_binary_path = sys.executable python_binary_path = sys.executable
else: else:
python_binary_path = bpy.app.binary_path_python python_binary_path = bpy.app.binary_path_python

View File

@ -238,7 +238,7 @@ class SessionConnectOperator(bpy.types.Operator):
settings.generate_supported_types() settings.generate_supported_types()
if bpy.app.version[1] >= 91: if bpy.app.version >= (2,91,0):
python_binary_path = sys.executable python_binary_path = sys.executable
else: else:
python_binary_path = bpy.app.binary_path_python python_binary_path = bpy.app.binary_path_python
@ -309,7 +309,7 @@ class SessionHostOperator(bpy.types.Operator):
settings.generate_supported_types() settings.generate_supported_types()
if bpy.app.version[1] >= 91: if bpy.app.version >= (2,91,0):
python_binary_path = sys.executable python_binary_path = sys.executable
else: else:
python_binary_path = bpy.app.binary_path_python python_binary_path = bpy.app.binary_path_python

View File

@ -374,9 +374,9 @@ 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( sidebar_advanced_uinfo_expanded: bpy.props.BoolProperty(
name="sidebar_advanced_hosting_expanded", name="sidebar_advanced_uinfo_expanded",
description="sidebar_advanced_hosting_expanded", description="sidebar_advanced_uinfo_expanded",
default=False default=False
) )
sidebar_advanced_net_expanded: bpy.props.BoolProperty( sidebar_advanced_net_expanded: bpy.props.BoolProperty(
@ -619,6 +619,11 @@ class SessionUser(bpy.types.PropertyGroup):
""" """
username: bpy.props.StringProperty(name="username") username: bpy.props.StringProperty(name="username")
current_frame: bpy.props.IntProperty(name="current_frame") current_frame: bpy.props.IntProperty(name="current_frame")
color: bpy.props.FloatVectorProperty(name="color", subtype="COLOR",
min=0.0,
max=1.0,
size=4,
default=(1.0, 1.0, 1.0, 1.0))
class SessionProps(bpy.types.PropertyGroup): class SessionProps(bpy.types.PropertyGroup):

View File

@ -203,10 +203,11 @@ class DynamicRightSelectTimer(Timer):
for node_id in to_lock: for node_id in to_lock:
node = session.repository.graph.get(node_id) node = session.repository.graph.get(node_id)
instance_mode = node.data.get('instance_type') if node and hasattr(node,'data'):
if instance_mode and instance_mode == 'COLLECTION': instance_mode = node.data.get('instance_type')
to_lock.remove(node_id) if instance_mode and instance_mode == 'COLLECTION':
instances_to_lock.append(node_id) to_lock.remove(node_id)
instances_to_lock.append(node_id)
if instances_to_lock: if instances_to_lock:
try: try:
porcelain.lock(session.repository, porcelain.lock(session.repository,

View File

@ -62,7 +62,41 @@ def printProgressBar(iteration, total, prefix='', suffix='', decimals=1, length=
bar = fill * filledLength + fill_empty * (length - filledLength) bar = fill * filledLength + fill_empty * (length - filledLength)
return f"{prefix} |{bar}| {iteration}/{total}{suffix}" return f"{prefix} |{bar}| {iteration}/{total}{suffix}"
def get_mode_icon(mode_name: str) -> str:
""" given a mode name retrieve a built-in icon
"""
mode_icon = "NONE"
if mode_name == "OBJECT" :
mode_icon = "OBJECT_DATAMODE"
elif mode_name == "EDIT_MESH" :
mode_icon = "EDITMODE_HLT"
elif mode_name == 'EDIT_CURVE':
mode_icon = "CURVE_DATA"
elif mode_name == 'EDIT_SURFACE':
mode_icon = "SURFACE_DATA"
elif mode_name == 'EDIT_TEXT':
mode_icon = "FILE_FONT"
elif mode_name == 'EDIT_ARMATURE':
mode_icon = "ARMATURE_DATA"
elif mode_name == 'EDIT_METABALL':
mode_icon = "META_BALL"
elif mode_name == 'EDIT_LATTICE':
mode_icon = "LATTICE_DATA"
elif mode_name == 'POSE':
mode_icon = "POSE_HLT"
elif mode_name == 'SCULPT':
mode_icon = "SCULPTMODE_HLT"
elif mode_name == 'PAINT_WEIGHT':
mode_icon = "WPAINT_HLT"
elif mode_name == 'PAINT_VERTEX':
mode_icon = "VPAINT_HLT"
elif mode_name == 'PAINT_TEXTURE':
mode_icon = "TPAINT_HLT"
elif mode_name == 'PARTICLE':
mode_icon = "PARTICLES"
elif mode_name == 'PAINT_GPENCIL' or mode_name =='EDIT_GPENCIL' or mode_name =='SCULPT_GPENCIL' or mode_name =='WEIGHT_GPENCIL' or mode_name =='VERTEX_GPENCIL':
mode_icon = "GREASEPENCIL"
return mode_icon
class SESSION_PT_settings(bpy.types.Panel): class SESSION_PT_settings(bpy.types.Panel):
"""Settings panel""" """Settings panel"""
bl_idname = "MULTIUSER_SETTINGS_PT_panel" bl_idname = "MULTIUSER_SETTINGS_PT_panel"
@ -149,10 +183,8 @@ class SESSION_PT_settings(bpy.types.Panel):
col.template_list("SESSION_UL_network", "", settings, "server_preset", context.window_manager, "server_index") col.template_list("SESSION_UL_network", "", settings, "server_preset", context.window_manager, "server_index")
col.separator() col.separator()
connectOp = col.row() connectOp = col.row()
connectOp.operator("session.host", text="Host") connectOp.enabled =is_server_selected
connectopcol = connectOp.column() connectOp.operator("session.connect", text="Connect")
connectopcol.enabled =is_server_selected
connectopcol.operator("session.connect", text="Connect")
col = row.column(align=True) col = row.column(align=True)
col.operator("session.preset_server_add", icon="ADD", text="") # TODO : add conditions (need a name, etc..) col.operator("session.preset_server_add", icon="ADD", text="") # TODO : add conditions (need a name, etc..)
@ -173,12 +205,18 @@ class SESSION_PT_settings(bpy.types.Panel):
info_msg = None info_msg = None
if current_state == STATE_LOBBY: if current_state == STATE_LOBBY:
usr = session.online_users.get(settings.username)
row= layout.row() row= layout.row()
info_msg = "Waiting for the session to start." info_msg = "Waiting for the session to start."
if usr and usr['admin']:
if info_msg: info_msg = "Init the session to start."
info_box = row.box() info_box = layout.row()
info_box.row().label(text=info_msg,icon='INFO') info_box.label(text=info_msg,icon='INFO')
init_row = layout.row()
init_row.operator("session.init", icon='TOOL_SETTINGS', text="Init")
else:
info_box = layout.row()
info_box.row().label(text=info_msg,icon='INFO')
# PROGRESS BAR # PROGRESS BAR
if current_state in [STATE_SYNCING, STATE_SRV_SYNC, STATE_WAITING]: if current_state in [STATE_SYNCING, STATE_SRV_SYNC, STATE_WAITING]:
@ -192,10 +230,57 @@ class SESSION_PT_settings(bpy.types.Panel):
length=16 length=16
)) ))
class SESSION_PT_host_settings(bpy.types.Panel):
bl_idname = "MULTIUSER_SETTINGS_HOST_PT_panel"
bl_label = "Hosting"
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):
settings = get_preferences()
return not session \
or (session and session.state == 0) \
and not settings.sidebar_advanced_shown \
and not settings.is_first_launch
def draw_header(self, context):
self.layout.label(text="", icon='NETWORK_DRIVE')
def draw(self, context):
layout = self.layout
settings = get_preferences()
#HOST
host_selection = layout.row().box()
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_row.label(text="Port:")
host_selection_row.prop(settings, "host_port", 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="")
host_selection = layout.column()
host_selection.operator("session.host", text="Host")
class SESSION_PT_advanced_settings(bpy.types.Panel): class SESSION_PT_advanced_settings(bpy.types.Panel):
bl_idname = "MULTIUSER_SETTINGS_REPLICATION_PT_panel" bl_idname = "MULTIUSER_SETTINGS_REPLICATION_PT_panel"
bl_label = "Advanced" bl_label = "General Settings"
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'
@ -216,30 +301,19 @@ class SESSION_PT_advanced_settings(bpy.types.Panel):
layout = self.layout layout = self.layout
settings = get_preferences() settings = get_preferences()
#ADVANCED HOST #ADVANCED USER INFO
host_selection = layout.row().box() uinfo_section = layout.row().box()
host_selection.prop( uinfo_section.prop(
settings, "sidebar_advanced_hosting_expanded", text="Hosting", settings,
icon=get_expanded_icon(settings.sidebar_advanced_hosting_expanded), "sidebar_advanced_uinfo_expanded",
text="User Info",
icon=get_expanded_icon(settings.sidebar_advanced_uinfo_expanded),
emboss=False) emboss=False)
if settings.sidebar_advanced_hosting_expanded: if settings.sidebar_advanced_uinfo_expanded:
host_selection_row = host_selection.row() uinfo_section_row = uinfo_section.row()
host_selection_row.prop(settings, "host_port", text="Port:") uinfo_section_split = uinfo_section_row.split(factor=0.7, align=True)
host_selection_row = host_selection.row() uinfo_section_split.prop(settings, "username", text="")
host_selection_row.label(text="Init the session from:") uinfo_section_split.prop(settings, "client_color", text="")
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 #ADVANCED NET
net_section = layout.row().box() net_section = layout.row().box()
@ -335,18 +409,31 @@ class SESSION_PT_user(bpy.types.Panel):
online_users)-1 >= selected_user else 0 online_users)-1 >= selected_user else 0
#USER LIST #USER LIST
row = layout.row() col = layout.column(align=True)
box = row.box() row = col.row(align=True)
split = box.split(factor=0.35) row = row.split(factor=0.35, align=True)
split.label(text="user")
split = split.split(factor=0.3)
split.label(text="mode")
split.label(text="frame")
split.label(text="location")
split.label(text="ping")
row = layout.row() box = row.box()
layout.template_list("SESSION_UL_users", "", context.window_manager, brow = box.row(align=True)
brow.label(text="user")
row = row.split(factor=0.25, align=True)
box = row.box()
brow = box.row(align=True)
brow.label(text="mode")
box = row.box()
brow = box.row(align=True)
brow.label(text="frame")
box = row.box()
brow = box.row(align=True)
brow.label(text="scene")
box = row.box()
brow = box.row(align=True)
brow.label(text="ping")
row = col.row(align=True)
row.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 #OPERATOR ON USER
@ -393,45 +480,32 @@ class SESSION_UL_users(bpy.types.UIList):
frame_current = str(metadata.get('frame_current','-')) frame_current = str(metadata.get('frame_current','-'))
scene_current = metadata.get('scene_current','-') scene_current = metadata.get('scene_current','-')
mode_current = metadata.get('mode_current','-') mode_current = metadata.get('mode_current','-')
if mode_current == "OBJECT" : mode_current = metadata.get('mode_current','-')
mode_icon = "OBJECT_DATAMODE" mode_icon = get_mode_icon(mode_current)
elif mode_current == "EDIT_MESH" : user_color = metadata.get('color',[1.0,1.0,1.0,1.0])
mode_icon = "EDITMODE_HLT" item.color = user_color
elif mode_current == 'EDIT_CURVE':
mode_icon = "CURVE_DATA"
elif mode_current == 'EDIT_SURFACE':
mode_icon = "SURFACE_DATA"
elif mode_current == 'EDIT_TEXT':
mode_icon = "FILE_FONT"
elif mode_current == 'EDIT_ARMATURE':
mode_icon = "ARMATURE_DATA"
elif mode_current == 'EDIT_METABALL':
mode_icon = "META_BALL"
elif mode_current == 'EDIT_LATTICE':
mode_icon = "LATTICE_DATA"
elif mode_current == 'POSE':
mode_icon = "POSE_HLT"
elif mode_current == 'SCULPT':
mode_icon = "SCULPTMODE_HLT"
elif mode_current == 'PAINT_WEIGHT':
mode_icon = "WPAINT_HLT"
elif mode_current == 'PAINT_VERTEX':
mode_icon = "VPAINT_HLT"
elif mode_current == 'PAINT_TEXTURE':
mode_icon = "TPAINT_HLT"
elif mode_current == 'PARTICLE':
mode_icon = "PARTICLES"
elif mode_current == 'PAINT_GPENCIL' or mode_current =='EDIT_GPENCIL' or mode_current =='SCULPT_GPENCIL' or mode_current =='WEIGHT_GPENCIL' or mode_current =='VERTEX_GPENCIL':
mode_icon = "GREASEPENCIL"
if user['admin']: if user['admin']:
status_icon = 'FAKE_USER_ON' status_icon = 'FAKE_USER_ON'
split = layout.split(factor=0.35) row = layout.split(factor=0.35, align=True)
split.label(text=item.username, icon=status_icon) entry = row.row(align=True)
split = split.split(factor=0.3) entry.scale_x = 0.05
split.label(icon=mode_icon) entry.enabled = False
split.label(text=frame_current) entry.prop(item, 'color', text="", event=False, full_event=False)
split.label(text=scene_current) entry.enabled = True
split.label(text=ping) entry.scale_x = 1.0
entry.label(icon=status_icon, text="")
entry.label(text=item.username)
row = row.split(factor=0.25, align=True)
entry = row.row()
entry.label(icon=mode_icon)
entry = row.row()
entry.label(text=frame_current)
entry = row.row()
entry.label(text=scene_current)
entry = row.row()
entry.label(text=ping)
def draw_property(context, parent, property_uuid, level=0): def draw_property(context, parent, property_uuid, level=0):
settings = get_preferences() settings = get_preferences()
@ -543,8 +617,7 @@ class SESSION_PT_repository(bpy.types.Panel):
admin = usr['admin'] admin = usr['admin']
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 and \
session.state == STATE_LOBBY and admin) and \
not settings.sidebar_repository_shown not settings.sidebar_repository_shown
def draw_header(self, context): def draw_header(self, context):
@ -590,12 +663,6 @@ class SESSION_PT_repository(bpy.types.Panel):
else: else:
layout.row().label(text="Empty") layout.row().label(text="Empty")
elif session.state == STATE_LOBBY and usr and usr['admin']:
row = layout.row()
row.operator("session.init", icon='TOOL_SETTINGS', text="Init")
else:
row = layout.row()
row.label(text="Waiting to start")
class VIEW3D_PT_overlay_session(bpy.types.Panel): class VIEW3D_PT_overlay_session(bpy.types.Panel):
bl_space_type = 'VIEW_3D' bl_space_type = 'VIEW_3D'
@ -614,6 +681,9 @@ class VIEW3D_PT_overlay_session(bpy.types.Panel):
pref = get_preferences() pref = get_preferences()
layout.active = settings.enable_presence layout.active = settings.enable_presence
row = layout.row()
row.prop(settings, "enable_presence",text="Presence Overlay")
row = layout.row() row = layout.row()
row.prop(settings, "presence_show_selected",text="Selected Objects") row.prop(settings, "presence_show_selected",text="Selected Objects")
@ -667,6 +737,7 @@ classes = (
SESSION_UL_users, SESSION_UL_users,
SESSION_UL_network, SESSION_UL_network,
SESSION_PT_settings, SESSION_PT_settings,
SESSION_PT_host_settings,
SESSION_PT_advanced_settings, SESSION_PT_advanced_settings,
SESSION_PT_user, SESSION_PT_user,
SESSION_PT_sync, SESSION_PT_sync,

View File

@ -1,7 +1,7 @@
# Download base image debian jessie # Download base image debian jessie
FROM python:slim FROM python:slim
ARG replication_version=0.1.13 ARG replication_version=0.9.1
ARG version=0.1.1 ARG version=0.1.1
# Infos # Infos
@ -22,4 +22,4 @@ RUN pip install replication==$replication_version
# Run the server with parameters # Run the server with parameters
ENTRYPOINT ["/bin/sh", "-c"] ENTRYPOINT ["/bin/sh", "-c"]
CMD ["python3 -m replication.server -pwd ${password} -p ${port} -t ${timeout} -l ${log_level} -lf ${log_file}"] CMD ["replication.serve -apwd ${password} -spwd '' -p ${port} -t ${timeout} -l ${log_level} -lf ${log_file}"]

View File

@ -7,7 +7,7 @@ import bpy
from multi_user.bl_types.bl_lightprobe import BlLightprobe from multi_user.bl_types.bl_lightprobe import BlLightprobe
@pytest.mark.skipif(bpy.app.version[1] < 83, reason="requires blender 2.83 or higher") @pytest.mark.skipif(bpy.app.version < (2,83,0), reason="requires blender 2.83 or higher")
@pytest.mark.parametrize('lightprobe_type', ['PLANAR','GRID','CUBEMAP']) @pytest.mark.parametrize('lightprobe_type', ['PLANAR','GRID','CUBEMAP'])
def test_lightprobes(clear_blend, lightprobe_type): def test_lightprobes(clear_blend, lightprobe_type):
bpy.ops.object.lightprobe_add(type=lightprobe_type) bpy.ops.object.lightprobe_add(type=lightprobe_type)