Blender

Un modelo maneja Blender 4.3.2 a través del Hub para construir una escena a partir de la predeterminada y renderizarla a un PNG. Esta página da el texto, el script, las respuestas del Hub y el renderizado, con el error de color que ocultó una respuesta satisfactoria y los 30 segundos del primer renderizado.

La escena del logotipo, construida y renderizada por el Hub en Blender: un corte pasa del renderizado a la malla de alambre mientras la cámara gira alrededor del núcleo.

Iniciar Blender con su puente

Blender ejecuta el puente en su propio Python. Iniciado en segundo plano, termina en cuanto se ha ejecutado el archivo del puente, así que un archivo de arranque entrega el hilo principal al puente con serve_main_thread(), como describe la guía. La ejecución no tenía la variable DISPLAY.

blender --background --python boot.py
import runpy

ns = runpy.run_path('/usr/share/libremodular-mcp/bridges/blender_bridge.py')
ns['serve_main_thread']()

La ejecución usó el puerto 9313 en config.toml y un token nuevo. También fijó default_timeout_s = 120 en la sección [hub], porque el primer renderizado de un perfil nuevo es lento y el plazo predeterminado es de 30 s (véase la respuesta de más abajo). El puerto empezó a escuchar 1,3 s después del inicio, y get_state sobre hub enumeró Blender como conectado:

Respuesta del Hub
{
  "app": "hub",
  "app_version": "0.1.0",
  "bridge_version": null,
  "captured_at": "2026-10-21T12:47:58.570Z",
  "connected": true,
  "content_origin": "hub",
  "screenshot": null,
  "state": {
    "modules": [
      {
        "app_version": "4.3.2",
        "bridge_version": "1",
        "connected": true,
        "mode": "tcp",
        "name": "blender"
      }
    ]
  },
  "warnings": []
}

Texto dado al modelo

Texto dado al modelo
In Blender, build a small scene from what is already open and render it: the cube in copper (#c25438), a light grey (#d8d4cf) ground just under it, the light turned into a sun, a dark blue (#1c385c) background. Render 640 by 360 pixels and write render.png in your working directory.

Antes de escribir, el modelo llamó a get_state sobre blender: la escena predeterminada, con los objetos Cube, Light y Camera, el motor BLENDER_EEVEE_NEXT y los fotogramas de 1 a 250. Escribió su script para lo que había: reutilizó el cubo y la luz, y conservó la cámara.

Script escrito

Un modelo, Claude Sonnet 5.5, escribió este script a partir de la guía y de ese estado. No es un script tomado de las pruebas del proyecto. Los colores se convierten de sRGB a lineal antes de llegar al material y al mundo, porque Blender los almacena en lineal.

python · Script escrito
import os

import bpy


def to_linear(value):
    return value / 12.92 if value <= 0.04045 else ((value + 0.055) / 1.055) ** 2.4


def colour(code):
    red, green, blue = (int(code[i:i + 2], 16) / 255 for i in (1, 3, 5))
    return (to_linear(red), to_linear(green), to_linear(blue), 1.0)


def material(name, code):
    item = bpy.data.materials.new(name)
    item.use_nodes = True
    item.node_tree.nodes["Principled BSDF"].inputs["Base Color"].default_value = colour(code)
    return item


scene = bpy.context.scene
cube = bpy.data.objects["Cube"]
cube.data.materials.clear()
cube.data.materials.append(material("Copper", "#c25438"))

ground_mesh = bpy.data.meshes.new("Ground")
ground_mesh.from_pydata([(-6, -6, -1), (6, -6, -1), (6, 6, -1), (-6, 6, -1)], [], [(0, 1, 2, 3)])
ground_mesh.update()
ground = bpy.data.objects.new("Ground", ground_mesh)
scene.collection.objects.link(ground)
ground.data.materials.append(material("Grey", "#d8d4cf"))

light = bpy.data.objects["Light"]
light.data.type = "SUN"
light.data.energy = 4.0
light.rotation_euler = (0.9, 0.2, 0.6)

world = scene.world or bpy.data.worlds.new("World")
scene.world = world
world.use_nodes = True
world.node_tree.nodes["Background"].inputs["Color"].default_value = colour("#1c385c")

scene.render.resolution_x = 640
scene.render.resolution_y = 360
scene.render.filepath = os.path.join(LMM_WORK_DIR, "render.png")
bpy.ops.render.render(write_still=True)
result = {"objects": [item.name for item in scene.objects], "png_bytes": os.path.getsize(scene.render.filepath)}

Respuesta del Hub

Respuesta del Hub
{
  "app": "blender",
  "duration_ms": 29947,
  "error": null,
  "result": {
    "objects": [
      "Ground",
      "Cube",
      "Light",
      "Camera"
    ],
    "png_bytes": 216713
  },
  "status": "ok",
  "stderr": "",
  "stdout": "",
  "truncated": false
}

status es ok, y duration_ms vale 29 947: el primer renderizado en un perfil nuevo tardó 30 s, como dice la guía. El renderizado siguiente, en el mismo Blender, tardó 149 ms (más abajo). Con el plazo predeterminado del Hub de 30 s, la misma llamada puede acabar en cambio en timeout, como registra la página de compatibilidad.

Lo que mostró el primer renderizado

La respuesta no dice si los colores son correctos. Leído en el PNG, el píxel de arriba a la izquierda, que es el mundo, era #103759, donde el script había fijado #1c385c. Un segundo script leyó la gestión del color de la escena:

python · Script escrito
import bpy

view = bpy.context.scene.view_settings
result = {"view_transform": view.view_transform, "look": view.look, "exposure": view.exposure, "gamma": view.gamma, "engine": bpy.context.scene.render.engine}
Respuesta del Hub
{
  "app": "blender",
  "duration_ms": 0,
  "error": null,
  "result": {
    "engine": "BLENDER_EEVEE_NEXT",
    "exposure": 0.0,
    "gamma": 1.0,
    "look": "None",
    "view_transform": "AgX"
  },
  "status": "ok",
  "stderr": "",
  "stdout": "",
  "truncated": false
}

La escena usa la transformación de vista AgX, la predeterminada de este Blender, que cambia los colores en el camino hacia el PNG. La transformación Standard deja un color uniforme tal como está.

El script corregido y su respuesta

Solo cambió lo que falló: el script fija la transformación de vista en Standard y renderiza de nuevo.

python · Script escrito
import os

import bpy

scene = bpy.context.scene
scene.view_settings.view_transform = "Standard"
scene.render.filepath = os.path.join(LMM_WORK_DIR, "render.png")
bpy.ops.render.render(write_still=True)
result = {"view_transform": scene.view_settings.view_transform, "png_bytes": os.path.getsize(scene.render.filepath)}
Respuesta del Hub
{
  "app": "blender",
  "duration_ms": 149,
  "error": null,
  "result": {
    "png_bytes": 210434,
    "view_transform": "Standard"
  },
  "status": "ok",
  "stderr": "",
  "stdout": "",
  "truncated": false
}

El píxel de arriba a la izquierda se lee ahora #1C385C, el color que se había pedido.

La imagen producida

Este es el renderizado, de 640 por 360 píxeles. El cubo y el suelo están iluminados por el sol, así que sus píxeles no son los colores de sus materiales: la cara superior del cubo se lee #A84A39 para un material fijado en #c25438, y el suelo se lee #BEBEC1 para #d8d4cf.

Un cubo de cobre sobre un suelo gris claro frente a un fondo azul oscuro.
render.png tras el script corregido, renderizado en EEVEE con la transformación de vista Standard.

Guía de referencia en el repositorio →

SiguienteInkscape →