Support (animated) strip opacity when batch-rendering
+56 −9 · 8 files changed
-
.gitignore+1 −0 -
CHANGELOG.md+4 −0 -
README.md+1 −1 -
spa_sequencer/__init__.py+1 −1 -
spa_sequencer/editorial/ops.py+0 −2 -
spa_sequencer/editorial/ui.py+0 −1 -
spa_sequencer/render/tasks.py+49 −3 -
spa_sequencer/sync/core.py+0 −1
@@ -9,6 +9,7 @@ __pycache__ *$py.classs .venv +venv*/ # datas or personal files /build @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +## [1.0.1] - 2023-3-08 + +- Added support for strip opacity in the batch render. Now values and keyframed animation on opacity strips is transferred during batch render. + ## [1.0.0] - 2023-01-09 Initial release. @@ -52,5 +52,5 @@ As per section 7 of the GPLv3, the license is supplemented with the following ad ## Authors -The SPA Studios pipeline team (2021-2023): +The SPA Studios pipeline team (2021-2023): Antoine Boellinger ([@aboellinger](https://github.com/aboellinger)), Bryan Fordney ([@bryab](https://github.com/bryab)), Falk David ([@falkdavid](https://github.com/falkdavid)), Mickael Villain ([@micka-06](https://github.com/micka-06)), Nick Alberelli ([@NickTiny](https://github.com/NickTiny)), Paolo Fazio ([@PaoloFazio](https://github.com/PaoloFazio)), Yann Lanthony ([@yann-lty](https://github.com/yann-lty)). @@ -18,7 +18,7 @@ bl_info = { "author": "The SPA Studios", "description": "Toolset to improve the sequence workflow in Blender.", "blender": (3, 3, 0), - "version": (1, 0, 0), + "version": (1, 0, 1), "location": "", "warning": "", "category": "SPA", @@ -203,14 +203,12 @@ class SEQUENCER_OT_edit_conform_shots_from_editorial(bpy.types.Operator): return context.window_manager.invoke_props_dialog(self) def execute(self, context: bpy.types.Context): - seq_editor = context.scene.sequence_editor regex = re.compile(self.shot_id_regex) shot_naming = ShotNaming() for strip in seq_editor.sequences: - if self.ref_channel != 0 and strip.channel != self.ref_channel: continue @@ -23,7 +23,6 @@ class SEQUENCER_MT_editorial(bpy.types.Menu): bl_label = "Editorial" def draw(self, context): - if HAS_OTIO: self.layout.menu("SEQUENCER_MT_edit_io") @@ -82,7 +82,6 @@ class BaseTask: @dataclass class BaseRenderTask(BaseTask): - is_modal: bool = True handlers_registered: bool = False @@ -319,7 +318,6 @@ class StripRenderTask(BaseRenderTask): channel=scene_strip.channel + channel_offset, frame_start=scene_strip.frame_final_start, ) - start_handle = frames_handles if frames_handles > 0 and render_options.renderer == "INTERNAL": # Internal render does not support negative frame rendering. @@ -338,6 +336,10 @@ class StripRenderTask(BaseRenderTask): (strip, scene_strip.scene.frame_start, scene_strip.scene.frame_end) ) + # Transfer properties from source to new strip + props = ("blend_alpha",) + _transfer_strip_properties(scene_strip, strip, props) + for strip, frame_start, frame_end in strips: # Re-assign the strip channel to force Blender to evalute overlaps strip.channel = strip.channel @@ -352,6 +354,51 @@ class StripRenderTask(BaseRenderTask): strip[STRIP_PROP_SOURCE_FRAME_END] = frame_end +def _transfer_strip_properties(src_strip, dst_strip, props): + src_scene = src_strip.id_data + dst_scene = dst_strip.id_data + + # First cover the common and simples case: no animation at all. + for prop in props: + setattr(dst_strip, prop, getattr(src_strip, prop)) + + # Then copy fcurves + src_paths = {prop: src_strip.path_from_id(prop) for prop in props} + for prop, src_path in src_paths.items(): + src_curve = _get_fcurve(src_scene, src_path) + if not src_curve: + continue + + # Touch destination curve + dst_path = dst_strip.path_from_id(prop) + dst_scene.keyframe_insert(dst_path) + dst_curve = _get_fcurve(dst_scene, dst_path) + + # Add back a copy of the points + src_points = src_curve.keyframe_points + dst_points = dst_curve.keyframe_points + dst_points.clear() + dst_points.add(len(src_points)) + for i, point in enumerate(src_points): + dst_points[i].co = point.co + + # TODO: Support the driver case ? + + +def _get_fcurve(block, path): + anim = block.animation_data + if not anim: + return + + action = anim.action + if not action: + return + + for curve in action.fcurves: + if curve.data_path == path: + return curve + + @dataclass class CopySoundStripsTask(BaseTask): src_scene: Optional[bpy.types.Scene] = None @@ -437,7 +484,6 @@ class CopySoundStripsTask(BaseTask): @dataclass class FitResolutionToContentTask(BaseTask): - scene: Optional[bpy.types.Scene] = None def run(self, context: bpy.types.Context, render_options: BatchRenderOptions): @@ -456,7 +456,6 @@ def sync_system_update(context: bpy.types.Context, force: bool = False): # Strip at target time is valid but differs from current master strip: if new_strip != strip: - # Discard changes coming from outside the strip range. if sync_settings.last_strip_scene_frame_out_of_range: return