I will summarize how to access mesh data with Blender Python. It's not that hard to look up, but I don't have a lot of information, so I'll put it together so that it can be listed as a cheat sheet. There is no guarantee that the methods described here are the best for speed. Please let me know if there is any other better way.
Below, I would like to summarize the sample code that creates cubes and sets them with UV, Vertex Color, Shape key, and Skin Weight (Vertex Group). It also contains the code and log to print the data set in them.
Postscript (2019/04/03): The version of Blender became 2.80, and the API was partially changed and stopped working, so I fixed it. Basically, the following 4 points.
bpy.context.scene.objects
to bpy.context.scene.collection.objects
.You can also create a mesh by entering edit mode and creating it as a BMesh, but here's how to create it in object mode.
create_mesh.py
import bpy
verts = [[-1.0, -1.0, 1.0], [1.0, -1.0, 1.0], [1.0, 1.0, 1.0], [-1.0, 1.0, 1.0],
[-1.0, -1.0, -1.0], [1.0, -1.0, -1.0], [1.0, 1.0, -1.0], [-1.0, 1.0, -1.0], ]
faces = [[0,1,2,3], [0,4,5,1], [1,5,6,2], [2,6,7,3], [0,3,7,4], [4,7,6,5]]
msh = bpy.data.meshes.new(name="cubemesh")
#msh = bpy.data.meshes.new("cubemesh")
msh.from_pydata(verts, [], faces)
msh.update()
obj = bpy.data.objects.new(name="cube", object_data=msh)
#obj = bpy.data.objects.new("cube", msh)
scene = bpy.context.scene
#scene.objects.link(obj)
scene.collection.objects.link(obj)
As a flow,
meshes.new
from_pydata
.msh.update ()
. It may not be necessary if you are calling from_pydata.scene.objects.link
to the scene.The [] part of the central argument of from_pydata
sets the edge information. If not, it will be calculated automatically from the Face information, so there is no problem with an empty array when creating a mesh that generates faces. Blender can also create a mesh without faces, in which case you can specify edge and empty the Face Index specification. You may want to script a mesh without faces, such as a mesh for a rig controller.
--vertices property --index: Index of vertices --co: Coordinates of vertices --normal: Vertex normal
#Enumeration of vertex information
print("num of vertices:", len(msh.vertices))
for vt in msh.vertices:
print("vertex index:{0:2} co:{1} normal:{2}".format(vt.index, vt.co, vt.normal))
output
num of vertices: 8
vertex index: 0 co:<Vector (-1.0000, -1.0000, 1.0000)> normal:<Vector (-0.5773, -0.5773, 0.5773)>
vertex index: 1 co:<Vector (1.0000, -1.0000, 1.0000)> normal:<Vector (0.5773, -0.5773, 0.5773)>
vertex index: 2 co:<Vector (1.0000, 1.0000, 1.0000)> normal:<Vector (0.5773, 0.5773, 0.5773)>
vertex index: 3 co:<Vector (-1.0000, 1.0000, 1.0000)> normal:<Vector (-0.5773, 0.5773, 0.5773)>
vertex index: 4 co:<Vector (-1.0000, -1.0000, -1.0000)> normal:<Vector (-0.5773, -0.5773, -0.5773)>
vertex index: 5 co:<Vector (1.0000, -1.0000, -1.0000)> normal:<Vector (0.5773, -0.5773, -0.5773)>
vertex index: 6 co:<Vector (1.0000, 1.0000, -1.0000)> normal:<Vector (0.5773, 0.5773, -0.5773)>
vertex index: 7 co:<Vector (-1.0000, 1.0000, -1.0000)> normal:<Vector (-0.5773, 0.5773, -0.5773)>
--edges property --index: Index of the edge --vertices: Index of adjacent vertices
print("num of edges:", len(msh.edges))
for ed in msh.edges:
print("edge index:{0: 2} v0:{0} v1:{1}".format(ed.index, ed.vertices[0], ed.vertices[1]))
output
num of edges: 12
edge index: 0 v0: 4 v1: 5
edge index: 1 v0: 3 v1: 7
edge index: 2 v0: 6 v1: 7
edge index: 3 v0: 2 v1: 6
edge index: 4 v0: 5 v1: 6
edge index: 5 v0: 0 v1: 1
edge index: 6 v0: 4 v1: 7
edge index: 7 v0: 1 v1: 2
edge index: 8 v0: 1 v1: 5
edge index: 9 v0: 2 v1: 3
edge index:10 v0: 0 v1: 3
edge index:11 v0: 0 v1: 4
The ** polygons ** property, not the faces. There are more software with the property name faces, and I'm just trying to access it with faces? I emphasize it because it becomes.
--polygons property --index: Polygon Index --vertices: Index of adjacent vertices --loop_start: Index of start loop data to iterate the edges and vertices of this polygon (Mesh has a loops property and Index to it) --loop_total: Number of loops, real, number of polygon vertices --loop_indices: List of loop indexes
Get
print("num of polygons:", len(msh.polygons))
for pl in msh.polygons:
print("polygon index:{0:2} ".format(pl.index), end="")
print("vertices:", end="")
for vi in pl.vertices:
print("{0:2}, ".format(vi), end="")
print("")
for pl in msh.polygons:
print("polygon index:{0:2} ".format(pl.index))
print(" > loops:", end="")
print(" total:", pl.loop_total, end="")
print(" start:", pl.loop_start, end="")
print(" indices:", end="")
for lp in pl.loop_indices:
print("{0:2}, ".format(lp), end="")
print("")
output
num of polygons: 6
polygon index: 0 vertices: 0, 1, 2, 3,
polygon index: 1 vertices: 0, 4, 5, 1,
polygon index: 2 vertices: 1, 5, 6, 2,
polygon index: 3 vertices: 2, 6, 7, 3,
polygon index: 4 vertices: 0, 3, 7, 4,
polygon index: 5 vertices: 4, 7, 6, 5,
polygon index: 0
> loops: total: 4 start: 0 indices: 0, 1, 2, 3,
polygon index: 1
> loops: total: 4 start: 4 indices: 4, 5, 6, 7,
polygon index: 2
> loops: total: 4 start: 8 indices: 8, 9, 10, 11,
polygon index: 3
> loops: total: 4 start: 12 indices:12, 13, 14, 15,
polygon index: 4
> loops: total: 4 start: 16 indices:16, 17, 18, 19,
polygon index: 5
> loops: total: 4 start: 20 indices:20, 21, 22, 23,
Data that manages the indexes of vertices and edges. It can be used when you want to trace the edges around a polygon. However, if you want to trace the adjacency of vertices, edges, and polygons properly, it is better to switch to Edit mode and use Bmesh, so I think that there is such a property.
print("num of loops:", len(msh.loops))
for lp in msh.loops:
print("loop index:{0:2} vertex index:{1:2} edge index:{2:2}".format(lp.index, lp.vertex_index, lp.edge_index))
output
num of loops: 24
loop index: 0 vertex index: 0 edge index: 5
loop index: 1 vertex index: 1 edge index: 7
loop index: 2 vertex index: 2 edge index: 9
loop index: 3 vertex index: 3 edge index:10
loop index: 4 vertex index: 0 edge index:11
loop index: 5 vertex index: 4 edge index: 0
loop index: 6 vertex index: 5 edge index: 8
loop index: 7 vertex index: 1 edge index: 5
loop index: 8 vertex index: 1 edge index: 8
loop index: 9 vertex index: 5 edge index: 4
loop index:10 vertex index: 6 edge index: 3
loop index:11 vertex index: 2 edge index: 7
loop index:12 vertex index: 2 edge index: 3
loop index:13 vertex index: 6 edge index: 2
loop index:14 vertex index: 7 edge index: 1
loop index:15 vertex index: 3 edge index: 9
loop index:16 vertex index: 0 edge index:10
loop index:17 vertex index: 3 edge index: 1
loop index:18 vertex index: 7 edge index: 6
loop index:19 vertex index: 4 edge index:11
loop index:20 vertex index: 4 edge index: 6
loop index:21 vertex index: 7 edge index: 2
loop index:22 vertex index: 6 edge index: 4
loop index:23 vertex index: 5 edge index: 0
Setting and getting UVs is as simple as adding a UV channel and using that channel as a key to access the UV array. Note that the channels displayed in UV Maps of the GUI are managed by the property called uv_textures of Mesh, but the actual UV coordinate information is held by the property called uv_layers.
It does not maintain a different topology for each UV channel like 3ds Max. Therefore, the number of UV coordinates held in each layer is always the sum of the number of vertices of all polygons. Blender doesn't seem to have the concept of weld, welding in the data.
>>> len(msh.uv_layers[channel_name].data.items())
24
#UV settings
tmp = [[0.0, 0.0], [0.5, 0.0], [0.5, 0.5], [0.0, 0.5]]
uvs = tmp * 6 #Initialize UV coordinates to set
channel_name = "uv0" #UV channel name
msh.uv_layers.new(name=channel_name) #Creating a UV Channel
#msh.uv_textures.new(channel_name) #Creating a UV Channel
for idx, dat in enumerate(msh.uv_layers[channel_name].data): #Iterate with an array of uv layers
dat.uv = uvs[idx]
Data confirmation
print("num of uv layers:", len(msh.uv_layers))
#print("num of uv layers:", len(msh.uv_textures))
for ly in msh.uv_layerss:
#for ly in msh.uv_textures:
print(ly.name)
for idx, dat in enumerate(msh.uv_layers[ly.name].data):
print(" {0}:{1}".format(idx, dat.uv))
print("")
output
uv0
0:<Vector (0.0000, 0.0000)>
1:<Vector (0.5000, 0.0000)>
2:<Vector (0.5000, 0.5000)>
3:<Vector (0.0000, 0.5000)>
4:<Vector (0.0000, 0.0000)>
5:<Vector (0.5000, 0.0000)>
6:<Vector (0.5000, 0.5000)>
7:<Vector (0.0000, 0.5000)>
8:<Vector (0.0000, 0.0000)>
9:<Vector (0.5000, 0.0000)>
10:<Vector (0.5000, 0.5000)>
11:<Vector (0.0000, 0.5000)>
12:<Vector (0.0000, 0.0000)>
13:<Vector (0.5000, 0.0000)>
14:<Vector (0.5000, 0.5000)>
15:<Vector (0.0000, 0.5000)>
16:<Vector (0.0000, 0.0000)>
17:<Vector (0.5000, 0.0000)>
18:<Vector (0.5000, 0.5000)>
19:<Vector (0.0000, 0.5000)>
20:<Vector (0.0000, 0.0000)>
21:<Vector (0.5000, 0.0000)>
22:<Vector (0.5000, 0.5000)>
23:<Vector (0.0000, 0.5000)>
Vertex Color
It is easily accessible via the vertex_colors property.
Setting
#6 sides Red, Green, Blue, Cyan, Magenta,Try painting with Yellow
#colormaps = [[1.0,0.0,0.0]]*4+[[0.0,1.0,0.0]]*4+[[0.0,0.0,1.0]]*4+[[0.0,1.0,1.0]]*4+[[1.0,0.0,1.0]]*4+[[1.0,1.0,0.0]]*4
colormaps = [[1.0,0.0,0.0,1.0]]*4+[[0.0,1.0,0.0,1.0]]*4+[[0.0,0.0,1.0,1.0]]*4+[[0.0,1.0,1.0,1.0]]*4+[[1.0,0.0,1.0,1.0]]*4+[[1.0,1.0,0.0,1.0]]*4
print("colormaps:", colormaps)
msh.vertex_colors.new(name='col')
# msh.vertex_colors.new('col')
for idx, vc in enumerate(msh.vertex_colors['col'].data):
vc.color = colormaps[idx]
Data acquisition
#Display of Vertex Color
print("num of vertex color layers:", len(msh.vertex_colors))
for ly in msh.vertex_colors:
print(ly.name)
for idx, vc in enumerate(msh.vertex_colors['col'].data):
print(" {0:2}:{1}".format(idx,vc.color))
output
num of vertex color layers: 1
col
0:<Color (r=1.0000, g=0.0000, b=0.0000)>
1:<Color (r=1.0000, g=0.0000, b=0.0000)>
2:<Color (r=1.0000, g=0.0000, b=0.0000)>
3:<Color (r=1.0000, g=0.0000, b=0.0000)>
4:<Color (r=0.0000, g=1.0000, b=0.0000)>
5:<Color (r=0.0000, g=1.0000, b=0.0000)>
6:<Color (r=0.0000, g=1.0000, b=0.0000)>
7:<Color (r=0.0000, g=1.0000, b=0.0000)>
8:<Color (r=0.0000, g=0.0000, b=1.0000)>
9:<Color (r=0.0000, g=0.0000, b=1.0000)>
10:<Color (r=0.0000, g=0.0000, b=1.0000)>
11:<Color (r=0.0000, g=0.0000, b=1.0000)>
12:<Color (r=0.0000, g=1.0000, b=1.0000)>
13:<Color (r=0.0000, g=1.0000, b=1.0000)>
14:<Color (r=0.0000, g=1.0000, b=1.0000)>
15:<Color (r=0.0000, g=1.0000, b=1.0000)>
16:<Color (r=1.0000, g=0.0000, b=1.0000)>
17:<Color (r=1.0000, g=0.0000, b=1.0000)>
18:<Color (r=1.0000, g=0.0000, b=1.0000)>
19:<Color (r=1.0000, g=0.0000, b=1.0000)>
20:<Color (r=1.0000, g=1.0000, b=0.0000)>
21:<Color (r=1.0000, g=1.0000, b=0.0000)>
22:<Color (r=1.0000, g=1.0000, b=0.0000)>
23:<Color (r=1.0000, g=1.0000, b=0.0000)>
Shape Key
It's basically the same as the UV setting. Unlike UVs, the mesh's shape_keys property is responsible for both shape key management and vertex data management. It is the property called key_blocks of the shape_keys property that actually has the data.
Setting
#In the verts variable defined when creating the Cube(0,0,1), (0,0,-1)MoveUp each,Use the Move Down key
shapemaps = {'MoveUp':[[v[0],v[1],v[2]+1.0] for v in verts],
'MoveDown':[[v[0],v[1],v[2]-1.0] for v in verts],}
#Create a Basis key if there is no Shape
obj.shape_key_add()
msh.shape_keys.key_blocks[-1].name = "Basis"
for sname in shapemaps:
lst = shapemaps[sname]
obj.shape_key_add()
kb = msh.shape_keys.key_blocks[-1]
kb.name = sname
for idx, co in enumerate(lst):
kb.data[idx].co = co
Data acquisition
#Get Shape Key
print("num of Shape Keys:", len(msh.shape_keys.key_blocks))
for kb in msh.shape_keys.key_blocks:
print(" Key Block:", kb.name)
for idx, dat in enumerate(kb.data):
print(" {0}:{1}".format(idx, dat.co))
output
num of Shape Keys: 3
Key Block: Basis
0:<Vector (-1.0000, -1.0000, 1.0000)>
1:<Vector (1.0000, -1.0000, 1.0000)>
2:<Vector (1.0000, 1.0000, 1.0000)>
3:<Vector (-1.0000, 1.0000, 1.0000)>
4:<Vector (-1.0000, -1.0000, -1.0000)>
5:<Vector (1.0000, -1.0000, -1.0000)>
6:<Vector (1.0000, 1.0000, -1.0000)>
7:<Vector (-1.0000, 1.0000, -1.0000)>
Key Block: MoveDown
0:<Vector (-1.0000, -1.0000, 0.0000)>
1:<Vector (1.0000, -1.0000, 0.0000)>
2:<Vector (1.0000, 1.0000, 0.0000)>
3:<Vector (-1.0000, 1.0000, 0.0000)>
4:<Vector (-1.0000, -1.0000, -2.0000)>
5:<Vector (1.0000, -1.0000, -2.0000)>
6:<Vector (1.0000, 1.0000, -2.0000)>
7:<Vector (-1.0000, 1.0000, -2.0000)>
Key Block: MoveUp
0:<Vector (-1.0000, -1.0000, 2.0000)>
1:<Vector (1.0000, -1.0000, 2.0000)>
2:<Vector (1.0000, 1.0000, 2.0000)>
3:<Vector (-1.0000, 1.0000, 2.0000)>
4:<Vector (-1.0000, -1.0000, 0.0000)>
5:<Vector (1.0000, -1.0000, 0.0000)>
6:<Vector (1.0000, 1.0000, 0.0000)>
7:<Vector (-1.0000, 1.0000, 0.0000)>
Vertex Group
I think it is the simplest data, but it is the most troublesome data structure. Blender holds the Skin Weight in this Vertex Group. Therefore, the data is held as an associative array with the Index of Vertex Group as the key and Weight as the value for each vertex. A function to set the Vertex Weight is provided in the structure of the Vertex Group so that it can be set with a feeling similar to UV. I don't think the processing speed is fast.
Setting
bone_list = ["bone1", "bone2"]
weight_map = {"bone1":[1.0]*4+[0.0]*4, # [1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0],
"bone2":[0.0]*4+[1.0]*4,
}
vg_list = [] #List of VertexGroup
#Creating a vertex group
for bname in bone_list:
obj.vertex_groups.new(name=bname)
#obj.vertex_groups.new(bname)
vg_list.append(obj.vertex_groups[-1])
#Settings via Vertex Group
for vg in vg_list:
weights = weight_map[vg.name]
for vidx, w in enumerate(weights):
if w != 0.0:
vg.add([vidx], w, 'REPLACE')
Try to get the data via the vertex properties.
Data acquisition
#Getting Weight via Vertex's groups property
msh = obj.data
for v in msh.vertices:
for vge in v.groups:
print("vindex:{0} group index:{1} weight:{2}".format(v.index, vge.group, vge.weight))
output
vindex:0 group index:0 weight:1.0
vindex:1 group index:0 weight:1.0
vindex:2 group index:0 weight:1.0
vindex:3 group index:0 weight:1.0
vindex:4 group index:1 weight:1.0
vindex:5 group index:1 weight:1.0
vindex:6 group index:1 weight:1.0
vindex:7 group index:1 weight:1.0
Collection data such as the data property of uv_layer has a function called foreach_set, and you can set the value in one shot with an array. It has not been verified whether it is faster than iterating arrays and assigning them one by one. Note that this method will pass an ordinary array that is flattened instead of the array of the above array.
python
tmp = [0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.0, 0.5]
uvs = tmp * 6 #Initialize UV coordinates to set
channel_name = "uv0"
msh.uv_textures.new(channel_name)
msh.uv_layers[channel_name].data.foreach_set("uv", uvlist)
python
shapemaps = {'MoveUp':[[v[0],v[1],v[2]+1.0] for v in verts],
'MoveDown':[[v[0],v[1],v[2]-1.0] for v in verts],}
for sname in shapemaps:
lst = shapemaps[sname]
lst2 = list(chain.from_iterable(lst)) #This seems to be faster with the method of flattening the array of arrays
obj.shape_key_add()
sk = msh.shape_keys.key_blocks[-1]
sk.name = sname
sk.data.foreach_set("co", lst2)
Recommended Posts