diff --git a/multi_user/handlers.py b/multi_user/handlers.py index d6f936e..c319c56 100644 --- a/multi_user/handlers.py +++ b/multi_user/handlers.py @@ -130,14 +130,29 @@ def load_pre_handler(dummy): if session and session.state in [STATE_ACTIVE, STATE_SYNCING]: bpy.ops.session.stop() - @persistent def update_client_frame(scene): + setting = bpy.context.window_manager.session + if setting.replay_mode == 'TIMELINE' and \ + setting.replay_files and \ + scene.active_replay_file != setting.replay_frame_current : + index = bpy.context.scene.active_replay_file + bpy.ops.session.load(filepath=bpy.context.window_manager.session.replay_files[index].name, + draw_users=True, + replay=True) + setting.replay_frame_current = index + if session and session.state == STATE_ACTIVE: porcelain.update_user_metadata(session.repository, { 'frame_current': scene.frame_current }) +@persistent +def post_frame_update(scene): + if bpy.context.window_manager.session.replay_mode == 'TIMELINE' and \ + not bpy.context.scene.animation_data: + bpy.context.scene.animation_data_create() + bpy.context.scene.animation_data.action = bpy.data.actions.get('replay_action') def register(): bpy.app.handlers.undo_post.append(resolve_deps_graph) diff --git a/multi_user/preferences.py b/multi_user/preferences.py index dde880f..4ac14cc 100644 --- a/multi_user/preferences.py +++ b/multi_user/preferences.py @@ -115,15 +115,60 @@ def set_active_replay(self, value): else: self.active_replay_file = value - # bpy.ops.session.load( ) - bpy.ops.session.load( - filepath=bpy.context.window_manager.session.replay_files[value].name, - draw_users=True) - + if bpy.context.window_manager.session.replay_mode == 'MANUAL': + bpy.ops.session.load( + filepath=bpy.context.window_manager.session.replay_files[value].name, + draw_users=True, + replay=True) def get_active_replay(self): return self.get('active_replay_file', 0) + +def set_replay_persistent_collection(self, value): + if hasattr(self, 'replay_persistent_collection'): + self["replay_persistent_collection"] = value + else: + self.replay_persistent_collection = value + + collection = bpy.data.collections.get("multiuser_timelapse", None) + + if collection is None and value: + collection = bpy.data.collections.new('multiuser_timelapse') + bpy.context.scene.collection.children.link(collection) + elif collection and not value: + for o in collection.objects: + bpy.data.objects.remove(o) + bpy.data.collections.remove(collection) + +def get_replay_persistent_collection(self): + return self.get('replay_persistent_collection', False) + +def set_replay_interval(self, value): + if hasattr(self, 'replay_interval'): + self["replay_interval"] = value + else: + self.replay_interval = value + + # Update the animation fcurve + replay_action = bpy.data.actions.get('replay_action') + replay_fcurve = None + + for fcurve in replay_action.fcurves: + if fcurve.data_path == 'active_replay_file': + replay_fcurve = fcurve + + if replay_fcurve: + for p in reversed(replay_fcurve.keyframe_points): + replay_fcurve.keyframe_points.remove(p, fast=True) + + intrv = bpy.context.window_manager.session.replay_interval + for frame in range(0, len(bpy.context.window_manager.session.replay_files)): + replay_fcurve.keyframe_points.insert((frame * intrv), frame) + +def get_replay_interval(self): + return self.get('replay_interval', 10) + def get_log_level(self): return logging.getLogger().level @@ -715,14 +760,28 @@ class SessionProps(bpy.types.PropertyGroup): name='File paths', type=bpy.types.OperatorFileListElement ) - active_replay_file: bpy.props.IntProperty( - name="active_replay_file", - default=0, - min=0, - description='Active snapshot', - set=set_active_replay, - get=get_active_replay, - options={'ANIMATABLE'} + replay_persistent_collection: bpy.props.BoolProperty( + name="replay_persistent_collection", + description='Enable a collection that persist accross frames loading', + get=get_replay_persistent_collection, + set=set_replay_persistent_collection, + ) + replay_mode: bpy.props.EnumProperty( + name='replay method', + description='Replay in keyframe (timeline) or manually', + items={ + ('TIMELINE', 'TIMELINE', 'Replay from the timeline.'), + ('MANUAL', 'MANUAL', 'Replay manually, from the replay frame widget.')}, + default='TIMELINE') + replay_interval: bpy.props.IntProperty( + name='replay interval', + default=10, + min=1, + set=set_replay_interval, + get=get_replay_interval, + ) + replay_frame_current: bpy.props.IntProperty( + name='replay_frame_current', ) diff --git a/multi_user/ui.py b/multi_user/ui.py index de19844..a78ad3f 100644 --- a/multi_user/ui.py +++ b/multi_user/ui.py @@ -581,16 +581,22 @@ class SESSION_PT_replay(bpy.types.Panel): def draw(self, context): layout = self.layout - + settings = context.window_manager.session row= layout.row() - - row.prop(bpy.context.scene, 'active_replay_file') + row.prop(settings,'replay_mode', toggle=True, expand=True) + row= layout.row() + if settings.replay_mode == 'MANUAL': + row.prop(bpy.context.scene, 'active_replay_file', text="Snapshot index") + else: + row.prop(settings, 'replay_interval', text="interval (frame)") + row= layout.row() + row.prop(settings, 'replay_persistent_collection', text="persistent collection", toggle=True, icon='OUTLINER_COLLECTION') class SESSION_PT_repository(bpy.types.Panel): bl_idname = "MULTIUSER_PROPERTIES_PT_panel" bl_label = "Repository" - bl_space_type = 'VIEW_3D' + bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_parent_id = 'MULTIUSER_SETTINGS_PT_panel' bl_options = {'DEFAULT_CLOSED'} diff --git a/multi_user/utils.py b/multi_user/utils.py index e64cf4b..b46c29e 100644 --- a/multi_user/utils.py +++ b/multi_user/utils.py @@ -38,15 +38,6 @@ from replication.constants import (STATE_ACTIVE, STATE_AUTH, STATE_LOBBY, CONNECTING) -CLEARED_DATABLOCKS = ['actions', 'armatures', 'cache_files', 'cameras', - 'collections', 'curves', 'filepath', 'fonts', - 'grease_pencils', 'images', 'lattices', 'libraries', - 'lightprobes', 'lights', 'linestyles', 'masks', - 'materials', 'meshes', 'metaballs', 'movieclips', - 'node_groups', 'objects', 'paint_curves', 'particles', - 'scenes', 'shape_keys', 'sounds', 'speakers', 'texts', - 'textures', 'volumes', 'worlds'] - def find_from_attr(attr_name, attr_value, list): for item in list: if getattr(item, attr_name, None) == attr_value: @@ -108,26 +99,6 @@ def get_state_str(state): return state_str -def clean_scene(): - for type_name in CLEARED_DATABLOCKS: - sub_collection_to_avoid = [ - bpy.data.linestyles.get('LineStyle'), - bpy.data.materials.get('Dots Stroke') - ] - - type_collection = getattr(bpy.data, type_name) - items_to_remove = [i for i in type_collection if i not in sub_collection_to_avoid] - for item in items_to_remove: - try: - type_collection.remove(item) - logging.info(item.name) - 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)]