Compare commits

...

16 Commits

Author SHA1 Message Date
45437660ba clean: remove unused lock 2020-10-22 17:37:53 +02:00
ee93a5b209 Merge branch 'develop' into 132-fix-undo-edit-last-operation-redo-handling 2020-10-22 16:21:31 +02:00
f90c12b27f doc: added missing fields
feat: changed session widget defaults
2020-10-22 16:07:19 +02:00
3573db0969 Merge branch '134-revamp-session-status-ui-widget' into 'develop'
Resolve "Revamp session status UI widget"

See merge request slumber/multi-user!67
2020-10-22 13:52:29 +00:00
92bde00a5a feat: store session widget settings to preferences 2020-10-22 15:48:13 +02:00
2c82560d24 fix: grease pencil material 2020-10-22 13:55:26 +02:00
6f364d2b88 feat: session widget position and scale settings
feat: ui_scale is now taken in account for session widget text size
2020-10-21 23:33:44 +02:00
760b52c02b Merge branch '135-empty-and-light-objects-user-selection-highlight-is-broken' into 'develop'
Resolve "Empty and Light objects user selection highlight is broken"

See merge request slumber/multi-user!66
2020-10-21 15:25:42 +00:00
4dd932fc56 fix: empty and light display broken 2020-10-21 17:23:59 +02:00
ba1a03cbfa Merge branch '133-material-renaming-is-unstable' into 'develop'
Resolve "Material renaming is unstable"

See merge request slumber/multi-user!65
2020-10-21 13:17:18 +00:00
18b5fa795c feat: resolve materials from uuid by default and fallback on regular name resolving 2020-10-21 15:10:37 +02:00
1a82ec72e4 fix: change owner call in opterator 2020-10-21 14:40:15 +02:00
804747c73b fix: owning parent when a child is already owned (ex: duplicate linked) 2020-10-21 14:15:42 +02:00
7ee705332f feat: update replication to prevent UnpicklingError from crashing the network Thred 2020-10-20 17:25:50 +02:00
4bd0055056 Merge branch 'develop' into 132-fix-undo-edit-last-operation-redo-handling 2020-10-16 14:57:36 +02:00
f151c61d7b feat: mimic blender undo handling 2020-10-15 17:21:14 +02:00
12 changed files with 134 additions and 61 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -251,6 +251,14 @@ it draw users related information in your viewport such as:
The presence overlay panel (see image above) allow you to enable/disable The presence overlay panel (see image above) allow you to enable/disable
various drawn parts via the following flags: various drawn parts via the following flags:
- **Show session statut**: display the session status in the viewport
.. figure:: img/quickstart_status.png
:align: center
- **Text scale**: session status text size
- **Vertical/Horizontal position**: session position in the viewport
- **Show selected objects**: display other users current selection - **Show selected objects**: display other users current selection
- **Show users**: display users current viewpoint - **Show users**: display users current viewpoint
- **Show different scenes**: display users working on other scenes - **Show different scenes**: display users working on other scenes

View File

@ -19,7 +19,7 @@
bl_info = { bl_info = {
"name": "Multi-User", "name": "Multi-User",
"author": "Swann Martinez", "author": "Swann Martinez",
"version": (0, 1, 1), "version": (0, 2, 0),
"description": "Enable real-time collaborative workflow inside blender", "description": "Enable real-time collaborative workflow inside blender",
"blender": (2, 82, 0), "blender": (2, 82, 0),
"location": "3D View > Sidebar > Multi-User tab", "location": "3D View > Sidebar > Multi-User tab",
@ -44,7 +44,7 @@ from . import environment
DEPENDENCIES = { DEPENDENCIES = {
("replication", '0.1.3'), ("replication", '0.2.0'),
} }

View File

@ -92,7 +92,6 @@ def load_driver(target_datablock, src_driver):
def get_datablock_from_uuid(uuid, default, ignore=[]): def get_datablock_from_uuid(uuid, default, ignore=[]):
if not uuid: if not uuid:
return default return default
for category in dir(bpy.data): for category in dir(bpy.data):
root = getattr(bpy.data, category) root = getattr(bpy.data, category)
if isinstance(root, Iterable) and category not in ignore: if isinstance(root, Iterable) and category not in ignore:
@ -123,14 +122,14 @@ class BlDatablock(ReplicatedDatablock):
# TODO: use is_library_indirect # TODO: use is_library_indirect
self.is_library = (instance and hasattr(instance, 'library') and self.is_library = (instance and hasattr(instance, 'library') and
instance.library) or \ instance.library) or \
(self.data and 'library' in self.data) (hasattr(self,'data') and self.data and 'library' in self.data)
if instance and hasattr(instance, 'uuid'): if instance and hasattr(instance, 'uuid'):
instance.uuid = self.uuid instance.uuid = self.uuid
self.diff_method = DIFF_BINARY # self.diff_method = DIFF_BINARY
def resolve(self): def resolve(self, construct = True):
datablock_ref = None datablock_ref = None
datablock_root = getattr(bpy.data, self.bl_id) datablock_root = getattr(bpy.data, self.bl_id)
datablock_ref = utils.find_from_attr('uuid', self.uuid, datablock_root) datablock_ref = utils.find_from_attr('uuid', self.uuid, datablock_root)
@ -139,14 +138,18 @@ class BlDatablock(ReplicatedDatablock):
try: try:
datablock_ref = datablock_root[self.data['name']] datablock_ref = datablock_root[self.data['name']]
except Exception: except Exception:
name = self.data.get('name') if construct:
logging.debug(f"Constructing {name}") name = self.data.get('name')
datablock_ref = self._construct(data=self.data) logging.debug(f"Constructing {name}")
datablock_ref = self._construct(data=self.data)
if datablock_ref: if datablock_ref is not None:
setattr(datablock_ref, 'uuid', self.uuid) setattr(datablock_ref, 'uuid', self.uuid)
self.instance = datablock_ref
return True
else:
return False
self.instance = datablock_ref
def remove_instance(self): def remove_instance(self):
""" """

View File

@ -259,15 +259,7 @@ class BlMaterial(BlDatablock):
] ]
data = mat_dumper.dump(instance) data = mat_dumper.dump(instance)
if instance.use_nodes: if instance.is_grease_pencil:
nodes = {}
data["node_tree"] = {}
for node in instance.node_tree.nodes:
nodes[node.name] = dump_node(node)
data["node_tree"]['nodes'] = nodes
data["node_tree"]["links"] = dump_links(instance.node_tree.links)
elif instance.is_grease_pencil:
gp_mat_dumper = Dumper() gp_mat_dumper = Dumper()
gp_mat_dumper.depth = 3 gp_mat_dumper.depth = 3
@ -299,6 +291,14 @@ class BlMaterial(BlDatablock):
# 'fill_image', # 'fill_image',
] ]
data['grease_pencil'] = gp_mat_dumper.dump(instance.grease_pencil) data['grease_pencil'] = gp_mat_dumper.dump(instance.grease_pencil)
elif instance.use_nodes:
nodes = {}
data["node_tree"] = {}
for node in instance.node_tree.nodes:
nodes[node.name] = dump_node(node)
data["node_tree"]['nodes'] = nodes
data["node_tree"]["links"] = dump_links(instance.node_tree.links)
return data return data
def _resolve_deps_implementation(self): def _resolve_deps_implementation(self):

View File

@ -25,7 +25,7 @@ import numpy as np
from .dump_anything import Dumper, Loader, np_load_collection_primitives, np_dump_collection_primitive, np_load_collection, np_dump_collection from .dump_anything import Dumper, Loader, np_load_collection_primitives, np_dump_collection_primitive, np_load_collection, np_dump_collection
from replication.constants import DIFF_BINARY from replication.constants import DIFF_BINARY
from replication.exception import ContextError from replication.exception import ContextError
from .bl_datablock import BlDatablock from .bl_datablock import BlDatablock, get_datablock_from_uuid
VERTICE = ['co'] VERTICE = ['co']
@ -70,8 +70,17 @@ class BlMesh(BlDatablock):
# MATERIAL SLOTS # MATERIAL SLOTS
target.materials.clear() target.materials.clear()
for m in data["material_list"]: for mat_uuid, mat_name in data["material_list"]:
target.materials.append(bpy.data.materials[m]) mat_ref = None
if mat_uuid is not None:
mat_ref = get_datablock_from_uuid(mat_uuid, None)
else:
mat_ref = bpy.data.materials.get(mat_name, None)
if mat_ref is None:
raise Exception("Material doesn't exist")
target.materials.append(mat_ref)
# CLEAR GEOMETRY # CLEAR GEOMETRY
if target.vertices: if target.vertices:
@ -166,7 +175,7 @@ class BlMesh(BlDatablock):
m_list = [] m_list = []
for material in instance.materials: for material in instance.materials:
if material: if material:
m_list.append(material.name) m_list.append((material.uuid,material.name))
data['material_list'] = m_list data['material_list'] = m_list

View File

@ -171,7 +171,8 @@ class DynamicRightSelectTimer(Timer):
session.change_owner( session.change_owner(
node.uuid, node.uuid,
RP_COMMON, RP_COMMON,
recursive=recursive) ignore_warnings=True,
affect_dependencies=recursive)
except NonAuthorizedOperationError: except NonAuthorizedOperationError:
logging.warning(f"Not authorized to change {node} owner") logging.warning(f"Not authorized to change {node} owner")
@ -188,7 +189,8 @@ class DynamicRightSelectTimer(Timer):
session.change_owner( session.change_owner(
node.uuid, node.uuid,
settings.username, settings.username,
recursive=recursive) ignore_warnings=True,
affect_dependencies=recursive)
except NonAuthorizedOperationError: except NonAuthorizedOperationError:
logging.warning(f"Not authorized to change {node} owner") logging.warning(f"Not authorized to change {node} owner")
else: else:
@ -213,7 +215,8 @@ class DynamicRightSelectTimer(Timer):
session.change_owner( session.change_owner(
key, key,
RP_COMMON, RP_COMMON,
recursive=recursive) ignore_warnings=True,
affect_dependencies=recursive)
except NonAuthorizedOperationError: except NonAuthorizedOperationError:
logging.warning(f"Not authorized to change {key} owner") logging.warning(f"Not authorized to change {key} owner")

View File

@ -226,7 +226,8 @@ class SessionStartOperator(bpy.types.Operator):
except Exception as e: except Exception as e:
self.report({'ERROR'}, repr(e)) self.report({'ERROR'}, repr(e))
logging.error(f"Error: {e}") logging.error(f"Error: {e}")
import traceback
traceback.print_exc()
# Join a session # Join a session
else: else:
if not runtime_settings.admin: if not runtime_settings.admin:
@ -426,7 +427,8 @@ class SessionPropertyRightOperator(bpy.types.Operator):
if session: if session:
session.change_owner(self.key, session.change_owner(self.key,
runtime_settings.clients, runtime_settings.clients,
recursive=self.recursive) ignore_warnings=True,
affect_dependencies=self.recursive)
return {"FINISHED"} return {"FINISHED"}
@ -695,10 +697,12 @@ def sanitize_deps_graph(dummy):
A future solution should be to avoid storing dataclock reference... A future solution should be to avoid storing dataclock reference...
""" """
if session and session.state['STATE'] == STATE_ACTIVE: if session and session.state['STATE'] == STATE_ACTIVE:
for node_key in session.list(): for node_key in session.list():
session.get(node_key).resolve() node = session.get(node_key)
if node and not node.resolve(construct=False):
session.remove(node_key)
@persistent @persistent
def load_pre_handler(dummy): def load_pre_handler(dummy):

View File

@ -238,6 +238,31 @@ class SessionPrefs(bpy.types.AddonPreferences):
set=set_log_level, set=set_log_level,
get=get_log_level get=get_log_level
) )
presence_hud_scale: bpy.props.FloatProperty(
name="Text scale",
description="Adjust the session widget text scale",
min=7,
max=90,
default=15,
)
presence_hud_hpos: bpy.props.FloatProperty(
name="Horizontal position",
description="Adjust the session widget horizontal position",
min=1,
max=90,
default=3,
step=1,
subtype='PERCENTAGE',
)
presence_hud_vpos: bpy.props.FloatProperty(
name="Vertical position",
description="Adjust the session widget vertical position",
min=1,
max=94,
default=1,
step=1,
subtype='PERCENTAGE',
)
conf_session_identity_expanded: bpy.props.BoolProperty( conf_session_identity_expanded: bpy.props.BoolProperty(
name="Identity", name="Identity",
description="Identity", description="Identity",
@ -412,6 +437,15 @@ class SessionPrefs(bpy.types.AddonPreferences):
emboss=False) emboss=False)
if self.conf_session_ui_expanded: if self.conf_session_ui_expanded:
box.row().prop(self, "panel_category", text="Panel category", expand=True) box.row().prop(self, "panel_category", text="Panel category", expand=True)
row = box.row()
row.label(text="Session widget:")
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)
if self.category == 'UPDATE': if self.category == 'UPDATE':
from . import addon_updater_ops from . import addon_updater_ops

View File

@ -35,7 +35,7 @@ from replication.constants import (STATE_ACTIVE, STATE_AUTH, STATE_CONFIG,
STATE_SYNCING, STATE_WAITING) STATE_SYNCING, STATE_WAITING)
from replication.interface import session from replication.interface import session
from .utils import find_from_attr, get_state_str from .utils import find_from_attr, get_state_str, get_preferences
# Helper functions # Helper functions
@ -301,40 +301,37 @@ class UserSelectionWidget(Widget):
if not ob: if not ob:
return return
position = None vertex_pos = bbox_from_obj(ob, 1.0)
vertex_indices = ((0, 1), (0, 2), (1, 3), (2, 3),
(4, 5), (4, 6), (5, 7), (6, 7),
(0, 4), (1, 5), (2, 6), (3, 7))
if ob.type == 'EMPTY': if ob.instance_collection:
# TODO: Child case for obj in ob.instance_collection.objects:
# Collection instance case if obj.type == 'MESH' and hasattr(obj, 'bound_box'):
indices = ( vertex_pos = get_bb_coords_from_obj(obj, instance=ob)
(0, 1), (1, 2), (2, 3), (0, 3), break
(4, 5), (5, 6), (6, 7), (4, 7), elif ob.type == 'EMPTY':
(0, 4), (1, 5), (2, 6), (3, 7)) vertex_pos = bbox_from_obj(ob, ob.empty_display_size)
if ob.instance_collection: elif ob.type == 'LIGHT':
for obj in ob.instance_collection.objects: vertex_pos = bbox_from_obj(ob, ob.data.shadow_soft_size)
if obj.type == 'MESH' and hasattr(obj, 'bound_box'): elif ob.type == 'LIGHT_PROBE':
positions = get_bb_coords_from_obj(obj, instance=ob) vertex_pos = bbox_from_obj(ob, ob.data.influence_distance)
break elif ob.type == 'CAMERA':
vertex_pos = bbox_from_obj(ob, ob.data.display_size)
elif hasattr(ob, 'bound_box'): elif hasattr(ob, 'bound_box'):
indices = ( vertex_indices = (
(0, 1), (1, 2), (2, 3), (0, 3), (0, 1), (1, 2), (2, 3), (0, 3),
(4, 5), (5, 6), (6, 7), (4, 7), (4, 5), (5, 6), (6, 7), (4, 7),
(0, 4), (1, 5), (2, 6), (3, 7)) (0, 4), (1, 5), (2, 6), (3, 7))
positions = get_bb_coords_from_obj(ob) vertex_pos = get_bb_coords_from_obj(ob)
if positions is None:
indices = (
(0, 1), (0, 2), (1, 3), (2, 3),
(4, 5), (4, 6), (5, 7), (6, 7),
(0, 4), (1, 5), (2, 6), (3, 7))
positions = bbox_from_obj(ob, ob.scale.x)
shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR') shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
batch = batch_for_shader( batch = batch_for_shader(
shader, shader,
'LINES', 'LINES',
{"pos": positions}, {"pos": vertex_pos},
indices=indices) indices=vertex_indices)
shader.bind() shader.bind()
shader.uniform_float("color", self.data.get('color')) shader.uniform_float("color", self.data.get('color'))
@ -387,6 +384,9 @@ class UserNameWidget(Widget):
class SessionStatusWidget(Widget): class SessionStatusWidget(Widget):
draw_type = 'POST_PIXEL' draw_type = 'POST_PIXEL'
def __init__(self):
self.preferences = get_preferences()
@property @property
def settings(self): def settings(self):
return getattr(bpy.context.window_manager, 'session', None) return getattr(bpy.context.window_manager, 'session', None)
@ -396,6 +396,8 @@ class SessionStatusWidget(Widget):
self.settings.enable_presence self.settings.enable_presence
def draw(self): def draw(self):
text_scale = self.preferences.presence_hud_scale
ui_scale = bpy.context.preferences.view.ui_scale
color = [1, 1, 0, 1] color = [1, 1, 0, 1]
state = session.state.get('STATE') state = session.state.get('STATE')
state_str = f"{get_state_str(state)}" state_str = f"{get_state_str(state)}"
@ -404,9 +406,11 @@ class SessionStatusWidget(Widget):
color = [0, 1, 0, 1] color = [0, 1, 0, 1]
elif state == STATE_INITIAL: elif state == STATE_INITIAL:
color = [1, 0, 0, 1] color = [1, 0, 0, 1]
hpos = (self.preferences.presence_hud_hpos*bpy.context.area.width)/100
vpos = (self.preferences.presence_hud_vpos*bpy.context.area.height)/100
blf.position(0, 10, 20, 0) blf.position(0, hpos, vpos, 0)
blf.size(0, 16, 45) blf.size(0, int(text_scale*ui_scale), 72)
blf.color(0, color[0], color[1], color[2], color[3]) blf.color(0, color[0], color[1], color[2], color[3])
blf.draw(0, state_str) blf.draw(0, state_str)

View File

@ -448,9 +448,17 @@ class SESSION_PT_presence(bpy.types.Panel):
layout = self.layout layout = self.layout
settings = context.window_manager.session settings = context.window_manager.session
pref = get_preferences()
layout.active = settings.enable_presence layout.active = settings.enable_presence
col = layout.column() col = layout.column()
col.prop(settings, "presence_show_session_status") col.prop(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)
col.prop(settings, "presence_show_selected") col.prop(settings, "presence_show_selected")
col.prop(settings, "presence_show_user") col.prop(settings, "presence_show_user")
row = layout.column() row = layout.column()