Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions data/tool-guides/aesthetic-quality-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "Aesthetic Quality & Stylistic Coherence Guide"
category: "scene-design"
tags: ["aesthetic", "quality", "style", "decorative", "detail", "execute_code", "procedural", "torch", "lantern", "ornament", "medieval", "modern", "furniture"]
description: "Principles for creating visually convincing, aesthetically rich 3D scenes. Prevents minimalistic primitive-based shortcuts and teaches the agent to match object detail to scene context using execute_code for complex geometry."
description: "Principles for creating visually convincing, aesthetically rich 3D scenes. Prevents minimalistic primitive-based shortcuts and teaches the agent to match object detail to scene context while preferring direct tools before execute_code fallbacks."
blender_version: "5.0+"
---

Expand Down Expand Up @@ -35,15 +35,18 @@ Every named object in the scene should have AT LEAST:
- **Architectural features:** Follow the Architectural Completeness Guide
- **Organic shapes:** Use subdivision + proportional editing, never raw primitives

### When to Use `execute_code` Instead of Basic Tools
Use `execute_code` with Python scripting for ANY object that:
### When to Use Direct Tools First

Use direct tools first for repeatable operations: transforms, materials, lighting, cameras, render settings, collection organization, parenting, modifiers, constraints, mesh validation, and viewport verification.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Use `execute_code` only when direct tools cannot express the required custom geometry, such as an object that:
1. Has **irregular or organic shapes** (flames, plants, terrain, fabric)
2. Requires **multi-part assembly** (torch = bracket + handle + flame)
3. Needs **vertex-level manipulation** (tapering, bending, sculpting)
4. Benefits from **loop cuts or edge detail** (beveled edges, chamfers)
5. Involves **array/curve modifiers** for repeated elements (chain links, bricks, spiral stairs)

**Rule:** If you catch yourself making a single `create_cube` or `create_cylinder` call for a decorative object, STOP and use `execute_code` to build it properly.
**Rule:** If you catch yourself making a single `create_cube` or `create_cylinder` call for a decorative object, STOP and decompose it into a multi-part assembly. Use direct tools for the parts they cover, and use a focused `execute_code` fallback only for the custom geometry that remains.

## STYLISTIC COHERENCE

Expand Down
13 changes: 2 additions & 11 deletions data/tool-guides/architectural-completeness-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,8 @@ bpy.data.objects.remove(cutter)
# Glass pane fills the window opening
glass = create_cube("Window_Glass", 1, (-2.0, 0, 1.4), (0.02, 1.9, 1.1))

# EEVEE glass material via execute_code
mat = bpy.data.materials.new("Glass_Mat")
mat.use_nodes = True
mat.blend_method = 'BLEND' # Required for transparency in 5.x
nodes = mat.node_tree.nodes
bsdf = nodes["Principled BSDF"]
bsdf.inputs["Transmission Weight"].default_value = 0.95
bsdf.inputs["IOR"].default_value = 1.5
bsdf.inputs["Roughness"].default_value = 0.0
bsdf.inputs["Base Color"].default_value = (0.9, 0.95, 1.0, 1.0)
glass.data.materials.append(mat)
# Then call create_material_preset with preset "glass" and object_name "Window_Glass".
# Follow with inspect_material_node_graph to verify Transmission Weight, IOR, and assignment.
```

**EEVEE Note:** For interior scenes, using a Transparent shader instead of Glass may prevent light bleed artifacts if exact refraction isn't needed.
Expand Down
3 changes: 2 additions & 1 deletion data/tool-guides/local-asset-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ Choose PolyHaven when:
- the local library does not contain a suitable match

Keep the workflow hybrid:
- use direct tools and `execute_code` for room structure, spatial layout, cameras, lights, and custom hero forms
- use direct tools for room structure, spatial layout, cameras, lights, transforms, and render setup
- reserve `execute_code` for unsupported custom hero forms or procedural geometry that direct tools cannot express
- use local curated assets for commodity props that benefit from a prepared silhouette
- after import, always treat scale and orientation as suspect until checked in scene context

Expand Down
23 changes: 3 additions & 20 deletions data/tool-guides/object-assembly-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,26 +209,9 @@ wheel_positions = [
### When to Parent
- **Always parent** when the object should move/rotate as one unit
- **Use an Empty as root** for easy manipulation of the whole assembly
- **Parent in Python** using `parent_set` tool or `execute_code`
- **Prefer direct tools**: use `parent_set` for parent-child links and `organize_collection_hierarchy` when grouping, collection movement, parenting, and visibility changes belong in one batch

### Parenting in execute_code
```python
import bpy

# Create root empty
bpy.ops.object.empty_add(type='PLAIN_AXES', location=(0, 0, 0))
root = bpy.context.active_object
root.name = "Lamp_Root"

# Parent all parts to root (keep visual position)
for part_name in ["Lamp_Pole", "Lamp_Arm", "Lamp_Head", "Lamp_Base"]:
part = bpy.data.objects.get(part_name)
if part:
part.parent = root
part.matrix_parent_inverse = root.matrix_world.inverted()
```

**Key:** Setting `matrix_parent_inverse` to the parent's inverted world matrix prevents the child from "jumping" to a new position when parented.
`parent_set` and `organize_collection_hierarchy` preserve world transforms so child objects do not visually jump when parented. Use `execute_code` only if the root helper object itself must be procedurally generated and no direct creation tool covers it.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
### Using parent_set MCP Tool
```
Expand All @@ -252,5 +235,5 @@ After assembling a compound object, verify:
3. ❌ **Forgetting that cylinder depth is FULL height, not half** — A `depth=3.5` cylinder extends 1.75m above and below its origin
4. ❌ **Not accounting for object radius in side attachments** — An arm touching a pole must offset by `pole_radius + arm_radius`, not just `arm_length/2`
5. ❌ **Hardcoding heights instead of calculating from connected parts** — If you change the pole height later, all attachment points break
6. ❌ **Parenting without setting `matrix_parent_inverse`** — Child objects will visually jump to unexpected positions
6. ❌ **Hand-writing parenting logic when direct tools cover it** — Use `parent_set` or `organize_collection_hierarchy` so world transforms are preserved and child objects do not visually jump
7. ❌ **Building top-down** — Always start from the ground up to prevent accumulated positioning errors
28 changes: 28 additions & 0 deletions scripts/test/test-direct-tool-guide-cleanup-secondary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import assert from "node:assert/strict"
import { readFileSync } from "node:fs"

const guides = {
architecturalCompleteness: readFileSync("data/tool-guides/architectural-completeness-guide.md", "utf8"),
localAsset: readFileSync("data/tool-guides/local-asset-guide.md", "utf8"),
objectAssembly: readFileSync("data/tool-guides/object-assembly-guide.md", "utf8"),
aestheticQuality: readFileSync("data/tool-guides/aesthetic-quality-guide.md", "utf8"),
}

assert.doesNotMatch(guides.architecturalCompleteness, /glass material via execute_code/i)
assert.match(guides.architecturalCompleteness, /create_material_preset/)
assert.match(guides.architecturalCompleteness, /inspect_material_node_graph/)

assert.doesNotMatch(guides.localAsset, /use direct tools and `execute_code` for room structure, spatial layout, cameras, lights/)
assert.match(guides.localAsset, /direct tools for room structure, spatial layout, cameras, lights/)

assert.doesNotMatch(guides.objectAssembly, /Parent in Python/)
assert.doesNotMatch(guides.objectAssembly, /Parenting in execute_code/)
assert.doesNotMatch(guides.objectAssembly, /matrix_parent_inverse/)
assert.match(guides.objectAssembly, /parent_set/)
assert.match(guides.objectAssembly, /organize_collection_hierarchy/)

Comment thread
coderabbitai[bot] marked this conversation as resolved.
assert.doesNotMatch(guides.aestheticQuality, /using execute_code for complex geometry/)
assert.doesNotMatch(guides.aestheticQuality, /Use `execute_code` with Python scripting for ANY object/)
assert.match(guides.aestheticQuality, /Use direct tools first/)

console.log("Secondary direct tool guide cleanup tests passed")
Loading