Sculpt job

A sculpt job (pipeline/sculpt/llm_blender.js, sculptWithLlm) is the LLM-driven build loop: prompt → the model writes bpy code → execute through the Blender session → iterate → export GLB → run the verification gate. The model is the brain; every execution still goes through the Blender MCP layer, and the model key comes from Skarbiec like every other secret.

Entry points: glina sculpt <prompt> (which Glina Desktop runs too) and MCP tool glina_sculpt.

Definition and shape

Job inputs: prompt, outDir (default assets/models), filename (default: slugified prompt + .glb), maxRounds (the request's value, else llm.maxRounds; with neither the job refuses sculpt needs a round cap: pass --rounds <n> or set llm.maxRounds in the pipeline config before Blender starts). Result: { outPath, verification, rounds } — the CLI strips the transcript from what it prints.

Each model reply must be one JSON object:

{ "thought": "one short sentence", "code": "python bpy code", "done": false }

The reply parser tolerates fences and surrounding prose (first balanced JSON object wins) but a round with no usable JSON fails the job: model reply not usable in round N: <reason>.

The system prompt's execution contract

The model is told, verbatim in spirit (see SYSTEM_PROMPT):

  • each block executes in a fresh namespace containing only bpy — nothing persists between calls except Blender data itself,
  • never use bpy.context for object access; work through bpy.data,
  • never call bpy.ops.wm.read_factory_settings — it kills the bridge,
  • flat shading, material colors only, Thronefall-style chunky low-poly; the triangle budget is named only when verify.triTarget states one,
  • when animations are required: one armature, automatic weights, named actions (idle plus characteristic motions; as many as verify.minAnimationClips states) keyframed over a short range of frames,
  • reply done: true with empty code when finished.

Animations become required in the job when verify.requireAnimations is true or verify.minAnimationClips > 0 — the requirement is injected into the first user message, since the gate will refuse static meshes anyway.

Round loop

  1. Ask the model with llm.maxTokens (required; refused as sculpt needs a reply length: set llm.maxTokens in the pipeline config).
  2. Execute step.code if non-empty. Execution errors are fed back to the model as ERROR: <message> — the model gets a chance to repair its own bug.
  3. Bridge-down rule: after a failed block the bridge is asked for its tools; when it refuses, the run ends with Blender bridge is down: <reason>; the bridge then refused listTools too: <reason> — the model cannot heal a dead bridge.
  4. If the Blender MCP server exposes get_viewport_screenshot, a screenshot is attached to the next round (best-effort; the loop works text-only).
  5. done: true breaks the loop. Running out of rounds fails the job: model did not finish within N rounds.

Export and gate

The scene is exported with exportGlb and then, unless verify.enabled: false, gated with throwOnFail — a failing asset raises asset failed verification: <reasons> and the job fails. The Blender session is closed in a finally either way.