Blender

Un modèle pilote Blender 4.3.2 par le Hub pour construire une scène à partir de celle par défaut et la rendre en PNG. Cette page donne le texte, le script, les réponses du Hub et le rendu, avec l’erreur de couleur qu’une réponse indiquant un succès a cachée, et les 30 secondes du premier rendu.

La scène du logo, construite et rendue par le Hub dans Blender : une coupe passe du rendu au maillage en fil de fer pendant que la caméra tourne autour du noyau.

Démarrer Blender avec son pont

Blender fait tourner le pont dans son propre Python. Démarré en mode arrière-plan, il quitte dès que le fichier du pont a fini de s’exécuter ; un fichier de démarrage donne donc le fil principal au pont avec serve_main_thread(), comme le décrit le guide. L’exécution n’avait aucune 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']()

L’exécution a utilisé le port 9313 dans config.toml et un jeton neuf. Elle a aussi réglé default_timeout_s = 120 dans la section [hub], parce que le premier rendu d’un profil neuf est lent et que le délai par défaut est de 30 s (voir la réponse plus bas). Le port s’est mis à l’écoute 1,3 s après le démarrage, et get_state sur hub a listé Blender comme connecté :

Réponse du 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": []
}

Texte donné au modèle

Texte donné au modèle
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.

Avant d’écrire, le modèle a appelé get_state sur blender : la scène par défaut, avec les objets Cube, Light et Camera, le moteur BLENDER_EEVEE_NEXT et les images d’animation 1 à 250. Il a écrit son script pour ce qui s’y trouvait : il a réutilisé le cube et la lumière, et gardé la caméra.

Script écrit

Un modèle, Claude Sonnet 5.5, a écrit ce script à partir du guide et de cet état. Ce n’est pas un script tiré des tests du projet. Les couleurs sont converties de sRGB en linéaire avant d’arriver au matériau et au monde, parce que Blender les stocke en linéaire.

python · Script écrit
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)}

La réponse du Hub

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

status vaut ok, et duration_ms vaut 29 947 : le premier rendu dans un profil neuf a pris 30 s, comme le dit le guide. Le rendu suivant, dans le même Blender, a pris 149 ms (plus bas). Avec le délai par défaut du Hub, qui est de 30 s, le même appel peut se terminer par timeout, comme le consigne la page de compatibilité.

Ce que le premier rendu a montré

La réponse ne dit pas si les couleurs sont justes. Lu dans le PNG, le pixel en haut à gauche, qui est le monde, valait #103759, alors que le script avait réglé #1c385c. Un second script a lu la gestion des couleurs de la scène :

python · Script écrit
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}
Réponse du 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 scène utilise la transformation de vue AgX, valeur par défaut de ce Blender, qui change les couleurs sur le chemin du PNG. La transformation Standard laisse une couleur unie telle quelle.

Le script corrigé et sa réponse

Seul ce qui a échoué a changé : le script règle la transformation de vue sur Standard et rend de nouveau.

python · Script écrit
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)}
Réponse du Hub
{
  "app": "blender",
  "duration_ms": 149,
  "error": null,
  "result": {
    "png_bytes": 210434,
    "view_transform": "Standard"
  },
  "status": "ok",
  "stderr": "",
  "stdout": "",
  "truncated": false
}

Le pixel en haut à gauche vaut maintenant #1C385C, la couleur demandée.

L’image produite

Voici le rendu, de 640 par 360 pixels. Le cube et le sol sont éclairés par le soleil, de sorte que leurs pixels ne sont pas les couleurs de leurs matériaux : le dessus du cube vaut #A84A39 pour un matériau réglé sur #c25438, et le sol vaut #BEBEC1 pour #d8d4cf.

Un cube cuivre sur un sol gris clair devant un fond bleu foncé.
render.png après le script corrigé, rendu dans EEVEE avec la transformation de vue Standard.

Guide de référence dans le dépôt →

SuiteInkscape →