Compare commits
38 Commits
206-draw-a
...
222-server
Author | SHA1 | Date | |
---|---|---|---|
060b7507b6 | |||
a4f9f6e051 | |||
10de88cdc9 | |||
e4fa34c984 | |||
0dd685d009 | |||
3e8c30c0ab | |||
21cc3cd917 | |||
81e620ee3d | |||
fb9bd108bd | |||
cab6625399 | |||
1b81251a11 | |||
77bf269fb5 | |||
1e675132d4 | |||
781287c390 | |||
d4476baa1b | |||
467e98906e | |||
64a25f94a3 | |||
e6996316be | |||
cf4cd94096 | |||
e9ab633aac | |||
297639e80f | |||
f0cc63b6f0 | |||
d433e8f241 | |||
963a551a1e | |||
8926ab44e1 | |||
a207c51973 | |||
e706c8e0bf | |||
e590e896da | |||
4140b62a8e | |||
6d9c9c4532 | |||
e9e1911840 | |||
ab350ca7bc | |||
2238a15c11 | |||
de73f022e6 | |||
f517205647 | |||
f33c3d8481 | |||
71c69000ec | |||
de1e684b3c |
30
CHANGELOG.md
@ -187,3 +187,33 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Sync missing armature bone Roll
|
- Sync missing armature bone Roll
|
||||||
- Sync missing driver data_path
|
- Sync missing driver data_path
|
||||||
- Constraint replication
|
- Constraint replication
|
||||||
|
|
||||||
|
## [0.4.0] - 2021-07-20
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Connection preset system (@Kysios)
|
||||||
|
- Display connected users active mode (users pannel and viewport) (@Kysios)
|
||||||
|
- Delta-based replication
|
||||||
|
- Sync timeline marker
|
||||||
|
- Sync images settings (@Kysios)
|
||||||
|
- Sync parent relation type (@Kysios)
|
||||||
|
- Sync uv project modifier
|
||||||
|
- Sync FCurves modifiers
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- User selection optimizations (draw and sync) (@Kysios)
|
||||||
|
- Improved shapekey syncing performances
|
||||||
|
- Improved gpencil syncing performances
|
||||||
|
- Integrate replication as a submodule
|
||||||
|
- The dependencies are now installed in a folder(blender addon folder) that no longer requires administrative rights
|
||||||
|
- Presence overlay UI optimization (@Kysios)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- User selection bounding box glitches for non-mesh objects (@Kysios)
|
||||||
|
- Transforms replication for animated objects
|
||||||
|
- GPencil fill stroke
|
||||||
|
- Sculpt and GPencil brushes deleted when joining a session (@Kysios)
|
||||||
|
- Auto-updater doesn't work for master and develop builds
|
||||||
|
17
README.md
@ -11,9 +11,8 @@ This tool aims to allow multiple users to work on the same scene over the networ
|
|||||||
|
|
||||||
## Quick installation
|
## Quick installation
|
||||||
|
|
||||||
1. Download latest release [multi_user.zip](https://gitlab.com/slumber/multi-user/-/jobs/artifacts/master/download?job=build).
|
1. Download [latest build](https://gitlab.com/slumber/multi-user/-/jobs/artifacts/develop/download?job=build) or [stable build](https://gitlab.com/slumber/multi-user/-/jobs/artifacts/master/download?job=build).
|
||||||
2. Run blender as administrator (dependencies installation).
|
2. Install last_version.zip from your addon preferences.
|
||||||
3. Install last_version.zip from your addon preferences.
|
|
||||||
|
|
||||||
[Dependencies](#dependencies) will be automatically added to your blender python during installation.
|
[Dependencies](#dependencies) will be automatically added to your blender python during installation.
|
||||||
|
|
||||||
@ -30,7 +29,7 @@ See the [troubleshooting guide](https://slumber.gitlab.io/multi-user/getting_sta
|
|||||||
Currently, not all data-block are supported for replication over the wire. The following list summarizes the status for each ones.
|
Currently, not all data-block are supported for replication over the wire. The following list summarizes the status for each ones.
|
||||||
|
|
||||||
| Name | Status | Comment |
|
| Name | Status | Comment |
|
||||||
| -------------- | :----: | :----------------------------------------------------------: |
|
| -------------- | :----: | :---------------------------------------------------------------------: |
|
||||||
| action | ✔️ | |
|
| action | ✔️ | |
|
||||||
| camera | ✔️ | |
|
| camera | ✔️ | |
|
||||||
| collection | ✔️ | |
|
| collection | ✔️ | |
|
||||||
@ -48,16 +47,16 @@ Currently, not all data-block are supported for replication over the wire. The f
|
|||||||
| volumes | ✔️ | |
|
| volumes | ✔️ | |
|
||||||
| lightprobes | ✔️ | |
|
| lightprobes | ✔️ | |
|
||||||
| physics | ✔️ | |
|
| physics | ✔️ | |
|
||||||
|
| textures | ✔️ | |
|
||||||
| curve | ❗ | Nurbs surfaces not supported |
|
| curve | ❗ | Nurbs surfaces not supported |
|
||||||
| textures | ❗ | Supported for modifiers/materials/geo nodes only |
|
| armature | ❗ | Only for Mesh. [Planned for GPencil](https://gitlab.com/slumber/multi-user/-/issues/161). Not stable yet |
|
||||||
| armature | ❗ | Not stable |
|
|
||||||
| particles | ❗ | The cache isn't syncing. |
|
| particles | ❗ | The cache isn't syncing. |
|
||||||
| speakers | ❗ | [Partial](https://gitlab.com/slumber/multi-user/-/issues/65) |
|
| speakers | ❗ | [Partial](https://gitlab.com/slumber/multi-user/-/issues/65) |
|
||||||
| vse | ❗ | Mask and Clip not supported yet |
|
| vse | ❗ | Mask and Clip not supported yet |
|
||||||
| libraries | ❗ | Partial |
|
| libraries | ❌ | |
|
||||||
| nla | ❌ | |
|
| nla | ❌ | |
|
||||||
| texts | ❌ | [Planned](https://gitlab.com/slumber/multi-user/-/issues/81) |
|
| texts | ❌ | [Planned for v0.5.0](https://gitlab.com/slumber/multi-user/-/issues/81) |
|
||||||
| compositing | ❌ | [Planned](https://gitlab.com/slumber/multi-user/-/issues/46) |
|
| compositing | ❌ | [Planned for v0.5.0](https://gitlab.com/slumber/multi-user/-/issues/46) |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,10 +19,10 @@ import sys
|
|||||||
|
|
||||||
project = 'multi-user'
|
project = 'multi-user'
|
||||||
copyright = '2020, Swann Martinez'
|
copyright = '2020, Swann Martinez'
|
||||||
author = 'Swann Martinez, with contributions from Poochy'
|
author = 'Swann Martinez, Poochy, Fabian'
|
||||||
|
|
||||||
# The full version, including alpha/beta/rc tags
|
# The full version, including alpha/beta/rc tags
|
||||||
release = '0.2.0'
|
release = '0.5.0-develop'
|
||||||
|
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 365 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 26 KiB |
@ -215,8 +215,10 @@ One of the most vital tools is the **Online user panel**. It lists all connected
|
|||||||
users' information including your own:
|
users' information including your own:
|
||||||
|
|
||||||
* **Role** : if a user is an admin or a regular user.
|
* **Role** : if a user is an admin or a regular user.
|
||||||
* **Location**: Where the user is actually working.
|
* **Username** : Name of the user.
|
||||||
|
* **Mode** : User's active editing mode (edit_mesh, paint,etc.).
|
||||||
* **Frame**: When (on which frame) the user is working.
|
* **Frame**: When (on which frame) the user is working.
|
||||||
|
* **Location**: Where the user is actually working.
|
||||||
* **Ping**: user's connection delay in milliseconds
|
* **Ping**: user's connection delay in milliseconds
|
||||||
|
|
||||||
.. figure:: img/quickstart_users.png
|
.. figure:: img/quickstart_users.png
|
||||||
@ -273,6 +275,7 @@ it draw users' related information in your viewport such as:
|
|||||||
|
|
||||||
* Username
|
* Username
|
||||||
* User point of view
|
* User point of view
|
||||||
|
* User active mode
|
||||||
* User selection
|
* User selection
|
||||||
|
|
||||||
.. figure:: img/quickstart_presence.png
|
.. figure:: img/quickstart_presence.png
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Multi-User",
|
"name": "Multi-User",
|
||||||
"author": "Swann Martinez",
|
"author": "Swann Martinez",
|
||||||
"version": (0, 5, 0),
|
"version": (0, 4, 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",
|
||||||
|
@ -56,7 +56,7 @@ class BlCamera(ReplicatedDatablock):
|
|||||||
background_images = data.get('background_images')
|
background_images = data.get('background_images')
|
||||||
|
|
||||||
datablock.background_images.clear()
|
datablock.background_images.clear()
|
||||||
|
# TODO: Use image uuid
|
||||||
if background_images:
|
if background_images:
|
||||||
for img_name, img_data in background_images.items():
|
for img_name, img_data in background_images.items():
|
||||||
img_id = img_data.get('image')
|
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_datablock import resolve_datablock_from_uuid
|
||||||
from .bl_action import dump_animation_data, load_animation_data, resolve_animation_dependencies
|
from .bl_action import dump_animation_data, load_animation_data, resolve_animation_dependencies
|
||||||
from ..utils import get_preferences
|
from ..utils import get_preferences
|
||||||
|
from ..timers import is_annotating
|
||||||
|
from .bl_material import load_materials_slots, dump_materials_slots
|
||||||
|
|
||||||
STROKE_POINT = [
|
STROKE_POINT = [
|
||||||
'co',
|
'co',
|
||||||
@ -65,36 +66,9 @@ def dump_stroke(stroke):
|
|||||||
|
|
||||||
:param stroke: target grease pencil stroke
|
:param stroke: target grease pencil stroke
|
||||||
:type stroke: bpy.types.GPencilStroke
|
:type stroke: bpy.types.GPencilStroke
|
||||||
:return: dict
|
:return: (p_count, p_data)
|
||||||
"""
|
"""
|
||||||
|
return (len(stroke.points), np_dump_collection(stroke.points, STROKE_POINT))
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def load_stroke(stroke_data, stroke):
|
def load_stroke(stroke_data, stroke):
|
||||||
@ -107,12 +81,12 @@ def load_stroke(stroke_data, stroke):
|
|||||||
"""
|
"""
|
||||||
assert(stroke and stroke_data)
|
assert(stroke and stroke_data)
|
||||||
|
|
||||||
stroke.points.add(stroke_data["p_count"])
|
stroke.points.add(stroke_data[0])
|
||||||
np_load_collection(stroke_data['points'], stroke.points, STROKE_POINT)
|
np_load_collection(stroke_data[1], stroke.points, STROKE_POINT)
|
||||||
|
|
||||||
# HACK: Temporary fix to trigger a BKE_gpencil_stroke_geometry_update to
|
# HACK: Temporary fix to trigger a BKE_gpencil_stroke_geometry_update to
|
||||||
# fix fill issues
|
# fix fill issues
|
||||||
stroke.uv_scale = stroke_data["uv_scale"]
|
stroke.uv_scale = 1.0
|
||||||
|
|
||||||
|
|
||||||
def dump_frame(frame):
|
def dump_frame(frame):
|
||||||
@ -147,10 +121,12 @@ def load_frame(frame_data, frame):
|
|||||||
|
|
||||||
assert(frame and frame_data)
|
assert(frame and frame_data)
|
||||||
|
|
||||||
|
# Load stroke points
|
||||||
for stroke_data in frame_data['strokes_points']:
|
for stroke_data in frame_data['strokes_points']:
|
||||||
target_stroke = frame.strokes.new()
|
target_stroke = frame.strokes.new()
|
||||||
load_stroke(stroke_data, target_stroke)
|
load_stroke(stroke_data, target_stroke)
|
||||||
|
|
||||||
|
# Load stroke metadata
|
||||||
np_load_collection(frame_data['strokes'], frame.strokes, STROKE)
|
np_load_collection(frame_data['strokes'], frame.strokes, STROKE)
|
||||||
|
|
||||||
|
|
||||||
@ -170,7 +146,6 @@ def dump_layer(layer):
|
|||||||
'opacity',
|
'opacity',
|
||||||
'channel_color',
|
'channel_color',
|
||||||
'color',
|
'color',
|
||||||
# 'thickness', #TODO: enabling only for annotation
|
|
||||||
'tint_color',
|
'tint_color',
|
||||||
'tint_factor',
|
'tint_factor',
|
||||||
'vertex_paint_opacity',
|
'vertex_paint_opacity',
|
||||||
@ -187,7 +162,7 @@ def dump_layer(layer):
|
|||||||
'hide',
|
'hide',
|
||||||
'annotation_hide',
|
'annotation_hide',
|
||||||
'lock',
|
'lock',
|
||||||
# 'lock_frame',
|
'lock_frame',
|
||||||
# 'lock_material',
|
# 'lock_material',
|
||||||
# 'use_mask_layer',
|
# 'use_mask_layer',
|
||||||
'use_lights',
|
'use_lights',
|
||||||
@ -195,12 +170,13 @@ def dump_layer(layer):
|
|||||||
'select',
|
'select',
|
||||||
'show_points',
|
'show_points',
|
||||||
'show_in_front',
|
'show_in_front',
|
||||||
|
# 'thickness'
|
||||||
# 'parent',
|
# 'parent',
|
||||||
# 'parent_type',
|
# 'parent_type',
|
||||||
# 'parent_bone',
|
# 'parent_bone',
|
||||||
# 'matrix_inverse',
|
# 'matrix_inverse',
|
||||||
]
|
]
|
||||||
if layer.id_data.is_annotation:
|
if layer.thickness != 0:
|
||||||
dumper.include_filter.append('thickness')
|
dumper.include_filter.append('thickness')
|
||||||
|
|
||||||
dumped_layer = dumper.dump(layer)
|
dumped_layer = dumper.dump(layer)
|
||||||
@ -255,10 +231,10 @@ class BlGpencil(ReplicatedDatablock):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(data: dict, datablock: object):
|
def load(data: dict, datablock: object):
|
||||||
datablock.materials.clear()
|
# MATERIAL SLOTS
|
||||||
if "materials" in data.keys():
|
src_materials = data.get('materials', None)
|
||||||
for mat in data['materials']:
|
if src_materials:
|
||||||
datablock.materials.append(bpy.data.materials[mat])
|
load_materials_slots(src_materials, datablock.materials)
|
||||||
|
|
||||||
loader = Loader()
|
loader = Loader()
|
||||||
loader.load(datablock, data)
|
loader.load(datablock, data)
|
||||||
@ -286,7 +262,6 @@ class BlGpencil(ReplicatedDatablock):
|
|||||||
dumper = Dumper()
|
dumper = Dumper()
|
||||||
dumper.depth = 2
|
dumper.depth = 2
|
||||||
dumper.include_filter = [
|
dumper.include_filter = [
|
||||||
'materials',
|
|
||||||
'name',
|
'name',
|
||||||
'zdepth_offset',
|
'zdepth_offset',
|
||||||
'stroke_thickness_space',
|
'stroke_thickness_space',
|
||||||
@ -294,7 +269,7 @@ class BlGpencil(ReplicatedDatablock):
|
|||||||
'stroke_depth_order'
|
'stroke_depth_order'
|
||||||
]
|
]
|
||||||
data = dumper.dump(datablock)
|
data = dumper.dump(datablock)
|
||||||
|
data['materials'] = dump_materials_slots(datablock.materials)
|
||||||
data['layers'] = {}
|
data['layers'] = {}
|
||||||
|
|
||||||
for layer in datablock.layers:
|
for layer in datablock.layers:
|
||||||
@ -323,7 +298,8 @@ class BlGpencil(ReplicatedDatablock):
|
|||||||
return bpy.context.mode == 'OBJECT' \
|
return bpy.context.mode == 'OBJECT' \
|
||||||
or layer_changed(datablock, data) \
|
or layer_changed(datablock, data) \
|
||||||
or frame_changed(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
|
_type = bpy.types.GreasePencil
|
||||||
_class = BlGpencil
|
_class = BlGpencil
|
||||||
|
@ -69,11 +69,12 @@ class BlImage(ReplicatedDatablock):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def load(data: dict, datablock: object):
|
def load(data: dict, datablock: object):
|
||||||
loader = Loader()
|
loader = Loader()
|
||||||
loader.load(data, datablock)
|
loader.load(datablock, data)
|
||||||
|
|
||||||
|
# datablock.name = data.get('name')
|
||||||
datablock.source = 'FILE'
|
datablock.source = 'FILE'
|
||||||
datablock.filepath_raw = get_filepath(data['filename'])
|
datablock.filepath_raw = get_filepath(data['filename'])
|
||||||
color_space_name = data["colorspace_settings"]["name"]
|
color_space_name = data.get("colorspace")
|
||||||
|
|
||||||
if color_space_name:
|
if color_space_name:
|
||||||
datablock.colorspace_settings.name = color_space_name
|
datablock.colorspace_settings.name = color_space_name
|
||||||
@ -92,12 +93,10 @@ class BlImage(ReplicatedDatablock):
|
|||||||
"name",
|
"name",
|
||||||
# 'source',
|
# 'source',
|
||||||
'size',
|
'size',
|
||||||
'height',
|
'alpha_mode']
|
||||||
'alpha',
|
|
||||||
'float_buffer',
|
|
||||||
'alpha_mode',
|
|
||||||
'colorspace_settings']
|
|
||||||
data.update(dumper.dump(datablock))
|
data.update(dumper.dump(datablock))
|
||||||
|
data['colorspace'] = datablock.colorspace_settings.name
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -132,10 +131,7 @@ class BlImage(ReplicatedDatablock):
|
|||||||
if datablock.is_dirty:
|
if datablock.is_dirty:
|
||||||
datablock.save()
|
datablock.save()
|
||||||
|
|
||||||
if not data or (datablock and (datablock.name != data.get('name'))):
|
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
_type = bpy.types.Image
|
_type = bpy.types.Image
|
||||||
_class = BlImage
|
_class = BlImage
|
||||||
|
@ -124,8 +124,7 @@ def dump_node(node: bpy.types.ShaderNode) -> dict:
|
|||||||
"show_preview",
|
"show_preview",
|
||||||
"show_texture",
|
"show_texture",
|
||||||
"outputs",
|
"outputs",
|
||||||
"width_hidden",
|
"width_hidden"
|
||||||
"image"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
dumped_node = node_dumper.dump(node)
|
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:
|
for mat_uuid, mat_name in src_materials:
|
||||||
mat_ref = None
|
mat_ref = None
|
||||||
if mat_uuid is not None:
|
if mat_uuid:
|
||||||
mat_ref = get_datablock_from_uuid(mat_uuid, None)
|
mat_ref = get_datablock_from_uuid(mat_uuid, None)
|
||||||
else:
|
else:
|
||||||
mat_ref = bpy.data.materials[mat_name]
|
mat_ref = bpy.data.materials[mat_name]
|
||||||
|
|
||||||
dst_materials.append(mat_ref)
|
dst_materials.append(mat_ref)
|
||||||
|
|
||||||
|
|
||||||
|
@ -620,10 +620,8 @@ class BlObject(ReplicatedDatablock):
|
|||||||
|
|
||||||
transform = data.get('transforms', None)
|
transform = data.get('transforms', None)
|
||||||
if transform:
|
if transform:
|
||||||
datablock.matrix_parent_inverse = mathutils.Matrix(
|
datablock.matrix_parent_inverse = mathutils.Matrix(transform['matrix_parent_inverse'])
|
||||||
transform['matrix_parent_inverse'])
|
|
||||||
datablock.matrix_basis = mathutils.Matrix(transform['matrix_basis'])
|
datablock.matrix_basis = mathutils.Matrix(transform['matrix_basis'])
|
||||||
datablock.matrix_local = mathutils.Matrix(transform['matrix_local'])
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -403,8 +403,9 @@ class BlScene(ReplicatedDatablock):
|
|||||||
datablock.world = bpy.data.worlds[data['world']]
|
datablock.world = bpy.data.worlds[data['world']]
|
||||||
|
|
||||||
# Annotation
|
# Annotation
|
||||||
if 'grease_pencil' in data.keys():
|
gpencil_uid = data.get('grease_pencil')
|
||||||
datablock.grease_pencil = bpy.data.grease_pencils[data['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 get_preferences().sync_flags.sync_render_settings:
|
||||||
if 'eevee' in data.keys():
|
if 'eevee' in data.keys():
|
||||||
@ -445,6 +446,15 @@ class BlScene(ReplicatedDatablock):
|
|||||||
elif datablock.sequence_editor and not sequences:
|
elif datablock.sequence_editor and not sequences:
|
||||||
datablock.sequence_editor_clear()
|
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
|
# FIXME: Find a better way after the replication big refacotoring
|
||||||
# Keep other user from deleting collection object by flushing their history
|
# Keep other user from deleting collection object by flushing their history
|
||||||
flush_history()
|
flush_history()
|
||||||
@ -461,7 +471,6 @@ class BlScene(ReplicatedDatablock):
|
|||||||
'name',
|
'name',
|
||||||
'world',
|
'world',
|
||||||
'id',
|
'id',
|
||||||
'grease_pencil',
|
|
||||||
'frame_start',
|
'frame_start',
|
||||||
'frame_end',
|
'frame_end',
|
||||||
'frame_step',
|
'frame_step',
|
||||||
@ -517,6 +526,13 @@ class BlScene(ReplicatedDatablock):
|
|||||||
dumped_sequences[seq.name] = dump_sequence(seq)
|
dumped_sequences[seq.name] = dump_sequence(seq)
|
||||||
data['sequences'] = dumped_sequences
|
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
|
return data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -52,7 +52,8 @@ def sanitize_deps_graph(remove_nodes: bool = False):
|
|||||||
def update_external_dependencies():
|
def update_external_dependencies():
|
||||||
"""Force external dependencies(files such as images) evaluation
|
"""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:
|
for node_id in nodes_ids:
|
||||||
node = session.repository.graph.get(node_id)
|
node = session.repository.graph.get(node_id)
|
||||||
if node and node.owner in [session.repository.username, RP_COMMON]:
|
if node and node.owner in [session.repository.username, RP_COMMON]:
|
||||||
@ -103,7 +104,8 @@ def on_scene_update(scene):
|
|||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
elif isinstance(update.id, bpy.types.Scene):
|
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.commit(session.repository, scn_uuid)
|
||||||
porcelain.push(session.repository, 'origin', scn_uuid)
|
porcelain.push(session.repository, 'origin', scn_uuid)
|
||||||
|
|
||||||
|
@ -273,8 +273,7 @@ class SessionStartOperator(bpy.types.Operator):
|
|||||||
|
|
||||||
session_update = timers.SessionStatusUpdate()
|
session_update = timers.SessionStatusUpdate()
|
||||||
session_user_sync = timers.SessionUserSync()
|
session_user_sync = timers.SessionUserSync()
|
||||||
session_background_executor = timers.MainThreadExecutor(
|
session_background_executor = timers.MainThreadExecutor(execution_queue=background_execution_queue)
|
||||||
execution_queue=background_execution_queue)
|
|
||||||
session_listen = timers.SessionListenTimer(timeout=0.001)
|
session_listen = timers.SessionListenTimer(timeout=0.001)
|
||||||
|
|
||||||
session_listen.register()
|
session_listen.register()
|
||||||
@ -286,6 +285,7 @@ class SessionStartOperator(bpy.types.Operator):
|
|||||||
deleyables.append(session_update)
|
deleyables.append(session_update)
|
||||||
deleyables.append(session_user_sync)
|
deleyables.append(session_user_sync)
|
||||||
deleyables.append(session_listen)
|
deleyables.append(session_listen)
|
||||||
|
deleyables.append(timers.AnnotationUpdates())
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@ -784,6 +784,22 @@ class SessionStopAutoSaveOperator(bpy.types.Operator):
|
|||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class SessionGetInfo(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):
|
||||||
|
infos = porcelain.request_session_info(self.target_server, timeout=100)
|
||||||
|
logging.info(f"Session info: {infos}")
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
|
class SessionLoadSaveOperator(bpy.types.Operator, ImportHelper):
|
||||||
bl_idname = "session.load"
|
bl_idname = "session.load"
|
||||||
@ -922,6 +938,7 @@ classes = (
|
|||||||
SessionPurgeOperator,
|
SessionPurgeOperator,
|
||||||
SessionPresetServerAdd,
|
SessionPresetServerAdd,
|
||||||
SessionPresetServerRemove,
|
SessionPresetServerRemove,
|
||||||
|
SessionGetInfo,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,18 +94,21 @@ def project_to_viewport(region: bpy.types.Region, rv3d: bpy.types.RegionView3D,
|
|||||||
return [target.x, target.y, target.z]
|
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
|
""" Generate a bounding box for a given object by using its world matrix
|
||||||
|
|
||||||
:param obj: target object
|
:param obj: target object
|
||||||
:type obj: bpy.types.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),...]
|
: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
|
radius = 1.0 # Radius of the bounding box
|
||||||
|
index = 8*index
|
||||||
vertex_indices = (
|
vertex_indices = (
|
||||||
(0, 1), (0, 2), (1, 3), (2, 3),
|
(0+index, 1+index), (0+index, 2+index), (1+index, 3+index), (2+index, 3+index),
|
||||||
(4, 5), (4, 6), (5, 7), (6, 7),
|
(4+index, 5+index), (4+index, 6+index), (5+index, 7+index), (6+index, 7+index),
|
||||||
(0, 4), (1, 5), (2, 6), (3, 7))
|
(0+index, 4+index), (1+index, 5+index), (2+index, 6+index), (3+index, 7+index))
|
||||||
|
|
||||||
if obj.type == 'EMPTY':
|
if obj.type == 'EMPTY':
|
||||||
radius = obj.empty_display_size
|
radius = obj.empty_display_size
|
||||||
@ -117,9 +120,12 @@ def bbox_from_obj(obj: bpy.types.Object) -> list:
|
|||||||
radius = obj.data.display_size
|
radius = obj.data.display_size
|
||||||
elif hasattr(obj, 'bound_box'):
|
elif hasattr(obj, 'bound_box'):
|
||||||
vertex_indices = (
|
vertex_indices = (
|
||||||
(0, 1), (1, 2), (2, 3), (0, 3),
|
(0+index, 1+index), (1+index, 2+index),
|
||||||
(4, 5), (5, 6), (6, 7), (4, 7),
|
(2+index, 3+index), (0+index, 3+index),
|
||||||
(0, 4), (1, 5), (2, 6), (3, 7))
|
(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)
|
vertex_pos = get_bb_coords_from_obj(obj)
|
||||||
return vertex_pos, vertex_indices
|
return vertex_pos, vertex_indices
|
||||||
|
|
||||||
@ -136,26 +142,21 @@ def bbox_from_obj(obj: bpy.types.Object) -> list:
|
|||||||
|
|
||||||
return vertex_pos, vertex_indices
|
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
|
""" Generate a bounding box for a given instance collection by using its objects
|
||||||
|
|
||||||
:param ic: target instance collection
|
:param ic: target instance collection
|
||||||
:type ic: bpy.types.Object
|
:type ic: bpy.types.Object
|
||||||
:param radius: bounding box radius
|
:param index: indice offset
|
||||||
:type radius: float
|
:type index: int
|
||||||
:return: list of 8*objs points [(x,y,z),...], tuple of 12*objs link between these points [(1,2),...]
|
:return: list of 8*objs points [(x,y,z),...], tuple of 12*objs link between these points [(1,2),...]
|
||||||
"""
|
"""
|
||||||
vertex_pos = []
|
vertex_pos = []
|
||||||
vertex_indices = ()
|
vertex_indices = ()
|
||||||
|
|
||||||
for obj_index, obj in enumerate(ic.instance_collection.objects):
|
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_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
|
vertex_indices += vertex_indices_temp
|
||||||
|
|
||||||
bbox_corners = [ic.matrix_world @ mathutils.Vector(vertex) for vertex in vertex_pos]
|
bbox_corners = [ic.matrix_world @ mathutils.Vector(vertex) for vertex in vertex_pos]
|
||||||
@ -322,6 +323,8 @@ class UserSelectionWidget(Widget):
|
|||||||
username):
|
username):
|
||||||
self.username = username
|
self.username = username
|
||||||
self.settings = bpy.context.window_manager.session
|
self.settings = bpy.context.window_manager.session
|
||||||
|
self.current_selection_ids = []
|
||||||
|
self.current_selected_objects = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def data(self):
|
def data(self):
|
||||||
@ -331,6 +334,15 @@ class UserSelectionWidget(Widget):
|
|||||||
else:
|
else:
|
||||||
return None
|
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):
|
def poll(self):
|
||||||
if self.data is None:
|
if self.data is None:
|
||||||
return False
|
return False
|
||||||
@ -345,22 +357,27 @@ class UserSelectionWidget(Widget):
|
|||||||
self.settings.enable_presence
|
self.settings.enable_presence
|
||||||
|
|
||||||
def draw(self):
|
def draw(self):
|
||||||
user_selection = self.data.get('selected_objects')
|
vertex_pos = []
|
||||||
for select_obj in user_selection:
|
vertex_ind = []
|
||||||
obj = find_from_attr("uuid", select_obj, bpy.data.objects)
|
collection_offset = 0
|
||||||
if not obj:
|
for obj_index, obj in enumerate(self.selected_objects):
|
||||||
return
|
if obj is None:
|
||||||
if obj.instance_collection:
|
continue
|
||||||
vertex_pos, vertex_indices = bbox_from_instance_collection(obj)
|
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 :
|
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')
|
shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
|
||||||
batch = batch_for_shader(
|
batch = batch_for_shader(
|
||||||
shader,
|
shader,
|
||||||
'LINES',
|
'LINES',
|
||||||
{"pos": vertex_pos},
|
{"pos": vertex_pos},
|
||||||
indices=vertex_indices)
|
indices=vertex_ind)
|
||||||
|
|
||||||
shader.bind()
|
shader.bind()
|
||||||
shader.uniform_float("color", self.data.get('color'))
|
shader.uniform_float("color", self.data.get('color'))
|
||||||
|
@ -41,7 +41,8 @@ this.registry = dict()
|
|||||||
def is_annotating(context: bpy.types.Context):
|
def is_annotating(context: bpy.types.Context):
|
||||||
""" Check if the annotate mode is enabled
|
""" 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):
|
class Timer(object):
|
||||||
@ -136,22 +137,15 @@ class ApplyTimer(Timer):
|
|||||||
force=True)
|
force=True)
|
||||||
|
|
||||||
|
|
||||||
class DynamicRightSelectTimer(Timer):
|
class AnnotationUpdates(Timer):
|
||||||
def __init__(self, timeout=.1):
|
def __init__(self, timeout=1):
|
||||||
super().__init__(timeout)
|
|
||||||
self._last_selection = []
|
|
||||||
self._user = None
|
|
||||||
self._annotating = False
|
self._annotating = False
|
||||||
|
self._settings = utils.get_preferences()
|
||||||
|
|
||||||
|
super().__init__(timeout)
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
settings = utils.get_preferences()
|
|
||||||
|
|
||||||
if session and session.state == STATE_ACTIVE:
|
if session and session.state == STATE_ACTIVE:
|
||||||
# Find user
|
|
||||||
if self._user is None:
|
|
||||||
self._user = session.online_users.get(settings.username)
|
|
||||||
|
|
||||||
if self._user:
|
|
||||||
ctx = bpy.context
|
ctx = bpy.context
|
||||||
annotation_gp = ctx.scene.grease_pencil
|
annotation_gp = ctx.scene.grease_pencil
|
||||||
|
|
||||||
@ -168,67 +162,76 @@ class DynamicRightSelectTimer(Timer):
|
|||||||
logging.debug(
|
logging.debug(
|
||||||
"Getting the right on the annotation GP")
|
"Getting the right on the annotation GP")
|
||||||
porcelain.lock(session.repository,
|
porcelain.lock(session.repository,
|
||||||
registered_gp.uuid,
|
[registered_gp.uuid],
|
||||||
ignore_warnings=True,
|
ignore_warnings=True,
|
||||||
affect_dependencies=False)
|
affect_dependencies=False)
|
||||||
|
|
||||||
if registered_gp.owner == settings.username:
|
if registered_gp.owner == self._settings.username:
|
||||||
gp_node = session.repository.graph.get(annotation_gp.uuid)
|
porcelain.commit(session.repository, annotation_gp.uuid)
|
||||||
porcelain.commit(session.repository, gp_node.uuid)
|
porcelain.push(session.repository, 'origin', annotation_gp.uuid)
|
||||||
porcelain.push(session.repository, 'origin', gp_node.uuid)
|
|
||||||
|
|
||||||
elif self._annotating:
|
elif self._annotating:
|
||||||
porcelain.unlock(session.repository,
|
porcelain.unlock(session.repository,
|
||||||
registered_gp.uuid,
|
[registered_gp.uuid],
|
||||||
ignore_warnings=True,
|
ignore_warnings=True,
|
||||||
affect_dependencies=False)
|
affect_dependencies=False)
|
||||||
|
self._annotating = False
|
||||||
|
|
||||||
current_selection = utils.get_selected_objects(
|
class DynamicRightSelectTimer(Timer):
|
||||||
|
def __init__(self, timeout=.1):
|
||||||
|
super().__init__(timeout)
|
||||||
|
self._last_selection = set()
|
||||||
|
self._user = None
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
settings = utils.get_preferences()
|
||||||
|
|
||||||
|
if session and session.state == STATE_ACTIVE:
|
||||||
|
# Find user
|
||||||
|
if self._user is None:
|
||||||
|
self._user = session.online_users.get(settings.username)
|
||||||
|
|
||||||
|
if self._user:
|
||||||
|
current_selection = set(utils.get_selected_objects(
|
||||||
bpy.context.scene,
|
bpy.context.scene,
|
||||||
bpy.data.window_managers['WinMan'].windows[0].view_layer
|
bpy.data.window_managers['WinMan'].windows[0].view_layer
|
||||||
)
|
))
|
||||||
if current_selection != self._last_selection:
|
if current_selection != self._last_selection:
|
||||||
obj_common = [
|
to_lock = list(current_selection.difference(self._last_selection))
|
||||||
o for o in self._last_selection if o not in current_selection]
|
to_release = list(self._last_selection.difference(current_selection))
|
||||||
obj_ours = [
|
instances_to_lock = list()
|
||||||
o for o in current_selection if o not in self._last_selection]
|
|
||||||
|
|
||||||
# change old selection right to common
|
|
||||||
for obj in obj_common:
|
|
||||||
node = session.repository.graph.get(obj)
|
|
||||||
|
|
||||||
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'
|
|
||||||
|
|
||||||
|
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:
|
try:
|
||||||
porcelain.lock(session.repository,
|
porcelain.lock(session.repository,
|
||||||
node.uuid,
|
instances_to_lock,
|
||||||
ignore_warnings=True,
|
ignore_warnings=True,
|
||||||
affect_dependencies=recursive)
|
affect_dependencies=False)
|
||||||
except NonAuthorizedOperationError:
|
except NonAuthorizedOperationError as e:
|
||||||
logging.warning(
|
logging.warning(e)
|
||||||
f"Not authorized to change {node} owner")
|
|
||||||
else:
|
if to_release:
|
||||||
return
|
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
|
self._last_selection = current_selection
|
||||||
|
|
||||||
@ -242,17 +245,16 @@ class DynamicRightSelectTimer(Timer):
|
|||||||
# Fix deselection until right managment refactoring (with Roles concepts)
|
# Fix deselection until right managment refactoring (with Roles concepts)
|
||||||
if len(current_selection) == 0 :
|
if len(current_selection) == 0 :
|
||||||
owned_keys = [k for k, v in session.repository.graph.items() if v.owner==settings.username]
|
owned_keys = [k for k, v in session.repository.graph.items() if v.owner==settings.username]
|
||||||
for key in owned_keys:
|
if owned_keys:
|
||||||
node = session.repository.graph.get(key)
|
|
||||||
try:
|
try:
|
||||||
porcelain.unlock(session.repository,
|
porcelain.unlock(session.repository,
|
||||||
key,
|
owned_keys,
|
||||||
ignore_warnings=True,
|
ignore_warnings=True,
|
||||||
affect_dependencies=True)
|
affect_dependencies=True)
|
||||||
except NonAuthorizedOperationError:
|
except NonAuthorizedOperationError as e:
|
||||||
logging.warning(
|
logging.warning(e)
|
||||||
f"Not authorized to change {key} owner")
|
|
||||||
|
|
||||||
|
# Objects selectability
|
||||||
for obj in bpy.data.objects:
|
for obj in bpy.data.objects:
|
||||||
object_uuid = getattr(obj, 'uuid', None)
|
object_uuid = getattr(obj, 'uuid', None)
|
||||||
if object_uuid:
|
if object_uuid:
|
||||||
|
@ -599,20 +599,15 @@ class SESSION_PT_repository(bpy.types.Panel):
|
|||||||
# Properties
|
# Properties
|
||||||
owned_nodes = [k for k, v in session.repository.graph.items() if v.owner==settings.username]
|
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:
|
if runtime_settings.filter_name:
|
||||||
for node_id in filtered_node:
|
filtered_node = [n for n in filtered_node if runtime_settings.filter_name.lower() in session.repository.graph.get(n).data.get('name').lower()]
|
||||||
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)
|
|
||||||
|
|
||||||
if filtered_node:
|
if filtered_node:
|
||||||
col = layout.column(align=True)
|
col = layout.column(align=True)
|
||||||
for key in filtered_node:
|
for key in filtered_node:
|
||||||
draw_property(context, col, key)
|
draw_property(context, col, key)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
layout.row().label(text="Empty")
|
layout.row().label(text="Empty")
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@ def test_scene(clear_blend):
|
|||||||
get_preferences().sync_flags.sync_render_settings = True
|
get_preferences().sync_flags.sync_render_settings = True
|
||||||
|
|
||||||
datablock = bpy.data.scenes.new("toto")
|
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
|
datablock.view_settings.use_curve_mapping = True
|
||||||
# Test
|
# Test
|
||||||
implementation = BlScene()
|
implementation = BlScene()
|
||||||
|