Майские праздники не прошли даром: на пару дней я основательно засел за Python и написал собственный лаунчер. Зачем? Он нужен для того, чтобы не привязывать игру к конкретной игровой платформе. В моем случае, я использую GameJolt, но такой подход работает для любого сервиса. Лаунчер служит посредником между игрой и API площадки. Все, что нужно делать игре — это сообщать лаунчеру о событиях наподобие «игрок получил такую-то ачивку». Лаунчер, в свою очередь, передает эти данные игровому сервису. Если надо переехать на другой сервис, достаточно просто обновить лаунчер, а игру патчить не придется — это удобно и экономит массу времени.
(далее…)python
Передаем данные из 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, что, как мне кажется, несколько усложняет весь процесс и добавляет лишнюю точку отказа.
Винтажные фильтры — обновление

Скачать можно здесь.
Журнал «FPS» №27
Вышел 27 номер электронного PDF-журнала «FPS», посвященного разработке игр, программированию, компьютерной графике и звуку.
Читайте в этом номере:
> Подборка новостей по Blender
> Тон Розендаль о будущем интерфейса Blender
> GIMP: цветокоррекция на Python
> От мольберта — к дисплею. Заметки о цифровой живописи
> Физический движок своими руками. Часть IV
> Математика в dlib
> Ranges: диапазоны в D
> Игровые новости из мира Linux
> Право на творчество
Номер доступен для онлайн-чтения и загрузки на сервисе Issuu.com, Документах Google и Dropbox.
Последние новости по проекту вы можете узнать в публичной странице журнала в социальной сети Google+: http://gplus.to/fpsmag. Добавляйте нас в круги, оставляйте свои комментарии и отписывайтесь в нашем сообществе.
Архив номеров журнала здесь.
Thumbnailer для GIMP
Терпеть не могу рутинную, механическую работу! Сейчас вот пришлось вручную изготовлять миниатюры изображений произвольного разрешения, центрируя и уменьшая их до квадрата размером 128х128. Не стал долго мучиться, написал для GIMP скрипт на Python – и решил сразу выложить, вдруг кому-нибудь тоже пригодится:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from gimpfu import *
def python_fu_thumbnailer(image, layer, width, height):
origWidth = pdb.gimp_image_width(image)
origHeight = pdb.gimp_image_height(image)
newWidth = origWidth
newHeight = origHeight
newX = 0
newY = 0
if (origWidth > origHeight):
newWidth = origHeight
newX = -(origWidth/2 - newWidth/2)
elif (origWidth < origHeight):
newHeight = origWidth
newY = -(origHeight/2 - newHeight/2)
pdb.gimp_layer_resize(layer, newWidth, newHeight, newX, newY)
pdb.gimp_image_resize_to_layers(image)
pdb.gimp_context_set_interpolation(INTERPOLATION_LANCZOS)
pdb.gimp_image_scale(image, width, height)
register(
"python-fu-thumbnailer",
"Thumbnailer",
"Thumbnailer 0.1",
"Timur Gafarov",
"(c) Copyright 2013 Timur Gafarov",
"09-02-2013",
"Make a thumbnail...",
"RGB*, GRAY*",
[
(PF_IMAGE, "image", "Target image", None),
(PF_DRAWABLE, "drawable", "Target layer", None),
(PF_SPINNER, "width", "Width:", 128, (1, 262144, 1)),
(PF_SPINNER, "height", "Height:", 128, (1, 262144, 1))
],
[],
python_fu_thumbnailer,
menu = "/Python-Fu/Transform")
main()
Тестировал с GIMP 2.7.4 и Python 2.7.1.