Передаем данные из GIMP в Blender
Не так давно столкнулся с интересной задачей при создании 2D-анимации в Blender: мне нужно было сделать плоскую сетку по форме объекта из PNG-изображения с прозрачным фоном. На обычную плоскость ее натянуть нельзя, так как предполагалось, что объект будет деформироваться при помощи скелета и shape keys. И таких сеток нужно было создать довольно много. Создавать их вручную, расставляя вершины по контуру картинки, как-то очень уж трудоемко – захотелось этот процесс как-то оптимизировать. И тут я вспомнил, что GIMP умеет преобразовывать маски в кривые, которые затем можно сохранить как SVG и импортировать в Blender. Осталось лишь заскриптовать эту последовательность действий!
Я решил, что переносить SVG вручную из одной программы в другую я тоже не хочу – пусть будет условно одна-единственная кнопка, по нажатию на которую слой из GIMP переносится в текущий открытый проект Blender. Подобное взаимодействие двух приложений можно реализовать при помощи технологий RPC (remote procedure call) – в частности XML-RPC, который позволяет через HTTP на клиенте вызвать серверную функцию, передав ей параметры, и затем получить результат. Преимущество XML-RPC в том, что он полностью скрывает транспортный механизм такого вызова – в скриптовых языках он выглядит просто как обычный вызов функции. Сервером я решил сделать плагин для Blender, клиентом – плагин для GIMP. Оба плагина я написал на Python, где протокол XML-RPC реализован в стандартной библиотеке. В GIMP и Blender используются разные версии Python, поэтому код работы с XML-RPC немного отличается.
Серверная часть выглядит достаточно тривиально: нужна лишь функция, которая принимает на вход строку, содержащую SVG – эта функция регистрируется как серверная функция в объекте SimpleXMLRPCServer:
import os
import bpy
import threading
import tempfile
from xmlrpc.server import SimpleXMLRPCServer
HOST = "127.0.0.1"
PORT = 8000
def svg_to_curve(svg:str):
tmp = tempfile.NamedTemporaryFile(delete=False, mode="w")
tmp.write(svg)
tmp.close()
bpy.ops.import_curve.svg(filepath=tmp.name, filter_glob='*')
os.unlink(tmp.name)
return {}
def launch_server():
server = SimpleXMLRPCServer((HOST, PORT))
server.register_function(svg_to_curve)
server.serve_forever()
(для краткости я опустил служебный код для регистрации плагина)
Проблема возникает лишь в момент импорта SVG – Blender умеет импортировать только по файловому имени, поэтому пришлось сохранить строку во временный файл. Выглядит не очень элегантно, но работает.
На стороне GIMP делается следующее: текущему слою создается маска из альфа-канала, из маски создается выделение (gimp_image_select_item), из выделения, в свою очередь – кривая (plug_in_sel2path). Кривая экспортируется в SVG (gimp_vectors_export_to_string), а затем мы просто вызываем удаленную функцию svg_to_curve, после чего удаляем все служебные объекты.
import xmlrpclib
from gimpfu import *
def export_svg(svg):
proxy = xmlrpclib.ServerProxy("http://localhost:8000/")
try:
proxy.svg_to_curve(svg)
except xmlrpclib.Fault as err:
pdb.gimp_message(err.faultString)
def layer_to_blender_curve(image, layer):
if not pdb.gimp_item_is_group(layer):
mask = layer.mask
if not mask:
mask = layer.create_mask(ADD_ALPHA_TRANSFER_MASK)
layer.add_mask(mask)
pdb.gimp_image_select_item(image, CHANNEL_OP_REPLACE, mask)
path = pdb.plug_in_sel2path(image, None)
pdb.gimp_selection_none(image)
vector_name = pdb.gimp_path_list(image)[1][0]
vec = pdb.gimp_image_get_vectors_by_name(image, vector_name)
vec.name = "mask_path"
svg = pdb.gimp_vectors_export_to_string(image, path)
export_svg(svg)
pdb.gimp_image_remove_vectors(image, vec)
pdb.gimp_layer_remove_mask(layer, 0)
Ошибки, которые могли возникнуть в процессе передачи данных, удобно выводить в лог функцией gimp_message.
Исходники плагинов вы можете найти в репозитории https://github.com/gecko0307/image2curve.
Недостатком данного решения является то, что на стороне Blender будет постоянно работать HTTP-сервер на localhost:8000, так что вы в это время не сможете привязать к этому порту ничего другого. В Python есть способы получить случайный незанятый номер порта, чтобы не конфликтовать с другими серверами, однако в этом случае придется как-то передать порт в GIMP, что, как мне кажется, несколько усложняет весь процесс и добавляет лишнюю точку отказа.