blender python官网文档译解之可能的问题


类别:3d应用   

发布时间:2019/10/17 17:32:19   更新时间:2020/07/31 19:49:08


本文官网地址:https://docs.blender.org/api/current/info_gotcha.html

本文基于Blender2.80。

提示:译解是指翻译和注解。【注解】是作者的一些个人解释,用【】符号括起来。

陷阱

【gotcha翻译做陷进,坑?大体是这个意思吧。】

本文档试图帮助您在可能麻烦的领域使用Blender API,并避免已知会导致不稳定的做法。

使用运算符

Blender的运算符是用户访问的工具,Python也可以访问它们,这非常有用,但是运算符有一些局限性,使它们难以编写脚本。

主要限制是…

* 无法传递要操作的对象,网格或材料等数据(运算符改用上下文)

* 调用操作符的返回值表示成功(如果完成或被取消),在某些情况下,从API角度来看,返回操作结果更合乎逻辑。

* 如果API函数会引发异常并提供确切原因的详细信息,则运算符的轮询功能可能会失败。

为什么运算符的轮训失败?

当调用运算符时时出现如下错误:

>>> bpy.ops.action.clean(threshold=0.001)
RuntimeError: Operator bpy.ops.action.clean.poll() failed, context is incorrect

这里raise了错误,正确上下文是什么?

通常,运算符会检查活动区域类型,可以对其进行操作的选择对象或活动对象,但是某些运算符在运行时会更加挑剔。

在大多数情况下,您只需弄清楚运算符在Blender中的使用方式并思考其作用,即可弄清楚运算符需要什么背景。

不幸的是,如果您仍然陷于困境– 真正知道发生了什么的唯一方法是读取poll函数的源代码并查看其检查内容。

对于Python运算符而言,找到它并不难,因为它已包含在Blender中,并且源文件/行包含在运算符参考文档中。

下载和搜索C代码不是那么简单,特别是如果您不熟悉C语言,但是通过搜索运算符名称或描述,您应该能够在不了解C的情况下找到poll函数。

注意

Blender确实具有用于轮询功能的功能,以描述其失败的原因,但是,当前使用的并不多。如果您有兴趣帮助改进我们的API,可以随时将调用CTX_wm_operator_poll_msg_set添加到轮询失败原因不明显的地方。

>>> bpy.ops.gpencil.draw()
RuntimeError: Operator bpy.ops.gpencil.draw.poll() Failed to find Grease Pencil data to draw into

运算符仍然无法工作!

Blender中的某些运算符仅用于特定的上下文,例如,某些运算符仅在属性窗口中调用,它们会检查当前材质,修改器或约束。

例如:

另一个可能性是,您是第一个尝试在脚本中使用此运算符的人,并且如果该运算符在逻辑上应该能够运行但从某个目录访问时失败,则您需要对该运算符进行一些修改才能在不同的上下文中运行。脚本,应将其报告给错误跟踪器。

过时数据

设定值后没有更新

有时您想从Python修改值并立即访问更新的值,例如:

更改对象bpy.types.Object.location后,您可能想在bpy.types.Object.matrix_world之后直接访问其转换,但这并不符合您的预期。

考虑可能要用于计算对象的最终转换的计算,其中包括:

* 动画功能曲线。

* 驱动程序及其Python表达式。

* 约束

* 父对象及其所有f曲线,约束等。

为了避免每次修改属性时进行昂贵的重新计算,Blender会推迟进行实际计算,直到需要它们为止。

但是,在脚本运行时,您可能需要访问更新的值。在这种情况下,您需要bpy.types.ViewLayer.update在修改值后调用,例如:

bpy.context.object.location = 1, 2, 3
bpy.context.view_layer.update()

现在,所有相关数据(子对象,修饰符,驱动程序等)都已重新计算,并且可用于活动视图层中的脚本。

我可以在脚本执行期间重画吗?

对此的官方答案是“否”,或者…… 您不想这样做”

为该主题提供一些背景知识...

当脚本执行时,Blender等待其完成并有效锁定,直到完成为止,而在这种状态下,Blender不会重绘或响应用户输入。通常这不是一个问题,因为使用Blender分发的脚本往往不会长时间运行,但是脚本可能需要花费一些时间才能执行,并且很高兴看到视图端口中发生的事情。

强烈不建议将Blender锁定在循环中并重新绘制的工具,因为它们与Blender的能力冲突,后者无法一次运行多个运算符并在工具运行时更新界面的不同部分。

因此,这里的解决方案是编写一个模态运算符,即-一个定义modal()函数的运算符,请参见文本编辑器中的模态运算符模板。

模态运算符根据用户输入执行或设置自己的计时器以使其频繁运行,它们可以处理事件或通过键映射或其他模态运算符进行处理。

变换,绘画,飞行模式和文件选择是模式运算符的示例。

编写模态运算符比重for画一个简单的循环要花更多的精力,但是它更灵活并且可以更好地与Blenders设计集成。

好的好的!我仍然想借鉴Python

如果您坚持认为,这是有可能的,但是使用此hack的脚本不会被认为包含在Blender中,使用它的任何问题也不会被视为bug,因此也不保证在将来的版本中也可以使用。

bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)

模式和网状网接入

使用网格数据时,您可能会遇到脚本无法在编辑模式下按预期运行的问题。这是由于具有自己的数据的编辑模式导致的,该数据仅在退出编辑模式时才写回到网格中。

一个常见的示例是,导出器可以通过obj.data(a bpy.types.Mesh)访问网格,但是用户处于编辑模式,在该模式下,网格数据可用,但与编辑网格不同步。

在这种情况下,您可以…

* 运行该工具之前,请退出编辑模式。

* 通过调用显式更新网格bmesh.types.BMesh.to_mesh

* 修改脚本以支持直接处理编辑模式数据,请参见:bmesh.from_edit_mesh

* 将上下文报告为不正确,并且仅允许脚本在编辑模式之外运行。

N-Gons和曲面细分

由于2.63开始支持NGons,因此增加了一些复杂性,因为在某些情况下您仍需要访问三角形(例如某些导出程序)。

现在有3种方法可以访问人脸:

* bpy.types.MeshPolygon-这是现在以对象模式存储人脸的数据结构(访问方式为mesh.polygons而不是mesh.faces)。

* bpy.types.MeshLoopTriangle-将多边形细分为三角形的结果(访问为mesh.loop_triangles)。

* bmesh.types.BMFace -在编辑模式下使用的多边形。

出于以下文档的目的,它们分别称为多边形,环形三角形和bmesh-faces。

5个以上的侧面称为ngons。

支持概述

用法

bpy.types.MeshPolygon

bpy.types.MeshTessFace

bmesh.types.BMFace

导入/创建

差(不灵活)

良好(支持作为升级路径)

最好

操纵

差(不灵活)

差(失去ngons)

最好

出口/输出

良好(ngon支持)

好(不能使用ngons)

好(ngons,额外的内存开销)

注意

使用bmeshAPI是与bpy完全分开的API,通常,您会根据所需的编辑级别使用一个或另一个,而不只是简单地以其他方式访问面部。

创建

所有3种数据类型均可用于创建人脸。

* 多边形是创建面的最有效方法,但是数据结构非常僵硬且不灵活,您必须准备好所有顶点和面并立即创建它们。每个多边形不存储自己的顶点,而是引用bpy.types.Mesh.loops同样也是固定数组的索引和大小,这使情况更加复杂。

* bmesh-faces最有可能是新脚本创建面的最简单方法,因为可以逐个添加面,并且api具有旨在进行网格处理的功能。虽然bmesh.types.BMesh使用更多的内存,但一次只能在一个网格上进行操作即可对其进行管理。

编辑

编辑是3种数据类型变化最大的地方。

* 多边形在编辑,更改材质和选项(如平滑作品)方面非常有限,但对于其他任何方面,它们都过于僵化,仅用于存储。

* 不应将Tessfaces用于编辑几何图形,因为这样做会导致现有的ngon被细分。

* 到目前为止,BMesh-Faces是操纵几何的最佳方法。

导出

所有3种数据类型均可用于导出,选择主要取决于目标格式是否支持ngons。

* 多边形是最直接,最有效的导出方式,只要它们足够容易地转换为输出格式即可。

* Tessfaces可以很好地导出为不支持ngons的格式,实际上,这是唯一鼓励使用它们的地方。

* BMesh-Faces也可以用于导出,但是如果可以使用多边形,则可能不是必需的,因为使用bmesh会带来一些开销,因为它不是对象模式下的本机存储格式。

EditBones,PoseBones,Bone……骨骼

Blender中的骨架骨骼具有三个包含它们的不同数据结构。如果要通过其中之一来访问骨骼,则可能无法访问您真正需要的属性。

注意

在以下示例bpy.context.object中,假定为电枢对象。

编辑骨骼

bpy.context.object.data.edit_bones包含一个编辑框;要访问它们,必须先将电枢模式设置为编辑模式(在对象或姿势模式下不存在编辑框)。使用这些来创建新的骨骼,设置其头/尾或滚动,将其育儿关系更改为其他骨骼等。

bpy.types.EditBone在电枢编辑模式下使用的示例:

这仅在编辑模式下可用。

>>> bpy.context.object.data.edit_bones["Bone"].head = Vector((1.0, 2.0, 3.0))

在editmode之外,该字段为空。

>>> mybones = bpy.context.selected_editable_bones

仅在编辑模式下返回一个Editbone。

>>> bpy.context.active_bone

骨骼(对象模式)

bpy.context.object.data.bones包含骨头。这些在物体模式,并有可以改变各种属性,注意头部和尾部的属性是只读的。

bpy.types.Bone在对象或姿势模式下使用的示例:

返回编辑模式之外的骨骼(不是编辑骨)

>>> bpy.context.active_bone

就像blender一样,此设置可以在任何模式下进行编辑

>>> bpy.context.object.data.bones["Bone"].use_deform = True

可访问但只读

>>> tail = myobj.data.bones["Bone"].tail

姿势骨骼

bpy.context.object.pose.bones包含姿势骨骼。这是动画数据所在的位置,即,可动画转换被应用于姿势骨骼,约束和ik设置也是如此。

bpy.types.PoseBone在对象或姿势模式下使用的示例:

# Gets the name of the first constraint (if it exists)
bpy.context.object.pose.bones["Bone"].constraints[0].name

 
# Gets the last selected pose bone (pose mode only)
bpy.context.active_pose_bone

注意

注意,姿势是从对象而不是对象数据访问的,这就是为什么搅拌器可以让2个或更多对象在不同姿势中共享相同的电枢。

注意

严格来说,PoseBone不是骨骼,它们只是电枢的状态,存储在中,bpy.types.Object而不是中bpy.types.Armature,但是可以从姿势骨骼访问真实的骨骼-bpy.types.PoseBone.bone

电枢模式切换

在编写处理电枢的脚本时,您可能会发现必须在模式之间进行切换,但在退出编辑模式时要小心,不要保留对编辑骨骼或其头部/尾部矢量的引用。进一步访问这些文件将使Blender崩溃,因此脚本很重要的一点是,脚本必须清楚地区分以不同模式运行的代码部分。

这主要是editmode的问题,因为可以在不处于姿势模式的情况下操作姿势数据,但是对于运算符访问,您可能仍需要进入姿势模式。

数据名称

命名限制

一个常见的错误是假定为新创建的数据指定了请求的名称。

当您添加一些数据(通常是导入的)然后稍后按名称引用时,这可能会导致错误。

bpy.data.meshes.new(name=meshid)

 
# normally some code, function calls...
bpy.data.meshes[meshid]

或使用名称分配…

obj.name = objname

 
# normally some code, function calls...
obj = bpy.data.meshes[objname]

如果数据名称超过最大长度,已经使用或为空字符串,则它们可能与分配的值不匹配。

更好的做法是根本不按名称引用对象,创建后就可以将数据存储在列表,字典,类等中,几乎没有理由必须按名称搜索相同的数据。

如果您确实需要使用名称引用,则最好使用字典来维护导入资产的名称和新创建的数据之间的映射,这样就不会冒从blend文件中引用现有数据的风险,或更糟糕的是修改它。

# typically declared in the main body of the function.
mesh_name_mapping = {}

 
mesh = bpy.data.meshes.new(name=meshid)
mesh_name_mapping[meshid] = mesh

 
# normally some code, or function calls...

 
# use own dictionary rather than bpy.data
mesh = mesh_name_mapping[meshid]

库名冲突

Blender使数据名称保持唯一- bpy.types.ID.name因此,您不能偶然地为两个对象,网格,场景等命名相同的东西。

但是,当从另一个blend文件链接库数据时,可能会发生命名冲突,因此最好避免完全按名称引用数据。

有时这可能很棘手,在某些情况下(甚至在选择修改器对象时,例如,您不能在多个具有相同名称的对象之间进行选择),搅拌器甚至无法正确地处理此问题,但是仍然可以尝试避免该区域出现问题。

如果需要在本地数据和库数据之间进行选择,则bpy.data成员中具有一项功能可允许这样做。

# typical name lookup, could be local or library.
obj = bpy.data.objects["my_obj"]

 
# library object name look up using a pair
# where the second argument is the library path matching bpy.types.Library.filepath
obj = bpy.data.objects["my_obj", "//my_lib.blend"]

 
# local object name look up using a pair
# where the second argument excludes library data from being returned.
obj = bpy.data.objects["my_obj", None]

 
# both the examples above also works for 'get'
obj = bpy.data.objects.get(("my_obj", None))

相对文件路径

Blenders的相对文件路径与标准Python模块(例如sys和)不兼容os。

内置的Python函数无法理解Blenders //前缀,后者表示Blend文件的路径。

遇到此问题的常见情况是在导出带有关联图像路径的材料时。

>>> bpy.path.abspath(image.filepath)

使用链接的库中的Blender数据时,由于路径将相对于库而不是相对于打开的blend文件,因此会带来麻烦。当数据块可能来自外部blend文件时,请从中传递库参数bpy.types.ID

>>> bpy.path.abspath(image.filepath, library=image.library)

这些返回可以与本机Python模块一起使用的绝对路径。

Unicode问题

Python支持许多不同的编码,因此没有什么可以阻止您使用latin1或编写脚本iso-8859-15。

参见pep-0263

但是,这对于Blender的Python API来说很复杂,因为.blend文件没有明确的编码。

为了避免Python的集成和脚本作者,我们决定在blend文件中的所有字符串的问题 必须是UTF-8,ASCII兼容。

例如,这意味着将不同编码的字符串分配给对象名称将引发错误。

路径是该规则的例外,因为我们不能忽略UTF-8用户文件系统上非路径的存在。

这意味着看似无害的表达式可能会引发错误,例如。

>>> print(bpy.data.filepath)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 10-21: ordinal not in range(128)
>>> bpy.context.object.name = bpy.data.filepath
Traceback (most recent call last):
  File "<blender_console>", line 1, in <module>
TypeError: bpy_struct: item.attr= val: Object.name expected a string type, not str

以下是解决文件系统编码问题的两种方法:

>>> print(repr(bpy.data.filepath))
>>> import os
>>> filepath_bytes = os.fsencode(bpy.data.filepath)
>>> filepath_utf8 = filepath_bytes.decode('utf-8', "replace")
>>> bpy.context.object.name = filepath_utf8

Unicode编码/解码是全面的Python文档中的一个大主题,以避免陷入编码问题的深处-以下是一些建议:

* 始终使用utf-8编码或在输入未知的情况下转换为utf-8。

* 避免直接将文件路径作为字符串处理,而应使用os.path函数。

* 在路径上操作时,请使用os.fsencode()/ os.fsdecode()而不是内置的字符串解码功能。

* 要打印路径或将其包含在用户界面中,repr(path)请先使用或使用字符串"%r" % path格式。

注意

有时,最好使用字节而不是Python字符串来避免字符串编码问题,在读取某些输入时,将其读取为二进制数据的麻烦较小,尽管您仍然需要决定如何处理想要与Blender一起使用的任何字符串,一些进口商做这个。

使用'threading'模块的奇怪错误

只有在脚本完成之前完成线程处理后,使用Blender进行的Python线程处理才能正常工作。通过使用threading.join()例如。

这是Blender支持的线程示例:

import threading
import time

 
def prod():
    print(threading.current_thread().name, "Starting")

 
    # do something vaguely useful
    import bpy
    from mathutils import Vector
    from random import random

 
    prod_vec = Vector((random() - 0.5, random() - 0.5, random() - 0.5))
    print("Prodding", prod_vec)
    bpy.data.objects["Cube"].location += prod_vec
    time.sleep(random() + 1.0)
    # finish

 
    print(threading.current_thread().name, "Exiting")

 
threads = [threading.Thread(name="Prod %d" % i, target=prod) for i in range(10)]

 

 
print("Starting threads...")

 
for t in threads:
    t.start()

 
print("Waiting for threads to finish...")

 
for t in threads:
    t.join()

这是一个计时器的示例,该计时器每秒运行多次,并在Blender运行时连续移动默认多维数据集(不受支持)

def func():
    print("Running...")
    import bpy
    bpy.data.objects['Cube'].location.x += 0.05

 
def my_timer():
    from threading import Timer
    t = Timer(0.1, my_timer)
    t.start()
    func()

 
my_timer()

像上面这样的用例在脚本完成后仍使线程运行,这似乎可以工作一段时间,但最终会导致Blender自己的绘图代码出现随机崩溃或错误。

到目前为止,还没有任何工作可以确保Blender的Python集成线程安全,因此,在得到其适当支持之前,最好不要使用此功能。

注意

Python线程仅允许并发运行,而不会在多处理器系统上加速脚本,subprocessand multiprocess模块可以与Blender一起使用,也可以使用多个CPU。

请求帮助!我的脚本使Blender崩溃

TL; DR在修改数据的容器时,和/或在某些撤消/重做可能发生时(例如,在模态运算符执行期间),请勿保留对Blender数据(任何类型)的直接引用。取而代之的是使用索引(或其他始终按值存储在Python中的数据,如字符串键等),这些索引使您可以访问所需的数据。

理想情况下,不可能从Python崩溃Blender,但是API可能会导致崩溃。

严格来说,这是API中的错误,但要修复它,则意味着在每次访问时都要添加内存验证,因为大多数崩溃是由直接引用Blenders内存的Python对象引起的,无论何时释放或重新分配内存,Python都可以对其进行进一步的访问使脚本崩溃。但是解决此问题将使脚本运行非常缓慢,或者编写了一种非常不同的API,该API无法直接引用内存。

这里有一些一般性提示,以避免遇到这些问题。

* 请注意内存限制,尤其是在处理大型列表时,因为Blender可能会因内存不足而崩溃。

* 许多难以修复的崩溃最终都是由于引用释放的数据而导致的,删除数据时请确保不要保留对其的任何引用。

* 重新分配可能导致相同的问题(例如,如果向某个Collection中添加了很多项目,则可能导致重新分配基础容器的内存,从而使以前对现有项目的所有引用无效)。

* 使用Blender时保持活动状态的模块或类不应保留对用户可能删除的数据的引用,而应在每次激活脚本时从上下文中获取数据。

* 崩溃不一定每次都发生,在某些配置/操作系统上可能更多。

* 提防递归模式,这些模式在隐藏此处描述的问题方面非常有效。

* 有关某些已知的异常例外,请参阅有关不幸的极端案例的最后一个小节。

注意

要查找崩溃的脚本行,可以使用该faulthandler模块。请参阅faulthandler文档

虽然崩溃可能发生在Blenders C / C ++代码中,但是这可以帮助跟踪导致崩溃的脚本区域。

注意

某些容器修改实际上是安全的,因为它们永远不会重新分配现有数据(例如,链表容器在添加或删除其他项目时永远不会重新分配现有项目)。

但是知道哪些情况是安全的,哪些不是安全的,则意味着对Blender内部的深刻理解。这就是为什么,除非您愿意深入研究RNA C的实现,否则总是容易地假设,以任何可能的方式修改数据容器时,数据引用将变得无效。

不要这样做:

class TestItems(bpy.types.PropertyGroup):
    name: bpy.props.StringProperty()

 
bpy.utils.register_class(TestItems)
bpy.types.Scene.test_items = bpy.props.CollectionProperty(type=TestItems)

 
first_item = bpy.context.scene.test_items.add()
for i in range(100):
    bpy.context.scene.test_items.add()

 
# This is likely to crash, as internal code may re-allocate
# the whole container (the collection) memory at some point.
first_item.name = "foobar"

这样做:

class TestItems(bpy.types.PropertyGroup):
    name: bpy.props.StringProperty()

 
bpy.utils.register_class(TestItems)
bpy.types.Scene.test_items = bpy.props.CollectionProperty(type=TestItems)

 
first_item = bpy.context.scene.test_items.add()
for i in range(100):
    bpy.context.scene.test_items.add()

 
# This is safe, we are getting again desired data *after*
# all modifications to its container are done.
first_item = bpy.context.scene.test_items[0]
first_item.name = "foobar"

撤销/重做

撤消会使所有bpy.types.ID实例(对象,场景,网格,灯等)无效。

本示例说明如何告诉撤消更改存储位置。

>>> hash(bpy.context.object)
-9223372036849950810
>>> hash(bpy.context.object)
-9223372036849950810

#…移动活动对象,然后撤消

>>> hash(bpy.context.object)
-9223372036849951740

如上所述,当用户交互使用Blender时,简单地不保留对数据的引用是确保脚本不会变得不稳定的唯一方法。

撤消和库数据

Blenders库链接系统的优点之一是,撤消可以跳过检查库数据中的更改,因为它被认为是静态的。

Blender中的工具不允许修改库数据。

但是,Python没有强制执行此限制。

在某些情况下,例如使用脚本调整材料值,这可能很有用。但是也可以使用脚本使库数据指向新创建的本地数据,这不受支持,因为调用undo会删除本地数据,但使库引用它,并且很可能崩溃。

因此,最好考虑将库数据修改为API的高级用法,并且仅在知道自己在做什么时才使用它。

编辑模式/内存访问

切换编辑模式bpy.ops.object.mode_set(mode='EDIT')/ bpy.ops.object.mode_set(mode='OBJECT') 将重新分配对象数据,切换编辑模式后将无法访问对网格顶点/多边形/ uvs,骨架骨骼,曲线点等的任何引用。

只能重新访问对自身数据的引用,以下示例将崩溃。

mesh = bpy.context.active_object.data
polygons = mesh.polygons
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='OBJECT')

 
# this will crash
print(polygons)

因此,在切换编辑模式后,您需要重新访问任何对象数据变量,以下示例显示了如何避免上述崩溃。

mesh = bpy.context.active_object.data
polygons = mesh.polygons
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='OBJECT')

 
# polygons have been re-allocated
polygons = mesh.polygons
print(polygons)

对于重新分配对象数据但在切换编辑模式时最常见的任何功能,都可能发生此类问题。

阵列重新分配

当向曲线添加新点或向网格添加顶点/边/多边形时,将在内部重新分配存储此数据的数组。

bpy.ops.curve.primitive_bezier_curve_add()
point = bpy.context.object.data.splines[0].bezier_points[0]
bpy.context.object.data.splines[0].bezier_points.add()

 
# this will crash!
point.co = 1.0, 2.0, 3.0

可以通过在添加新变量后重新分配点变量或通过将索引存储到点而不是点本身来避免这种情况。

最好的方法是避开问题,将所有点一次性全部添加到曲线上。这意味着您不必担心数组的重新分配及其更快的速度,因为为添加的每个点重新分配整个数组效率很低。

删除数据

您删除的所有数据之后均不得修改或访问,包括f曲线,驱动程序,渲染层,时间轴标记,修改器,约束以及对象,场景,集合,骨骼等。

该remove()API调用将无效,他们释放,以防止常见的错误的数据。

以下示例显示了此预防措施的工作原理。

mesh = bpy.data.meshes.new(name="MyMesh")
# normally the script would use the mesh here...
bpy.data.meshes.remove(mesh)
print(mesh.name)  # <- give an exception rather than crashing:

 
# ReferenceError: StructRNA of type Mesh has been removed

但是要小心,因为这仅限于脚本访问被删除的变量,下一个示例仍然会崩溃。

mesh = bpy.data.meshes.new(name="MyMesh")
vertices = mesh.vertices
bpy.data.meshes.remove(mesh)
print(vertices)  # <- this may crash

不幸的极端情况

除了上面列出的所有预期情况外,还有一些其他问题不应该成为问题,但是由于内部实施的细节,当前还有:

* Object.hide_viewport,Object.hide_select以及Object.hide_render:设置这些布尔值中的任何一个都会触发Collection缓存的重建,从而中断上的任何当前迭代Collection.all_objects。

sys.exit 

一些Python模块会sys.exit()在发生错误时调用自己,而这不是常见的行为,因此需要提防,因为它看起来好像Blender崩溃了,因为sys.exit()它将立即关闭Blender。

例如,argparse如果参数无效,模块将显示错误并退出。

解决此问题的一种不太好的方法是设置sys.exit = None并查看退出了哪些Python代码行,您当然可以替换sys.exit为自己的函数,但是以这种方式操作Python是不好的做法。


本文网址:https://www.pyfield.com/blog/?id=36