一些实用的Python小技巧

分享点有意思的 Python 小技巧,帮助你将工作中的一部分交给计算机自动化运行,没有程序经验也能学会,不然就不叫小技巧了!

关于渲染和合成流程

举个栗子,我们用 Gooseberry 项目的尽头1.2.1为例,这是由 Hjalti 制作的一个片段一些实用的Python小技巧

感谢 Blender 的库和链接系统,这可以使得我在为场景设计灯光等工作的同时,不受 Hjalti 对场景或人物做任何修改后的影响,我只需要重新更新一下我的 blend 文件,或者重新打开一次,已经在 SVN 上提交后的最新文件就会自动在我的场景文件中更新了。不过如果你不知道什么是 SVN,我可以试着去之前的开源电影博客中找找类似的介绍文章,不过你也能试试搜索一下,网上有很多关于 SVN 的介绍。不过到后面等 Lukas 完成了新的资源管理系统开发,这种协同方式可能会有所调整,不过未来还未来,我们先看看现在可以做些什么吧。

当我们完成了高模场景、完整的光照设计、渲染需要的雾效或各种随机生成的石块等网格物体,还包含 Victor 和 Franck 的动作文件,他们在被链接至合成 .comp 文件之后,属性都将被锁定起来,仅能在源文件中编辑修改,但是当我们在测试渲染时,需要临时改动大量的属性,就可能需要对各个物体进行单独调整。我的意思是,这一步你可以手工完成,当然,也可以靠脚本来批量化执行,很明显这是脚本最能体现其价值和优势的地方啦!

有了脚本,以下这些工作只需要动动鼠标按键就可以了:

  • 开启/关闭各类设置中的属性开关
  • 为各类设置中的数值属性重新赋值
  • 隐藏一些不需要显示的粒子系统,或者修改他们的粒子树和随机因子(这些修改绝对不会影响源文件的设置)
  • 重写 Victor 的一些关键属性,例如将他的头变大两倍!
  • 删除掉导入至视频编辑器中的一些无用参考视频(未来的 BAM 系统将不会调用这些未使用到的片段,不过现在不行)
  • 开启并添加一些渲染印章信息,包括采样数、渲染时间和渲染农场的节点名称等,统统都打印出来
  • 其实还有很多啦!

好吧,现在开始正题。

让 Python 能自动运行

我接下来会在Blender 的文本编辑器中新建两个文件,分别如下(Shift + F11):

  1. settings.py – 这个脚本将用于修改渲染设置、用户参数,以及场景或视图中可优化的属性,单击菜单上的 “Run Script”即可运行。
  2. startup.py – 这个脚本将用于修改粒子系统的显示,以及一些渲染印章的信息,这个脚本在每次打开 .blend 文件后将自动运行(主要用于渲染农场),所以这个脚本必须开启注册选项,即 ‘Register’ 需要被选中

settings.py 可以命名为任意名称,因为他是一个自行执行的文件,但 startup.py 必须以 .py 结尾,否则将无法被执行注册执行(实际上,只有当文件以 .py 结尾, “Register” 选项才会显示为可开启的状态,否则为灰色)。

文章最后你可以下载到完整的脚本。


这里再加一个小提示,那就是 Pablo 的 Amaranth 脚本是一个相当不错的生产优化插件,点击这里查看器完整的功能列表。我最喜欢的其中一个功能,就是场景的调试选项,可以帮你找到丢失的贴图等。Amaranth 可以由你自行编译,也可以在 Blender 的自动编译版本中下载。


好了,接下来我们就分别看看这两个脚本是如何编写的。

settings.py:

1. 属性的定义申明

我喜欢在脚本的前面将一些我常用的参数做一个定义并赋值,这样在脚本中直接调用这些变量即可,而不是满屏幕的到处找这些属性然后分别修改参数。脚本的注释以 # 开头,所以随便写什么都不会印象代码的运行。

专业的脚本都会有一些注释规范,不过这篇文章不是写给程序员的,所以大家就随意吧

  ____                      _                          
 / ___| ___   ___  ___  ___| |__   ___ _ __ _ __ _   _ 
| |  _ / _  / _ / __|/ _  '_  / _  '__| '__| | | |
| |_| | (_) | (_) __   __/ |_) |  __/ |  | |  | |_| |
 ____|___/ ___/|___/___|_.__/ ___|_|  |_|   __, |
                                                 |___/
##############################################################
#
#                                                                     SETTINGS
Samples = 50
Depth_of_field = 0.006

开头的符号文字可以在右侧链接中生成: ASCII 生成器


2. 导入 Blender Python 并定义当前场景

脚本开头要这么写就行了:

##############################################################
#
#                                                                        SCRIPT
import bpy
Scene = bpy.context.scene

3. 开启简化选项,设置细分数为0,然后开启 Amaranth 的非简化渲染

我们在本文中涉及到的大部分代码和方法都可以在 Blender 的界面上找到,你只需要将鼠标移动到某个参数上面,就会看到下图中得类似提示,虽然你看到的也许根本就不是最后实际调用的方法,但至少可以给你一些编写提示:

一些实用的Python小技巧

接着单击 Shift + F4 进入 Python 控制台,然后敲入:bpy.data.sce… 并单击组合键 CTRL + Space 来获取代码自动提示,再按一次进入下一个提示。如果要清除当前输入,可以单击 Shift + Enter。我们可以试试 bpy.data.scenes[‘Scene’].render.use_simplify 然后再按回车,控制台就会根据当前系统设置显示出对应的结果,如果我们希望对参数做出修改,可以先单击方向上键调出上一次输入的代码,然后在后面跟上 = True” (或者是 1),回车后看看面板上参数的变化吧!

看到了吧,已经被脚本修改了,漂亮!

一些实用的Python小技巧

这样,我们就已经大致找到了用脚本控制参数的方法,剩下的事情就简单了,你只需要按照类似方式书写即可:

Scene.render.use_simplify = True
Scene.render.simplify_subdivision = 0
Scene.use_unsimplify_render = True

完成编辑后,单击 ‘Run Script’ 按钮即可运行当前脚本,而且是批量执行的哦!

学会了吧,我们继续。


4. 切换渲染为全屏,并且锁定界面和后期处理计算

一些实用的Python小技巧

# Render Display Mode:
Scene.render.display_mode = 'SCREEN'
Scene.render.use_lock_interface = True
# Uncheck Post Processing:
Scene.render.use_compositing = False
Scene.render.use_sequencer = False

你可以看到场景的命名 ‘SCREEN’ 其实是你可以手动在页面布局菜单上设置的,当然也可以使用脚本来批量统一重命名,使用下面的命名可以查看当前激活场景的名称:

一些实用的Python小技巧


5. 摄像机的参数修改

变量 Depth_of_field 在脚本的最开头已经做了赋值,所以这里直接调用即可。

# Camera settings:
Scene.camera.data.cycles.aperture_size = Depth_of_field
Scene.camera.data.cycles.aperture_ratio = 2
Scene.camera.data.show_name = True

6. 根据帧来添加采样数

这里的代码是可以使用脚本来为渲染添加一个驱动,让每一帧的采样都可以不同(当然你可以手动为某一帧添加采样因子)。

注意:这里我们假定场景中没有其他的渲染驱动(场景等级和角色的骨骼驱动不算),所以我们使用的代码是默认从0开始 Scene.animation_data.drivers[0],如果你场景中有现成的驱动,那么就需要根据情况增加驱动树字,例如 Scene: [1], [2], 等等。

# Sampling seed to "#frame":
Scene.cycles.driver_add('seed')
Scene.animation_data.drivers[0].driver.expression = 'frame'

7. 图片格式设置

我经常使用 JPEG 来保存我的测试渲染,但是在发送到渲染农场 Flamenco之后,我希望渲染输出为 PNG 格式。

# Output to JPEG 100% (for easy test render saving):
Scene.render.image_settings.file_format = 'JPEG'
Scene.render.image_settings.quality = 100

8. 渲染印章设置

渲染的印章数据一般会被写入到图片的元数据中,所以我们其实并不需要开启渲染结果的印章显示(但印章参数依然需要被激活)。在另外一个 startup.py 脚本中,我们再定义这些参数的输出格式(例如渲染农场中的节点名称,或者是采样数等等)

如果你希望将这些参数打印出来,只需要激活 use_stamp 的值即可,参考阅读 《如何配置渲染图章的显示

# Disable burned-in stamp:
Scene.render.use_stamp = False
# Enable needed stamp values for metadata:
Scene.render.use_stamp_time = True
Scene.render.use_stamp_date = True
Scene.render.use_stamp_render_time = True
Scene.render.use_stamp_frame = True
Scene.render.use_stamp_camera = True
Scene.render.use_stamp_lens = True
Scene.render.use_stamp_filename = True
Scene.render.use_stamp_marker = True
Scene.render.use_stamp_note = True

9. 关闭预览范围和照明设置

这些设置对我们很有用,但未必适合你们,例如在 Gooseberry 中得洗衣房场景中,洗衣机上有大量的反射材质,这在室内看上去效果很棒,但是如果场景切换至海岛上,这些反光效果基本上不可能被注意到了,所以为了节约资源我们选择关掉。

# Use Preview Range OFF:
Scene.use_preview_range = False
# Sampling:
Scene.cycles.samples = Samples
Scene.cycles.sample_clamp_direct = 11
Scene.cycles.sample_clamp_indirect = 5
Scene.cycles.volume_step_size = 1
Scene.cycles.volume_max_steps = 1024
# Light Paths:
Scene.cycles.transparent_max_bounces = 8
Scene.cycles.transparent_min_bounces = 8
Scene.cycles.max_bounces = 2
Scene.cycles.min_bounces = 1
Scene.cycles.diffuse_bounces = 2
Scene.cycles.glossy_bounces = 2
Scene.cycles.transmission_bounces = 2
Scene.cycles.volume_bounces = 1
Scene.cycles.caustics_reflective = False
Scene.cycles.caustics_refractive = False
Scene.cycles.blur_glossy = 2

10. 毛发设置

这里通过调整 Cycles 的毛发细分参数来控制关联的毛发对象,并且设置是基于整个场景的,所以不会影响到导入的源文件本身。我们可以根据镜头来分别调整,让他们在近景时看上去更茂密,但是在远镜头的时候就适当减少细分等级,优化渲染时间。

# Hair settings:
Scene.cycles_curves['primitive'] = 2
Scene.cycles_curves.subdivisions = 4
Scene.cycles_curves['shape'] = 0

11. 最后,删掉视频编辑器上的这些片段

我说实话我也不知道为什么他们会出现在这里,因为一旦导入之后,就会自动加载到序列编辑器上,我们需要手动进入全选然后删除掉。这对于小文件来说没什么,但是我们需要将这些文件发送到渲染农场,那么系统会将这些文件也一起打包传上去,那么问题就大了,因为文件会增大很多,不过后面的 BAM 系统将优化这一块的功能。

如果你希望完成更多功能的脚本,那么我建议你可以到问答社区 Blender Stack Exchange 中看看,或者找找你周围有没有类似 Pablo 这样优秀脾气又好的程序员帮忙吧。

# Remove Sequencer Strips:
class SequencerStripSlayer(bpy.types.Operator):
    """Delete All Strips"""
    bl_idname = "sequencer.strip_slayer"
    bl_label = "Sequencer Strip Slayer"
    def execute(self, context):
        # Save which editor are we in when we run the operator
        where = bpy.context.area.type
        # Temporary switch to the Sequencer to get context data
        bpy.context.area.type = 'SEQUENCE_EDITOR'
        # Build the context to override
        override = {
            "window": bpy.context.window,
            "screen": bpy.context.screen,
            "scene": context.scene,
            "area": bpy.context.area,
            "region": bpy.context.area.regions[0],
            "blend_data": context.blend_data}
        # Check if we have sequences at all
        if context.scene.sequence_editor and context.scene.sequence_editor.sequences_all:
            i = 0
            # loop through all the strips
            for s in context.scene.sequence_editor.sequences_all:
                # select them!
                s.select = True
                # this is just to count them (for the report message)
                i += 1
            # DIE DIE DIE!
            bpy.ops.sequencer.delete(override)
            # Report, if we have more than 0
            if i != 0:
                self.report({"INFO"}, "BAM! {0} Strips Destroyed!".format(i))
            else:
                self.report({"INFO"}, "No strips to murder")
        else:
            self.report({"INFO"}, "No sequences")
        # Go back to the area we were before
        bpy.context.area.type = where
        return {'FINISHED'}

下面的代码将作为整个脚本的结尾,用于实现注册,如果你要加什么新功能,请务必加载以下代码之前。

def register():
    bpy.utils.register_class(SequencerStripSlayer)
def unregister():
    bpy.utils.unregister_class(SequencerStripSlayer)
if __name__ == "__main__":
    register()
# Now actually run the operator. By commenting this line below, the delete operator will not run, but it will be registered. So you can find it via Space bar search and use it when needed.
bpy.ops.sequencer.strip_slayer()

 

startup.py:

1. 脚本前的定义和申明

##############################################################
#
#                                                                      SETTINGS
# Define some names to the stamp note:
Animator = 'Hjalti'
Compositor = 'Manu'

这里的命名尽量不要和选项中的名称相同,否则你自己都可能分不出哪个是变量哪个是属性了。


2. 导入 Python 和接口

##############################################################
#
#                                                                          SCRIPT
import bpy
import socket

接口主要用于检测渲染结点的名称,方便后面打印到元数据中。


3. 修改导入的粒子系统

确定脚本中使用最佳参数的方法,就是在控制台中一边调,一边在视图中查看效果啦:

一些实用的Python小技巧

你可以看到,在这个例子中,我们刻意向你展示了,如果粒子系统没有做合理命名,那么在脚本调用中,查找对应的粒子是多么的痛苦。所以对于粒子的相关设置,务必要有一个良好的命名规范,包括粒子的修改器等。未来如果粒子的系统引擎得意优化之后,整个粒子的调用应该会更简单,不过这不是我说了算,还是先看看现在该怎么做吧。

这个范例中的代码并非通用,而是完全根据你的文件结构来实现,所以你也许还需要打开源文件来一一对比查找(在 Amaranth 中倒是十分的轻松,方法和之前 settings.py 中提到的类似。

# Particle systems:
bpy.data.objects['GEO-cliff_ground_high'].modifiers['ParticleSystem 3'].show_viewport = 1
bpy.data.particles['cliff_grass.001'].count = 12000
bpy.data.particles['cliff_grass.001'].hair_length = 200

以下代码是用于关闭某些粒子系统的渲染输出,以及修改一些粒子的随机因子参数(其实就是用来调整效果啦):

bpy.data.objects['GEO-cliff_ground_high'].modifiers['ParticleSystem 3'].show_render = 0
bpy.data.objects['GEO-cliff_ground_high'].particle_systems['island_plants_small_dead'].seed = 16

4. 渲染印章的元数据输出

下面你可以找到用于自定义输出渲染印章的元数据代码,它们在 Blender 运行的开始就将收集所需要的信息,例如渲染节点的名称。

另外,印章数据还可以包含采样数,摄像机的景深参数,Blender 的版本号以及谁在修改当前源文件(这个在脚本的开始已经做了定义)

当渲染启动之后,你就可以使用一个专用软件来查看完整信息,我用的是  XnView MP 以及 ExifTool ,我们的 gooseberry 项目团队使用的有 nomacs Image Lounge (Win/Mac/Linux),在其 2.4.5 的开发版本中,其已经可以完美支持 Blender 的 PNG 元数据查看啦!(点这里下载 Nomacs 2.4.5 .deb installer for a 64-bit Ubuntu based OS – 但请记住,这不是一个稳定的版本哦!)

一些实用的Python小技巧

你其实还可以用 Blender 的图片查看器来查看元数据。

# Stamp note script:
def stamp_set(scene):
    render = scene.render
    is_cycles = (render.engine == 'CYCLES')
    render.use_stamp_note = True
    render.stamp_note_text = (
        "Samples: {samples}  |  "
        "Aperture Radius: {aperture:.4f}  |  "
        "Blender {ver} {branch} {hash}  |  "
        "Animator: {anim}  |  "
        "Compositor: {comp}  |  "
        "Rendernode: {hostname}"
    ).format(
        samples=scene.cycles.samples if is_cycles else render.antialiasing_samples,
        aperture=scene.camera.data.cycles.aperture_size if is_cycles else "NONE",
        ver=bpy.app.version_string,
        branch=bpy.app.build_branch,
        hash=bpy.app.build_hash,
        hostname=socket.gethostname(),
        anim=Animator,
        comp=Compositor,
        )
bpy.app.handlers.render_pre.append(stamp_set)

5. 覆盖掉一些数据

下面的脚本将覆盖掉导入的某些参数,例如 Victor 的骨骼控制器。这里将演示如何切换 Victor的头发粒子系统开关,让其在载入时就直接关闭掉,不过其本身依然拥有之前开启的动画属性。

# Override keyed PARTICLES_TOGGLE value:
from bpy.app.handlers import persistent
@persistent
def load_handler(dummy):
 bpy.data.objects['victor_high_proxy.001'].pose.bones['properties']['PARTICLES_TOGGLE'] = 0
bpy.app.handlers.render_pre.append(load_handler)

还有,你可以静默掉一些动画通道,例如关闭摄像机的晃动曲线:

bpy.data.objects['camera'].animation_data.action.fcurves[0].mute = True
#(or False)

控制其他通道只需要修改数字下标即可。


如何调试

一些实用的Python小技巧


补充:

Pablo 新增加了一个控制 Franck 毛发反光效果的结点,当然这个效果是 Andy 做的,你现在可以试试用脚本来快速查看其渲染效果,当然,你现在调试的是一个完整的导入角色:

一些实用的Python小技巧

还有一个,Pablo 采用了一个很简单的小技巧,借助一个简单平面来屏蔽场景中的光照,如果大家觉得有用可以参考看看:

一些实用的Python小技巧


好了,完整脚本下载在这: settings.pystartup.py

希望你觉得有用!

原文地址