Blender и Python для визуализации молекул

Blender – свободный пакет для создания трехмерной компьютерной графики. В старых версиях работа с ним происходила в основном с помощью горячих клавиш, что породило слухи о невероятной сложности работы с ним. Сейчас действия горячих клавиш доступны и в виде кнопок графического интерфейса. Самая полезная для данной заметки функция Blender – возможность скриптовать рутинные действия на Python. Задача: построить простейшую молекулу, состоящую из нескольких сфер и цилиндров, их соединяющих. Для этих целей напишем скрипт на Python. Во-первых, скрипт будет рисовать сферы. Это делается с помощью

bpy.ops.mesh.primitive_uv_sphere_add()

Во-вторых, рисовать цилиндры между ними. Тут несколько сложнее. Цилиндры задаются как координаты его центра и поворот. Для поворота необходимо указать ось, перпендикулярную плоскости вращения, и угол в радианах.

Немного математики. После добавления цилиндр направлен вдоль оси z, то есть вдоль вектора z = (0,0,1). Нужно, чтобы он соединял некоторые точки r1 и r2. С координатой центра цилиндра все просто, она составляет r3 = (r1+r2)/2. Необходимо повернуть z так, чтобы он стал параллелен z_desired = (r1-r2).normalized() (нормировать лучше здесь, чтобы не таскать нормы потом). Через две пересекающиеся прямые можно провести плоскость, и причем только одну. В ней и будет происходить поворот. А ось, перпендикулярную плоскости вращения, можно получить из векторного произведения векторов, задающих прямые: rot_axis = z.cross(z_desired). Угол можно получить с использованием скаларного произведения: angle = acos(z.dot(z_desired)) (тут пригодилась нормировка).

Все, что нужно, теперь есть. Код на Python:

from math import degrees, acos
from mathutils import Vector

spheres = (Vector((2,3,3)),Vector((1,1,2)),Vector((2,3,4)),Vector((4,5,3)))
edges = ((0,1),(1,2),(2,3))

for i in range(0, len(spheres)):
    r1 = spheres[i]
    bpy.ops.mesh.primitive_uv_sphere_add(location=(r1.x, r1.y, r1.z))

for i in range(0,len(edges)):
     r1 = spheres[edges[i][0]]
     r2 = spheres[edges[i][1]]
     r3 = (r1+r2)/2
     z = Vector((0,0,1))
     z_desired = (r1-r2).normalized()
     rot_axis = z.cross(z_desired)
     angle = acos(z.dot(z_desired))
     bpy.ops.mesh.primitive_cylinder_add(radius=0.3, depth=(r2-r1).length,location=(r3.x,r3.y,r3.z))
     bpy.ops.transform.rotate(value=(angle,), axis=rot_axis)

Его достаточно скопировать в консоль Blender и запустить. Должна получиться следующая картинка:

{% img center /images/2012/blender_python.png %}