VSE: Add Compositor modifier

This patch adds a new Compositor modifier that applies a compositing
node group on a sequencer strip. This patch also introduces the concept
of a compositor node tree space subtype, where we now have a Scene and a
Sequencer subtypes. Practically, this just means that node like the
Render Layers node will not be available in the node editor in the
Sequencer subtype.

Future improvements includes:

- The compositor context is recreated on every modifier application,
  while it should ideally be persistent somehow to make use of the
  compositor static cache. This might require work from the compositor
  side by moving the static cache outside of the context and make it
  thread safe if needed. See `Render.compositor` for an example on
  persistent context.
- GPU execution is not supported. This just needs a GPU context to be
  bound before execution, but the tricky part is getting a GPU context.
  See `render::Compositor::execute` for an example on bounding a GPU
  context and why it is less straight forward.
- Node inputs are not exposed on the sequencer modifier interface. An
  approach similar to Geometry Nodes modifier could be used, look at
  `update_input_properties_from_node_tree` for reference, but notice
  that Geometry Nodes naturally exempt the main Geometry socket because
  Geometry inputs can't be exposed, but for the compositor, we will have
  to exempt the main Color and Mask sockets manually. !145971

Co-authored-by: Aras Pranckevicius <aras@nesnausk.org>
Co-authored-by: Falk David <falk@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/139634
This commit is contained in:
Omar Emara
2025-09-26 08:40:42 +02:00
committed by Omar Emara
parent 9c5fda87a5
commit 73fcbaf7c4
43 changed files with 793 additions and 48 deletions

View File

@@ -49,7 +49,8 @@ class NODE_MT_compositor_node_input_scene_base(node_add_menu.NodeMenu):
def draw(self, context):
layout = self.layout
self.node_operator(layout, "CompositorNodeRLayers")
if context.space_data.node_tree_sub_type == 'SCENE':
self.node_operator(layout, "CompositorNodeRLayers")
self.node_operator_with_outputs(context, layout, "CompositorNodeSceneTime", ["Frame", "Seconds"])
self.node_operator(layout, "CompositorNodeTime")
@@ -60,12 +61,12 @@ class NODE_MT_compositor_node_output_base(node_add_menu.NodeMenu):
bl_label = "Output"
def draw(self, context):
del context
layout = self.layout
self.node_operator(layout, "NodeGroupOutput")
self.node_operator(layout, "CompositorNodeViewer")
layout.separator()
self.node_operator(layout, "CompositorNodeOutputFile")
if context.space_data.node_tree_sub_type == 'SCENE':
layout.separator()
self.node_operator(layout, "CompositorNodeOutputFile")
self.draw_assets_for_catalog(layout, self.bl_label)

View File

@@ -145,13 +145,29 @@ class NODE_HT_header(Header):
layout.template_ID(id_from, "active_texture", new="texture.new")
elif snode.tree_type == 'CompositorNodeTree':
layout.prop(snode, "node_tree_sub_type", text="")
NODE_MT_editor_menus.draw_collapsible(context, layout)
layout.separator_spacer()
row = layout.row()
row.enabled = not snode.pin
row.template_ID(scene, "compositing_node_group", new="node.new_compositing_node_group")
if snode.node_tree_sub_type == 'SCENE':
row = layout.row()
row.enabled = not snode.pin
row.template_ID(scene, "compositing_node_group", new="node.new_compositing_node_group")
elif snode.node_tree_sub_type == 'SEQUENCER':
row = layout.row()
sequencer_scene = context.workspace.sequencer_scene
sequencer_editor = sequencer_scene.sequence_editor if sequencer_scene else None
active_strip = sequencer_editor.active_strip if sequencer_editor else None
active_modifier = active_strip.modifiers.active if active_strip else None
is_compositor_modifier_active = active_modifier and active_modifier.type == 'COMPOSITOR'
if is_compositor_modifier_active and not snode.pin:
row.template_ID(
active_modifier,
"node_group",
new="node.new_compositor_sequencer_node_group")
else:
row.enabled = False
row.template_ID(snode, "node_tree", new="node.new_compositor_sequencer_node_group")
elif snode.tree_type == 'GeometryNodeTree':
layout.prop(snode, "node_tree_sub_type", text="")
@@ -200,7 +216,7 @@ class NODE_HT_header(Header):
op.parent_tree_index = len(snode.path) - 2
# Backdrop
if is_compositor:
if is_compositor and snode.node_tree_sub_type == 'SCENE':
row = layout.row(align=True)
row.prop(snode, "show_backdrop", toggle=True)
row.active = snode.node_tree is not None

View File

@@ -1571,6 +1571,7 @@ class SEQUENCER_MT_modifier_add(Menu):
else:
self.operator_modifier_add(layout, 'BRIGHT_CONTRAST')
self.operator_modifier_add(layout, 'COLOR_BALANCE')
self.operator_modifier_add(layout, 'COMPOSITOR')
self.operator_modifier_add(layout, 'CURVES')
self.operator_modifier_add(layout, 'HUE_CORRECT')
self.operator_modifier_add(layout, 'MASK')