Python 脚本

Python 脚本模式提供完全可编程的线条样式。在这种控制模式下,所有样式操作符都以 Python 脚本的形式编写,在 Freestyle 术语中称为样式模块。样式模块的输入是视图(即一组检测到的特征边),输出是一组样式化的笔画。

样式模块由五个基本运算符的连续调用组成:选择、链接、拆分、排序和笔画创建。选择运算符根据一个或多个用户定义的选择条件(谓词)标识输入要素边的子集。所选边将使用链接、分割和排序运算符进行处理,以构建特征边链。这些运算符还由用户提供的谓词和函数控制,以确定如何将特征边转换为链。最后,笔画创建运算符将链转换为风格化的描边,该运算符采用用户定义的描边着色器列表。

Python样式模块作为文本数据块存储在混合文档中。外部样式模块文档首先需要在文本编辑器中加载。然后,样式模块堆栈条目中的选择菜单允许您从加载的样式模块列表中选择一个模块。

../../_images/render_freestyle_python_scripting-mode.png

在文本编辑器中加载的样式模块 cartoon.py 的屏幕截图(左图),以及视图层按钮(右)中 Python 脚本模式中的 Freestyle 选项。

Blender的 Freestyle 附带了许多 Python 风格的模块,可以作为你自己风格模块写作的起点。另请参阅 Blender Python API 参考手册中的 Freestyle 部分,以获取样式模块构造的完整详细信息。

../../_images/render_freestyle_python_scripting-mode-example-1.jpg

使用 Python 脚本模式,由 T.K. 提供(blend 文件, CC0)。

../../_images/render_freestyle_python_scripting-mode-example-2.png

使用 Python 脚本模式,由 T.K. 提供(blend 文件, CC0)。

写作风格模块

样式模块是一段代码,负责 Freestyle 线条绘制的风格化。样式模块的输入是一组称为视图映射的特征边。输出是一组样式化的线条,也称为笔画。样式模块的结构为操作管道,允许从视图映射内的输入边构建笔画。

有五种类型的操作(与相应的运算符函数一起列出):

  • 选择 Operators.select()

  • 链形 Operators.chain(), Operators.bidirectional_chain()

  • 拆分 Operators.sequential_split(), Operators.recursive_split()

  • 排序 Operators.sort()

  • 笔画创建 Operators.create()

输入视图映射由一组视图边缘对象填充。选择操作用于根据用户定义的选择条件(谓词)选取艺术家感兴趣的ViewEdges。链接操作采用ViewEdges的子集,并通过根据用户定义的谓词和函数连接ViewEdges来构建链。可以通过将它们分成更小的碎片(例如,在边缘发生急转弯的点)并选择其中的一小部分(例如,仅保留那些长度超过长度阈值的点)来进一步细化链。排序操作用于排列链的堆叠顺序,以在另一条线的顶部绘制一条线。通过描边创建操作(将一系列描边着色器应用于各个链),链最终转换为风格化的描边。

视图边缘、链和笔触通常称为一维(1D)元素。1D 元素是一条折线,它是一系列连接的直线。一维元素的顶点通常称为 0D 元素。

输入视图映射由一组视图边缘对象填充。选择操作用于根据用户定义的选择条件(谓词)选取艺术家感兴趣的ViewEdges。链接操作采用ViewEdges的子集,并通过根据用户定义的谓词和函数连接ViewEdges来构建链。可以通过将它们分成更小的碎片(例如,在边缘发生急转弯的点)并选择其中的一小部分(例如,仅保留那些长度超过长度阈值的点)来进一步细化链。排序操作用于排列链的堆叠顺序,以在另一条线的顶部绘制一条线。通过描边创建操作(将一系列描边着色器应用于各个链),链最终转换为风格化的描边。

选择

选择运算符遍历活动集的每个元素,并仅保留满足特定谓词的元素。Operators.select() 方法将一元谓词作为参数,该谓词适用于表示 1D 元素的任何 Interface1D。例如:

Operators.select(QuantitativeInvisibilityUP1D(0))

此选择操作使用 QuantitativeInvisibilityUP1D 谓词仅选择可见的 ``ViewEdge``(更准确地说,定量不可见度等于 0 的那些)。选择运算符旨在有选择地将样式应用于活动 1D 元素的一小部分。

需要注意的是,QuantitativeInvisibilityUP1D 是实现测试行可见性的谓词的类,并且 Operators.select() 方法将谓词类的实例作为参数。给定 1D 元素的谓词测试实际上是通过调用谓词实例来完成的,即通过调用谓词类的 __call__ 方法。换句话说,Operators.select() 方法将函子作为参数,而函子又将 Interface0D 对象作为参数。Freestyle Python API 广泛使用函子来实现谓词和函数。

链形

链接运算符作用于活动 ViewEdge 对象集,并确定未来笔画的拓扑结构。我们的想法是实现一个迭代器,通过沿视图边缘行进来遍历视图映射图形。迭代器定义了一个链接规则,该规则确定在给定顶点处要跟随的下一个 ViewEdge(请参见 ViewEdgeIterator)。几个这样的迭代器作为 Freestyle Python API 的一部分提供(参见 ChainPredicateIteratorChainSilhouetteIterator)。自定义迭代器可以通过继承 ViewEdgeIterator 类来定义。链接运算符还将在 Interface1D 上工作的一元谓词作为参数作为停止条件。当迭代器在沿图行进期间达到满足此谓词的 ViewEdge 时,链接将停止。

Chaining can be either unidirectional Operators.chain() or bidirectional Operators.bidirectional_chain(). In the latter case, the chaining will propagate in the two directions from the starting edge.

下面是双向链接的代码示例:

Operators.bidirectional_chain(
        ChainSilhouetteIterator(),
        NotUP1D(QuantitativeInvisibilityUP1D(0)),
        )

链接运算符使用 ChainSilhouetteIterator 作为链接规则,并在迭代器到达不可见的 ViewEdge 后立即停止链接。

The chaining operators process the set of active ViewEdge objects in order. The active ViewEdges can be previously sorted using the Operators.sort() method (see below). It starts a chain with the first ViewEdge of the active set. All ViewEdges that have already been involved in the chaining process are marked (in the case of the example above, the time stamp of each ViewEdge is modified by default), in order not to process the same ViewEdge twice. Once the chaining reaches a ViewEdge that satisfies the stopping predicate, the chain is terminated. Then a new chain is started from the first unmarked ViewEdge in the active set. This operation is repeated until the last unmarked ViewEdge of the active set was processed. At the end of the chaining operation, the active set is set to the Chains that have just been constructed.

拆分

拆分操作用于优化每个链的拓扑。拆分按顺序或递归执行。顺序拆分基本形式的 Operators.sequentialSplit(),以给定的任意分辨率解析链,并在链的每个点上计算一元谓词(在 0D 元素上工作)。每次满足谓词时,链就会被拆分为两个链。在顺序分割操作结束时,活动链集将设置为新链。:

Operators.sequentialSplit(TrueUP0D(), 2)

In this example, the chain is split every 2 units. A more elaborated version uses two predicates instead of one: One to determine the starting point of the new chain and the other to determine its ending point. This second version can lead to a set of Chains that are disjoint or that overlap if the two predicates are different (see Operators.sequentialSplit() for more details).

递归拆分 Operators.recursiveSplit() 以给定的分辨率计算沿链的 0D 元素上的函数,并找到给出函数最大值的点。然后,链在这一点上被分成两部分。此过程在两个新链中的每一个上递归重复,直到输入链满足用户指定的停止条件。:

func = Curvature2DAngleF0D()
Operators.recursive_split(func, NotUP1D(HigherLengthUP1D(5)), 5)

在上面的代码示例中,链在最高 2D 曲率的点处递归拆分。在链上的点以 5 个单位的分辨率评估曲率。短于5个单位的链条将不再分裂。

排序

排序运算符 Operators.sort() 排列活动 1D 元素的堆叠顺序。它将用作 "小于" 运算符的二元谓词作为参数,对两个 1D 元素进行排序。:

Operators.sort(Length2DBP1D())

在此代码示例中,排序使用 Length2DBP1D 二进制谓词按 2D 长度的升序对 Interface1D 对象进行排序。

当与因果密度相结合时,排序特别有用。实际上,因果密度在修改生成的图像时会评估其密度。如果我们希望使用这样的工具来决定在局部密度太高时删除笔画,那幺控制描边的绘制顺序非常重要。在这种情况下,我们将使用排序运算符来确保首先绘制最 "重要" 的线条。

笔画创建

最后,笔画创建运算符 Operators.create() 将活动的链集作为输入并构建笔画。运算符采用两个参数。第一个是一元谓词,适用于 Interface1D,旨在对链集进行最后选择。不满足条件的链条不会导致中风。第二个输入是着色器列表,这些着色器将负责每个构建的描边的着色。:

shaders_list = [
    SamplingShader(5.0),
    ConstantThicknessShader(2),
    ConstantColorShader(0.2,0.2,0.2,1),
    ]
Operators.create(DensityUP1D(8,0.1, IntegrationType.MEAN), shaders_list)

在此示例中,DensityUP1D 谓词用于移除平均密度高于 0.1 的所有链。每个链通过重新采样转换为笔画,以便每 5 个单位有一个点,并为其分配 2 个单位的恒定厚度和深灰色恒定颜色。

管道定义上的用户控件

样式模块编写提供不同类型的用户控件,即使单个样式模块具有固定的管道结构。一个是不同管道控制结构的顺序,另一个是通过定义沿着管道作为参数传递的函子对象。

可以通过对选择、链接、拆分和排序操作进行排序来定义不同的管道控制结构。笔画创建始终是结束样式模块的最后一个操作。

谓词、函数、链接迭代器和描边着色器可以通过继承基类并重写适当的方法来定义。有关用户可编写脚本的构造的详细信息,请参阅以下基类的参考手册条目。

See also

谓词、函数、链接迭代器和描边着色器可以通过继承基类并重写适当的方法来定义。请参阅 Freestyle python 模块,了解有关用户可编写脚本的结构的更多信息。