You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python修改Nuke的Contact Sheet实现EXR与HUD合并?

解决Nuke中Contact Sheet的EXR+HUD叠加及节点布局问题

问题描述

我用Python脚本在Nuke里生成Contact Sheet,现有逻辑是把实体列表按30个一组拆分,给每个实体创建EXR的Read节点(路径为空时用Constant节点),再连到Contact Sheet。现在要优化成:每个实体同时创建EXR和HUD的Read节点,用Merge节点叠加后再连到Contact Sheet,但尝试时出现节点布局混乱、连接错误的问题,求解决实现流程和布局问题。

现有脚本代码:

def create_contact_sheet(self,entities, version_paths, first_frame,last_frame):
    movie_per_sheet = 30

    print ("no of movies per sheet:", movie_per_sheet)
    shot_list_splits = [entities[i:i + movie_per_sheet] for i in range(0, len(entities), movie_per_sheet)]
    print ("no of required sheets:", len(shot_list_splits))

    print("entities", entities)
    print("shot version split", version_paths)
    last_batch_rnode_pos = 0
    first_rnode_xpos = None
    r_node_xpos = None

    print('version map', version_paths)
    for sht_index, e_sht_list in enumerate(shot_list_splits):
        nodes_for_backdrop = []
        # create contact sheet:
        ctt_sheet = nuke.createNode("ContactSheet", "sheet_%s" %(sht_index + 1))
        print (">>> create ctt_sheet",ctt_sheet.name())
        nodes_for_backdrop.append(ctt_sheet)
        ctt_sheet["rows"].setValue(5)
        ctt_sheet["columns"].setValue(6)
        ctt_sheet["width"].setValue(5 * 1920)
        ctt_sheet["height"].setValue(6 * 1080)

        ctt_sheet.setSelected(False)

        for index, e_shot in enumerate(e_sht_list):
            exr_path = version_paths[e_shot][0]
            if exr_path == None:
                exr_r_node = nuke.createNode("Constant")
            else:
                exr_r_node = nuke.createNode("Read", "name %s file %s" % (e_shot, exr_path))
            nodes_for_backdrop.append(exr_r_node)
            r_node_xpos = last_batch_rnode_pos + index * 100
            exr_r_node["xpos"].setValue(r_node_xpos)
            exr_r_node["ypos"].setValue(0)
            exr_r_node['first'].setValue(first_frame)
            exr_r_node['last'].setValue(last_frame)
            ctt_sheet.setInput(index, exr_r_node)
            exr_r_node.setSelected(False)
            # store first node xpos to place ctt_sheet_xpos
            if index == 0:
                first_rnode_xpos = r_node_xpos

        # fix ctt_sheet_xpos
        ctt_sheet_xpos = (len(e_sht_list) * 100) / 2 + first_rnode_xpos
        ctt_sheet["xpos"].setValue(ctt_sheet_xpos)
        ctt_sheet["ypos"].setValue(300)

        last_batch_rnode_pos = r_node_xpos + 200
        self.create_back_drop(nodes_for_backdrop, ctt_sheet)

def create_back_drop(self, nodes, ctt_sheet):
    """
    make sure nodes were selected before execution this function
    """
    # select nodes
    for e_node in nodes:
        e_node.setSelected(True)

    node = nukescripts.autoBackdrop()
    node["label"].setValue(ctt_sheet.name())

    # de-select nodes
    for e_node in nodes:
        e_node.setSelected(False)

注:version_paths字典格式为{'实体名':[exr路径, hud路径]},部分路径可能为None。


解决方案

修改思路

  1. 为每个实体创建EXR节点、HUD节点,通过Merge节点(默认Over模式)叠加后再连接到Contact Sheet
  2. 对每组节点(EXR、HUD、Merge)做纵向分层布局,横向保持统一偏移,避免节点重叠
  3. 处理空路径情况:HUD路径为空时,直接将EXR节点作为Merge的输入,保证流程正常运行

修改后的完整代码

def create_contact_sheet(self, entities, version_paths, first_frame, last_frame):
    movie_per_sheet = 30

    print("no of movies per sheet:", movie_per_sheet)
    shot_list_splits = [entities[i:i + movie_per_sheet] for i in range(0, len(entities), movie_per_sheet)]
    print("no of required sheets:", len(shot_list_splits))

    print("entities", entities)
    print("shot version split", version_paths)
    last_batch_rnode_pos = 0
    first_rnode_xpos = None
    group_xpos = None

    print('version map', version_paths)
    for sht_index, e_sht_list in enumerate(shot_list_splits):
        nodes_for_backdrop = []
        # 创建Contact Sheet节点
        ctt_sheet = nuke.createNode("ContactSheet", f"sheet_{sht_index + 1}")
        print(f">>> create ctt_sheet {ctt_sheet.name()}")
        nodes_for_backdrop.append(ctt_sheet)
        ctt_sheet["rows"].setValue(5)
        ctt_sheet["columns"].setValue(6)
        ctt_sheet["width"].setValue(5 * 1920)
        ctt_sheet["height"].setValue(6 * 1080)
        ctt_sheet.setSelected(False)

        for index, e_shot in enumerate(e_sht_list):
            # 计算当前节点组的横向位置(扩大偏移量避免拥挤)
            group_xpos = last_batch_rnode_pos + index * 150
            # 1. 创建EXR节点
            exr_path = version_paths[e_shot][0]
            if exr_path is None:
                exr_r_node = nuke.createNode("Constant", f"name {e_shot}_EXR")
            else:
                exr_r_node = nuke.createNode("Read", f"name {e_shot}_EXR file {exr_path}")
            exr_r_node["xpos"].setValue(group_xpos)
            exr_r_node["ypos"].setValue(0)
            exr_r_node['first'].setValue(first_frame)
            exr_r_node['last'].setValue(last_frame)
            exr_r_node.setSelected(False)
            nodes_for_backdrop.append(exr_r_node)

            # 2. 创建HUD节点
            hud_path = version_paths[e_shot][1]
            hud_r_node = None
            if hud_path is not None:
                hud_r_node = nuke.createNode("Read", f"name {e_shot}_HUD file {hud_path}")
                hud_r_node["xpos"].setValue(group_xpos)
                hud_r_node["ypos"].setValue(100)
                hud_r_node['first'].setValue(first_frame)
                hud_r_node['last'].setValue(last_frame)
                hud_r_node.setSelected(False)
                nodes_for_backdrop.append(hud_r_node)

            # 3. 创建Merge节点并连接
            merge_node = nuke.createNode("Merge", f"name {e_shot}_Merge")
            merge_node["xpos"].setValue(group_xpos)
            merge_node["ypos"].setValue(200)
            merge_node.setSelected(False)
            nodes_for_backdrop.append(merge_node)

            # 连接Merge节点:A接EXR,B接HUD(HUD存在时)
            merge_node.setInput(0, exr_r_node)
            if hud_r_node is not None:
                merge_node.setInput(1, hud_r_node)

            # 将Merge节点连接到Contact Sheet的对应输入口
            ctt_sheet.setInput(index, merge_node)

            # 记录第一个节点组的位置,用于定位Contact Sheet
            if index == 0:
                first_rnode_xpos = group_xpos

        # 调整Contact Sheet的位置,使其居中于节点组上方
        ctt_sheet_xpos = (len(e_sht_list) * 150) / 2 + first_rnode_xpos
        ctt_sheet["xpos"].setValue(ctt_sheet_xpos)
        ctt_sheet["ypos"].setValue(350)  # 下移一点避免和Merge重叠

        # 更新下一批节点的起始位置
        last_batch_rnode_pos = group_xpos + 200
        # 创建背景板
        self.create_back_drop(nodes_for_backdrop, ctt_sheet)

def create_back_drop(self, nodes, ctt_sheet):
    """
    为节点创建背景板,需确保节点被选中
    """
    # 选中所有节点
    for e_node in nodes:
        e_node.setSelected(True)

    node = nukescripts.autoBackdrop()
    node["label"].setValue(ctt_sheet.name())

    # 取消选中
    for e_node in nodes:
        e_node.setSelected(False)

关键修改说明

  • 节点布局:每个实体的节点组横向偏移index * 150(比原100大,避免拥挤),纵向分层:EXR在y=0,HUD在y=100,Merge在y=200,结构清晰不重叠
  • 连接逻辑:Merge节点的A输入固定接EXR,B输入接HUD(路径非空时),最终将Merge输出连到Contact Sheet的对应输入口,替换原有的EXR直接连接
  • 空路径处理:HUD路径为空时跳过HUD节点创建,Merge仅保留EXR输入,保证流程不中断
  • Contact Sheet定位:根据节点组总宽度重新计算居中位置,并下移y坐标避免和Merge节点重叠
  • 背景板范围:将HUD、Merge节点加入nodes_for_backdrop列表,确保所有相关节点都被包含在背景板内

内容的提问来源于stack exchange,提问作者Kartikey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 18:04:51