# Blender пользователя (сцены, материалы, узлы, рендер)

## S0. ГДЕ ИДЁТ ХОД: проверь до всего остального

Машину хода называет блок `<environment>`, а не набор инструментов: `exec_command` бывает и на облачном маршруте.

## S1. ПРАВИЛА

**1. Первый вызов хода: `scene_probe`.** Версия Blender, язык интерфейса, движок и состав сцены известны до первой строки кода, а не угадываются:

В `data`: `blender.version_tuple`, `ui.language` и `ui.use_translate_new_dataname`, `render.engine`, все объекты с типом, трансформом, `world_bbox`, модификаторами и материалами; материалы и мир узлами по `type`/`bl_idname` со связями по `identifier`; коллекции, камеры. Большая сцена: `--max-objects 200` (счёт `object_count` остаётся полным).

**2. Узлы по роду, сокеты по identifier.** Узел ищется по `n.type == "BSDF_PRINCIPLED"` или `n.bl_idname`, создаётся по `bl_idname`, сокет берётся по `identifier`. Имена локализованы: на русском интерфейсе с `use_translate_new_dataname` новый узел получает русское имя, и `nodes["Principled BSDF"]` падает KeyError. Имена сокетов меняются между версиями (Principled переименовал входы в 4.0).

**3. Enum не пиши по памяти.** Идентификаторы меняются между версиями (EEVEE: `BLENDER_EEVEE` до 4.2, `BLENDER_EEVEE_NEXT` в 4.2-4.5, снова `BLENDER_EEVEE` с 5.0; Boolean `FAST` стал `FLOAT` в 5.0). Допустимые значения читаются из живого `bl_rna`:

**4. `material.diffuse_color` красит только вьюпорт.** Цвет в рендере задают входы узлов (`Base Color` у Principled). В срезе поле так и названо: `diffuse_color_viewport_only`.

**6. Маленькие шаги.** Один вызов делает одно изменение и печатает, что сделал. Каждый вызов `blender_exec` через сокет становится одним шагом отмены, и при ошибке тоже: Ctrl+Z пользователя откатывает ровно этот вызов, упавший на десятой строке вместе с первыми девятью (`undo: "pushed"` в ответе; `unavailable` значит, что шаг не лёг, и откатывать нечем). Имя шага задаёт `--label "<что сделано>"`. Правку данных делай в режиме OBJECT: шаг, положенный из режима правки меша, откатывается не так, как ждёшь. Headless шагов отмены не кладёт (`undo: null`). Каждый вызов исполняется в свежем пространстве имён: переменные прошлого вызова не живут.

Человек читает её и приносит недостающее (версию, файл, скриншот окна, настройку аддона).

## S2. ТРИ ПУТИ, ОДНА ДВЕРЬ

На порту 9876 говорят два аддона, дверь понимает оба и на каждом вызове узнаёт ответивший по его кадру (`protocol` в ответе, у blender-mcp ещё `addon`: имя, версия, протокол): сменил пользователь аддон, следующий вызов это уже видит. Как включить сервер аддона, говорит `hint` двери при закрытом порте: пересказывай его, а не память.

Отдельный MCP-сервер (`uvx mcp-for-blender`) не нужен: он только мост stdio → тот же сокет, а дверь говорит с сокетом напрямую. Порт не 9876: `--port`.

### Код в Blender

Коротко: `--code "..."`; в `cmd` heredoc нет, поэтому код кладётся файлом в папку чата (`edit_file`) и передаётся `--file <путь>`. Флаги правки: `--label "<что сделано>"` (имя шага отмены), `--look` (кадр после кода в том же вызове, `data.snapshot`; в headless ракурс `--view`). Ответ: `{status, hint, via, protocol, blender_version, stdout, traceback, data, undo}`. `traceback` со строкой из твоего кода: читай его и `hint`, прежде чем править. Строка stdout, начатая `BLENDER_RU_JSON:`, переезжает разобранной в `data`: так удобно вернуть структуру (`print("BLENDER_RU_JSON:" + json.dumps(d))`, `json` импортируй в коде сам).

Сокет: потолок вызова 180 с (`--timeout`), рукопожатие и ответ делят его. Долгий рендер или тяжёлая операция держит интерфейс пользователя всё это время: предупреди и дроби. Таймаут не значит, что код не исполнился: сначала `scene_probe`, потом решение о повторе.

## S3. СНИМОК

После правки кадр снимает `--look` того же вызова (правило 5). Отдельный вызов снимка нужен, когда правки не было: первый взгляд на сцену, другой ракурс рендером, файл с диска.

- Сокет: 3D-вид открытого окна рисуется offscreen (`GPUOffScreen.draw_view3d`), так что картинка не зависит от того, на переднем ли плане окно; без GPU-контекста запасной ход `screenshot_area`. Ракурс и режим затенения те, что у пользователя в окне.
- Headless (или `--mode render`): Workbench-рендер во временную камеру, наведённую на всю видимую геометрию (`--view iso|front|back|left|right|top`), либо камерой сцены (`--camera scene`). Движок, камера и разрешение сцены возвращаются. Временная камера каждый раз заново охватывает сцену: сдвиг всей сцены целиком на снимке не виден, для положения верь срезу. Workbench показывает цвета вьюпорта (`diffuse_color`) и текстуры, а не узлы Principled: вид материала в рендере проверяй рендером нужного движка через `blender_exec`.
- PNG не больше 1000 px ложится в `$WORK_ROOT/blender/` (путь в `data.path`); в артефакты попадает только с явным `-o имя.png` (копия в `$OUTPUT_ROOT`), и только когда снимок и есть результат для человека.
- С `-o имя.png` рядом ложится `имя.glb`: та же сцена glTF-экспортом (видимые объекты, модификаторы применены, без камер и света). В чате GLB открывается 3D-вьюером, человек вращает модель, а картинка остаётся для мессенджера. Путь в поле `glb`; `glb_error` значит, что выложен только PNG. `--no-glb`, когда результат и есть картинка (кадр рендера, композиция), а не модель.
- Сам `.blend` в `$OUTPUT_ROOT` кладёшь, только если человек попросил исходник: копией, файл пользователя не трогая (правило 9): в headless `--save-as "$OUTPUT_ROOT/имя.blend"`, через сокет `bpy.ops.wm.save_as_mainfile(filepath=..., copy=True)` в коде, путь `$OUTPUT_ROOT` раскрыт шеллом (у Blender этой переменной нет).
- Посмотреть картинку: `analyze` с `kind="describe"` и путём из `data.path`; если путь с компьютера не читается, сначала `copy_from_computer`. `change`: `FIRST`, `CHANGED` (с `ahash_distance`, насколько сдвинулся кадр) или `UNCHANGED` (правило 5).

## S7. ГРАНИЦЫ

*Сначала узнай, где ход и какая версия; меняй маленьким шагом; сверяй числом, потом глазами; упёрся трижды, отдай человеку строку HUMAN_REVIEW.*
