Merge branch '167-sequencer-strips-does-not-sync-in-132-fix-undo-edit-last-operation-redo-handling-2-branch' into '132-fix-undo-edit-last-operation-redo-handling-2'
Resolve "Sequencer strips does not sync in `132-fix-undo-edit-last-operation-redo-handling-2 branch`" See merge request slumber/multi-user!101
This commit is contained in:
@ -39,7 +39,7 @@ __all__ = [
|
|||||||
'bl_font',
|
'bl_font',
|
||||||
'bl_sound',
|
'bl_sound',
|
||||||
'bl_file',
|
'bl_file',
|
||||||
'bl_sequencer',
|
# 'bl_sequencer',
|
||||||
'bl_node_group',
|
'bl_node_group',
|
||||||
'bl_texture',
|
'bl_texture',
|
||||||
] # Order here defines execution order
|
] # Order here defines execution order
|
||||||
|
@ -17,18 +17,20 @@
|
|||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import mathutils
|
import mathutils
|
||||||
from deepdiff import DeepDiff
|
from deepdiff import DeepDiff
|
||||||
from replication.constants import DIFF_JSON, MODIFIED
|
from replication.constants import DIFF_JSON, MODIFIED
|
||||||
|
|
||||||
|
from ..utils import flush_history
|
||||||
from .bl_collection import (dump_collection_children, dump_collection_objects,
|
from .bl_collection import (dump_collection_children, dump_collection_objects,
|
||||||
load_collection_childrens, load_collection_objects,
|
load_collection_childrens, load_collection_objects,
|
||||||
resolve_collection_dependencies)
|
resolve_collection_dependencies)
|
||||||
from .bl_datablock import BlDatablock
|
from .bl_datablock import BlDatablock
|
||||||
|
from .bl_file import get_filepath
|
||||||
from .dump_anything import Dumper, Loader
|
from .dump_anything import Dumper, Loader
|
||||||
from ..utils import flush_history
|
|
||||||
|
|
||||||
RENDER_SETTINGS = [
|
RENDER_SETTINGS = [
|
||||||
'dither_intensity',
|
'dither_intensity',
|
||||||
@ -266,10 +268,104 @@ VIEW_SETTINGS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
# TODO: Support filepath updates
|
||||||
|
loader.exclure_filter = ['filepath', 'sound', 'filenames','fps']
|
||||||
|
loader.load(sequence, sequence_data)
|
||||||
|
sequence.select = False
|
||||||
|
|
||||||
|
|
||||||
class BlScene(BlDatablock):
|
class BlScene(BlDatablock):
|
||||||
bl_id = "scenes"
|
bl_id = "scenes"
|
||||||
@ -284,6 +380,7 @@ class BlScene(BlDatablock):
|
|||||||
def _construct(self, data):
|
def _construct(self, data):
|
||||||
instance = bpy.data.scenes.new(data["name"])
|
instance = bpy.data.scenes.new(data["name"])
|
||||||
instance.uuid = self.uuid
|
instance.uuid = self.uuid
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def _load_implementation(self, data, target):
|
def _load_implementation(self, data, target):
|
||||||
@ -325,6 +422,25 @@ class BlScene(BlDatablock):
|
|||||||
'view_settings']['curve_mapping']['black_level']
|
'view_settings']['curve_mapping']['black_level']
|
||||||
target.view_settings.curve_mapping.update()
|
target.view_settings.curve_mapping.update()
|
||||||
|
|
||||||
|
# Sequencer
|
||||||
|
sequences = data.get('sequences')
|
||||||
|
|
||||||
|
if sequences:
|
||||||
|
# Create sequencer data
|
||||||
|
target.sequence_editor_create()
|
||||||
|
vse = target.sequence_editor
|
||||||
|
|
||||||
|
# Clear removed sequences
|
||||||
|
for seq in vse.sequences_all:
|
||||||
|
if seq.name not in sequences:
|
||||||
|
vse.sequences.remove(seq)
|
||||||
|
# Load existing sequences
|
||||||
|
for seq_name, seq_data in sequences.items():
|
||||||
|
load_sequence(seq_data, vse)
|
||||||
|
# If the sequence is no longer used, clear it
|
||||||
|
elif target.sequence_editor and not sequences:
|
||||||
|
target.sequence_editor_clear()
|
||||||
|
|
||||||
# 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()
|
||||||
@ -387,10 +503,14 @@ class BlScene(BlDatablock):
|
|||||||
data['view_settings']['curve_mapping']['curves'] = scene_dumper.dump(
|
data['view_settings']['curve_mapping']['curves'] = scene_dumper.dump(
|
||||||
instance.view_settings.curve_mapping.curves)
|
instance.view_settings.curve_mapping.curves)
|
||||||
|
|
||||||
if instance.sequence_editor:
|
# Sequence
|
||||||
data['has_sequence'] = True
|
vse = instance.sequence_editor
|
||||||
else:
|
if vse:
|
||||||
data['has_sequence'] = False
|
dumped_sequences = {}
|
||||||
|
for seq in vse.sequences_all:
|
||||||
|
dumped_sequences[seq.name] = dump_sequence(seq)
|
||||||
|
data['sequences'] = dumped_sequences
|
||||||
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -409,9 +529,18 @@ class BlScene(BlDatablock):
|
|||||||
deps.append(self.instance.grease_pencil)
|
deps.append(self.instance.grease_pencil)
|
||||||
|
|
||||||
# Sequences
|
# Sequences
|
||||||
# deps.extend(list(self.instance.sequence_editor.sequences_all))
|
vse = self.instance.sequence_editor
|
||||||
if self.instance.sequence_editor:
|
if vse:
|
||||||
deps.append(self.instance.sequence_editor)
|
for sequence in vse.sequences_all:
|
||||||
|
if sequence.type == 'MOVIE' and sequence.filepath:
|
||||||
|
deps.append(Path(bpy.path.abspath(sequence.filepath)))
|
||||||
|
elif sequence.type == 'SOUND' and sequence.sound:
|
||||||
|
deps.append(sequence.sound)
|
||||||
|
elif sequence.type == 'IMAGE':
|
||||||
|
for elem in sequence.elements:
|
||||||
|
sequence.append(
|
||||||
|
Path(bpy.path.abspath(sequence.directory),
|
||||||
|
elem.filename))
|
||||||
|
|
||||||
return deps
|
return deps
|
||||||
|
|
||||||
|
@ -1,198 +0,0 @@
|
|||||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
# ##### END GPL LICENSE BLOCK #####
|
|
||||||
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
import 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'
|
|
||||||
bl_reload_parent = False
|
|
||||||
|
|
||||||
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, construct = True):
|
|
||||||
scene = bpy.data.scenes.get(self.data['name'], None)
|
|
||||||
if scene:
|
|
||||||
if scene.sequence_editor is None and construct:
|
|
||||||
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
|
|
@ -465,6 +465,7 @@ class Loader:
|
|||||||
self.type_subset = self.match_subset_all
|
self.type_subset = self.match_subset_all
|
||||||
self.occlude_read_only = False
|
self.occlude_read_only = False
|
||||||
self.order = ['*']
|
self.order = ['*']
|
||||||
|
self.exclure_filter = []
|
||||||
|
|
||||||
def load(self, dst_data, src_dumped_data):
|
def load(self, dst_data, src_dumped_data):
|
||||||
self._load_any(
|
self._load_any(
|
||||||
@ -475,7 +476,8 @@ class Loader:
|
|||||||
|
|
||||||
def _load_any(self, any, dump):
|
def _load_any(self, any, dump):
|
||||||
for filter_function, load_function in self.type_subset:
|
for filter_function, load_function in self.type_subset:
|
||||||
if filter_function(any):
|
if filter_function(any) and \
|
||||||
|
any.sub_element_name not in self.exclure_filter:
|
||||||
load_function(any, dump)
|
load_function(any, dump)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user