Не так давно столкнулся с интересной задачей при создании 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, что, как мне кажется, несколько усложняет весь процесс и добавляет лишнюю точку отказа.

Written by Gecko

Разработчик компьютерной графики

Оставить комментарий

Ваш адрес email не будет опубликован.