Compare commits
3 Commits
242-replay
...
v0.4.0
Author | SHA1 | Date | |
---|---|---|---|
ac84509b83 | |||
69565b3852 | |||
57fdd492ef |
@ -212,14 +212,14 @@ You can run the dedicated server on any platform by following these steps:
|
|||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
replication.serve
|
replication.server
|
||||||
|
|
||||||
.. 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.serve -p 5555 -pwd admin -t 5000 -l INFO -lf server.log
|
replication.server -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.serve -pwd admin -p 5555 -t 5000 -l DEBUG -lf multiuser_server.log
|
replication.server -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
|
||||||
|
|
||||||
replication.serve -pwd supersecretpassword -p 5555 -t 3000 -l DEBUG -lf logname.log
|
python3 -m replication.server -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
|
||||||
|
|
||||||
replication.serve -p 5555 -pwd admin -t 5000 -l INFO -lf server.log
|
python3 -m replication.server -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
|
||||||
|
|
||||||
|
@ -61,7 +61,6 @@ 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
|
||||||
|
|
||||||
@ -71,7 +70,6 @@ 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)
|
||||||
@ -85,9 +83,7 @@ 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():
|
||||||
@ -95,17 +91,14 @@ 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()
|
||||||
@ -114,6 +107,5 @@ 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()
|
||||||
|
@ -43,7 +43,7 @@ __all__ = [
|
|||||||
"bl_particle",
|
"bl_particle",
|
||||||
] # Order here defines execution order
|
] # Order here defines execution order
|
||||||
|
|
||||||
if bpy.app.version >= (2,91,0):
|
if bpy.app.version[1] >= 91:
|
||||||
__all__.append('bl_volume')
|
__all__.append('bl_volume')
|
||||||
|
|
||||||
from . import *
|
from . import *
|
||||||
|
@ -53,12 +53,12 @@ STROKE = [
|
|||||||
"uv_translation",
|
"uv_translation",
|
||||||
"vertex_color_fill",
|
"vertex_color_fill",
|
||||||
]
|
]
|
||||||
if bpy.app.version >= (2,91,0):
|
if bpy.app.version[1] >= 91:
|
||||||
STROKE.append('use_cyclic')
|
STROKE.append('use_cyclic')
|
||||||
else:
|
else:
|
||||||
STROKE.append('draw_cyclic')
|
STROKE.append('draw_cyclic')
|
||||||
|
|
||||||
if bpy.app.version >= (2,83,0):
|
if bpy.app.version[1] >= 83:
|
||||||
STROKE_POINT.append('vertex_color')
|
STROKE_POINT.append('vertex_color')
|
||||||
|
|
||||||
def dump_stroke(stroke):
|
def dump_stroke(stroke):
|
||||||
|
@ -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 >= (2,83,0):
|
if bpy.app.version[1] >= 83:
|
||||||
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 < (2,83,0):
|
if bpy.app.version[1] < 83:
|
||||||
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()
|
||||||
|
@ -48,7 +48,7 @@ SHAPEKEY_BLOCK_ATTR = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
if bpy.app.version >= (2,93,0):
|
if bpy.app.version[1] >= 93:
|
||||||
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,24 +56,14 @@ else:
|
|||||||
blender 2.92.")
|
blender 2.92.")
|
||||||
|
|
||||||
|
|
||||||
def get_node_group_properties_identifiers(node_group):
|
def get_node_group_inputs(node_group):
|
||||||
props_ids = []
|
inputs = []
|
||||||
# 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:
|
||||||
props_ids.append((inpt.identifier, inpt.type))
|
inputs.append(inpt)
|
||||||
|
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]
|
||||||
|
|
||||||
|
|
||||||
@ -132,35 +122,29 @@ 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_props(modifier: bpy.types.Modifier) -> list:
|
def dump_modifier_geometry_node_inputs(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_props = []
|
dumped_inputs = []
|
||||||
|
for inpt in get_node_group_inputs(modifier.node_group):
|
||||||
|
input_value = modifier[inpt.identifier]
|
||||||
|
|
||||||
for prop_value, prop_type in get_node_group_properties_identifiers(modifier.node_group):
|
dumped_input = None
|
||||||
try:
|
if isinstance(input_value, bpy.types.ID):
|
||||||
prop_value = modifier[prop_value]
|
dumped_input = input_value.uuid
|
||||||
except KeyError as e:
|
elif isinstance(input_value, SUPPORTED_GEOMETRY_NODE_PARAMETERS):
|
||||||
logging.error(f"fail to dump geomety node modifier property : {prop_value} ({e})")
|
dumped_input = input_value
|
||||||
else:
|
elif hasattr(input_value, 'to_list'):
|
||||||
dump = None
|
dumped_input = input_value.to_list()
|
||||||
if isinstance(prop_value, bpy.types.ID):
|
dumped_inputs.append(dumped_input)
|
||||||
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()
|
|
||||||
|
|
||||||
dumped_props.append((dump, prop_type))
|
return dumped_inputs
|
||||||
# logging.info(prop_value)
|
|
||||||
|
|
||||||
return dumped_props
|
|
||||||
|
|
||||||
|
|
||||||
def load_modifier_geometry_node_props(dumped_modifier: dict, target_modifier: bpy.types.Modifier):
|
def load_modifier_geometry_node_inputs(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
|
||||||
@ -169,17 +153,17 @@ def load_modifier_geometry_node_props(dumped_modifier: dict, target_modifier: bp
|
|||||||
:type target_modifier: bpy.type.Modifier
|
:type target_modifier: bpy.type.Modifier
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for input_index, inpt in enumerate(get_node_group_properties_identifiers(target_modifier.node_group)):
|
for input_index, inpt in enumerate(get_node_group_inputs(target_modifier.node_group)):
|
||||||
dumped_value, dumped_type = dumped_modifier['props'][input_index]
|
dumped_value = dumped_modifier['inputs'][input_index]
|
||||||
input_value = target_modifier[inpt[0]]
|
input_value = target_modifier[inpt.identifier]
|
||||||
if dumped_type in ['INT', 'VALUE', 'STR']:
|
if isinstance(input_value, SUPPORTED_GEOMETRY_NODE_PARAMETERS):
|
||||||
logging.info(f"{inpt[0]}/{dumped_value}")
|
target_modifier[inpt.identifier] = dumped_value
|
||||||
target_modifier[inpt[0]] = dumped_value
|
elif hasattr(input_value, 'to_list'):
|
||||||
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 dumped_type in ['COLLECTION', 'OBJECT', 'IMAGE', 'TEXTURE', 'MATERIAL']:
|
elif inpt.type in ['COLLECTION', 'OBJECT']:
|
||||||
target_modifier[inpt[0]] = get_datablock_from_uuid(dumped_value, None)
|
target_modifier[inpt.identifier] = get_datablock_from_uuid(
|
||||||
|
dumped_value, None)
|
||||||
|
|
||||||
|
|
||||||
def load_pose(target_bone, data):
|
def load_pose(target_bone, data):
|
||||||
@ -214,12 +198,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 >= (2,83,0):
|
if bpy.app.version[1] >= 83:
|
||||||
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 >= (2,91,0) and name in bpy.data.volumes.keys():
|
elif bpy.app.version[1] >= 91 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
|
||||||
@ -266,11 +250,10 @@ 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, inpt_type in get_node_group_properties_identifiers(mod.node_group):
|
# for inpt in get_node_group_inputs(mod.node_group):
|
||||||
inpt_value = mod.get(inpt)
|
# parameter = mod.get(inpt.identifier)
|
||||||
# Avoid to handle 'COLLECTION', 'OBJECT' to avoid circular dependencies
|
# if parameter and isinstance(parameter, bpy.types.ID):
|
||||||
if inpt_type in ['IMAGE', 'TEXTURE', 'MATERIAL'] and inpt_value:
|
# dependencies.append(parameter)
|
||||||
dependencies.append(inpt_value)
|
|
||||||
|
|
||||||
return dependencies
|
return dependencies
|
||||||
|
|
||||||
@ -404,7 +387,10 @@ 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_modifier['props'] = dump_modifier_geometry_node_props(modifier)
|
dumped_inputs = dump_modifier_geometry_node_inputs(
|
||||||
|
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",
|
||||||
@ -469,7 +455,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_props(dumped_modifier, loaded_modifier)
|
load_modifier_geometry_node_inputs(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']
|
||||||
|
@ -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.values():
|
for seq_data in sequences.value():
|
||||||
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:
|
||||||
|
@ -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 >= (2,91,0):
|
if bpy.app.version[1] >= 91:
|
||||||
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
|
||||||
|
@ -79,6 +79,8 @@ def on_scene_update(scene):
|
|||||||
logging.debug(f"Ignoring distant update of {dependency_updates[0].id.name}")
|
logging.debug(f"Ignoring distant update of {dependency_updates[0].id.name}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
update_external_dependencies()
|
||||||
|
|
||||||
# NOTE: maybe we don't need to check each update but only the first
|
# NOTE: maybe we don't need to check each update but only the first
|
||||||
for update in reversed(dependency_updates):
|
for update in reversed(dependency_updates):
|
||||||
update_uuid = getattr(update.id, 'uuid', None)
|
update_uuid = getattr(update.id, 'uuid', None)
|
||||||
@ -107,11 +109,6 @@ def on_scene_update(scene):
|
|||||||
porcelain.commit(session.repository, scn_uuid)
|
porcelain.commit(session.repository, scn_uuid)
|
||||||
porcelain.push(session.repository, 'origin', scn_uuid)
|
porcelain.push(session.repository, 'origin', scn_uuid)
|
||||||
|
|
||||||
scene_graph_changed = [u for u in reversed(dependency_updates) if getattr(u.id, 'uuid', None) and isinstance(u.id,(bpy.types.Scene,bpy.types.Collection))]
|
|
||||||
if scene_graph_changed:
|
|
||||||
porcelain.purge_orphan_nodes(session.repository)
|
|
||||||
|
|
||||||
update_external_dependencies()
|
|
||||||
|
|
||||||
@persistent
|
@persistent
|
||||||
def resolve_deps_graph(dummy):
|
def resolve_deps_graph(dummy):
|
||||||
@ -130,29 +127,14 @@ def load_pre_handler(dummy):
|
|||||||
if session and session.state in [STATE_ACTIVE, STATE_SYNCING]:
|
if session and session.state in [STATE_ACTIVE, STATE_SYNCING]:
|
||||||
bpy.ops.session.stop()
|
bpy.ops.session.stop()
|
||||||
|
|
||||||
|
|
||||||
@persistent
|
@persistent
|
||||||
def update_client_frame(scene):
|
def update_client_frame(scene):
|
||||||
setting = bpy.context.window_manager.session
|
|
||||||
if setting.replay_mode == 'TIMELINE' and \
|
|
||||||
setting.replay_files and \
|
|
||||||
scene.active_replay_file != setting.replay_frame_current :
|
|
||||||
index = bpy.context.scene.active_replay_file
|
|
||||||
bpy.ops.session.load(filepath=bpy.context.window_manager.session.replay_files[index].name,
|
|
||||||
draw_users=True,
|
|
||||||
replay=True)
|
|
||||||
setting.replay_frame_current = index
|
|
||||||
|
|
||||||
if session and session.state == STATE_ACTIVE:
|
if session and session.state == STATE_ACTIVE:
|
||||||
porcelain.update_user_metadata(session.repository, {
|
porcelain.update_user_metadata(session.repository, {
|
||||||
'frame_current': scene.frame_current
|
'frame_current': scene.frame_current
|
||||||
})
|
})
|
||||||
|
|
||||||
@persistent
|
|
||||||
def post_frame_update(scene):
|
|
||||||
if bpy.context.window_manager.session.replay_mode == 'TIMELINE' and \
|
|
||||||
not bpy.context.scene.animation_data:
|
|
||||||
bpy.context.scene.animation_data_create()
|
|
||||||
bpy.context.scene.animation_data.action = bpy.data.actions.get('replay_action')
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
bpy.app.handlers.undo_post.append(resolve_deps_graph)
|
bpy.app.handlers.undo_post.append(resolve_deps_graph)
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
# ##### 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
|
|
Before Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 11 KiB |
@ -20,7 +20,6 @@ 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
|
||||||
@ -29,16 +28,13 @@ 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
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from time import gmtime, strftime
|
from time import gmtime, strftime
|
||||||
from numpy import interp
|
|
||||||
|
|
||||||
from bpy.props import FloatProperty
|
from bpy.props import FloatProperty
|
||||||
import bmesh
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import _pickle as pickle
|
import _pickle as pickle
|
||||||
@ -60,164 +56,13 @@ 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, refresh_sidebar_view, bbox_from_obj
|
from .presence import SessionStatusWidget, renderer, view3d_find
|
||||||
from .timers import registry
|
from .timers import registry
|
||||||
|
|
||||||
background_execution_queue = Queue()
|
background_execution_queue = Queue()
|
||||||
deleyables = []
|
deleyables = []
|
||||||
stop_modal_executor = False
|
stop_modal_executor = False
|
||||||
|
|
||||||
|
|
||||||
CLEARED_DATABLOCKS = ['actions', 'armatures', 'cache_files', 'cameras',
|
|
||||||
'collections', 'curves', 'fonts',
|
|
||||||
'grease_pencils', 'images', 'lattices', 'libraries',
|
|
||||||
'lightprobes', 'lights', 'linestyles', 'masks',
|
|
||||||
'materials', 'meshes', 'metaballs', 'movieclips',
|
|
||||||
'node_groups', 'objects', 'paint_curves', 'particles',
|
|
||||||
'scenes', 'shape_keys', 'sounds', 'speakers', 'texts',
|
|
||||||
'textures', 'volumes', 'worlds']
|
|
||||||
|
|
||||||
PERSISTENT_DATABLOCKS = ['LineStyle', 'Dots Stroke', 'replay_action']
|
|
||||||
|
|
||||||
def clean_scene(ignored_datablocks: list = None):
|
|
||||||
"""
|
|
||||||
Delete all datablock of the scene except PERSISTENT_DATABLOCKS and ignored
|
|
||||||
ones in ignored_datablocks.
|
|
||||||
"""
|
|
||||||
PERSISTENT_DATABLOCKS.extend(ignored_datablocks)
|
|
||||||
# Avoid to trigger a runtime error by keeping the last scene
|
|
||||||
PERSISTENT_DATABLOCKS.append(bpy.data.scenes[0].name)
|
|
||||||
|
|
||||||
for type_name in CLEARED_DATABLOCKS:
|
|
||||||
type_collection = getattr(bpy.data, type_name)
|
|
||||||
for datablock in type_collection:
|
|
||||||
if datablock.name in PERSISTENT_DATABLOCKS:
|
|
||||||
logging.debug(f"Skipping {datablock.name}")
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
logging.debug(f"Removing {datablock.name}")
|
|
||||||
type_collection.remove(datablock)
|
|
||||||
|
|
||||||
# Clear sequencer
|
|
||||||
bpy.context.scene.sequence_editor_clear()
|
|
||||||
|
|
||||||
|
|
||||||
def draw_user(username, metadata, radius=0.01, intensity=10.0):
|
|
||||||
"""
|
|
||||||
Generate a mesh representation of a given user frustum and
|
|
||||||
sight of view.
|
|
||||||
"""
|
|
||||||
view_corners = metadata.get('view_corners')
|
|
||||||
color = metadata.get('color', (1,1,1,0))
|
|
||||||
objects = metadata.get('selected_objects', None)
|
|
||||||
scene = metadata.get('scene_current', bpy.context.scene.name)
|
|
||||||
|
|
||||||
user_collection = bpy.data.collections.new(username)
|
|
||||||
# User Color
|
|
||||||
user_mat = bpy.data.materials.new(username)
|
|
||||||
user_mat.use_nodes = True
|
|
||||||
nodes = user_mat.node_tree.nodes
|
|
||||||
nodes.remove(nodes['Principled BSDF'])
|
|
||||||
emission_node = nodes.new('ShaderNodeEmission')
|
|
||||||
emission_node.inputs['Color'].default_value = color
|
|
||||||
emission_node.inputs['Strength'].default_value = intensity
|
|
||||||
|
|
||||||
output_node = nodes['Material Output']
|
|
||||||
user_mat.node_tree.links.new(
|
|
||||||
emission_node.outputs['Emission'], output_node.inputs['Surface'])
|
|
||||||
|
|
||||||
# Generate camera mesh
|
|
||||||
camera_vertices = view_corners[:4]
|
|
||||||
camera_vertices.append(view_corners[6])
|
|
||||||
camera_mesh = bpy.data.meshes.new(f"{username}_camera")
|
|
||||||
camera_obj = bpy.data.objects.new(f"{username}_camera", camera_mesh)
|
|
||||||
frustum_bm = bmesh.new()
|
|
||||||
frustum_bm.from_mesh(camera_mesh)
|
|
||||||
|
|
||||||
for p in camera_vertices:
|
|
||||||
frustum_bm.verts.new(p)
|
|
||||||
frustum_bm.verts.ensure_lookup_table()
|
|
||||||
|
|
||||||
frustum_bm.edges.new((frustum_bm.verts[0], frustum_bm.verts[2]))
|
|
||||||
frustum_bm.edges.new((frustum_bm.verts[2], frustum_bm.verts[1]))
|
|
||||||
frustum_bm.edges.new((frustum_bm.verts[1], frustum_bm.verts[3]))
|
|
||||||
frustum_bm.edges.new((frustum_bm.verts[3], frustum_bm.verts[0]))
|
|
||||||
|
|
||||||
frustum_bm.edges.new((frustum_bm.verts[0], frustum_bm.verts[4]))
|
|
||||||
frustum_bm.edges.new((frustum_bm.verts[2], frustum_bm.verts[4]))
|
|
||||||
frustum_bm.edges.new((frustum_bm.verts[1], frustum_bm.verts[4]))
|
|
||||||
frustum_bm.edges.new((frustum_bm.verts[3], frustum_bm.verts[4]))
|
|
||||||
frustum_bm.edges.ensure_lookup_table()
|
|
||||||
|
|
||||||
frustum_bm.to_mesh(camera_mesh)
|
|
||||||
frustum_bm.free() # free and prevent further access
|
|
||||||
|
|
||||||
camera_obj.modifiers.new("wireframe", "SKIN")
|
|
||||||
camera_obj.data.skin_vertices[0].data[0].use_root = True
|
|
||||||
for v in camera_obj.data.skin_vertices[0].data:
|
|
||||||
v.radius = [radius, radius]
|
|
||||||
|
|
||||||
camera_mesh.materials.append(user_mat)
|
|
||||||
user_collection.objects.link(camera_obj)
|
|
||||||
|
|
||||||
# Generate sight mesh
|
|
||||||
sight_mesh = bpy.data.meshes.new(f"{username}_sight")
|
|
||||||
sight_obj = bpy.data.objects.new(f"{username}_sight", sight_mesh)
|
|
||||||
sight_verts = view_corners[4:6]
|
|
||||||
sight_bm = bmesh.new()
|
|
||||||
sight_bm.from_mesh(sight_mesh)
|
|
||||||
|
|
||||||
for p in sight_verts:
|
|
||||||
sight_bm.verts.new(p)
|
|
||||||
sight_bm.verts.ensure_lookup_table()
|
|
||||||
|
|
||||||
sight_bm.edges.new((sight_bm.verts[0], sight_bm.verts[1]))
|
|
||||||
sight_bm.edges.ensure_lookup_table()
|
|
||||||
sight_bm.to_mesh(sight_mesh)
|
|
||||||
sight_bm.free()
|
|
||||||
|
|
||||||
sight_obj.modifiers.new("wireframe", "SKIN")
|
|
||||||
sight_obj.data.skin_vertices[0].data[0].use_root = True
|
|
||||||
for v in sight_mesh.skin_vertices[0].data:
|
|
||||||
v.radius = [radius, radius]
|
|
||||||
|
|
||||||
sight_mesh.materials.append(user_mat)
|
|
||||||
user_collection.objects.link(sight_obj)
|
|
||||||
|
|
||||||
# Draw selected objects
|
|
||||||
if objects:
|
|
||||||
for o in list(objects):
|
|
||||||
instance = bl_types.bl_datablock.get_datablock_from_uuid(o, None)
|
|
||||||
if instance:
|
|
||||||
bbox_mesh = bpy.data.meshes.new(f"{instance.name}_bbox")
|
|
||||||
bbox_obj = bpy.data.objects.new(
|
|
||||||
f"{instance.name}_bbox", bbox_mesh)
|
|
||||||
bbox_verts, bbox_ind = bbox_from_obj(instance, index=0)
|
|
||||||
bbox_bm = bmesh.new()
|
|
||||||
bbox_bm.from_mesh(bbox_mesh)
|
|
||||||
|
|
||||||
for p in bbox_verts:
|
|
||||||
bbox_bm.verts.new(p)
|
|
||||||
bbox_bm.verts.ensure_lookup_table()
|
|
||||||
|
|
||||||
for e in bbox_ind:
|
|
||||||
bbox_bm.edges.new(
|
|
||||||
(bbox_bm.verts[e[0]], bbox_bm.verts[e[1]]))
|
|
||||||
|
|
||||||
bbox_bm.to_mesh(bbox_mesh)
|
|
||||||
bbox_bm.free()
|
|
||||||
bpy.data.collections[username].objects.link(bbox_obj)
|
|
||||||
|
|
||||||
bbox_obj.modifiers.new("wireframe", "SKIN")
|
|
||||||
bbox_obj.data.skin_vertices[0].data[0].use_root = True
|
|
||||||
for v in bbox_mesh.skin_vertices[0].data:
|
|
||||||
v.radius = [radius, radius]
|
|
||||||
|
|
||||||
bbox_mesh.materials.append(user_mat)
|
|
||||||
|
|
||||||
bpy.data.scenes[scene].collection.children.link(user_collection)
|
|
||||||
|
|
||||||
|
|
||||||
def session_callback(name):
|
def session_callback(name):
|
||||||
""" Session callback wrapper
|
""" Session callback wrapper
|
||||||
|
|
||||||
@ -236,6 +81,7 @@ 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:
|
||||||
@ -262,6 +108,7 @@ 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()
|
||||||
|
|
||||||
@ -295,12 +142,32 @@ 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}. ") #TODO: change op session.notify to add ui + change reason (in replication->interface)
|
bpy.ops.session.notify('INVOKE_DEFAULT', message=f"Disconnected from session. Reason: {reason}. ")
|
||||||
|
|
||||||
|
|
||||||
|
# OPERATORS
|
||||||
|
class SessionStartOperator(bpy.types.Operator):
|
||||||
|
bl_idname = "session.start"
|
||||||
|
bl_label = "start"
|
||||||
|
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
|
||||||
|
|
||||||
def setup_logging():
|
|
||||||
""" Session setup logging (host/connect)
|
|
||||||
"""
|
|
||||||
settings = utils.get_preferences()
|
settings = utils.get_preferences()
|
||||||
|
runtime_settings = context.window_manager.session
|
||||||
|
users = bpy.data.window_managers['WinMan'].online_users
|
||||||
|
admin_pass = settings.password
|
||||||
|
|
||||||
|
users.clear()
|
||||||
|
deleyables.clear()
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
if len(logger.handlers) == 1:
|
if len(logger.handlers) == 1:
|
||||||
formatter = logging.Formatter(
|
formatter = logging.Formatter(
|
||||||
@ -324,10 +191,82 @@ def setup_logging():
|
|||||||
|
|
||||||
handler.setFormatter(formatter)
|
handler.setFormatter(formatter)
|
||||||
|
|
||||||
def setup_timer():
|
bpy_protocol = bl_types.get_data_translation_protocol()
|
||||||
""" Session setup timer (host/connect)
|
|
||||||
"""
|
# Check if supported_datablocks are up to date before starting the
|
||||||
settings = utils.get_preferences()
|
# 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)
|
||||||
|
|
||||||
|
# Host a session
|
||||||
|
if self.host:
|
||||||
|
if settings.init_method == 'EMPTY':
|
||||||
|
utils.clean_scene()
|
||||||
|
|
||||||
|
runtime_settings.is_host = True
|
||||||
|
runtime_settings.internet_ip = environment.get_ip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Init repository
|
||||||
|
for scene in bpy.data.scenes:
|
||||||
|
porcelain.add(repo, scene)
|
||||||
|
|
||||||
|
porcelain.remote_add(
|
||||||
|
repo,
|
||||||
|
'origin',
|
||||||
|
'127.0.0.1',
|
||||||
|
settings.port,
|
||||||
|
admin_password=admin_pass)
|
||||||
|
session.host(
|
||||||
|
repository= repo,
|
||||||
|
remote='origin',
|
||||||
|
timeout=settings.connection_timeout,
|
||||||
|
password=admin_pass,
|
||||||
|
cache_directory=settings.cache_directory,
|
||||||
|
server_log_level=logging.getLevelName(
|
||||||
|
logging.getLogger().level),
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
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
|
||||||
deleyables.append(timers.ClientUpdate())
|
deleyables.append(timers.ClientUpdate())
|
||||||
deleyables.append(timers.DynamicRightSelectTimer())
|
deleyables.append(timers.DynamicRightSelectTimer())
|
||||||
deleyables.append(timers.ApplyTimer(timeout=settings.depsgraph_update_rate))
|
deleyables.append(timers.ApplyTimer(timeout=settings.depsgraph_update_rate))
|
||||||
@ -348,163 +287,6 @@ def setup_timer():
|
|||||||
deleyables.append(session_listen)
|
deleyables.append(session_listen)
|
||||||
deleyables.append(timers.AnnotationUpdates())
|
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
|
|
||||||
class SessionConnectOperator(bpy.types.Operator):
|
|
||||||
bl_idname = "session.connect"
|
|
||||||
bl_label = "connect"
|
|
||||||
bl_description = "connect to a net server"
|
|
||||||
|
|
||||||
@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(context)
|
|
||||||
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 ''
|
|
||||||
|
|
||||||
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 >= (2,91,0):
|
|
||||||
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:
|
|
||||||
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
|
|
||||||
def poll(cls, context):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
global deleyables
|
|
||||||
|
|
||||||
settings = utils.get_preferences()
|
|
||||||
runtime_settings = context.window_manager.session
|
|
||||||
users = bpy.data.window_managers['WinMan'].online_users
|
|
||||||
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 ''
|
|
||||||
|
|
||||||
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 >= (2,91,0):
|
|
||||||
python_binary_path = sys.executable
|
|
||||||
else:
|
|
||||||
python_binary_path = bpy.app.binary_path_python
|
|
||||||
|
|
||||||
repo = Repository(
|
|
||||||
rdp=bpy_protocol,
|
|
||||||
username=settings.username)
|
|
||||||
|
|
||||||
# Host a session
|
|
||||||
if settings.init_method == 'EMPTY':
|
|
||||||
clean_scene()
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Init repository
|
|
||||||
for scene in bpy.data.scenes:
|
|
||||||
porcelain.add(repo, scene)
|
|
||||||
|
|
||||||
porcelain.remote_add(
|
|
||||||
repo,
|
|
||||||
'origin',
|
|
||||||
'127.0.0.1',
|
|
||||||
settings.host_port,
|
|
||||||
server_password=server_pass,
|
|
||||||
admin_password=admin_pass)
|
|
||||||
session.host(
|
|
||||||
repository= repo,
|
|
||||||
remote='origin',
|
|
||||||
timeout=settings.connection_timeout,
|
|
||||||
server_password=server_pass,
|
|
||||||
admin_password=admin_pass,
|
|
||||||
cache_directory=settings.cache_directory,
|
|
||||||
server_log_level=logging.getLevelName(
|
|
||||||
logging.getLogger().level),
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self.report({'ERROR'}, repr(e))
|
|
||||||
logging.error(f"Error: {e}")
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
# Background client updates service
|
|
||||||
setup_timer()
|
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@ -537,7 +319,7 @@ class SessionInitOperator(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
if self.init_method == 'EMPTY':
|
if self.init_method == 'EMPTY':
|
||||||
clean_scene()
|
utils.clean_scene()
|
||||||
|
|
||||||
for scene in bpy.data.scenes:
|
for scene in bpy.data.scenes:
|
||||||
porcelain.add(session.repository, scene)
|
porcelain.add(session.repository, scene)
|
||||||
@ -907,6 +689,7 @@ class SessionPurgeOperator(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
try:
|
try:
|
||||||
|
sanitize_deps_graph(remove_nodes=True)
|
||||||
porcelain.purge_orphan_nodes(session.repository)
|
porcelain.purge_orphan_nodes(session.repository)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.report({'ERROR'}, repr(e))
|
self.report({'ERROR'}, repr(e))
|
||||||
@ -1001,6 +784,7 @@ 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"
|
||||||
@ -1016,89 +800,14 @@ class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
|
|||||||
maxlen=255, # Max internal buffer length, longer would be clamped.
|
maxlen=255, # Max internal buffer length, longer would be clamped.
|
||||||
)
|
)
|
||||||
|
|
||||||
draw_users: bpy.props.BoolProperty(
|
|
||||||
name="Load users",
|
|
||||||
description="Draw users in the scene",
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
replay: bpy.props.BoolProperty(
|
|
||||||
name="Replay mode",
|
|
||||||
description="Enable replay functions",
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
user_skin_radius: bpy.props.FloatProperty(
|
|
||||||
name="Wireframe radius",
|
|
||||||
description="Wireframe radius",
|
|
||||||
default=0.01,
|
|
||||||
)
|
|
||||||
user_color_intensity: bpy.props.FloatProperty(
|
|
||||||
name="Shading intensity",
|
|
||||||
description="Shading intensity",
|
|
||||||
default=1.0,
|
|
||||||
)
|
|
||||||
|
|
||||||
files: bpy.props.CollectionProperty(
|
|
||||||
name='File paths',
|
|
||||||
type=bpy.types.OperatorFileListElement
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
from replication.repository import Repository
|
from replication.repository import Repository
|
||||||
|
|
||||||
runtime_settings = context.window_manager.session
|
|
||||||
|
|
||||||
# init the factory with supported types
|
# init the factory with supported types
|
||||||
bpy_protocol = bl_types.get_data_translation_protocol()
|
bpy_protocol = bl_types.get_data_translation_protocol()
|
||||||
repo = Repository(bpy_protocol)
|
repo = Repository(bpy_protocol)
|
||||||
|
|
||||||
try:
|
|
||||||
repo.loads(self.filepath)
|
repo.loads(self.filepath)
|
||||||
except TypeError:
|
utils.clean_scene()
|
||||||
# Load legacy snapshots
|
|
||||||
db = pickle.load(gzip.open(self.filepath, "rb"))
|
|
||||||
|
|
||||||
nodes = db.get("nodes")
|
|
||||||
|
|
||||||
logging.info(f"Loading legacy {len(nodes)} node")
|
|
||||||
repo.object_store.clear()
|
|
||||||
for node, node_data in nodes:
|
|
||||||
instance = Node(
|
|
||||||
uuid=node,
|
|
||||||
data=node_data.get('data'),
|
|
||||||
owner=node_data.get('owner'),
|
|
||||||
dependencies=node_data.get('dependencies'),
|
|
||||||
state=FETCHED)
|
|
||||||
# Patch data for compatibility
|
|
||||||
type_id = node_data.get('str_type')[2:]
|
|
||||||
if type_id == "File":
|
|
||||||
type_id = "WindowsPath"
|
|
||||||
instance.data['type_id'] = type_id
|
|
||||||
repo.do_commit(instance)
|
|
||||||
instance.state = FETCHED
|
|
||||||
|
|
||||||
# Persitstent collection
|
|
||||||
ignored_datablocks = []
|
|
||||||
|
|
||||||
persistent_collection = bpy.data.collections.get("multiuser_timelapse")
|
|
||||||
if self.replay and \
|
|
||||||
runtime_settings.replay_persistent_collection and \
|
|
||||||
persistent_collection:
|
|
||||||
collection_repo = Repository(
|
|
||||||
rdp=bpy_protocol,
|
|
||||||
username="None")
|
|
||||||
porcelain.add(collection_repo, persistent_collection)
|
|
||||||
porcelain.commit(collection_repo, persistent_collection.uuid)
|
|
||||||
for node in collection_repo.graph.values():
|
|
||||||
ignored_datablocks.append(node.data.get('name'))
|
|
||||||
|
|
||||||
clean_scene(ignored_datablocks=ignored_datablocks)
|
|
||||||
|
|
||||||
nodes = [repo.graph.get(n) for n in repo.index_sorted]
|
nodes = [repo.graph.get(n) for n in repo.index_sorted]
|
||||||
|
|
||||||
@ -1113,69 +822,6 @@ class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
|
|||||||
for node in nodes:
|
for node in nodes:
|
||||||
porcelain.apply(repo, node.uuid)
|
porcelain.apply(repo, node.uuid)
|
||||||
|
|
||||||
if len(self.files) > 1:
|
|
||||||
runtime_settings.replay_files.clear()
|
|
||||||
context.scene.active_replay_file = len(self.files)-1
|
|
||||||
directory = Path(self.filepath).parent
|
|
||||||
file_list = [f['name'] for f in self.files]
|
|
||||||
file_list.sort()
|
|
||||||
for f in file_list:
|
|
||||||
snap = runtime_settings.replay_files.add()
|
|
||||||
snap.name = str(Path(directory, f))
|
|
||||||
print(f)
|
|
||||||
|
|
||||||
if runtime_settings.replay_mode == 'TIMELINE':
|
|
||||||
replay_action = bpy.data.actions.get('replay_action', bpy.data.actions.new('replay_action'))
|
|
||||||
|
|
||||||
bpy.context.scene.animation_data_create()
|
|
||||||
bpy.context.scene.animation_data.action = replay_action
|
|
||||||
if len(replay_action.fcurves) > 0 and replay_action.fcurves[0].data_path == 'active_replay_file':
|
|
||||||
replay_fcurve = replay_action.fcurves[0]
|
|
||||||
else:
|
|
||||||
replay_fcurve = replay_action.fcurves.new('active_replay_file')
|
|
||||||
|
|
||||||
for p in reversed(replay_fcurve.keyframe_points):
|
|
||||||
replay_fcurve.keyframe_points.remove(p, fast=True)
|
|
||||||
|
|
||||||
duration = runtime_settings.replay_duration
|
|
||||||
file_count = len(self.files)-1
|
|
||||||
for index in range(0, file_count):
|
|
||||||
frame = interp(index, [0, file_count], [bpy.context.scene.frame_start, duration])
|
|
||||||
replay_fcurve.keyframe_points.insert(frame, index)
|
|
||||||
|
|
||||||
|
|
||||||
if self.draw_users:
|
|
||||||
f = gzip.open(self.filepath, "rb")
|
|
||||||
db = pickle.load(f)
|
|
||||||
|
|
||||||
users = db.get("users")
|
|
||||||
|
|
||||||
for username, user_data in users.items():
|
|
||||||
metadata = user_data['metadata']
|
|
||||||
|
|
||||||
if metadata:
|
|
||||||
draw_user(username, metadata, radius=self.user_skin_radius, intensity=self.user_color_intensity)
|
|
||||||
|
|
||||||
# Relink the persistent collection
|
|
||||||
if self.replay and persistent_collection:
|
|
||||||
logging.info(f"Relinking {persistent_collection.name}")
|
|
||||||
bpy.context.scene.collection.children.link(persistent_collection)
|
|
||||||
|
|
||||||
# Reasign scene action
|
|
||||||
if self.replay and \
|
|
||||||
runtime_settings.replay_mode == 'TIMELINE' and \
|
|
||||||
not bpy.context.scene.animation_data :
|
|
||||||
bpy.context.scene.animation_data_create()
|
|
||||||
bpy.context.scene.animation_data.action = bpy.data.actions.get('replay_action')
|
|
||||||
bpy.context.scene.frame_end = runtime_settings.replay_duration
|
|
||||||
|
|
||||||
# Reasign the scene camera
|
|
||||||
if self.replay and \
|
|
||||||
runtime_settings.replay_persistent_collection and \
|
|
||||||
runtime_settings.replay_camera:
|
|
||||||
bpy.context.scene.camera = runtime_settings.replay_camera
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@ -1183,169 +829,54 @@ class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
|
|||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
class SESSION_PT_ImportUser(bpy.types.Panel):
|
|
||||||
bl_space_type = 'FILE_BROWSER'
|
|
||||||
bl_region_type = 'TOOL_PROPS'
|
|
||||||
bl_label = "Users"
|
|
||||||
bl_parent_id = "FILE_PT_operator"
|
|
||||||
bl_options = {'DEFAULT_CLOSED'}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
sfile = context.space_data
|
|
||||||
operator = sfile.active_operator
|
|
||||||
|
|
||||||
return operator.bl_idname == "SESSION_OT_load"
|
|
||||||
|
|
||||||
def draw_header(self, context):
|
|
||||||
sfile = context.space_data
|
|
||||||
operator = sfile.active_operator
|
|
||||||
|
|
||||||
self.layout.prop(operator, "draw_users", text="")
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
layout.use_property_split = True
|
|
||||||
layout.use_property_decorate = False # No animation.
|
|
||||||
|
|
||||||
sfile = context.space_data
|
|
||||||
operator = sfile.active_operator
|
|
||||||
|
|
||||||
layout.enabled = operator.draw_users
|
|
||||||
|
|
||||||
layout.prop(operator, "user_skin_radius")
|
|
||||||
layout.prop(operator, "user_color_intensity")
|
|
||||||
|
|
||||||
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 a server to the server preset list"
|
bl_description = "add the current server to the server preset list"
|
||||||
bl_options = {"REGISTER"}
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
server_name: bpy.props.StringProperty(default="")
|
name : bpy.props.StringProperty(default="server_preset")
|
||||||
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
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def invoke(self, context, event):
|
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)
|
assert(context)
|
||||||
return context.window_manager.invoke_props_dialog(self)
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
|
||||||
row = layout.row()
|
col = layout.column()
|
||||||
row.prop(self, "server_name", text="Server name")
|
settings = utils.get_preferences()
|
||||||
row = layout.row(align = True)
|
|
||||||
row.prop(self, "ip", text="IP+port")
|
col.prop(settings, "server_name", text="server name")
|
||||||
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):
|
def execute(self, context):
|
||||||
assert(context)
|
assert(context)
|
||||||
|
|
||||||
settings = utils.get_preferences()
|
settings = utils.get_preferences()
|
||||||
existing_preset = settings.get_server_preset(self.server_name)
|
|
||||||
|
existing_preset = settings.server_preset.get(settings.server_name)
|
||||||
|
|
||||||
new_server = existing_preset if existing_preset else settings.server_preset.add()
|
new_server = existing_preset if existing_preset else settings.server_preset.add()
|
||||||
new_server.name = str(uuid4())
|
new_server.name = settings.server_name
|
||||||
new_server.server_name = self.server_name
|
new_server.server_ip = settings.ip
|
||||||
new_server.ip = self.ip
|
new_server.server_port = settings.port
|
||||||
new_server.port = self.port
|
new_server.server_password = settings.password
|
||||||
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()
|
settings.server_preset_interface = settings.server_name
|
||||||
|
|
||||||
if new_server == existing_preset :
|
if new_server == existing_preset :
|
||||||
self.report({'INFO'}, "Server '" + self.server_name + "' edited")
|
self.report({'INFO'}, "Server '" + settings.server_name + "' override")
|
||||||
else :
|
else :
|
||||||
self.report({'INFO'}, "New '" + self.server_name + "' server preset")
|
self.report({'INFO'}, "New '" + settings.server_name + "' server preset")
|
||||||
|
|
||||||
return {'FINISHED'}
|
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
|
|
||||||
def poll(cls, context):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def invoke(self, context, event):
|
|
||||||
assert(context)
|
|
||||||
return context.window_manager.invoke_props_dialog(self)
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
settings = utils.get_preferences()
|
|
||||||
settings_active_server = settings.server_preset.get(self.target_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):
|
|
||||||
assert(context)
|
|
||||||
|
|
||||||
settings = utils.get_preferences()
|
|
||||||
settings_active_server = settings.server_preset.get(self.target_server_name)
|
|
||||||
|
|
||||||
refresh_sidebar_view()
|
|
||||||
|
|
||||||
self.report({'INFO'}, "Server '" + settings_active_server.server_name + "' edited")
|
|
||||||
|
|
||||||
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"
|
||||||
@ -1353,8 +884,6 @@ 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
|
||||||
@ -1363,97 +892,19 @@ 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)')
|
|
||||||
|
|
||||||
class SessionRenderReplay(bpy.types.Operator):
|
|
||||||
bl_idname = "session.render_replay"
|
|
||||||
bl_label = "Render Replay"
|
|
||||||
bl_description = "Render Replay"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
return context.window_manager.session.replay_files
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
base_path = str(context.scene.render.filepath)
|
|
||||||
for frame in range(0,context.scene.frame_end):
|
|
||||||
logging.info(f"Rendering frame {frame} to {base_path}_{frame}.png")
|
|
||||||
context.scene.frame_current = frame
|
|
||||||
filename = Path(bpy.context.window_manager.session.replay_files[context.scene.active_replay_file].name)
|
|
||||||
context.scene.render.filepath = f"{base_path}{frame}_{filename.stem}"
|
|
||||||
bpy.ops.render.render(write_still=True)
|
|
||||||
|
|
||||||
context.scene.render.filepath = base_path
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
SessionConnectOperator,
|
SessionStartOperator,
|
||||||
SessionHostOperator,
|
|
||||||
SessionStopOperator,
|
SessionStopOperator,
|
||||||
SessionPropertyRemoveOperator,
|
SessionPropertyRemoveOperator,
|
||||||
SessionSnapUserOperator,
|
SessionSnapUserOperator,
|
||||||
@ -1467,16 +918,10 @@ classes = (
|
|||||||
SessionNotifyOperator,
|
SessionNotifyOperator,
|
||||||
SessionSaveBackupOperator,
|
SessionSaveBackupOperator,
|
||||||
SessionLoadSaveOperator,
|
SessionLoadSaveOperator,
|
||||||
SESSION_PT_ImportUser,
|
|
||||||
SessionStopAutoSaveOperator,
|
SessionStopAutoSaveOperator,
|
||||||
SessionPurgeOperator,
|
SessionPurgeOperator,
|
||||||
SessionPresetServerAdd,
|
SessionPresetServerAdd,
|
||||||
SessionPresetServerEdit,
|
|
||||||
SessionPresetServerRemove,
|
SessionPresetServerRemove,
|
||||||
RefreshServerStatus,
|
|
||||||
GetDoc,
|
|
||||||
FirstLaunch,
|
|
||||||
SessionRenderReplay,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,17 +17,15 @@
|
|||||||
|
|
||||||
import random
|
import random
|
||||||
import logging
|
import logging
|
||||||
from uuid import uuid4
|
|
||||||
import bpy
|
import bpy
|
||||||
import string
|
import string
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
from numpy import interp
|
|
||||||
|
|
||||||
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, get_folder_size
|
from .utils import get_preferences, get_expanded_icon
|
||||||
from replication.constants import RP_COMMON
|
from replication.constants import RP_COMMON
|
||||||
from replication.interface import session
|
from replication.interface import session
|
||||||
|
|
||||||
@ -35,21 +33,15 @@ 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_name": "localhost",
|
"server_ip": "localhost",
|
||||||
"ip": "localhost",
|
"server_port": 5555,
|
||||||
"port": 5555,
|
"server_password": "admin"
|
||||||
"use_admin_password": True,
|
|
||||||
"admin_password": "admin",
|
|
||||||
"server_password": ""
|
|
||||||
},
|
},
|
||||||
"public session" : {
|
"public session" : {
|
||||||
"server_name": "public session",
|
"server_ip": "51.75.71.183",
|
||||||
"ip": "51.75.71.183",
|
"server_port": 5555,
|
||||||
"port": 5555,
|
|
||||||
"admin_password": "",
|
|
||||||
"server_password": ""
|
"server_password": ""
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -86,6 +78,11 @@ 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)
|
||||||
@ -100,77 +97,6 @@ def update_directory(self, context):
|
|||||||
def set_log_level(self, value):
|
def set_log_level(self, value):
|
||||||
logging.getLogger().setLevel(value)
|
logging.getLogger().setLevel(value)
|
||||||
|
|
||||||
def set_active_replay(self, value):
|
|
||||||
files_count = len(bpy.context.window_manager.session.replay_files)
|
|
||||||
|
|
||||||
if files_count == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
max_index = files_count-1
|
|
||||||
|
|
||||||
if value > max_index:
|
|
||||||
value = max_index
|
|
||||||
|
|
||||||
if hasattr(self, 'active_replay_file'):
|
|
||||||
self["active_replay_file"] = value
|
|
||||||
else:
|
|
||||||
self.active_replay_file = value
|
|
||||||
|
|
||||||
if bpy.context.window_manager.session.replay_mode == 'MANUAL':
|
|
||||||
bpy.ops.session.load(
|
|
||||||
filepath=bpy.context.window_manager.session.replay_files[value].name,
|
|
||||||
draw_users=True,
|
|
||||||
replay=True)
|
|
||||||
|
|
||||||
def get_active_replay(self):
|
|
||||||
return self.get('active_replay_file', 0)
|
|
||||||
|
|
||||||
|
|
||||||
def set_replay_persistent_collection(self, value):
|
|
||||||
if hasattr(self, 'replay_persistent_collection'):
|
|
||||||
self["replay_persistent_collection"] = value
|
|
||||||
else:
|
|
||||||
self.replay_persistent_collection = value
|
|
||||||
|
|
||||||
collection = bpy.data.collections.get("multiuser_timelapse", None)
|
|
||||||
|
|
||||||
if collection is None and value:
|
|
||||||
collection = bpy.data.collections.new('multiuser_timelapse')
|
|
||||||
bpy.context.scene.collection.children.link(collection)
|
|
||||||
elif collection and not value:
|
|
||||||
for o in collection.objects:
|
|
||||||
bpy.data.objects.remove(o)
|
|
||||||
bpy.data.collections.remove(collection)
|
|
||||||
|
|
||||||
def get_replay_persistent_collection(self):
|
|
||||||
return self.get('replay_persistent_collection', False)
|
|
||||||
|
|
||||||
def set_replay_duration(self, value):
|
|
||||||
if hasattr(self, 'replay_duration'):
|
|
||||||
self["replay_duration"] = value
|
|
||||||
else:
|
|
||||||
self.replay_duration = value
|
|
||||||
|
|
||||||
# Update the animation fcurve
|
|
||||||
replay_action = bpy.data.actions.get('replay_action')
|
|
||||||
replay_fcurve = None
|
|
||||||
|
|
||||||
for fcurve in replay_action.fcurves:
|
|
||||||
if fcurve.data_path == 'active_replay_file':
|
|
||||||
replay_fcurve = fcurve
|
|
||||||
|
|
||||||
if replay_fcurve:
|
|
||||||
for p in reversed(replay_fcurve.keyframe_points):
|
|
||||||
replay_fcurve.keyframe_points.remove(p, fast=True)
|
|
||||||
|
|
||||||
bpy.context.scene.frame_end = value
|
|
||||||
files_count = len(bpy.context.window_manager.session.replay_files)-1
|
|
||||||
for index in range(0, files_count):
|
|
||||||
frame = interp(index,[0, files_count],[bpy.context.scene.frame_start, value])
|
|
||||||
replay_fcurve.keyframe_points.insert(frame, index)
|
|
||||||
|
|
||||||
def get_replay_duration(self):
|
|
||||||
return self.get('replay_duration', 10)
|
|
||||||
|
|
||||||
def get_log_level(self):
|
def get_log_level(self):
|
||||||
return logging.getLogger().level
|
return logging.getLogger().level
|
||||||
@ -184,15 +110,9 @@ class ReplicatedDatablock(bpy.types.PropertyGroup):
|
|||||||
icon: bpy.props.StringProperty()
|
icon: bpy.props.StringProperty()
|
||||||
|
|
||||||
class ServerPreset(bpy.types.PropertyGroup):
|
class ServerPreset(bpy.types.PropertyGroup):
|
||||||
server_name: bpy.props.StringProperty(default="")
|
server_ip: bpy.props.StringProperty()
|
||||||
ip: bpy.props.StringProperty(default="127.0.0.1", update=update_ip)
|
server_port: bpy.props.IntProperty(default=5555)
|
||||||
port: bpy.props.IntProperty(default=5555)
|
server_password: bpy.props.StringProperty(default="admin", subtype = "PASSWORD")
|
||||||
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
|
||||||
@ -242,60 +162,35 @@ class ReplicationFlags(bpy.types.PropertyGroup):
|
|||||||
class SessionPrefs(bpy.types.AddonPreferences):
|
class SessionPrefs(bpy.types.AddonPreferences):
|
||||||
bl_idname = __package__
|
bl_idname = __package__
|
||||||
|
|
||||||
# User settings
|
ip: bpy.props.StringProperty(
|
||||||
|
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',
|
||||||
)
|
)
|
||||||
server_index: bpy.props.IntProperty(
|
password: bpy.props.StringProperty(
|
||||||
name="server_index",
|
name="password",
|
||||||
description="index of the server",
|
default=random_string_digits(),
|
||||||
)
|
|
||||||
# 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
|
||||||
)
|
)
|
||||||
@ -319,11 +214,6 @@ 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)',
|
||||||
@ -335,12 +225,11 @@ 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"),
|
||||||
],
|
],
|
||||||
@ -384,58 +273,38 @@ class SessionPrefs(bpy.types.AddonPreferences):
|
|||||||
step=1,
|
step=1,
|
||||||
subtype='PERCENTAGE',
|
subtype='PERCENTAGE',
|
||||||
)
|
)
|
||||||
presence_text_distance: bpy.props.FloatProperty(
|
presence_mode_distance: bpy.props.FloatProperty(
|
||||||
name="Distance text visibilty",
|
name="Distance mode visibilty",
|
||||||
description="Adjust the distance visibilty of user's mode/name",
|
description="Adjust the distance visibilty of user's mode",
|
||||||
min=0.1,
|
min=0.1,
|
||||||
max=10000,
|
max=1000,
|
||||||
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=False
|
default=True
|
||||||
)
|
)
|
||||||
conf_session_net_expanded: bpy.props.BoolProperty(
|
conf_session_net_expanded: bpy.props.BoolProperty(
|
||||||
name="Net",
|
name="Net",
|
||||||
description="net",
|
description="net",
|
||||||
default=False
|
default=True
|
||||||
)
|
)
|
||||||
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",
|
||||||
@ -446,11 +315,6 @@ class SessionPrefs(bpy.types.AddonPreferences):
|
|||||||
description="sidebar_advanced_log_expanded",
|
description="sidebar_advanced_log_expanded",
|
||||||
default=False
|
default=False
|
||||||
)
|
)
|
||||||
sidebar_advanced_uinfo_expanded: bpy.props.BoolProperty(
|
|
||||||
name="sidebar_advanced_uinfo_expanded",
|
|
||||||
description="sidebar_advanced_uinfo_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",
|
||||||
@ -507,6 +371,12 @@ 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(
|
||||||
@ -516,28 +386,38 @@ 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(
|
||||||
@ -545,57 +425,9 @@ 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()
|
||||||
@ -606,18 +438,25 @@ 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)})")
|
|
||||||
|
|
||||||
# LOGGING
|
# INTERFACE SETTINGS
|
||||||
box = grid.box()
|
box = grid.box()
|
||||||
box.prop(
|
box.prop(
|
||||||
self, "conf_session_log_expanded", text="Logging",
|
self, "conf_session_ui_expanded", text="Interface",
|
||||||
icon=get_expanded_icon(self.conf_session_log_expanded),
|
icon=get_expanded_icon(self.conf_session_ui_expanded),
|
||||||
emboss=False)
|
emboss=False)
|
||||||
if self.conf_session_log_expanded:
|
if self.conf_session_ui_expanded:
|
||||||
|
box.row().prop(self, "panel_category", text="Panel category", expand=True)
|
||||||
row = box.row()
|
row = box.row()
|
||||||
row.label(text="Log level:")
|
row.label(text="Session widget:")
|
||||||
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
|
||||||
@ -638,31 +477,18 @@ 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
|
|
||||||
|
|
||||||
for server_preset in self.server_preset :
|
# custom at launch 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.get_server_preset(preset_name)
|
existing_preset = self.server_preset.get(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 = str(uuid4())
|
new_server.name = preset_name
|
||||||
new_server.server_name = preset_data.get('server_name')
|
new_server.server_ip = preset_data.get('server_ip')
|
||||||
new_server.ip = preset_data.get('ip')
|
new_server.server_port = preset_data.get('server_port')
|
||||||
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):
|
||||||
@ -750,6 +576,11 @@ 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
|
||||||
)
|
)
|
||||||
@ -759,37 +590,6 @@ class SessionProps(bpy.types.PropertyGroup):
|
|||||||
is_host: bpy.props.BoolProperty(
|
is_host: bpy.props.BoolProperty(
|
||||||
default=False
|
default=False
|
||||||
)
|
)
|
||||||
replay_files: bpy.props.CollectionProperty(
|
|
||||||
name='File paths',
|
|
||||||
type=bpy.types.OperatorFileListElement
|
|
||||||
)
|
|
||||||
replay_persistent_collection: bpy.props.BoolProperty(
|
|
||||||
name="replay_persistent_collection",
|
|
||||||
description='Enable a collection that persist accross frames loading',
|
|
||||||
get=get_replay_persistent_collection,
|
|
||||||
set=set_replay_persistent_collection,
|
|
||||||
)
|
|
||||||
replay_mode: bpy.props.EnumProperty(
|
|
||||||
name='replay method',
|
|
||||||
description='Replay in keyframe (timeline) or manually',
|
|
||||||
items={
|
|
||||||
('TIMELINE', 'TIMELINE', 'Replay from the timeline.'),
|
|
||||||
('MANUAL', 'MANUAL', 'Replay manually, from the replay frame widget.')},
|
|
||||||
default='TIMELINE')
|
|
||||||
replay_duration: bpy.props.IntProperty(
|
|
||||||
name='replay interval',
|
|
||||||
default=250,
|
|
||||||
min=10,
|
|
||||||
set=set_replay_duration,
|
|
||||||
get=get_replay_duration,
|
|
||||||
)
|
|
||||||
replay_frame_current: bpy.props.IntProperty(
|
|
||||||
name='replay_frame_current',
|
|
||||||
)
|
|
||||||
replay_camera: bpy.props.PointerProperty(
|
|
||||||
name='Replay camera',
|
|
||||||
type=bpy.types.Object
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
@ -816,16 +616,6 @@ def register():
|
|||||||
# at launch server presets
|
# at launch server presets
|
||||||
prefs.generate_default_presets()
|
prefs.generate_default_presets()
|
||||||
|
|
||||||
bpy.types.Scene.active_replay_file = bpy.props.IntProperty(
|
|
||||||
name="active_replay_file",
|
|
||||||
default=0,
|
|
||||||
min=0,
|
|
||||||
description='Active snapshot',
|
|
||||||
set=set_active_replay,
|
|
||||||
get=get_active_replay,
|
|
||||||
options={'ANIMATABLE'}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
@ -833,5 +623,3 @@ def unregister():
|
|||||||
|
|
||||||
for cls in reversed(classes):
|
for cls in reversed(classes):
|
||||||
unregister_class(cls)
|
unregister_class(cls)
|
||||||
|
|
||||||
del bpy.types.Scene.active_replay_file
|
|
||||||
|
@ -203,7 +203,6 @@ 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)
|
||||||
if node and hasattr(node,'data'):
|
|
||||||
instance_mode = node.data.get('instance_type')
|
instance_mode = node.data.get('instance_type')
|
||||||
if instance_mode and instance_mode == 'COLLECTION':
|
if instance_mode and instance_mode == 'COLLECTION':
|
||||||
to_lock.remove(node_id)
|
to_lock.remove(node_id)
|
||||||
|
467
multi_user/ui.py
@ -16,9 +16,7 @@
|
|||||||
# ##### 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,
|
||||||
@ -73,180 +71,162 @@ 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 = offline_icon
|
connection_icon = "KEYTYPE_MOVING_HOLD_VEC"
|
||||||
|
|
||||||
if state == STATE_ACTIVE:
|
if state == STATE_ACTIVE:
|
||||||
connection_icon = online_icon
|
connection_icon = 'PROP_ON'
|
||||||
else:
|
else:
|
||||||
connection_icon = waiting_icon
|
connection_icon = 'PROP_CON'
|
||||||
|
|
||||||
layout.label(text=f"{str(settings.server_name)} - {get_state_str(cli_state)}", icon_value=connection_icon.icon_id)
|
layout.label(text=f"Session - {get_state_str(cli_state)}", icon=connection_icon)
|
||||||
else:
|
else:
|
||||||
layout.label(text=f"Multi-user - v{__version__}", icon="ANTIALIASED")
|
layout.label(text=f"Session - v{__version__}",icon="PROP_OFF")
|
||||||
|
|
||||||
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 settings.is_first_launch:
|
|
||||||
# USER SETTINGS
|
|
||||||
row = layout.row()
|
|
||||||
row.label(text="1. Enter your username and color:")
|
|
||||||
row = layout.row()
|
|
||||||
split = row.split(factor=0.7, align=True)
|
|
||||||
split.prop(settings, "username", text="")
|
|
||||||
split.prop(settings, "client_color", text="")
|
|
||||||
|
|
||||||
# DOC
|
|
||||||
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'):
|
if hasattr(context.window_manager, 'session'):
|
||||||
# STATE INITIAL
|
# STATE INITIAL
|
||||||
if not session \
|
if not session \
|
||||||
or (session and session.state == STATE_INITIAL):
|
or (session and session.state == STATE_INITIAL):
|
||||||
layout = self.layout
|
pass
|
||||||
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
|
|
||||||
|
|
||||||
# SERVER LIST
|
|
||||||
row = layout.row()
|
|
||||||
box = row.box()
|
|
||||||
box.scale_y = 0.7
|
|
||||||
split = box.split(factor=0.7)
|
|
||||||
split.label(text="Server")
|
|
||||||
split.label(text="Online")
|
|
||||||
|
|
||||||
col = row.column(align=True)
|
|
||||||
col.operator("session.get_info", icon="FILE_REFRESH", text="")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
col = row.column(align=True)
|
|
||||||
col.template_list("SESSION_UL_network", "", settings, "server_preset", context.window_manager, "server_index")
|
|
||||||
col.separator()
|
|
||||||
connectOp = col.row()
|
|
||||||
connectOp.enabled =is_server_selected
|
|
||||||
connectOp.operator("session.connect", text="Connect")
|
|
||||||
|
|
||||||
col = row.column(align=True)
|
|
||||||
col.operator("session.preset_server_add", icon="ADD", text="") # TODO : add conditions (need a name, etc..)
|
|
||||||
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
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
exitbutton = layout.row()
|
|
||||||
exitbutton.scale_y = 1.5
|
|
||||||
exitbutton.operator("session.stop", icon='QUIT', text="Disconnect")
|
|
||||||
|
|
||||||
progress = session.state_progress
|
progress = session.state_progress
|
||||||
|
row = layout.row()
|
||||||
|
|
||||||
current_state = session.state
|
current_state = session.state
|
||||||
info_msg = None
|
info_msg = None
|
||||||
|
|
||||||
if current_state == STATE_LOBBY:
|
if current_state in [STATE_ACTIVE]:
|
||||||
usr = session.online_users.get(settings.username)
|
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')
|
||||||
|
|
||||||
row= layout.row()
|
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:
|
||||||
info_msg = "Waiting for the session to start."
|
info_msg = "Waiting for the session to start."
|
||||||
if usr and usr['admin']:
|
|
||||||
info_msg = "Init the session to start."
|
if info_msg:
|
||||||
info_box = layout.row()
|
info_box = row.box()
|
||||||
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')
|
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]:
|
||||||
row= layout.row()
|
|
||||||
row.label(text=f"Status: {get_state_str(current_state)}")
|
|
||||||
row= layout.row()
|
|
||||||
info_box = row.box()
|
info_box = row.box()
|
||||||
info_box.label(text=printProgressBar(
|
info_box.row().label(text=printProgressBar(
|
||||||
progress['current'],
|
progress['current'],
|
||||||
progress['total'],
|
progress['total'],
|
||||||
length=16
|
length=16
|
||||||
))
|
))
|
||||||
|
|
||||||
class SESSION_PT_host_settings(bpy.types.Panel):
|
layout.row().operator("session.stop", icon='QUIT', text="Exit")
|
||||||
bl_idname = "MULTIUSER_SETTINGS_HOST_PT_panel"
|
|
||||||
bl_label = "Hosting"
|
class SESSION_PT_settings_network(bpy.types.Panel):
|
||||||
|
bl_idname = "MULTIUSER_SETTINGS_NETWORK_PT_panel"
|
||||||
|
bl_label = "Network"
|
||||||
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):
|
||||||
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='NETWORK_DRIVE')
|
self.layout.label(text="", icon='URL')
|
||||||
|
|
||||||
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()
|
||||||
|
|
||||||
#HOST
|
# USER SETTINGS
|
||||||
host_selection = layout.row().box()
|
row = layout.row()
|
||||||
host_selection_row = host_selection.row()
|
row.prop(runtime_settings, "session_mode", expand=True)
|
||||||
host_selection_row.label(text="Init the session from:")
|
row = layout.row()
|
||||||
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()
|
col = row.row(align=True)
|
||||||
host_selection.operator("session.host", text="Host")
|
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):
|
||||||
bl_idname = "MULTIUSER_SETTINGS_REPLICATION_PT_panel"
|
bl_idname = "MULTIUSER_SETTINGS_REPLICATION_PT_panel"
|
||||||
bl_label = "General Settings"
|
bl_label = "Advanced"
|
||||||
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'
|
||||||
@ -254,34 +234,19 @@ 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 USER INFO
|
|
||||||
uinfo_section = layout.row().box()
|
|
||||||
uinfo_section.prop(
|
|
||||||
settings,
|
|
||||||
"sidebar_advanced_uinfo_expanded",
|
|
||||||
text="User Info",
|
|
||||||
icon=get_expanded_icon(settings.sidebar_advanced_uinfo_expanded),
|
|
||||||
emboss=False)
|
|
||||||
if settings.sidebar_advanced_uinfo_expanded:
|
|
||||||
uinfo_section_row = uinfo_section.row()
|
|
||||||
uinfo_section_split = uinfo_section_row.split(factor=0.7, align=True)
|
|
||||||
uinfo_section_split.prop(settings, "username", text="")
|
|
||||||
uinfo_section_split.prop(settings, "client_color", text="")
|
|
||||||
|
|
||||||
#ADVANCED NET
|
|
||||||
net_section = layout.row().box()
|
net_section = layout.row().box()
|
||||||
net_section.prop(
|
net_section.prop(
|
||||||
settings,
|
settings,
|
||||||
@ -289,15 +254,12 @@ 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,
|
||||||
@ -305,12 +267,16 @@ 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:
|
||||||
@ -319,7 +285,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,
|
||||||
@ -337,8 +303,6 @@ 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,
|
||||||
@ -346,11 +310,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"
|
||||||
@ -360,8 +324,7 @@ class SESSION_PT_user(bpy.types.Panel):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return session \
|
return session and session.state in [STATE_ACTIVE, STATE_LOBBY]
|
||||||
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')
|
||||||
@ -373,8 +336,9 @@ 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
|
||||||
|
|
||||||
#USER LIST
|
# Create a simple row.
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
box = row.box()
|
box = row.box()
|
||||||
split = box.split(factor=0.35)
|
split = box.split(factor=0.35)
|
||||||
@ -389,7 +353,6 @@ 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()
|
||||||
@ -473,8 +436,57 @@ 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)
|
||||||
@ -494,18 +506,15 @@ 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_value=sync_status.icon_id).target = item.uuid
|
icon='TRIA_UP').target = item.uuid
|
||||||
detail_item_box.separator()
|
detail_item_box.separator()
|
||||||
|
|
||||||
if item.state in [FETCHED, UP]:
|
if item.state in [FETCHED, UP]:
|
||||||
@ -537,72 +546,12 @@ 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_replay(bpy.types.Panel):
|
|
||||||
bl_idname = "MULTIUSER_REPLAY_PT_panel"
|
|
||||||
bl_label = "Replay"
|
|
||||||
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 context.window_manager.session.replay_files
|
|
||||||
|
|
||||||
def draw_header(self, context):
|
|
||||||
self.layout.label(text="", icon='RECOVER_LAST')
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
settings = context.window_manager.session
|
|
||||||
row= layout.row()
|
|
||||||
row.prop(settings,'replay_mode', toggle=True, expand=True)
|
|
||||||
row= layout.row()
|
|
||||||
if settings.replay_mode == 'MANUAL':
|
|
||||||
row.prop(bpy.context.scene, 'active_replay_file', text="Snapshot index")
|
|
||||||
else:
|
|
||||||
row.prop(settings, 'replay_duration', text="Replay Duration")
|
|
||||||
row= layout.row()
|
|
||||||
row.prop(settings, 'replay_persistent_collection', text="persistent collection", toggle=True, icon='OUTLINER_COLLECTION')
|
|
||||||
|
|
||||||
if settings.replay_persistent_collection:
|
|
||||||
row= layout.row()
|
|
||||||
row.prop(settings, 'replay_camera', text="", 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):
|
||||||
@ -615,8 +564,8 @@ 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 and \
|
(session.state == STATE_ACTIVE or \
|
||||||
not settings.sidebar_repository_shown
|
session.state == STATE_LOBBY and admin)
|
||||||
|
|
||||||
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')
|
||||||
@ -630,18 +579,19 @@ 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="Only show owned data blocks", icon_only=True, icon="DECORATE_UNLOCKED")
|
row.prop(runtime_settings, "filter_owned", text="Show only owned Nodes", 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()
|
||||||
@ -661,6 +611,10 @@ 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.operator("session.init", icon='TOOL_SETTINGS', text="Init")
|
||||||
|
else:
|
||||||
|
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'
|
||||||
@ -680,70 +634,41 @@ 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.prop(settings, "enable_presence",text="Presence Overlay")
|
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 = layout.row()
|
row.prop(settings, "presence_show_user", text="",icon_only=True, icon='CAMERA_DATA')
|
||||||
row.prop(settings, "presence_show_selected",text="Selected Objects")
|
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')
|
||||||
row = layout.row(align=True)
|
|
||||||
row.prop(settings, "presence_show_user", text="Users camera")
|
|
||||||
row.prop(settings, "presence_show_mode", text="Users mode")
|
|
||||||
|
|
||||||
col = layout.column()
|
col = layout.column()
|
||||||
if settings.presence_show_mode or settings.presence_show_user:
|
if settings.presence_show_mode :
|
||||||
row = col.column()
|
row = col.column()
|
||||||
row.prop(pref, "presence_text_distance", expand=True)
|
row.prop(pref, "presence_mode_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 :
|
||||||
split = layout.split()
|
row = col.column()
|
||||||
text_pos = split.column(align=True)
|
row.active = settings.presence_show_session_status
|
||||||
text_pos.active = settings.presence_show_session_status
|
row.prop(pref, "presence_hud_scale", expand=True)
|
||||||
text_pos.prop(pref, "presence_hud_hpos", expand=True)
|
row = col.column(align=True)
|
||||||
text_pos.prop(pref, "presence_hud_vpos", expand=True)
|
row.active = settings.presence_show_session_status
|
||||||
text_scale = split.column()
|
row.prop(pref, "presence_hud_hpos", expand=True)
|
||||||
text_scale.active = settings.presence_show_session_status
|
row.prop(pref, "presence_hud_vpos", expand=True)
|
||||||
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_host_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,
|
||||||
SESSION_PT_replay,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
register, unregister = bpy.utils.register_classes_factory(classes)
|
register, unregister = bpy.utils.register_classes_factory(classes)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -38,6 +38,15 @@ from replication.constants import (STATE_ACTIVE, STATE_AUTH,
|
|||||||
STATE_LOBBY,
|
STATE_LOBBY,
|
||||||
CONNECTING)
|
CONNECTING)
|
||||||
|
|
||||||
|
CLEARED_DATABLOCKS = ['actions', 'armatures', 'cache_files', 'cameras',
|
||||||
|
'collections', 'curves', 'filepath', 'fonts',
|
||||||
|
'grease_pencils', 'images', 'lattices', 'libraries',
|
||||||
|
'lightprobes', 'lights', 'linestyles', 'masks',
|
||||||
|
'materials', 'meshes', 'metaballs', 'movieclips',
|
||||||
|
'node_groups', 'objects', 'paint_curves', 'particles',
|
||||||
|
'scenes', 'shape_keys', 'sounds', 'speakers', 'texts',
|
||||||
|
'textures', 'volumes', 'worlds']
|
||||||
|
|
||||||
def find_from_attr(attr_name, attr_value, list):
|
def find_from_attr(attr_name, attr_value, list):
|
||||||
for item in list:
|
for item in list:
|
||||||
if getattr(item, attr_name, None) == attr_value:
|
if getattr(item, attr_name, None) == attr_value:
|
||||||
@ -99,6 +108,26 @@ def get_state_str(state):
|
|||||||
return state_str
|
return state_str
|
||||||
|
|
||||||
|
|
||||||
|
def clean_scene():
|
||||||
|
for type_name in CLEARED_DATABLOCKS:
|
||||||
|
sub_collection_to_avoid = [
|
||||||
|
bpy.data.linestyles.get('LineStyle'),
|
||||||
|
bpy.data.materials.get('Dots Stroke')
|
||||||
|
]
|
||||||
|
|
||||||
|
type_collection = getattr(bpy.data, type_name)
|
||||||
|
items_to_remove = [i for i in type_collection if i not in sub_collection_to_avoid]
|
||||||
|
for item in items_to_remove:
|
||||||
|
try:
|
||||||
|
type_collection.remove(item)
|
||||||
|
logging.info(item.name)
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Clear sequencer
|
||||||
|
bpy.context.scene.sequence_editor_clear()
|
||||||
|
|
||||||
|
|
||||||
def get_selected_objects(scene, active_view_layer):
|
def get_selected_objects(scene, active_view_layer):
|
||||||
return [obj.uuid for obj in scene.objects if obj.select_get(view_layer=active_view_layer)]
|
return [obj.uuid for obj in scene.objects if obj.select_get(view_layer=active_view_layer)]
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Download base image debian jessie
|
# Download base image debian jessie
|
||||||
FROM python:slim
|
FROM python:slim
|
||||||
|
|
||||||
ARG replication_version=0.9.1
|
ARG replication_version=0.1.13
|
||||||
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 ["replication.serve -apwd ${password} -spwd '' -p ${port} -t ${timeout} -l ${log_level} -lf ${log_file}"]
|
CMD ["python3 -m replication.server -pwd ${password} -p ${port} -t ${timeout} -l ${log_level} -lf ${log_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 < (2,83,0), reason="requires blender 2.83 or higher")
|
@pytest.mark.skipif(bpy.app.version[1] < 83, 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)
|
||||||
|
20
tests/test_operators.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from deepdiff import DeepDiff
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
def test_start_session():
|
||||||
|
result = bpy.ops.session.start()
|
||||||
|
|
||||||
|
|
||||||
|
assert 'FINISHED' in result
|
||||||
|
|
||||||
|
def test_stop_session():
|
||||||
|
|
||||||
|
result = bpy.ops.session.stop()
|
||||||
|
|
||||||
|
assert 'FINISHED' in result
|