diff --git a/data/tool-guides/aesthetic-quality-guide.md b/data/tool-guides/aesthetic-quality-guide.md index 5c3be39..38c311e 100644 --- a/data/tool-guides/aesthetic-quality-guide.md +++ b/data/tool-guides/aesthetic-quality-guide.md @@ -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+" --- @@ -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. + +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 diff --git a/data/tool-guides/architectural-completeness-guide.md b/data/tool-guides/architectural-completeness-guide.md index b40cf32..96aabce 100644 --- a/data/tool-guides/architectural-completeness-guide.md +++ b/data/tool-guides/architectural-completeness-guide.md @@ -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. diff --git a/data/tool-guides/local-asset-guide.md b/data/tool-guides/local-asset-guide.md index 311846d..d53c186 100644 --- a/data/tool-guides/local-asset-guide.md +++ b/data/tool-guides/local-asset-guide.md @@ -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 diff --git a/data/tool-guides/object-assembly-guide.md b/data/tool-guides/object-assembly-guide.md index 23015a1..6757197 100644 --- a/data/tool-guides/object-assembly-guide.md +++ b/data/tool-guides/object-assembly-guide.md @@ -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. ### Using parent_set MCP Tool ``` @@ -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 diff --git a/scripts/test/test-direct-tool-guide-cleanup-secondary.ts b/scripts/test/test-direct-tool-guide-cleanup-secondary.ts new file mode 100644 index 0000000..ee5f477 --- /dev/null +++ b/scripts/test/test-direct-tool-guide-cleanup-secondary.ts @@ -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/) + +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")