Большинство объектов в Blender имеют в своей структуре множество списков данных. Например, внутри объекта “меш” присутствуют списки с набором вертексов этого меша, набором ребер, набором полигонов. А в структуре каждого вертекса присутствует, например, список с его координатами по осям X, Y и Z.
Для доступа к таким внутренним спискам данных обычно приходится составлять циклы из нескольких уровней вложенности. Однако в языке Pythoh, который используется в Blender API, существует удобная возможность формировать такие вложенные циклы одной строкой – list comprehension.
Рассмотрим простой пример: пусть у нас на меше есть несколько выделенных полигонов, и нам нужно найти максимальную координату по оси Z среди точек этих полигонов.
В простейшем случае мы создадим цикл сначала по выделенным полигонам:
1 2 |
for polygon in bpy.context.object.data.polygons: if polygon.select: |
Внутри него создадим еще один цикл уже по вертексам, принадлежащим выделенным полигонам:
1 |
for vertex_id in polygon.vertices: |
И уже только внутри него мы сможем получить доступ к координатам каждого вертекса. Получается следующая структура кода из четырех уровней вложения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
z_co = [] for polygon in bpy.context.object.data.polygons: if polygon.select: for vertex_id in polygon.vertices: z_co.append(bpy.context.object.data.vertices[vertex_id].co.z) print(z_co) # [0.2421875, 0.1171875, 0.15625, ...] print(max(z_co)) # 0.375 |
Если же использовать list comprehension, можно все это выражение записать в одну строку.
List comprehension в Python работает следующим образом: один уровень цикла, формирующий список, записывается в строку в квадратных скобках. Слева указывается получаемое выражение для каждой итерации цикла, справа, при необходимости – дополнительное условие.
new_list = [expression for item in iterable (if condition)]
Так первый и второй уровни нашего кода можно представить следующим выражением в одну строку:
1 2 3 |
[polygon.vertices for polygon in bpy.context.object.data.polygons if polygon.select] # [bpy.data.meshes['Suzanne'].polygons[49].vertices, bpy.data.meshes['Suzanne'].polygons[51].vertices, ...] |
Разложив при помощи list comprehension в строку только первые два уровня, мы получаем список списков – каждый элемент нового списка является сам по себе списком с координатами вертексов.
Мы могли бы еще раз пробежаться по полученному списку циклом:
1 2 3 4 5 6 7 |
z_co = [] for polygon_vertices in [polygon.vertices for polygon in bpy.context.object.data.polygons if polygon.select]: for vertex_id in polygon_vertices: z_co.append(bpy.context.object.data.vertices[vertex_id].co.z) print(z_co) |
Уровень вложенности стал чуть меньше, однако еще осталось пространство для дальнейшего сжатия, так как list comprehension в Python поддерживает несколько уровней вложенности.
Каждый последующий уровень записывается слева направо с опциональным указанием условий. А выражение слева может оперировать переменными с последнего (чаще всего), или же с каждого из уровней.
Продолжим превращать наш код в однострочное выражение.
Слева мы указываем финальное выражение для получения координаты Z вертекса. Далее, слева направо указываем выражения на каждом уровне циклов и условия, если они есть.
И в итоге мы получаем следующую строчку кода:
1 2 3 4 5 6 7 |
z_co = [bpy.context.object.data.vertices[vertex_id].co.z for polygon in bpy.context.object.data.polygons if polygon.select for vertex_id in polygon.vertices] print(z_co) # [0.2421875, 0.1171875, 0.15625, ...] print(max(z_co)) # 0.375 |
В результате мы получили точно такой же список значений, как и при использовании многоуровневых циклов.
Давайте рассмотрим еще один пример: пусть у нас есть несколько выделенных точек на меше, и нам нужно получить список ребер, выходящих из этих точек, которые не выделены.
Для быстрого получения связанных с вертексами ребер будем использовать объект bmesh.
1 2 3 4 |
bm = bmesh.new() bm.from_mesh(bpy.context.object.data) bm.verts.ensure_lookup_table() bm.edges.ensure_lookup_table() |
Сначала получим список не выделенных ребер обычным путем при помощи вложенных циклов:
1 2 3 4 5 6 7 8 9 10 11 |
non_selected_edges = [] for vertex in bm.verts: if vertex.select: for edge in vertex.link_edges: if not edge.select: non_selected_edges.append(edge) print(non_selected_edges) # [<BMEdge(0x00000243184671F0), index=6>, ...] |
А затем получим этот список при помощи list comprehension, записывая все в одну строку.
Слева пишем конечное выражение – что именно мы получаем в списке. В нашем случае это ребро – edge. Далее, слева направо указываем каждый уровень плюс условие.
1 2 3 4 5 |
non_selected_edges = [edge for vertex in bm.verts if vertex.select for edge in vertex.link_edges if not edge.select] print(non_selected_edges) # [<BMEdge(0x00000243184671F0), index=6>, ...] |
Таким образом мы получили тот же самый список нужных нам ребер, использовав всего одну строку кода.