【Blender 开发】常用的若干简单 bpy Tips

938 阅读5分钟

Blender Python Tips

访问数据(bpy.data)

objects / meshes / scenes

image.png

  • 红框为 对象名
  • 蓝框为 网格名
import bpy

# 打印所有 对象名
for obj in bpy.data.objects:
    print(obj.name)

# 移除 网格名 叫 `Cube` 的网格
if "Cube" in bpy.data.meshes:
    mesh = bpy.data.meshes["Cube"]
    print("removing mesh", mesh)
    bpy.data.meshes.remove(mesh)
    
# 打印列表中所有 场景 名字
print(bpy.data.scenes.keys())

filepath / images

.blend 文件目录下新建一个 .txt,往其中录入项目中的 图像路径 和 尺寸 信息

import os
# 分离 blend 文件名 与 扩展名 ;默认返回 (fname, fextension) 元组,可做分片操作
with open(os.path.splitext(bpy.data.filepath)[0] + ".txt", 'w') as fs:
    for image in bpy.data.images:
        fs.write("%s %d x %d\n" % (image.filepath, image.size[0], image.size[1]))

按名称选择对象(bpy.ops)

object.select_all

def select(objName):
    # 清除其他选择 
    bpy.ops.object.select_all(action = 'DESELECT')
    # 将数据块的 'select' 属性设置为 True 
    bpy.data.objects[objName].select_set(True)

或者更高级的写法

def mySelector(objName, additive=False):  
    # 默认情况下,清除其他选择  
    if not additive:  
        bpy.ops.object.select_all(action='DESELECT')  
    # 将数据块的 'select' 属性设置为 True  
    bpy.data.objects[objName].select_set(True)

输出所选对象的属性(bpy.context)

selected_objects

遍历 bpy.context.selected_objects 输出 bpy.data.objects 数据块

比如输出 [ bpy.data.objects['Sphere'], bpy.data.objects['Circle'], bpy.data.objects['Cube'] ]

# 返回所选对象的名称
k.name for k in bpy.context.selected_objects

# 返回所选对象的位置
k.location for k in bpy.context.selected_objects

变换选择的对象(bpy.ops)

平移、缩放、旋转(分别 x,y,z 轴)

transform

# Differential    
def translate(v):
    bpy.ops.transform.translate(value = v, constraint_axis = (True, True, True))

# Differential           
def scale(v):
    bpy.ops.transform.resize(value = v, constraint_axis = (True, True, True))

# Differential    
def rotate_x(v):
    bpy.ops.transform.rotate(value = v, constraint_axis = (1, 0, 0))

# Differential    
def rotate_y(v):
    bpy.ops.transform.rotate(value = v, constraint_axis = (0, 1, 0))

# Differential    
def rotate_z(v):
    bpy.ops.transform.rotate(value = v, constraint_axis = (0, 0, 1))  

object.transform_apply

# 将对象的变换应用于其数据
def transform_apply():
        bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)

按名称激活对象(bpy.context)

scene.objects.active (已过时)

def activate(objName):
    bpy.context.scene.objects.active = bpy.data.objects[objName]

由于在任何给定时间 只有一个 对象是激活的,因此激活函数要简单得多。我们将 bpy.data.objects 数据块传递给在激活时处理内部数据的场景属性。因为 Blender 只允许 单个 对象处于活动状态,我们可以对 bpy.context.scene 进行单个赋值,并允许 Blender 的内部引擎对其他对象的取消激活进行排序。

访问激活对象(bpy.context)

object / active_object

# 返回 bpy.data.objects 数据块
bpy.context.object  

相较上面一行的较长些的同义词

bpy.context.active_object

访问 数据块 的 名称位置 属性

bpy.context.object.name
bpy.context.object.location

重命名对象(bpy.context)

重命名激活对象

def rename(objName):
   bpy.context.object.name = objName

删除对象(bpy.ops)

object.delete

根据名称删除对象

def delete(objName):
    select(objName)
    bpy.ops.object.delete(use_global=False)

object.select_all

删除所有对象

def delete_all():
    if( len(bpy.data.objects) != 0 ):
        bpy.ops.object.select_all(action = 'SELECT')
        bpy.ops.object.delete(use_global=False)

设置对象的交互模式(bpy.ops)

object.mode_set

设置对象的交互模式(OBJECT/EDIT

函数用于进入 编辑模式 而不选择任何顶点,或进入 对象模式 而没有任何其他的处理

def mode(mode_name):
    bpy.ops.object.mode_set(mode = mode_name)
    if mode_name == "EDIT":
        bpy.ops.mesh.select_all(action="DESELECT")

网格编辑模式下选择 点、线、面 模式(bpy.ops)

mesh.select_mode

选择模式: VERTEDGEFACE

bpy.ops.mesh.select_mode(type = type)  

网格的全选/取消全选(bpy.ops)

mesh.select_all

全选

def select_all():
    bpy.ops.mesh.select_all(action="SELECT")  

取消全选

def deselect_all():
    bpy.ops.mesh.select_all(action="DESELECT")

网格挤出(bpy.ops)

mesh.extrude_region_move

挤出区域并移动结果

def extrude(v):
    bpy.ops.mesh.extrude_region_move(TRANSFORM_OT_translate = 
          {"value":v,
           "constraint_axis": (True, True, True),
           "constraint_orientation":'NORMAL'}) # 沿法线方向

网格细分(bpy.ops)

mesh.subdivide

对所选边进行细分

def subdivide(cuts = 1):
    bpy.ops.mesh.subdivide(number_cuts = cuts)

读取网格 UV 数据的简单示例代码

loops / uv_layers

注意 网格环(Mesh Loops)通常比顶点多很多。除非是一个原始的未分割平面的情况

import bpy

# access mesh data:
obj = bpy.context.active_object
mesh_data = obj.data
mesh_loops = mesh_data.loops
uv_index = 0

# iterate teh mesh loops:
for lp in mesh_loops:
    # access uv loop:
    uv_loop = mesh_data.uv_layers[uv_index].data[lp.index]
    uv_coords = uv_loop.uv
    
    # 得到了 顶点索引 和对应 uv 坐标
    print('vert: {}, U: {}, V: {}'.format(lp.vertex_index, uv_coords[0], uv_coords[1]))

网格转换(bpy.types)

bpy.types.Mesh -> bmesh.types.BMesh

从当前激活的网格返回一个 BMesh,当前网格 必须 已经处于 编辑 模式。

def register_bmesh():
   return bmesh.from_edit_mesh(bpy.context.object.data)

类似的变换是

  • bmesh.types.BMesh.from_mesh: 从现有的 网格数据块 中初始化这个 bmesh 。
  • bmesh.types.BMesh.from_object: 从现有的 对象数据块 初始化这个 bmesh (目前只支持网格)。

PS: 常常与之搭配的是 bmesh.types.BMVertSeq/bmesh.types.BMEdgeSeq/bmesh.types.BMFaceSeqensure_lookup_table() 调用。

官方文档中说,该函数的作用是 确保 int 订阅所需的内部数据和 verts /edges/faces 一起初始化,例如 bm.vert[index]。在此序列 添加/删除数据 之后需要再次调用。

或者在使用此序列前进行调用,如

def select_vert(bm, i):
    bm.verts.ensure_lookup_table()
    bm.verts[i].select = True
 
def select_edge(bm, e):
    bm.edges.ensure_lookup_table()
    bm.edges[e].select = True

def select_face(bm, f):
    bm.faces.ensure_lookup_table()
    bm.faces[f].select = True

我在某本电子书中看到作者声称 —— “鼓励对 ensure_lookup_table() 的大量调用。我们使用这些函数来提醒 Blender 保持 bmesh 对象的某些部分在操作之间 不被垃圾回收。这些函数只占用最小的性能成本,因此我们可以随意调用它们,而不会产生太多后果。”

读取网格动画顶点数据的简单示例(bpy.context)

evaluated_depsgraph_get

注意,访问模型的 动画顶点位置 需要 每帧 读取模型的 评估(变形)网格状态

因此,使用模型的 更新依赖关系图 depgraph ,每一帧都会初始化一个新的 bmesh 对象。

import bpy
import bmesh

obj = bpy.context.active_object
frames = range(0,10)
# get the object's evaluated dependency graph:
depgraph = bpy.context.evaluated_depsgraph_get()
# iterate animation frames:
for f in frames:
    bpy.context.scene.frame_set(f)
    
    # define new bmesh object:
    bm = bmesh.new()
    bm.verts.ensure_lookup_table()
    # read the evaluated mesh data into the bmesh object:
    bm.from_object( obj, depgraph )
    
    # iterate the bmesh verts:
    for i, v in enumerate(bm.verts):
        print("frame: {}, vert: {}, location: {}".format(f, i, v.co))

访问网格的三角形

calc_loop_triangle / loop_triangles / vertices

默认情况下,Blender 中的 Python 无法 访问网格三角形。 当需要访问网格三角形时,它们 必须 首先使用 calc_loop_triangle 网格对象方法进行计算(细分三角形)。

该函数的作用是:从四边形或 NGons 计算三角形细分。

在调用 calc_loop_triangle 方法之前,Mesh 对象的 loop_triangle 属性将引用一个空集合(collection)。 在调用 calc_loop_triangle 方法之后,loop_triangle 属性将引用一个 MeshLoopTriangle 对象集合,其中 vertices 属性将保存一个由 3 个整数组成的数组,它们是三角形顶点的索引。

下面的示例脚本创建并放置球体对象在立方体三角形的中心:

import bpy
mesh = bpy.data.objects['Cube'].data
mesh.calc_loop_triangles()
for tri in mesh.loop_triangles:
     tri_center = (mesh.vertices[tri.vertices[0]].co * 0.333) +\
                  (mesh.vertices[tri.vertices[1]].co * 0.333) +\
                  (mesh.vertices[tri.vertices[2]].co * 0.333)
     bpy.ops.mesh.primitive_uv_sphere_add(radius=0.1,
                                          enter_editmode=False, 
                                          location=tri_center)

image.png

将一个不可选择的对象设置为本地,使其可以被选中

make_local

如果你碰巧链接了一个在原始 .blend 文件中不可选择的外部对象,你将无法通过交互方式使其再次可选择,或者本地可选择,甚至从当前 .blend 文件中删除它。

好消息是,这可以通过一个非常简短的 Python 脚本轻松完成:

import bpy

obj = bpy.data.objects['the_untouchable_object']
obj.make_local()