Merge branch '45-vse-support' into 'develop'
Resolve "VSE Support" See merge request slumber/multi-user!72
This commit is contained in:
@@ -49,7 +49,7 @@ Currently, not all data-block are supported for replication over the wire. The f
|
||||
| volumes | ❌ | |
|
||||
| particles | ❌ | [On-going](https://gitlab.com/slumber/multi-user/-/issues/24) |
|
||||
| speakers | ❗ | [Partial](https://gitlab.com/slumber/multi-user/-/issues/65) |
|
||||
| vse | ❌ | [Planned](https://gitlab.com/slumber/multi-user/-/issues/45) |
|
||||
| vse | ❗ | Mask and Clip not supported yet |
|
||||
| physics | ❌ | [Planned](https://gitlab.com/slumber/multi-user/-/issues/45) |
|
||||
| libraries | ❗ | Partial |
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ __all__ = [
|
||||
'bl_font',
|
||||
'bl_sound',
|
||||
'bl_file',
|
||||
'bl_sequencer',
|
||||
'bl_node_group'
|
||||
] # Order here defines execution order
|
||||
|
||||
|
||||
@@ -71,6 +71,15 @@ def load_collection_childrens(dumped_childrens, collection):
|
||||
if child_collection.uuid not in dumped_childrens:
|
||||
collection.children.unlink(child_collection)
|
||||
|
||||
def resolve_collection_dependencies(collection):
|
||||
deps = []
|
||||
|
||||
for child in collection.children:
|
||||
deps.append(child)
|
||||
for object in collection.objects:
|
||||
deps.append(object)
|
||||
|
||||
return deps
|
||||
|
||||
class BlCollection(BlDatablock):
|
||||
bl_id = "collections"
|
||||
@@ -124,11 +133,4 @@ class BlCollection(BlDatablock):
|
||||
return data
|
||||
|
||||
def _resolve_deps_implementation(self):
|
||||
deps = []
|
||||
|
||||
for child in self.instance.children:
|
||||
deps.append(child)
|
||||
for object in self.instance.objects:
|
||||
deps.append(object)
|
||||
|
||||
return deps
|
||||
return resolve_collection_dependencies(self.instance)
|
||||
|
||||
@@ -21,7 +21,7 @@ from collections.abc import Iterable
|
||||
|
||||
import bpy
|
||||
import mathutils
|
||||
from replication.constants import DIFF_BINARY, UP
|
||||
from replication.constants import DIFF_BINARY, DIFF_JSON, UP
|
||||
from replication.data import ReplicatedDatablock
|
||||
|
||||
from .. import utils
|
||||
@@ -127,7 +127,10 @@ class BlDatablock(ReplicatedDatablock):
|
||||
if instance and hasattr(instance, 'uuid'):
|
||||
instance.uuid = self.uuid
|
||||
|
||||
self.diff_method = DIFF_BINARY
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
self.diff_method = DIFF_JSON
|
||||
else:
|
||||
self.diff_method = DIFF_BINARY
|
||||
|
||||
def resolve(self):
|
||||
datablock_ref = None
|
||||
@@ -216,7 +219,7 @@ class BlDatablock(ReplicatedDatablock):
|
||||
if not self.is_library:
|
||||
dependencies.extend(self._resolve_deps_implementation())
|
||||
|
||||
logging.debug(f"{self.instance.name} dependencies: {dependencies}")
|
||||
logging.debug(f"{self.instance} dependencies: {dependencies}")
|
||||
return dependencies
|
||||
|
||||
def _resolve_deps_implementation(self):
|
||||
|
||||
@@ -16,15 +16,18 @@
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
import bpy
|
||||
import mathutils
|
||||
|
||||
from .dump_anything import Loader, Dumper
|
||||
from .bl_datablock import BlDatablock
|
||||
from .bl_collection import dump_collection_children, dump_collection_objects, load_collection_childrens, load_collection_objects
|
||||
from replication.constants import (DIFF_JSON, MODIFIED)
|
||||
from deepdiff import DeepDiff
|
||||
import logging
|
||||
from replication.constants import DIFF_JSON, MODIFIED
|
||||
|
||||
from .bl_collection import (dump_collection_children, dump_collection_objects,
|
||||
load_collection_childrens, load_collection_objects,
|
||||
resolve_collection_dependencies)
|
||||
from .bl_datablock import BlDatablock
|
||||
from .dump_anything import Dumper, Loader
|
||||
|
||||
RENDER_SETTINGS = [
|
||||
'dither_intensity',
|
||||
@@ -261,6 +264,12 @@ VIEW_SETTINGS = [
|
||||
'black_level'
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class BlScene(BlDatablock):
|
||||
bl_id = "scenes"
|
||||
bl_class = bpy.types.Scene
|
||||
@@ -310,7 +319,7 @@ class BlScene(BlDatablock):
|
||||
if 'view_settings' in data.keys():
|
||||
loader.load(target.view_settings, data['view_settings'])
|
||||
if target.view_settings.use_curve_mapping and \
|
||||
'curve_mapping' in data['view_settings']:
|
||||
'curve_mapping' in data['view_settings']:
|
||||
# TODO: change this ugly fix
|
||||
target.view_settings.curve_mapping.white_level = data[
|
||||
'view_settings']['curve_mapping']['white_level']
|
||||
@@ -320,8 +329,8 @@ class BlScene(BlDatablock):
|
||||
|
||||
def _dump_implementation(self, data, instance=None):
|
||||
assert(instance)
|
||||
data = {}
|
||||
|
||||
# Metadata
|
||||
scene_dumper = Dumper()
|
||||
scene_dumper.depth = 1
|
||||
scene_dumper.include_filter = [
|
||||
@@ -336,11 +345,9 @@ class BlScene(BlDatablock):
|
||||
if self.preferences.sync_flags.sync_active_camera:
|
||||
scene_dumper.include_filter.append('camera')
|
||||
|
||||
data = scene_dumper.dump(instance)
|
||||
data.update(scene_dumper.dump(instance))
|
||||
|
||||
scene_dumper.depth = 3
|
||||
|
||||
scene_dumper.include_filter = ['children', 'objects', 'name']
|
||||
# Master collection
|
||||
data['collection'] = {}
|
||||
data['collection']['children'] = dump_collection_children(
|
||||
instance.collection)
|
||||
@@ -350,6 +357,7 @@ class BlScene(BlDatablock):
|
||||
scene_dumper.depth = 1
|
||||
scene_dumper.include_filter = None
|
||||
|
||||
# Render settings
|
||||
if self.preferences.sync_flags.sync_render_settings:
|
||||
scene_dumper.include_filter = RENDER_SETTINGS
|
||||
|
||||
@@ -377,18 +385,18 @@ class BlScene(BlDatablock):
|
||||
data['view_settings']['curve_mapping']['curves'] = scene_dumper.dump(
|
||||
instance.view_settings.curve_mapping.curves)
|
||||
|
||||
if instance.sequence_editor:
|
||||
data['has_sequence'] = True
|
||||
else:
|
||||
data['has_sequence'] = False
|
||||
|
||||
return data
|
||||
|
||||
def _resolve_deps_implementation(self):
|
||||
deps = []
|
||||
|
||||
# child collections
|
||||
for child in self.instance.collection.children:
|
||||
deps.append(child)
|
||||
|
||||
# childs objects
|
||||
for object in self.instance.collection.objects:
|
||||
deps.append(object)
|
||||
# Master Collection
|
||||
deps.extend(resolve_collection_dependencies(self.instance.collection))
|
||||
|
||||
# world
|
||||
if self.instance.world:
|
||||
@@ -398,6 +406,11 @@ class BlScene(BlDatablock):
|
||||
if self.instance.grease_pencil:
|
||||
deps.append(self.instance.grease_pencil)
|
||||
|
||||
# Sequences
|
||||
# deps.extend(list(self.instance.sequence_editor.sequences_all))
|
||||
if self.instance.sequence_editor:
|
||||
deps.append(self.instance.sequence_editor)
|
||||
|
||||
return deps
|
||||
|
||||
def diff(self):
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
|
||||
import bpy
|
||||
import mathutils
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
from .bl_file import get_filepath
|
||||
from .dump_anything import Loader, Dumper
|
||||
from .bl_datablock import BlDatablock, get_datablock_from_uuid
|
||||
|
||||
def dump_sequence(sequence: bpy.types.Sequence) -> dict:
|
||||
""" Dump a sequence to a dict
|
||||
|
||||
:arg sequence: sequence to dump
|
||||
:type sequence: bpy.types.Sequence
|
||||
:return dict:
|
||||
"""
|
||||
dumper = Dumper()
|
||||
dumper.exclude_filter = [
|
||||
'lock',
|
||||
'select',
|
||||
'select_left_handle',
|
||||
'select_right_handle',
|
||||
'strobe'
|
||||
]
|
||||
dumper.depth = 1
|
||||
data = dumper.dump(sequence)
|
||||
|
||||
|
||||
# TODO: Support multiple images
|
||||
if sequence.type == 'IMAGE':
|
||||
data['filenames'] = [e.filename for e in sequence.elements]
|
||||
|
||||
|
||||
# Effect strip inputs
|
||||
input_count = getattr(sequence, 'input_count', None)
|
||||
if input_count:
|
||||
for n in range(input_count):
|
||||
input_name = f"input_{n+1}"
|
||||
data[input_name] = getattr(sequence, input_name).name
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def load_sequence(sequence_data: dict, sequence_editor: bpy.types.SequenceEditor):
|
||||
""" Load sequence from dumped data
|
||||
|
||||
:arg sequence_data: sequence to dump
|
||||
:type sequence_data:dict
|
||||
:arg sequence_editor: root sequence editor
|
||||
:type sequence_editor: bpy.types.SequenceEditor
|
||||
"""
|
||||
strip_type = sequence_data.get('type')
|
||||
strip_name = sequence_data.get('name')
|
||||
strip_channel = sequence_data.get('channel')
|
||||
strip_frame_start = sequence_data.get('frame_start')
|
||||
|
||||
sequence = sequence_editor.sequences_all.get(strip_name, None)
|
||||
|
||||
if sequence is None:
|
||||
if strip_type == 'SCENE':
|
||||
strip_scene = bpy.data.scenes.get(sequence_data.get('scene'))
|
||||
sequence = sequence_editor.sequences.new_scene(strip_name,
|
||||
strip_scene,
|
||||
strip_channel,
|
||||
strip_frame_start)
|
||||
elif strip_type == 'MOVIE':
|
||||
filepath = get_filepath(Path(sequence_data['filepath']).name)
|
||||
sequence = sequence_editor.sequences.new_movie(strip_name,
|
||||
filepath,
|
||||
strip_channel,
|
||||
strip_frame_start)
|
||||
elif strip_type == 'SOUND':
|
||||
filepath = bpy.data.sounds[sequence_data['sound']].filepath
|
||||
sequence = sequence_editor.sequences.new_sound(strip_name,
|
||||
filepath,
|
||||
strip_channel,
|
||||
strip_frame_start)
|
||||
elif strip_type == 'IMAGE':
|
||||
images_name = sequence_data.get('filenames')
|
||||
filepath = get_filepath(images_name[0])
|
||||
sequence = sequence_editor.sequences.new_image(strip_name,
|
||||
filepath,
|
||||
strip_channel,
|
||||
strip_frame_start)
|
||||
# load other images
|
||||
if len(images_name)>1:
|
||||
for img_idx in range(1,len(images_name)):
|
||||
sequence.elements.append((images_name[img_idx]))
|
||||
else:
|
||||
seq = {}
|
||||
|
||||
for i in range(sequence_data['input_count']):
|
||||
seq[f"seq{i+1}"] = sequence_editor.sequences_all.get(sequence_data.get(f"input_{i+1}", None))
|
||||
|
||||
sequence = sequence_editor.sequences.new_effect(name=strip_name,
|
||||
type=strip_type,
|
||||
channel=strip_channel,
|
||||
frame_start=strip_frame_start,
|
||||
frame_end=sequence_data['frame_final_end'],
|
||||
**seq)
|
||||
|
||||
loader = Loader()
|
||||
loader.load(sequence, sequence_data)
|
||||
sequence.select = False
|
||||
|
||||
|
||||
class BlSequencer(BlDatablock):
|
||||
bl_id = "scenes"
|
||||
bl_class = bpy.types.SequenceEditor
|
||||
bl_delay_refresh = 1
|
||||
bl_delay_apply = 1
|
||||
bl_automatic_push = True
|
||||
bl_check_common = True
|
||||
bl_icon = 'SEQUENCE'
|
||||
|
||||
def _construct(self, data):
|
||||
# Get the scene
|
||||
scene_id = data.get('name')
|
||||
scene = bpy.data.scenes.get(scene_id, None)
|
||||
|
||||
# Create sequencer data
|
||||
scene.sequence_editor_clear()
|
||||
scene.sequence_editor_create()
|
||||
|
||||
return scene.sequence_editor
|
||||
|
||||
def resolve(self):
|
||||
scene = bpy.data.scenes.get(self.data['name'], None)
|
||||
if scene:
|
||||
if scene.sequence_editor is None:
|
||||
self.instance = self._construct(self.data)
|
||||
else:
|
||||
self.instance = scene.sequence_editor
|
||||
else:
|
||||
logging.warning("Sequencer editor scene not found")
|
||||
|
||||
def _load_implementation(self, data, target):
|
||||
loader = Loader()
|
||||
# Sequencer
|
||||
sequences = data.get('sequences')
|
||||
if sequences:
|
||||
for seq in target.sequences_all:
|
||||
if seq.name not in sequences:
|
||||
target.sequences.remove(seq)
|
||||
for seq_name, seq_data in sequences.items():
|
||||
load_sequence(seq_data, target)
|
||||
|
||||
def _dump_implementation(self, data, instance=None):
|
||||
assert(instance)
|
||||
sequence_dumper = Dumper()
|
||||
sequence_dumper.depth = 1
|
||||
sequence_dumper.include_filter = [
|
||||
'proxy_storage',
|
||||
]
|
||||
data = {}#sequence_dumper.dump(instance)
|
||||
# Sequencer
|
||||
sequences = {}
|
||||
|
||||
for seq in instance.sequences_all:
|
||||
sequences[seq.name] = dump_sequence(seq)
|
||||
|
||||
data['sequences'] = sequences
|
||||
data['name'] = instance.id_data.name
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def _resolve_deps_implementation(self):
|
||||
deps = []
|
||||
|
||||
for seq in self.instance.sequences_all:
|
||||
if seq.type == 'MOVIE' and seq.filepath:
|
||||
deps.append(Path(bpy.path.abspath(seq.filepath)))
|
||||
elif seq.type == 'SOUND' and seq.sound:
|
||||
deps.append(seq.sound)
|
||||
elif seq.type == 'IMAGE':
|
||||
for e in seq.elements:
|
||||
deps.append(Path(bpy.path.abspath(seq.directory), e.filename))
|
||||
return deps
|
||||
+3
-1
@@ -99,7 +99,9 @@ def clean_scene():
|
||||
type_collection.remove(item)
|
||||
except:
|
||||
continue
|
||||
|
||||
|
||||
# Clear sequencer
|
||||
bpy.context.scene.sequence_editor_clear()
|
||||
|
||||
def get_selected_objects(scene, active_view_layer):
|
||||
return [obj.uuid for obj in scene.objects if obj.select_get(view_layer=active_view_layer)]
|
||||
|
||||
Reference in New Issue
Block a user