# blender编程

参考文章:

# 配置环境

blender本身提供一个简单IDE,但是输入切换有些别扭,还是使用Visual Studio Code比较好。这个时候就需要安装一些插件。

  1. 安装插件Blender Development和Blender Python Code Templates。

  2. 下载fake-bpy-module (opens new window)模块并安装,这样就有自动补全了。

    "blender.executables":[
        {
            "path": "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Blender",
            "name": "",
            "isDebug": false,
        }
    ],
    "python.autoComplete.extraPaths": [
        "D:\\ProjectFiles\\blender\\fake-bpy-module",
    ],
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

除此之外,blender自身需要开启Blender Preferences->Interface->Display:

  1. Developer Extras
  2. Python Tooltips

# 安装第三方库

肯定会有一些需求来装python的第三方库。此时需要:

  1. pip installation (opens new window)下载get-pip.py并运行该脚本:

    .\python.exe .\get-pip.py

  2. 去scripts目录下载第三方库:

    .\pip3.exe install opencv-contrib-python -i https://pypi.tuna.tsinghua.edu.cn/simple

  3. 在Blender里面引用第三方库:

    import cv2

# 坐标系

参考资料:

如果是2d,那么一般x为右方,y轴为上方。

如果是3d,那么有两种坐标系:

  • X轴向右,Y轴向上,Z轴向 由屏幕指向我们,叫做右手坐标系

  • X轴向右,Y轴向上,Z轴向 由我们指向屏幕,叫做左手坐标系

  • blender里面z为上,-y轴为前方,x轴为右方。为右手坐标系。

  • UE里面z为上,x为前方,y为右方。为左手坐标系。

  • Unity里面y为上,z为前方,x为右方。为左手坐标系。

  • 3dMax里面z为上,x为前方,y为右方。为右手坐标系。

  • Maya里面y为上,z为前后,x为左右。默认为右手坐标系。

模型从blender里面导出ue4里面,如果以y轴为前方导出,则y需要乘以-1为ue4的y的值。

# 重要模块

blender按模块划分为下面几个模块:

# bpy.context

bpy.context可以理解为blender当前可被获取的区域的集合。

  • bpy.context.object为当前鼠标选择的对象。
  • 他们的值为bpy.data.objects中的某个对象。
  • 该对象为只读数据。

# bpy.data

bpy.data用来获取blender的内部数据。

  • bpy.data.objects为当前场景中的所有对象。
  • bpy.data.objects可通过场景中的名字索引,也可以根据数字索引。

# bpy.msgbus

消息系统可用来在blender的数据块通过bpy.data改变时接收通知。

  • 消息系统是通过RNA系统的更新来触发的,这意味着下面两个操作是会触发的:
    • 通过python的api改变,比如some_object.location.x += 3。
    • 通过slider,fields和用户界面的button。
  • 而下面的操作是不会触发的:
    • 在3D视角里移动对象。
    • 通过动画系统来实现的改变。

# bpy.ops

给python提供调用操作的入口,这些操作可能用c,python以及一些宏来写的。

  • 只有关键的参数才会被用来传送给操作属性。

  • 这些操作并不会返回你期望的值,他们可能会返回一个集合{'RUNNING_MODAL', 'CANCELLED', 'FINISHED', 'PASS_THROUGH'}。通常只会返回{'FINISHED'} and {'CANCELLED'}。

  • 在一个错误的上下文调用操作会返回RuntimeError,会有一个poll()方法来阻止这个问题。

    if bpy.ops.object.mode_set.poll():
        bpy.ops.object.mode_set(mode='EDIT')
    
    1
    2
  • 注意bpy.ops只是python的一个获取路径,它的操作id是后面开始的,比如bpy.ops.mesh.subdivide它的操作id是mesh.subdivide。

# bpy.types

包含了blender里的所有对象类型。

# bpy.utils

这个模块包含了blender的工具函数,这些工具函数没有涉及到blender的内部数据。

# bpy.path

这个模块类似于os.path,包含了blender里要处理的各种路径的工具函数。

# bpy.app

这个模块包含了blender运行期间未改变的值。

# bpy.props

这个模块包含了扩展blender内部数据的一些属性。这些函数会被用来赋值blender内部注册类的属性。

  • 传给这些函数的参数必须以关键词的形式传入。

# 其他操作

如果一个类我们想获取它的方法,可以使用dir()方法来获取。

# 示例

# 插入关键帧

import bpy  # 导入bpy模块
import random #导入随机模块
startFrame = 1  #设置起始帧
endFrame = 601 #设置结束帧
scene = bpy.context.scene #获得场景缩写
obj = bpy.context.object     #获得当前所选的对象             # Reference to selected object

for frame_nr in range(startFrame,endFrame): #进入从起始帧到结束帧到循环
        # set current frame 设置当前关键帧
        scene.frame_set(frame_nr)
        # print(frame_nr) 调试信息,打印当前帧
        ploc = obj.location #获得之前的对象的位置
        xpos = ploc[0] #获得x坐标
        ypos = ploc[1] #获得y坐标
        zpos = ploc[2] #获得z坐标
        zpos = zpos + ( random.random()) # 赋值新的z坐标
        obj.location = (xpos, ypos, zpos) #将新的三纬度坐标应用到对象上
        obj.keyframe_insert(data_path="location", index=-1) #为新的位置插入关键帧 其中data_path可通过鼠标右键copy data path来获取。假如index为0,为只记录x。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 生成数字模型

import bpy
import csv
num=1

startNum=111501
endNum=116500

with open(r"D:\blender\loc.csv") as f:
    filecontent = csv.reader(f)
    for row in filecontent:
    #for i in range(31500,41500):
        #row = next(filecontent)
        if num < startNum:
            num+=1
            continue
        
        if num > endNum:
            break

        bpy.ops.object.text_add(enter_editmode=False, align='WORLD', location=(float(row[0])/100, float(row[1])/-100, 0), scale=(1, 1, 1))
        bpy.context.object.data.resolution_u = 2

        bpy.context.object.data.align_x = 'CENTER'
        bpy.context.object.data.align_y = 'CENTER'

        bpy.context.object.data.body=row[2]
        #bpy.context.object.data.extrude = 0.1
        bpy.context.object.data.size = 2.5
        bpy.ops.object.convert(target='MESH')
        
        bpy.context.object.name="point"+str(num)
        num+=1
        
        levelNumTemp = float(row[2])//5
        levelNum = int(levelNumTemp)
        if levelNum<0:
            bpy.ops.object.move_to_collection(collection_index=6)
        else:
            if levelNum<4:
                levelNum+=1
                bpy.ops.object.move_to_collection(collection_index=levelNum)
            else:
                bpy.ops.object.move_to_collection(collection_index=5)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

下面是另一种生成数字模型的方法,暂未使用,后面有机会可以用下看看效率:

import bpy

font_curve = bpy.data.curves.new(type="FONT", name="Font Curve")
font_curve.body = "my text"
font_obj = bpy.data.objects.new(name="Font Object", object_data=font_curve)
bpy.context.scene.collection.objects.link(font_obj)
1
2
3
4
5
6

# 检测场景

检测当前场景中以point开头命名的物体数量是否符合条件

import bpy

num=0

startNum=291501
endNum=296500

if len(bpy.data.objects) != 5000:
    print("场景中物体数量不对!")
else:
    for i in range(startNum,endNum+1):
        if bpy.data.objects[num].name != ("point"+str(i)):
            print("point"+str(i)+" not exist")
            break
        else:
            num+=1
            print("point"+str(i)+" is ok!")

print("check point total num:"+str(num));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 删除一些物体

import bpy
num=0

startNum=291501
endNum=296500

for i in range(startNum,endNum+1):
    if bpy.data.objects[num].name == ("point"+str(i)):
        print("point"+str(i)+" delete")
        bpy.data.objects.remove(bpy.data.objects[num],do_unlink=True)
1
2
3
4
5
6
7
8
9
10

# 合并blender文件

import bpy
import os

workpath=r"D:\blender\okay"

num=0
for root, dirs, files in os.walk(workpath, topdown=False):
    for name in files:
        num+=1
        currentfile = os.path.join(root, name)
        print("deal files:"+currentfile)
        for i in range(0,6):
            print("deal collection:"+str(i))
            bpy.context.view_layer.active_layer_collection = bpy.context.view_layer.layer_collection.children[i]
            bpy.ops.wm.append(directory=currentfile + "/Collection/", files=[{'name':"Collection "+str(i+1)}],instance_collections=False)
print(num)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16