# Blender

Ein Modell steuert Blender 4.3.2 über den Hub, um aus der Standardszene eine Szene zu bauen und als PNG zu rendern. Die Seite nennt den Text, das Skript, die Antworten des Hubs und das gerenderte Bild, samt dem Farbfehler, den eine erfolgreiche Antwort verbarg, und den 30 Sekunden des ersten Renderns.

*Die Szene des Logos, vom Hub in Blender aufgebaut und gerendert: Ein Schnitt wechselt vom gerenderten Bild zum Drahtgitter, während die Kamera um den Kern kreist.*

## Fakten

- Erprobte Version: 4.3.2
- Modus: `tcp`

## Blender mit seiner Brücke starten

Blender führt die Brücke in seinem eigenen Python aus. Im Hintergrund gestartet, beendet es sich, sobald die Brückendatei gelaufen ist; deshalb übergibt eine Boot-Datei der Brücke mit `serve_main_thread()` den Hauptthread, wie der [Leitfaden](https://github.com/RonanDavalan/LibreModular-MCP/blob/main/docs/modules/blender.md) es beschreibt. Der Lauf hatte keine Variable `DISPLAY`.

```sh
blender --background --python boot.py
```

```python
import runpy

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

Der Lauf nutzte in `config.toml` den Port 9313 und ein neues Token. Außerdem setzte er im Abschnitt `[hub]` `default_timeout_s = 120`, weil das erste Rendern eines neuen Profils langsam ist und die Standardfrist 30 s beträgt (siehe die Antwort unten). Der Port lauschte 1,3 s nach dem Start, und `get_state` für `hub` listete Blender als verbunden auf:

**Antwort des Hubs**

```json
{
  "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": []
}
```


## Dem Modell gegebener Text

```text
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.
```


Vor dem Schreiben rief das Modell `get_state` für `blender` auf: die Standardszene mit den Objekten `Cube`, `Light` und `Camera`, der Engine `BLENDER_EEVEE_NEXT` und den Einzelbildern 1 bis 250. Es schrieb sein Skript für das, was vorhanden war: Es nutzte den Würfel und das Licht weiter und behielt die Kamera.

## Geschriebenes Skript

Ein Modell, Claude Sonnet 5.5, schrieb dieses Skript nach dem Leitfaden und nach diesem Zustand. Es ist kein Skript aus den Tests des Projekts. Die Farben werden von sRGB in linear umgerechnet, bevor sie das Material und die Welt erreichen, weil Blender sie linear speichert.

**Geschriebenes Skript**

```python
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)}
```


## Antwort des Hubs

**Antwort des Hubs**

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


`status` ist `ok`, und `duration_ms` ist 29 947: Das erste Rendern in einem neuen Profil dauerte 30 s, wie der Leitfaden sagt. Das nächste Rendern im selben Blender dauerte 149 ms (unten). Mit der Standardfrist des Hubs von 30 s kann derselbe Aufruf stattdessen mit `timeout` enden, wie die Seite [Kompatibilität](https://libremodular.davalan.fr/de/compatibility/index.md#blender) festhält.

## Was das erste gerenderte Bild zeigte

Die Antwort sagt nicht, ob die Farben stimmen. Im PNG gelesen, war das Pixel oben links, also die Welt, `#103759`, wo das Skript `#1c385c` gesetzt hatte. Ein zweites Skript las die Farbverwaltung der Szene:

**Geschriebenes Skript**

```python
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}
```


**Antwort des Hubs**

```json
{
  "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
}
```


Die Szene nutzt die Ansichtstransformation `AgX`, den Standard dieses Blender, die die Farben auf dem Weg zum PNG verändert. Die Transformation `Standard` lässt eine einfarbige Fläche, wie sie ist.

## Das korrigierte Skript und seine Antwort

Nur das, was fehlschlug, änderte sich: Das Skript setzt die Ansichtstransformation auf `Standard` und rendert erneut.

**Geschriebenes Skript**

```python
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)}
```


**Antwort des Hubs**

```json
{
  "app": "blender",
  "duration_ms": 149,
  "error": null,
  "result": {
    "png_bytes": 210434,
    "view_transform": "Standard"
  },
  "status": "ok",
  "stderr": "",
  "stdout": "",
  "truncated": false
}
```


Das Pixel oben links liest sich jetzt `#1C385C`, die Farbe, die verlangt war.

## Erzeugtes Bild

Das ist das gerenderte Bild, 640 × 360 Pixel. Der Würfel und der Boden werden von der Sonne beleuchtet, daher sind ihre Pixel nicht die Farben ihrer Materialien: Die Oberseite des Würfels liest sich `#A84A39` bei einem auf `#c25438` gesetzten Material, und der Boden liest sich `#BEBEC1` für `#d8d4cf`.

![Ein kupferfarbener Würfel auf einem hellgrauen Boden vor einem dunkelblauen Hintergrund.](https://libremodular.davalan.fr/images/applications/blender-cube.png)

*render.png nach dem korrigierten Skript, in EEVEE gerendert mit der Ansichtstransformation Standard.*


> **Grenze**
>
> Nicht ausprobiert: die Brücke aus der Konsole des Arbeitsbereichs Scripting eines geöffneten Blender starten, ein echter Bildschirm, andere Operatoren als das Hinzufügen von Objekten und das Rendern sowie die Engine Workbench, deren Farbfallen der [Leitfaden](https://github.com/RonanDavalan/LibreModular-MCP/blob/main/docs/modules/blender.md) auflistet. Die Ansichtstransformation wurde nur in der Standard-Engine gemessen.

[Referenz-Leitfaden im Repository](https://github.com/RonanDavalan/LibreModular-MCP/blob/main/docs/modules/blender.md)

