При построении геометрии в Blender с использованием Blender Python API может понадобиться создать в текущем меше новый полигон, который лежит в той же самой плоскости, что и один из имеющихся уже в меше полигонов.
Для простоты будем считать, что мы находимся в режиме редактирования объекта и не переключаем режим работы с ним.
Для начала создадим объект BMesh и скопируем в него геометрию текущего активного меша. Также пересчитаем таблицу вертексов для правильной индексации.
|
1 2 |
bm = bmesh.from_edit_mesh(bpy.context.object.data) bm.verts.ensure_lookup_table() |
Зададим начальные координаты для нового полигона. Пусть это будет квадрат с центром в точке (0.0, 0.0) и длиной стороны равной 2. Начальные координаты задаем в 2D пространстве плоскости самого полигона.
|
1 2 3 4 5 6 7 8 |
new_face_cos = [ Vector((-1.0, -1.0)), Vector((-1.0, 1.0)), Vector((1.0, 1.0)), Vector((1.0, -1.0)) ] # [Vector((-1.0, -1.0)), Vector((-1.0, 1.0)), Vector((1.0, 1.0)), Vector((1.0, -1.0))] |
Чтобы оперировать с координатами меша в глобальной системе координат, получим указатель на матрицу трансформации нашего меша, а так же сделаем ее инвертированную копию.
|
1 2 |
world_matrix = bpy.context.object.matrix_world world_matrix_inv = bpy.context.object.matrix_world.inverted() |
Опять же для простоты примера, пусть наш исходный меш состоит всего из одного треугольного полигона, в плоскости которого нам и нужно построить новый полигон. В реальных случаях можно брать, например, три выделенные точки на любом полигоне меша.
Получим координаты трех точек полигона меша в глобальной системе координат.
|
1 2 3 4 5 6 7 |
p0 = world_matrix @ bm.verts[0].co p1 = world_matrix@ bm.verts[1].co p2 = world_matrix @ bm.verts[2].co # <Vector (1.7921, 1.1420, 0.9210)> # <Vector (1.3010, 1.0365, 0.7220)> # <Vector (1.4724, 1.3446, 0.8692)> |
Теперь мы на основании этих трех точек можем вычислить так называемый «базис» — матрицу трансформации, определенную по трем этим точкам.
|
1 2 3 4 5 6 |
vec_x = (p1 - p0).normalized() vec_temp = (p2 - p0).normalized() vec_z = vec_x.cross(vec_temp).normalized() vec_y = vec_z.cross(vec_x).normalized() rot_matrix = Matrix((vec_x, vec_y, vec_z)).transposed() basis = rot_matrix.to_4x4() |
Добавим в базис еще оффсет, чтобы начальная точка базиса совпадала бы с первой из трех точек нашего исходного полигона меша.
|
1 |
basis.translation = p0 |
Теперь у нас есть готовая матрица трансформации для пересчета координат в плоскость исходного полигона.
|
1 2 3 4 5 6 |
print(basis) # <Matrix 4x4 (-0.9090, -0.2745, 0.3136, 1.7921) # (-0.1951, 0.9452, 0.2617, 1.1420) # (-0.3683, 0.1766, -0.9128, 0.9210) # ( 0.0000, 0.0000, 0.0000, 1.0000)> |
Пересчитаем 2D координаты нашего нового полигона в 3D координаты по полученному базису.
|
1 2 3 4 5 6 7 8 9 10 11 |
world_cos = [] for dx, dy in new_face_cos: local_vector = Vector((dx, dy, 0.0)) world_pos1 = basis @ local_vector world_pos = world_matrix_inv @ world_pos1 world_cos.append(world_pos) # [Vector((-0.4627564549446106, -0.079158253967762, -1.2500395774841309)), # Vector((0.6229296326637268, 1.405601143836975, -0.4646884799003601)), # Vector((-0.48234763741493225, 1.3329771757125854, 1.2005704641342163)), # Vector((-1.5680336952209473, -0.1517820954322815, 0.415219247341156))] |
Добавим в BMesh новые точки по вычисленным координатам.
|
1 |
bm_verts = [bm.verts.new(pos) for pos in world_cos] |
И заполним их полигоном.
|
1 |
bm.faces.new(bm_verts) |
Для правильного отображения пересчитаем нормали.
|
1 |
bmesh.ops.recalc_face_normals(bm, faces=bm.faces) |
На этом все. Теперь мы можем вернуть обновленную геометрию из объекта BMesh в базовый меш и очистить BMesh, который нам больше не нужен.
|
1 2 |
bmesh.update_edit_mesh(bpy.context.object.data) bm.free() |
Таким образом мы построили в меше новый полигон, который лежит точно в плоскости исходного полигона, от которого мы взяли три точки.
Обратите внимание, что центр нового полигона лежит в точке исходного полигона с индексом 0, так как именно ее мы задали, как центр координатной системы в базисе.

.blend file on Patreon