Compare commits
32 Commits
197-user-s
...
221-optimi
Author | SHA1 | Date | |
---|---|---|---|
1e675132d4 | |||
781287c390 | |||
d4476baa1b | |||
467e98906e | |||
64a25f94a3 | |||
e6996316be | |||
cf4cd94096 | |||
e9ab633aac | |||
297639e80f | |||
f0cc63b6f0 | |||
d433e8f241 | |||
963a551a1e | |||
d01a434fb7 | |||
3a5a5fc633 | |||
8926ab44e1 | |||
a8f96581c5 | |||
440a4cc1cd | |||
a207c51973 | |||
e706c8e0bf | |||
e590e896da | |||
4140b62a8e | |||
6d9c9c4532 | |||
e9e1911840 | |||
ab350ca7bc | |||
0a8f0b5f88 | |||
2238a15c11 | |||
de73f022e6 | |||
f517205647 | |||
f33c3d8481 | |||
71c69000ec | |||
de1e684b3c | |||
d87730cffb |
@ -19,10 +19,10 @@ import sys
|
||||
|
||||
project = 'multi-user'
|
||||
copyright = '2020, Swann Martinez'
|
||||
author = 'Swann Martinez, with contributions from Poochy'
|
||||
author = 'Swann Martinez, Poochy, Fabian'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.2.0'
|
||||
release = '0.5.0-develop'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
@ -56,7 +56,7 @@ class BlCamera(ReplicatedDatablock):
|
||||
background_images = data.get('background_images')
|
||||
|
||||
datablock.background_images.clear()
|
||||
|
||||
# TODO: Use image uuid
|
||||
if background_images:
|
||||
for img_name, img_data in background_images.items():
|
||||
img_id = img_data.get('image')
|
||||
|
@ -28,7 +28,8 @@ from replication.protocol import ReplicatedDatablock
|
||||
from .bl_datablock import resolve_datablock_from_uuid
|
||||
from .bl_action import dump_animation_data, load_animation_data, resolve_animation_dependencies
|
||||
from ..utils import get_preferences
|
||||
|
||||
from ..timers import is_annotating
|
||||
from .bl_material import load_materials_slots, dump_materials_slots
|
||||
|
||||
STROKE_POINT = [
|
||||
'co',
|
||||
@ -65,36 +66,9 @@ def dump_stroke(stroke):
|
||||
|
||||
:param stroke: target grease pencil stroke
|
||||
:type stroke: bpy.types.GPencilStroke
|
||||
:return: dict
|
||||
:return: (p_count, p_data)
|
||||
"""
|
||||
|
||||
assert(stroke)
|
||||
|
||||
dumper = Dumper()
|
||||
dumper.include_filter = [
|
||||
"aspect",
|
||||
"display_mode",
|
||||
"draw_cyclic",
|
||||
"end_cap_mode",
|
||||
"hardeness",
|
||||
"line_width",
|
||||
"material_index",
|
||||
"start_cap_mode",
|
||||
"uv_rotation",
|
||||
"uv_scale",
|
||||
"uv_translation",
|
||||
"vertex_color_fill",
|
||||
]
|
||||
dumped_stroke = dumper.dump(stroke)
|
||||
|
||||
# Stoke points
|
||||
p_count = len(stroke.points)
|
||||
dumped_stroke['p_count'] = p_count
|
||||
dumped_stroke['points'] = np_dump_collection(stroke.points, STROKE_POINT)
|
||||
|
||||
# TODO: uv_factor, uv_rotation
|
||||
|
||||
return dumped_stroke
|
||||
return (len(stroke.points), np_dump_collection(stroke.points, STROKE_POINT))
|
||||
|
||||
|
||||
def load_stroke(stroke_data, stroke):
|
||||
@ -107,12 +81,12 @@ def load_stroke(stroke_data, stroke):
|
||||
"""
|
||||
assert(stroke and stroke_data)
|
||||
|
||||
stroke.points.add(stroke_data["p_count"])
|
||||
np_load_collection(stroke_data['points'], stroke.points, STROKE_POINT)
|
||||
stroke.points.add(stroke_data[0])
|
||||
np_load_collection(stroke_data[1], stroke.points, STROKE_POINT)
|
||||
|
||||
# HACK: Temporary fix to trigger a BKE_gpencil_stroke_geometry_update to
|
||||
# fix fill issues
|
||||
stroke.uv_scale = stroke_data["uv_scale"]
|
||||
stroke.uv_scale = 1.0
|
||||
|
||||
|
||||
def dump_frame(frame):
|
||||
@ -147,10 +121,12 @@ def load_frame(frame_data, frame):
|
||||
|
||||
assert(frame and frame_data)
|
||||
|
||||
# Load stroke points
|
||||
for stroke_data in frame_data['strokes_points']:
|
||||
target_stroke = frame.strokes.new()
|
||||
load_stroke(stroke_data, target_stroke)
|
||||
|
||||
# Load stroke metadata
|
||||
np_load_collection(frame_data['strokes'], frame.strokes, STROKE)
|
||||
|
||||
|
||||
@ -170,7 +146,6 @@ def dump_layer(layer):
|
||||
'opacity',
|
||||
'channel_color',
|
||||
'color',
|
||||
# 'thickness', #TODO: enabling only for annotation
|
||||
'tint_color',
|
||||
'tint_factor',
|
||||
'vertex_paint_opacity',
|
||||
@ -255,10 +230,10 @@ class BlGpencil(ReplicatedDatablock):
|
||||
|
||||
@staticmethod
|
||||
def load(data: dict, datablock: object):
|
||||
datablock.materials.clear()
|
||||
if "materials" in data.keys():
|
||||
for mat in data['materials']:
|
||||
datablock.materials.append(bpy.data.materials[mat])
|
||||
# MATERIAL SLOTS
|
||||
src_materials = data.get('materials', None)
|
||||
if src_materials:
|
||||
load_materials_slots(src_materials, datablock.materials)
|
||||
|
||||
loader = Loader()
|
||||
loader.load(datablock, data)
|
||||
@ -286,7 +261,6 @@ class BlGpencil(ReplicatedDatablock):
|
||||
dumper = Dumper()
|
||||
dumper.depth = 2
|
||||
dumper.include_filter = [
|
||||
'materials',
|
||||
'name',
|
||||
'zdepth_offset',
|
||||
'stroke_thickness_space',
|
||||
@ -294,7 +268,7 @@ class BlGpencil(ReplicatedDatablock):
|
||||
'stroke_depth_order'
|
||||
]
|
||||
data = dumper.dump(datablock)
|
||||
|
||||
data['materials'] = dump_materials_slots(datablock.materials)
|
||||
data['layers'] = {}
|
||||
|
||||
for layer in datablock.layers:
|
||||
@ -323,7 +297,8 @@ class BlGpencil(ReplicatedDatablock):
|
||||
return bpy.context.mode == 'OBJECT' \
|
||||
or layer_changed(datablock, data) \
|
||||
or frame_changed(data) \
|
||||
or get_preferences().sync_flags.sync_during_editmode
|
||||
or get_preferences().sync_flags.sync_during_editmode \
|
||||
or is_annotating(bpy.context)
|
||||
|
||||
_type = bpy.types.GreasePencil
|
||||
_class = BlGpencil
|
||||
|
@ -69,11 +69,12 @@ class BlImage(ReplicatedDatablock):
|
||||
@staticmethod
|
||||
def load(data: dict, datablock: object):
|
||||
loader = Loader()
|
||||
loader.load(data, datablock)
|
||||
loader.load(datablock, data)
|
||||
|
||||
# datablock.name = data.get('name')
|
||||
datablock.source = 'FILE'
|
||||
datablock.filepath_raw = get_filepath(data['filename'])
|
||||
color_space_name = data["colorspace_settings"]["name"]
|
||||
color_space_name = data.get("colorspace")
|
||||
|
||||
if color_space_name:
|
||||
datablock.colorspace_settings.name = color_space_name
|
||||
@ -92,12 +93,10 @@ class BlImage(ReplicatedDatablock):
|
||||
"name",
|
||||
# 'source',
|
||||
'size',
|
||||
'height',
|
||||
'alpha',
|
||||
'float_buffer',
|
||||
'alpha_mode',
|
||||
'colorspace_settings']
|
||||
'alpha_mode']
|
||||
data.update(dumper.dump(datablock))
|
||||
data['colorspace'] = datablock.colorspace_settings.name
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
@ -132,10 +131,7 @@ class BlImage(ReplicatedDatablock):
|
||||
if datablock.is_dirty:
|
||||
datablock.save()
|
||||
|
||||
if not data or (datablock and (datablock.name != data.get('name'))):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
_type = bpy.types.Image
|
||||
_class = BlImage
|
||||
|
@ -124,8 +124,7 @@ def dump_node(node: bpy.types.ShaderNode) -> dict:
|
||||
"show_preview",
|
||||
"show_texture",
|
||||
"outputs",
|
||||
"width_hidden",
|
||||
"image"
|
||||
"width_hidden"
|
||||
]
|
||||
|
||||
dumped_node = node_dumper.dump(node)
|
||||
@ -388,11 +387,10 @@ def load_materials_slots(src_materials: list, dst_materials: bpy.types.bpy_prop_
|
||||
|
||||
for mat_uuid, mat_name in src_materials:
|
||||
mat_ref = None
|
||||
if mat_uuid is not None:
|
||||
if mat_uuid:
|
||||
mat_ref = get_datablock_from_uuid(mat_uuid, None)
|
||||
else:
|
||||
mat_ref = bpy.data.materials[mat_name]
|
||||
|
||||
dst_materials.append(mat_ref)
|
||||
|
||||
|
||||
|
@ -620,10 +620,8 @@ class BlObject(ReplicatedDatablock):
|
||||
|
||||
transform = data.get('transforms', None)
|
||||
if transform:
|
||||
datablock.matrix_parent_inverse = mathutils.Matrix(
|
||||
transform['matrix_parent_inverse'])
|
||||
datablock.matrix_parent_inverse = mathutils.Matrix(transform['matrix_parent_inverse'])
|
||||
datablock.matrix_basis = mathutils.Matrix(transform['matrix_basis'])
|
||||
datablock.matrix_local = mathutils.Matrix(transform['matrix_local'])
|
||||
|
||||
|
||||
@staticmethod
|
||||
|
@ -403,8 +403,9 @@ class BlScene(ReplicatedDatablock):
|
||||
datablock.world = bpy.data.worlds[data['world']]
|
||||
|
||||
# Annotation
|
||||
if 'grease_pencil' in data.keys():
|
||||
datablock.grease_pencil = bpy.data.grease_pencils[data['grease_pencil']]
|
||||
gpencil_uid = data.get('grease_pencil')
|
||||
if gpencil_uid:
|
||||
datablock.grease_pencil = resolve_datablock_from_uuid(gpencil_uid, bpy.data.grease_pencils)
|
||||
|
||||
if get_preferences().sync_flags.sync_render_settings:
|
||||
if 'eevee' in data.keys():
|
||||
@ -445,6 +446,15 @@ class BlScene(ReplicatedDatablock):
|
||||
elif datablock.sequence_editor and not sequences:
|
||||
datablock.sequence_editor_clear()
|
||||
|
||||
# Timeline markers
|
||||
markers = data.get('timeline_markers')
|
||||
if markers:
|
||||
datablock.timeline_markers.clear()
|
||||
for name, frame, camera in markers:
|
||||
marker = datablock.timeline_markers.new(name, frame=frame)
|
||||
if camera:
|
||||
marker.camera = resolve_datablock_from_uuid(camera, bpy.data.objects)
|
||||
marker.select = False
|
||||
# FIXME: Find a better way after the replication big refacotoring
|
||||
# Keep other user from deleting collection object by flushing their history
|
||||
flush_history()
|
||||
@ -461,7 +471,6 @@ class BlScene(ReplicatedDatablock):
|
||||
'name',
|
||||
'world',
|
||||
'id',
|
||||
'grease_pencil',
|
||||
'frame_start',
|
||||
'frame_end',
|
||||
'frame_step',
|
||||
@ -517,6 +526,13 @@ class BlScene(ReplicatedDatablock):
|
||||
dumped_sequences[seq.name] = dump_sequence(seq)
|
||||
data['sequences'] = dumped_sequences
|
||||
|
||||
# Timeline markers
|
||||
if datablock.timeline_markers:
|
||||
data['timeline_markers'] = [(m.name, m.frame, getattr(m.camera, 'uuid', None)) for m in datablock.timeline_markers]
|
||||
|
||||
if datablock.grease_pencil:
|
||||
data['grease_pencil'] = datablock.grease_pencil.uuid
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
|
@ -52,7 +52,8 @@ def sanitize_deps_graph(remove_nodes: bool = False):
|
||||
def update_external_dependencies():
|
||||
"""Force external dependencies(files such as images) evaluation
|
||||
"""
|
||||
nodes_ids = [n.uuid for n in session.repository.graph.values() if n.data['type_id'] in ['WindowsPath', 'PosixPath']]
|
||||
external_types = ['WindowsPath', 'PosixPath', 'Image']
|
||||
nodes_ids = [n.uuid for n in session.repository.graph.values() if n.data['type_id'] in external_types]
|
||||
for node_id in nodes_ids:
|
||||
node = session.repository.graph.get(node_id)
|
||||
if node and node.owner in [session.repository.username, RP_COMMON]:
|
||||
@ -103,7 +104,8 @@ def on_scene_update(scene):
|
||||
else:
|
||||
continue
|
||||
elif isinstance(update.id, bpy.types.Scene):
|
||||
scn_uuid = porcelain.add(session.repository, update.id)
|
||||
scene = bpy.data.scenes.get(update.id.name)
|
||||
scn_uuid = porcelain.add(session.repository, scene)
|
||||
porcelain.commit(session.repository, scn_uuid)
|
||||
porcelain.push(session.repository, 'origin', scn_uuid)
|
||||
|
||||
|
Submodule multi_user/libs/replication updated: ce3a7b4363...fb70cc135b
@ -273,8 +273,7 @@ class SessionStartOperator(bpy.types.Operator):
|
||||
|
||||
session_update = timers.SessionStatusUpdate()
|
||||
session_user_sync = timers.SessionUserSync()
|
||||
session_background_executor = timers.MainThreadExecutor(
|
||||
execution_queue=background_execution_queue)
|
||||
session_background_executor = timers.MainThreadExecutor(execution_queue=background_execution_queue)
|
||||
session_listen = timers.SessionListenTimer(timeout=0.001)
|
||||
|
||||
session_listen.register()
|
||||
@ -286,6 +285,7 @@ class SessionStartOperator(bpy.types.Operator):
|
||||
deleyables.append(session_update)
|
||||
deleyables.append(session_user_sync)
|
||||
deleyables.append(session_listen)
|
||||
deleyables.append(timers.AnnotationUpdates())
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
@ -273,6 +273,13 @@ class SessionPrefs(bpy.types.AddonPreferences):
|
||||
step=1,
|
||||
subtype='PERCENTAGE',
|
||||
)
|
||||
presence_mode_distance: bpy.props.FloatProperty(
|
||||
name="Distance mode visibilty",
|
||||
description="Adjust the distance visibilty of user's mode",
|
||||
min=0.1,
|
||||
max=1000,
|
||||
default=100,
|
||||
)
|
||||
conf_session_identity_expanded: bpy.props.BoolProperty(
|
||||
name="Identity",
|
||||
description="Identity",
|
||||
@ -446,10 +453,11 @@ class SessionPrefs(bpy.types.AddonPreferences):
|
||||
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':
|
||||
from . import addon_updater_ops
|
||||
addon_updater_ops.update_settings_ui(self, context)
|
||||
@ -538,6 +546,11 @@ class SessionProps(bpy.types.PropertyGroup):
|
||||
description='Enable user overlay ',
|
||||
default=True,
|
||||
)
|
||||
presence_show_mode: bpy.props.BoolProperty(
|
||||
name="Show users current mode",
|
||||
description='Enable user mode overlay ',
|
||||
default=False,
|
||||
)
|
||||
presence_show_far_user: bpy.props.BoolProperty(
|
||||
name="Show users on different scenes",
|
||||
description="Show user on different scenes",
|
||||
|
@ -94,18 +94,21 @@ def project_to_viewport(region: bpy.types.Region, rv3d: bpy.types.RegionView3D,
|
||||
return [target.x, target.y, target.z]
|
||||
|
||||
|
||||
def bbox_from_obj(obj: bpy.types.Object) -> list:
|
||||
def bbox_from_obj(obj: bpy.types.Object, index: int = 1) -> list:
|
||||
""" Generate a bounding box for a given object by using its world matrix
|
||||
|
||||
:param obj: target object
|
||||
:type obj: bpy.types.Object
|
||||
:param index: indice offset
|
||||
:type index: int
|
||||
:return: list of 8 points [(x,y,z),...], list of 12 link between these points [(1,2),...]
|
||||
"""
|
||||
radius = 1.0 # Radius of the bounding box
|
||||
index = 8*index
|
||||
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))
|
||||
(0+index, 1+index), (0+index, 2+index), (1+index, 3+index), (2+index, 3+index),
|
||||
(4+index, 5+index), (4+index, 6+index), (5+index, 7+index), (6+index, 7+index),
|
||||
(0+index, 4+index), (1+index, 5+index), (2+index, 6+index), (3+index, 7+index))
|
||||
|
||||
if obj.type == 'EMPTY':
|
||||
radius = obj.empty_display_size
|
||||
@ -117,9 +120,12 @@ def bbox_from_obj(obj: bpy.types.Object) -> list:
|
||||
radius = obj.data.display_size
|
||||
elif hasattr(obj, 'bound_box'):
|
||||
vertex_indices = (
|
||||
(0, 1), (1, 2), (2, 3), (0, 3),
|
||||
(4, 5), (5, 6), (6, 7), (4, 7),
|
||||
(0, 4), (1, 5), (2, 6), (3, 7))
|
||||
(0+index, 1+index), (1+index, 2+index),
|
||||
(2+index, 3+index), (0+index, 3+index),
|
||||
(4+index, 5+index), (5+index, 6+index),
|
||||
(6+index, 7+index), (4+index, 7+index),
|
||||
(0+index, 4+index), (1+index, 5+index),
|
||||
(2+index, 6+index), (3+index, 7+index))
|
||||
vertex_pos = get_bb_coords_from_obj(obj)
|
||||
return vertex_pos, vertex_indices
|
||||
|
||||
@ -136,26 +142,21 @@ def bbox_from_obj(obj: bpy.types.Object) -> list:
|
||||
|
||||
return vertex_pos, vertex_indices
|
||||
|
||||
def bbox_from_instance_collection(ic: bpy.types.Object) -> list:
|
||||
def bbox_from_instance_collection(ic: bpy.types.Object, index: int = 0) -> list:
|
||||
""" Generate a bounding box for a given instance collection by using its objects
|
||||
|
||||
:param ic: target instance collection
|
||||
:type ic: bpy.types.Object
|
||||
:param radius: bounding box radius
|
||||
:type radius: float
|
||||
:param index: indice offset
|
||||
:type index: int
|
||||
:return: list of 8*objs points [(x,y,z),...], tuple of 12*objs link between these points [(1,2),...]
|
||||
"""
|
||||
vertex_pos = []
|
||||
vertex_indices = ()
|
||||
|
||||
for obj_index, obj in enumerate(ic.instance_collection.objects):
|
||||
vertex_pos_temp, vertex_indices_temp = bbox_from_obj(obj)
|
||||
vertex_pos_temp, vertex_indices_temp = bbox_from_obj(obj, index=index+obj_index)
|
||||
vertex_pos += vertex_pos_temp
|
||||
vertex_indices_list_temp = list(list(indice) for indice in vertex_indices_temp)
|
||||
for indice in vertex_indices_list_temp:
|
||||
indice[0] += 8*obj_index
|
||||
indice[1] += 8*obj_index
|
||||
vertex_indices_temp = tuple(tuple(indice) for indice in vertex_indices_list_temp)
|
||||
vertex_indices += vertex_indices_temp
|
||||
|
||||
bbox_corners = [ic.matrix_world @ mathutils.Vector(vertex) for vertex in vertex_pos]
|
||||
@ -223,7 +224,7 @@ def get_bb_coords_from_obj(object: bpy.types.Object, instance: bpy.types.Object
|
||||
|
||||
bbox_corners = [base @ mathutils.Vector(
|
||||
corner) for corner in object.bound_box]
|
||||
|
||||
|
||||
|
||||
return [(point.x, point.y, point.z) for point in bbox_corners]
|
||||
|
||||
@ -251,6 +252,13 @@ class Widget(object):
|
||||
"""
|
||||
return True
|
||||
|
||||
def configure_bgl(self):
|
||||
bgl.glLineWidth(2.)
|
||||
bgl.glEnable(bgl.GL_DEPTH_TEST)
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
||||
|
||||
|
||||
def draw(self):
|
||||
"""How to draw the widget
|
||||
"""
|
||||
@ -304,11 +312,6 @@ class UserFrustumWidget(Widget):
|
||||
{"pos": positions},
|
||||
indices=self.indices)
|
||||
|
||||
bgl.glLineWidth(2.)
|
||||
bgl.glEnable(bgl.GL_DEPTH_TEST)
|
||||
bgl.glEnable(bgl.GL_BLEND)
|
||||
bgl.glEnable(bgl.GL_LINE_SMOOTH)
|
||||
|
||||
shader.bind()
|
||||
shader.uniform_float("color", self.data.get('color'))
|
||||
batch.draw(shader)
|
||||
@ -320,6 +323,8 @@ class UserSelectionWidget(Widget):
|
||||
username):
|
||||
self.username = username
|
||||
self.settings = bpy.context.window_manager.session
|
||||
self.current_selection_ids = []
|
||||
self.current_selected_objects = []
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
@ -329,6 +334,15 @@ class UserSelectionWidget(Widget):
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def selected_objects(self):
|
||||
user_selection = self.data.get('selected_objects')
|
||||
if self.current_selection_ids != user_selection:
|
||||
self.current_selected_objects = [find_from_attr("uuid", uid, bpy.data.objects) for uid in user_selection]
|
||||
self.current_selection_ids = user_selection
|
||||
|
||||
return self.current_selected_objects
|
||||
|
||||
def poll(self):
|
||||
if self.data is None:
|
||||
return False
|
||||
@ -343,26 +357,31 @@ class UserSelectionWidget(Widget):
|
||||
self.settings.enable_presence
|
||||
|
||||
def draw(self):
|
||||
user_selection = self.data.get('selected_objects')
|
||||
for select_obj in user_selection:
|
||||
obj = find_from_attr("uuid", select_obj, bpy.data.objects)
|
||||
if not obj:
|
||||
return
|
||||
if obj.instance_collection:
|
||||
vertex_pos, vertex_indices = bbox_from_instance_collection(obj)
|
||||
vertex_pos = []
|
||||
vertex_ind = []
|
||||
collection_offset = 0
|
||||
for obj_index, obj in enumerate(self.selected_objects):
|
||||
if obj is None:
|
||||
continue
|
||||
obj_index+=collection_offset
|
||||
if hasattr(obj, 'instance_collection') and obj.instance_collection:
|
||||
bbox_pos, bbox_ind = bbox_from_instance_collection(obj, index=obj_index)
|
||||
collection_offset+=len(obj.instance_collection.objects)-1
|
||||
else :
|
||||
vertex_pos, vertex_indices = bbox_from_obj(obj)
|
||||
bbox_pos, bbox_ind = bbox_from_obj(obj, index=obj_index)
|
||||
vertex_pos += bbox_pos
|
||||
vertex_ind += bbox_ind
|
||||
|
||||
shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
|
||||
batch = batch_for_shader(
|
||||
shader,
|
||||
'LINES',
|
||||
{"pos": vertex_pos},
|
||||
indices=vertex_indices)
|
||||
shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
|
||||
batch = batch_for_shader(
|
||||
shader,
|
||||
'LINES',
|
||||
{"pos": vertex_pos},
|
||||
indices=vertex_ind)
|
||||
|
||||
shader.bind()
|
||||
shader.uniform_float("color", self.data.get('color'))
|
||||
batch.draw(shader)
|
||||
shader.bind()
|
||||
shader.uniform_float("color", self.data.get('color'))
|
||||
batch.draw(shader)
|
||||
|
||||
class UserNameWidget(Widget):
|
||||
draw_type = 'POST_PIXEL'
|
||||
@ -406,6 +425,62 @@ class UserNameWidget(Widget):
|
||||
blf.color(0, color[0], color[1], color[2], color[3])
|
||||
blf.draw(0, self.username)
|
||||
|
||||
class UserModeWidget(Widget):
|
||||
draw_type = 'POST_PIXEL'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
username):
|
||||
self.username = username
|
||||
self.settings = bpy.context.window_manager.session
|
||||
self.preferences = get_preferences()
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
user = session.online_users.get(self.username)
|
||||
if user:
|
||||
return user.get('metadata')
|
||||
else:
|
||||
return None
|
||||
|
||||
def poll(self):
|
||||
if self.data is None:
|
||||
return False
|
||||
|
||||
scene_current = self.data.get('scene_current')
|
||||
mode_current = self.data.get('mode_current')
|
||||
user_selection = self.data.get('selected_objects')
|
||||
|
||||
return (scene_current == bpy.context.scene.name or
|
||||
mode_current == bpy.context.mode or
|
||||
self.settings.presence_show_far_user) and \
|
||||
user_selection and \
|
||||
self.settings.presence_show_mode and \
|
||||
self.settings.enable_presence
|
||||
|
||||
def draw(self):
|
||||
user_selection = self.data.get('selected_objects')
|
||||
area, region, rv3d = view3d_find()
|
||||
viewport_coord = project_to_viewport(region, rv3d, (0, 0))
|
||||
|
||||
obj = find_from_attr("uuid", user_selection[0], bpy.data.objects)
|
||||
if not obj:
|
||||
return
|
||||
mode_current = self.data.get('mode_current')
|
||||
color = self.data.get('color')
|
||||
origin_coord = project_to_screen(obj.location)
|
||||
|
||||
distance_viewport_object = math.sqrt((viewport_coord[0]-obj.location[0])**2+(viewport_coord[1]-obj.location[1])**2+(viewport_coord[2]-obj.location[2])**2)
|
||||
|
||||
if distance_viewport_object > self.preferences.presence_mode_distance :
|
||||
return
|
||||
|
||||
if origin_coord :
|
||||
blf.position(0, origin_coord[0]+8, origin_coord[1]-15, 0)
|
||||
blf.size(0, 16, 72)
|
||||
blf.color(0, color[0], color[1], color[2], color[3])
|
||||
blf.draw(0, mode_current)
|
||||
|
||||
|
||||
class SessionStatusWidget(Widget):
|
||||
draw_type = 'POST_PIXEL'
|
||||
@ -488,6 +563,7 @@ class DrawFactory(object):
|
||||
try:
|
||||
for widget in self.widgets.values():
|
||||
if widget.draw_type == 'POST_VIEW' and widget.poll():
|
||||
widget.configure_bgl()
|
||||
widget.draw()
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
@ -497,6 +573,7 @@ class DrawFactory(object):
|
||||
try:
|
||||
for widget in self.widgets.values():
|
||||
if widget.draw_type == 'POST_PIXEL' and widget.poll():
|
||||
widget.configure_bgl()
|
||||
widget.draw()
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
@ -509,6 +586,7 @@ this.renderer = DrawFactory()
|
||||
|
||||
def register():
|
||||
this.renderer.register_handlers()
|
||||
|
||||
|
||||
this.renderer.add_widget("session_status", SessionStatusWidget())
|
||||
|
||||
|
@ -27,7 +27,7 @@ from replication.interface import session
|
||||
from replication import porcelain
|
||||
|
||||
from . import operators, utils
|
||||
from .presence import (UserFrustumWidget, UserNameWidget, UserSelectionWidget,
|
||||
from .presence import (UserFrustumWidget, UserNameWidget, UserModeWidget, UserSelectionWidget,
|
||||
generate_user_camera, get_view_matrix, refresh_3d_view,
|
||||
refresh_sidebar_view, renderer)
|
||||
|
||||
@ -41,7 +41,8 @@ this.registry = dict()
|
||||
def is_annotating(context: bpy.types.Context):
|
||||
""" Check if the annotate mode is enabled
|
||||
"""
|
||||
return bpy.context.workspace.tools.from_space_view3d_mode('OBJECT', create=False).idname == 'builtin.annotate'
|
||||
active_tool = bpy.context.workspace.tools.from_space_view3d_mode('OBJECT', create=False)
|
||||
return (active_tool and active_tool.idname == 'builtin.annotate')
|
||||
|
||||
|
||||
class Timer(object):
|
||||
@ -136,12 +137,50 @@ class ApplyTimer(Timer):
|
||||
force=True)
|
||||
|
||||
|
||||
class AnnotationUpdates(Timer):
|
||||
def __init__(self, timeout=1):
|
||||
self._annotating = False
|
||||
self._settings = utils.get_preferences()
|
||||
|
||||
super().__init__(timeout)
|
||||
|
||||
def execute(self):
|
||||
if session and session.state == STATE_ACTIVE:
|
||||
ctx = bpy.context
|
||||
annotation_gp = ctx.scene.grease_pencil
|
||||
|
||||
if annotation_gp and not annotation_gp.uuid:
|
||||
ctx.scene.update_tag()
|
||||
|
||||
# if an annotation exist and is tracked
|
||||
if annotation_gp and annotation_gp.uuid:
|
||||
registered_gp = session.repository.graph.get(annotation_gp.uuid)
|
||||
if is_annotating(bpy.context):
|
||||
# try to get the right on it
|
||||
if registered_gp.owner == RP_COMMON:
|
||||
self._annotating = True
|
||||
logging.debug(
|
||||
"Getting the right on the annotation GP")
|
||||
porcelain.lock(session.repository,
|
||||
registered_gp.uuid,
|
||||
ignore_warnings=True,
|
||||
affect_dependencies=False)
|
||||
|
||||
if registered_gp.owner == self._settings.username:
|
||||
porcelain.commit(session.repository, annotation_gp.uuid)
|
||||
porcelain.push(session.repository, 'origin', annotation_gp.uuid)
|
||||
|
||||
elif self._annotating:
|
||||
porcelain.unlock(session.repository,
|
||||
registered_gp.uuid,
|
||||
ignore_warnings=True,
|
||||
affect_dependencies=False)
|
||||
|
||||
class DynamicRightSelectTimer(Timer):
|
||||
def __init__(self, timeout=.1):
|
||||
super().__init__(timeout)
|
||||
self._last_selection = []
|
||||
self._last_selection = set()
|
||||
self._user = None
|
||||
self._annotating = False
|
||||
|
||||
def execute(self):
|
||||
settings = utils.get_preferences()
|
||||
@ -152,83 +191,46 @@ class DynamicRightSelectTimer(Timer):
|
||||
self._user = session.online_users.get(settings.username)
|
||||
|
||||
if self._user:
|
||||
ctx = bpy.context
|
||||
annotation_gp = ctx.scene.grease_pencil
|
||||
|
||||
if annotation_gp and not annotation_gp.uuid:
|
||||
ctx.scene.update_tag()
|
||||
|
||||
# if an annotation exist and is tracked
|
||||
if annotation_gp and annotation_gp.uuid:
|
||||
registered_gp = session.repository.graph.get(annotation_gp.uuid)
|
||||
if is_annotating(bpy.context):
|
||||
# try to get the right on it
|
||||
if registered_gp.owner == RP_COMMON:
|
||||
self._annotating = True
|
||||
logging.debug(
|
||||
"Getting the right on the annotation GP")
|
||||
porcelain.lock(session.repository,
|
||||
registered_gp.uuid,
|
||||
ignore_warnings=True,
|
||||
affect_dependencies=False)
|
||||
|
||||
if registered_gp.owner == settings.username:
|
||||
gp_node = session.repository.graph.get(annotation_gp.uuid)
|
||||
porcelain.commit(session.repository, gp_node.uuid)
|
||||
porcelain.push(session.repository, 'origin', gp_node.uuid)
|
||||
|
||||
elif self._annotating:
|
||||
porcelain.unlock(session.repository,
|
||||
registered_gp.uuid,
|
||||
ignore_warnings=True,
|
||||
affect_dependencies=False)
|
||||
|
||||
current_selection = utils.get_selected_objects(
|
||||
current_selection = set(utils.get_selected_objects(
|
||||
bpy.context.scene,
|
||||
bpy.data.window_managers['WinMan'].windows[0].view_layer
|
||||
)
|
||||
))
|
||||
if current_selection != self._last_selection:
|
||||
obj_common = [
|
||||
o for o in self._last_selection if o not in current_selection]
|
||||
obj_ours = [
|
||||
o for o in current_selection if o not in self._last_selection]
|
||||
to_lock = list(current_selection.difference(self._last_selection))
|
||||
to_release = list(self._last_selection.difference(current_selection))
|
||||
instances_to_lock = list()
|
||||
|
||||
# change old selection right to common
|
||||
for obj in obj_common:
|
||||
node = session.repository.graph.get(obj)
|
||||
for node_id in to_lock:
|
||||
node = session.repository.graph.get(node_id)
|
||||
instance_mode = node.data.get('instance_type')
|
||||
if instance_mode and instance_mode == 'COLLECTION':
|
||||
to_lock.remove(node_id)
|
||||
instances_to_lock.append(node_id)
|
||||
if instances_to_lock:
|
||||
try:
|
||||
porcelain.lock(session.repository,
|
||||
instances_to_lock,
|
||||
ignore_warnings=True,
|
||||
affect_dependencies=False)
|
||||
except NonAuthorizedOperationError as e:
|
||||
logging.warning(e)
|
||||
|
||||
if node and (node.owner == settings.username or node.owner == RP_COMMON):
|
||||
recursive = True
|
||||
if node.data and 'instance_type' in node.data.keys():
|
||||
recursive = node.data['instance_type'] != 'COLLECTION'
|
||||
try:
|
||||
porcelain.unlock(session.repository,
|
||||
node.uuid,
|
||||
ignore_warnings=True,
|
||||
affect_dependencies=recursive)
|
||||
except NonAuthorizedOperationError:
|
||||
logging.warning(
|
||||
f"Not authorized to change {node} owner")
|
||||
|
||||
# change new selection to our
|
||||
for obj in obj_ours:
|
||||
node = session.repository.graph.get(obj)
|
||||
|
||||
if node and node.owner == RP_COMMON:
|
||||
recursive = True
|
||||
if node.data and 'instance_type' in node.data.keys():
|
||||
recursive = node.data['instance_type'] != 'COLLECTION'
|
||||
|
||||
try:
|
||||
porcelain.lock(session.repository,
|
||||
node.uuid,
|
||||
ignore_warnings=True,
|
||||
affect_dependencies=recursive)
|
||||
except NonAuthorizedOperationError:
|
||||
logging.warning(
|
||||
f"Not authorized to change {node} owner")
|
||||
else:
|
||||
return
|
||||
if to_release:
|
||||
try:
|
||||
porcelain.unlock(session.repository,
|
||||
to_release,
|
||||
ignore_warnings=True,
|
||||
affect_dependencies=True)
|
||||
except NonAuthorizedOperationError as e:
|
||||
logging.warning(e)
|
||||
if to_lock:
|
||||
try:
|
||||
porcelain.lock(session.repository,
|
||||
to_lock,
|
||||
ignore_warnings=True,
|
||||
affect_dependencies=True)
|
||||
except NonAuthorizedOperationError as e:
|
||||
logging.warning(e)
|
||||
|
||||
self._last_selection = current_selection
|
||||
|
||||
@ -242,17 +244,16 @@ class DynamicRightSelectTimer(Timer):
|
||||
# Fix deselection until right managment refactoring (with Roles concepts)
|
||||
if len(current_selection) == 0 :
|
||||
owned_keys = [k for k, v in session.repository.graph.items() if v.owner==settings.username]
|
||||
for key in owned_keys:
|
||||
node = session.repository.graph.get(key)
|
||||
if owned_keys:
|
||||
try:
|
||||
porcelain.unlock(session.repository,
|
||||
key,
|
||||
owned_keys,
|
||||
ignore_warnings=True,
|
||||
affect_dependencies=True)
|
||||
except NonAuthorizedOperationError:
|
||||
logging.warning(
|
||||
f"Not authorized to change {key} owner")
|
||||
except NonAuthorizedOperationError as e:
|
||||
logging.warning(e)
|
||||
|
||||
# Objects selectability
|
||||
for obj in bpy.data.objects:
|
||||
object_uuid = getattr(obj, 'uuid', None)
|
||||
if object_uuid:
|
||||
@ -309,7 +310,8 @@ class ClientUpdate(Timer):
|
||||
settings.client_color.b,
|
||||
1),
|
||||
'frame_current': bpy.context.scene.frame_current,
|
||||
'scene_current': scene_current
|
||||
'scene_current': scene_current,
|
||||
'mode_current': bpy.context.mode
|
||||
}
|
||||
porcelain.update_user_metadata(session.repository, metadata)
|
||||
|
||||
@ -323,6 +325,9 @@ class ClientUpdate(Timer):
|
||||
local_user_metadata['view_matrix'] = get_view_matrix(
|
||||
)
|
||||
porcelain.update_user_metadata(session.repository, local_user_metadata)
|
||||
elif bpy.context.mode != local_user_metadata['mode_current']:
|
||||
local_user_metadata['mode_current'] = bpy.context.mode
|
||||
porcelain.update_user_metadata(session.repository, local_user_metadata)
|
||||
|
||||
|
||||
class SessionStatusUpdate(Timer):
|
||||
@ -350,6 +355,7 @@ class SessionUserSync(Timer):
|
||||
renderer.remove_widget(f"{user.username}_cam")
|
||||
renderer.remove_widget(f"{user.username}_select")
|
||||
renderer.remove_widget(f"{user.username}_name")
|
||||
renderer.remove_widget(f"{user.username}_mode")
|
||||
ui_users.remove(index)
|
||||
break
|
||||
|
||||
@ -365,6 +371,8 @@ class SessionUserSync(Timer):
|
||||
f"{user}_select", UserSelectionWidget(user))
|
||||
renderer.add_widget(
|
||||
f"{user}_name", UserNameWidget(user))
|
||||
renderer.add_widget(
|
||||
f"{user}_mode", UserModeWidget(user))
|
||||
|
||||
|
||||
class MainThreadExecutor(Timer):
|
||||
|
122
multi_user/ui.py
122
multi_user/ui.py
@ -107,7 +107,7 @@ class SESSION_PT_settings(bpy.types.Panel):
|
||||
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='OBJECT_DATAMODE')
|
||||
row.prop(settings.sync_flags, "sync_active_camera", text="",icon_only=True, icon='VIEW_CAMERA')
|
||||
|
||||
row= layout.row()
|
||||
|
||||
@ -343,9 +343,10 @@ class SESSION_PT_user(bpy.types.Panel):
|
||||
box = row.box()
|
||||
split = box.split(factor=0.35)
|
||||
split.label(text="user")
|
||||
split = split.split(factor=0.5)
|
||||
split.label(text="location")
|
||||
split = split.split(factor=0.3)
|
||||
split.label(text="mode")
|
||||
split.label(text="frame")
|
||||
split.label(text="location")
|
||||
split.label(text="ping")
|
||||
|
||||
row = layout.row()
|
||||
@ -383,6 +384,8 @@ class SESSION_UL_users(bpy.types.UIList):
|
||||
ping = '-'
|
||||
frame_current = '-'
|
||||
scene_current = '-'
|
||||
mode_current = '-'
|
||||
mode_icon = 'BLANK1'
|
||||
status_icon = 'BLANK1'
|
||||
if session:
|
||||
user = session.online_users.get(item.username)
|
||||
@ -392,13 +395,45 @@ class SESSION_UL_users(bpy.types.UIList):
|
||||
if metadata and 'frame_current' in metadata:
|
||||
frame_current = str(metadata.get('frame_current','-'))
|
||||
scene_current = metadata.get('scene_current','-')
|
||||
mode_current = metadata.get('mode_current','-')
|
||||
if mode_current == "OBJECT" :
|
||||
mode_icon = "OBJECT_DATAMODE"
|
||||
elif mode_current == "EDIT_MESH" :
|
||||
mode_icon = "EDITMODE_HLT"
|
||||
elif mode_current == 'EDIT_CURVE':
|
||||
mode_icon = "CURVE_DATA"
|
||||
elif mode_current == 'EDIT_SURFACE':
|
||||
mode_icon = "SURFACE_DATA"
|
||||
elif mode_current == 'EDIT_TEXT':
|
||||
mode_icon = "FILE_FONT"
|
||||
elif mode_current == 'EDIT_ARMATURE':
|
||||
mode_icon = "ARMATURE_DATA"
|
||||
elif mode_current == 'EDIT_METABALL':
|
||||
mode_icon = "META_BALL"
|
||||
elif mode_current == 'EDIT_LATTICE':
|
||||
mode_icon = "LATTICE_DATA"
|
||||
elif mode_current == 'POSE':
|
||||
mode_icon = "POSE_HLT"
|
||||
elif mode_current == 'SCULPT':
|
||||
mode_icon = "SCULPTMODE_HLT"
|
||||
elif mode_current == 'PAINT_WEIGHT':
|
||||
mode_icon = "WPAINT_HLT"
|
||||
elif mode_current == 'PAINT_VERTEX':
|
||||
mode_icon = "VPAINT_HLT"
|
||||
elif mode_current == 'PAINT_TEXTURE':
|
||||
mode_icon = "TPAINT_HLT"
|
||||
elif mode_current == 'PARTICLE':
|
||||
mode_icon = "PARTICLES"
|
||||
elif mode_current == 'PAINT_GPENCIL' or mode_current =='EDIT_GPENCIL' or mode_current =='SCULPT_GPENCIL' or mode_current =='WEIGHT_GPENCIL' or mode_current =='VERTEX_GPENCIL':
|
||||
mode_icon = "GREASEPENCIL"
|
||||
if user['admin']:
|
||||
status_icon = 'FAKE_USER_ON'
|
||||
split = layout.split(factor=0.35)
|
||||
split.label(text=item.username, icon=status_icon)
|
||||
split = split.split(factor=0.5)
|
||||
split.label(text=scene_current)
|
||||
split = split.split(factor=0.3)
|
||||
split.label(icon=mode_icon)
|
||||
split.label(text=frame_current)
|
||||
split.label(text=scene_current)
|
||||
split.label(text=ping)
|
||||
|
||||
|
||||
@ -425,20 +460,29 @@ class SESSION_PT_presence(bpy.types.Panel):
|
||||
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")
|
||||
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_user")
|
||||
row = layout.column()
|
||||
row.active = settings.presence_show_user
|
||||
row.prop(settings, "presence_show_far_user")
|
||||
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):
|
||||
settings = get_preferences()
|
||||
@ -555,20 +599,15 @@ class SESSION_PT_repository(bpy.types.Panel):
|
||||
# Properties
|
||||
owned_nodes = [k for k, v in session.repository.graph.items() if v.owner==settings.username]
|
||||
|
||||
filtered_node = owned_nodes if runtime_settings.filter_owned else session.repository.graph.keys()
|
||||
filtered_node = owned_nodes if runtime_settings.filter_owned else list(session.repository.graph.keys())
|
||||
|
||||
if runtime_settings.filter_name:
|
||||
for node_id in filtered_node:
|
||||
node_instance = session.repository.graph.get(node_id)
|
||||
name = node_instance.data.get('name')
|
||||
if runtime_settings.filter_name not in name:
|
||||
filtered_node.remove(node_id)
|
||||
filtered_node = [n for n in filtered_node if runtime_settings.filter_name.lower() in session.repository.graph.get(n).data.get('name').lower()]
|
||||
|
||||
if filtered_node:
|
||||
col = layout.column(align=True)
|
||||
for key in filtered_node:
|
||||
draw_property(context, col, key)
|
||||
|
||||
else:
|
||||
layout.row().label(text="Empty")
|
||||
|
||||
@ -590,23 +629,32 @@ class VIEW3D_PT_overlay_session(bpy.types.Panel):
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
view = context.space_data
|
||||
overlay = view.overlay
|
||||
display_all = overlay.show_overlays
|
||||
|
||||
col = layout.column()
|
||||
|
||||
row = col.row(align=True)
|
||||
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")
|
||||
col.prop(settings, "presence_show_selected")
|
||||
col.prop(settings, "presence_show_user")
|
||||
|
||||
row = layout.column()
|
||||
row.active = settings.presence_show_user
|
||||
row.prop(settings, "presence_show_far_user")
|
||||
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)
|
||||
|
||||
|
||||
classes = (
|
||||
SESSION_UL_users,
|
||||
|
@ -12,6 +12,8 @@ def test_scene(clear_blend):
|
||||
get_preferences().sync_flags.sync_render_settings = True
|
||||
|
||||
datablock = bpy.data.scenes.new("toto")
|
||||
datablock.timeline_markers.new('toto', frame=10)
|
||||
datablock.timeline_markers.new('tata', frame=1)
|
||||
datablock.view_settings.use_curve_mapping = True
|
||||
# Test
|
||||
implementation = BlScene()
|
||||
|
Reference in New Issue
Block a user