evals/evals.json
[
{ "query": "Export the scene as glTF for the web", "should_trigger": true },
{ "query": "Save this as FBX for Unity", "should_trigger": true },
{ "query": "Output an OBJ with materials", "should_trigger": true },
{ "query": "Export to USDZ for Apple AR", "should_trigger": true },
{ "query": "Save as STL for 3D printing", "should_trigger": true },
{ "query": "Export as GLB with embedded textures", "should_trigger": true },
{ "query": "Apply Decimate modifier before exporting glTF", "should_trigger": true },
{ "query": "Make the model under 10 MB GLB", "should_trigger": true },
{ "query": "Export with Y-up axis orientation for Unreal", "should_trigger": true },
{ "query": "Export the animated rig as FBX with bake_anim", "should_trigger": true },
{ "query": "Add a beveled cube", "should_trigger": false, "rationale": "modeling" },
{ "query": "Apply gold material", "should_trigger": false, "rationale": "materials" },
{ "query": "Light the scene", "should_trigger": false, "rationale": "lighting" },
{ "query": "Render to PNG", "should_trigger": false, "rationale": "rendering" },
{ "query": "Animate the object", "should_trigger": false, "rationale": "animation" },
{ "query": "Move the camera", "should_trigger": false, "rationale": "cameras" },
{ "query": "What's the max polycount for mobile?", "should_trigger": false, "rationale": "knowledge" },
{ "query": "Compare glTF vs FBX advantages", "should_trigger": false, "rationale": "knowledge/comparison" },
{ "query": "How do I write a custom exporter addon?", "should_trigger": false, "rationale": "addon development" },
{ "query": "Convert FBX to glTF without using Blender", "should_trigger": false, "rationale": "external pipeline" }
]
references/overview.md
# Import / Export — Pro Knowledge Overview
**Domain**: 14 — glTF, FBX, OBJ, USD, STL, optimization for target platform
**Status**: Initial pass complete
**Last update**: 2026-04-27
---
## Decision tree — which format for which target?
```
Where is this asset going?
├── Game engine (Unity, Unreal, Godot)
│ ├── Animated/rigged → FBX
│ ├── Static/mesh-only → OBJ or FBX
│ └── Modern engines → glTF (increasingly preferred)
│
├── Web (Three.js, Babylon.js, A-Frame, model-viewer)
│ └── glTF / GLB (only sensible choice)
│
├── AR (USDZ for Apple, glTF for Android)
│ └── USDZ (iOS) or glTF (Android)
│
├── 3D Printing
│ └── STL (geometry only, watertight required)
│
├── VFX pipeline (Maya, Houdini, Nuke, USD-based)
│ └── USD (Universal Scene Description)
│
├── DCC tool roundtrip
│ └── FBX (industry standard) or .blend (best for Blender↔Blender)
│
└── Unsure / generic
└── glTF (open spec, modern, well-supported)
```
---
## glTF / GLB — the modern web/AR standard
**Already covered in detail** in `wireframe-to-3d/references/best-practices.md`. Quick reminders:
- **GLB** = single binary file (preferred); **glTF** = JSON + .bin + textures (debug-friendly).
- **Material support**: only Principled BSDF exports cleanly. Procedural shaders must be baked.
- **Hard cap**: 15 MB; soft target 8 MB.
- **No KTX2** unless you load extra Three.js KTX2Loader; no Draco unless you load DRACOLoader.
```python
import bpy
bpy.ops.export_scene.gltf(
filepath='/tmp/output.glb',
export_format='GLB',
export_apply=True, # apply modifiers
export_materials='EXPORT',
export_image_format='AUTO', # PNG; AUTO falls back to JPEG for opaque
export_yup=True, # Y-up convention (most engines expect this)
export_animations=False, # toggle on for animated models
export_morph=True, # shape keys
export_skins=True, # armatures + weights
)
```
---
## FBX — the industry-standard DCC format
**Strengths**: Universal in animation/VFX. Carries skeletons, animations, materials reasonably.
**Weaknesses**: Proprietary (Autodesk). Texture/material handling is flaky between tools. Animation linking can break.
```python
import bpy
bpy.ops.export_scene.fbx(
filepath='/tmp/output.fbx',
use_selection=False,
apply_unit_scale=True,
apply_scale_options='FBX_SCALE_ALL',
bake_space_transform=True, # critical: applies rotation to mesh
object_types={'MESH', 'ARMATURE', 'EMPTY'},
use_mesh_modifiers=True,
mesh_smooth_type='FACE', # 'FACE' or 'EDGE' or 'OFF'
use_subsurf=False, # apply SubSurf or skip; usually apply
use_armature_deform_only=True, # only export deform bones
bake_anim=True,
bake_anim_use_all_bones=True,
bake_anim_use_nla_strips=True,
bake_anim_use_all_actions=True,
bake_anim_force_startend_keying=True,
bake_anim_step=1.0,
bake_anim_simplify_factor=1.0,
embed_textures=True, # critical: embed PNG into FBX
path_mode='COPY', # textures copied next to FBX
axis_forward='-Z',
axis_up='Y',
)
```
### FBX gotchas (the "why is this broken" list)
| Issue | Cause | Fix |
|-------|-------|-----|
| Model rotated 90° in Unity | Blender Z-up, Unity Y-up | Set `axis_up='Y'`, `axis_forward='-Z'` |
| Model 100× too large | Unit scale mismatch | Apply Transform on object, set unit scale to 1 |
| Textures missing | Not embedded | `embed_textures=True, path_mode='COPY'` |
| Animation only plays one action | FBX limitation: one default action | Use NLA + bake actions |
| Materials look wrong in target | Principled BSDF doesn't fully translate | Bake textures manually before export |
| Bone count exceeds limit | Game engine bone caps | Limit weights to 4 per vertex |
---
## OBJ — the simple, universal format
**Strengths**: Everything reads it. Stores geometry + UVs + simple materials (.mtl sidecar).
**Weaknesses**: No animation. No rigging. Materials limited (basic Phong shading).
```python
import bpy
bpy.ops.wm.obj_export(
filepath='/tmp/output.obj',
export_animation=False,
export_selected_objects=False,
apply_modifiers=True,
export_eval_mode='DAG_EVAL_VIEWPORT', # use viewport state of modifiers
export_uv=True,
export_normals=True,
export_materials=True,
export_triangulated_mesh=False,
export_colors=False,
export_smooth_groups=False,
forward_axis='NEGATIVE_Z',
up_axis='Y',
)
```
**Use OBJ for**: simple model exchange, 3D scanning output, generic geometry transfer where you control both ends.
---
## USD — the future of pipeline interchange
**Strengths**: Pixar's standard. Composition (layers, references, variants). Massive scenes. Multi-DCC native.
**Weaknesses**: Complex setup. Limited Blender material translation.
```python
import bpy
bpy.ops.wm.usd_export(
filepath='/tmp/scene.usdc',
export_animation=True,
export_hair=False,
export_uvmaps=True,
export_normals=True,
export_materials=True,
use_instancing=True,
export_textures=True,
overwrite_textures=True,
)
```
**USD variants**:
- `.usd` — text-based (debuggable, large)
- `.usdc` — binary (compact, fast)
- `.usda` — ASCII (human-readable, larger)
- `.usdz` — zipped USD with all assets (Apple AR/iOS standard)
---
## STL — for 3D printing
```python
import bpy
bpy.ops.wm.stl_export(
filepath='/tmp/output.stl',
ascii_format=False, # binary STL (smaller, faster)
apply_modifiers=True,
)
```
**STL pitfalls**:
- ❌ STL has NO units. Most slicers assume mm.
- ❌ STL has NO color/material — always single-color grey.
- ❌ Mesh must be **watertight** (no holes, no flipped normals, no internal faces).
- ✅ Pre-export check: `Mesh → Clean Up → Make Manifold` in Edit Mode.
---
## Asset Browser (production studio workflow)
The Asset Browser is Blender's library system — share materials, models, brushes, node groups across projects.
### Mark something as an asset
```python
import bpy
obj = bpy.data.objects['GEO-character']
obj.asset_mark() # makes it appear in Asset Browser
obj.asset_data.tags.new('character')
obj.asset_data.tags.new('hero')
obj.asset_data.description = 'Main character with full rig'
obj.asset_generate_preview() # auto-render thumbnail
```
### Asset library locations
Configure in Preferences → File Paths → Asset Libraries. Each library is a folder of `.blend` files; all marked assets across them appear in the browser.
### Linked vs Appended assets
| | Linked | Appended |
|---|--------|---------|
| File size | Tiny (just reference) | Full embed |
| Updates from source | Yes (auto) | No (snapshot) |
| Editable | Read-only (use override) | Yes |
| Use case | Studio asset library | One-off use, modify freely |
### Library Overrides
Linked assets are read-only. **Library Override** creates an editable proxy:
- Mesh, materials, modifiers stay linked (source updates propagate)
- Position, scale, custom properties become editable per-instance
- Best of both: shared source + per-shot tweaks
```python
# Make selected linked object an override
bpy.ops.object.make_override_library()
```
---
## Production pipeline patterns
### Pattern A: Single-artist project
```
project/
├── assets/
│ ├── characters/
│ │ └── hero.blend (with marked assets)
│ ├── environments/
│ │ └── forest.blend
│ └── props/
├── shots/
│ ├── shot01.blend (links from assets/)
│ └── shot02.blend
└── render/
├── shot01/
└── shot02/
```
Edit assets in their .blend; shots link them. Update propagates.
### Pattern B: Studio with version control
- Asset .blend files live in Git LFS or Perforce.
- Shots link assets via relative paths.
- Library Overrides per-shot for tweaks.
- Render outputs go to network storage (NAS or cloud).
### Pattern C: USD-based pipeline
- Each department exports USD layers.
- Shots compose layers via USD composition.
- Materials, animation, lighting, FX in separate USDs.
- Final composite in USD-aware renderer (Karma, Renderman, Cycles via Hydra).
---
## Common pitfalls
| Mistake | Why | Fix |
|---------|-----|-----|
| Exported FBX has no textures | Forgot embed | `embed_textures=True` |
| Game engine bone count exceeded | Too many weights per vertex | `Limit Total → 4` in vertex groups |
| glTF material looks different | Procedural shader didn't export | Bake to image textures first |
| OBJ won't import elsewhere | UTF-8 vs ASCII issue | Stick to ASCII filenames |
| STL won't print (slicer error) | Non-manifold geometry | Make Manifold, recompute normals |
| USD imports broken | Material translation lossy | Use shared MaterialX or PBR-only materials |
| Linked asset doesn't update | Path is absolute | Use relative paths (`//assets/...`) |
| Forgot to set animation export | Static mesh only | Toggle `export_animations=True` for glTF, `bake_anim=True` for FBX |
---
## Sources
- [glTF 2.0 — Blender 5.1 Manual](https://docs.blender.org/manual/en/latest/addons/import_export/scene_gltf2.html)
- [FBX — Blender 5.1 Manual](https://docs.blender.org/manual/en/latest/addons/import_export/scene_fbx.html)
- [Asset Browser — Blender 5.1 Manual](https://docs.blender.org/manual/en/latest/editors/asset_browser.html)
- [Library Overrides — Blender Developer Docs](https://developer.blender.org/docs/features/core/overrides/library/functional_design/)
- [Fixie — STL vs OBJ vs FBX vs STEP comparison](https://www.fixie3d.com/fixie-blog/2025/9/25/stl-vs-obj-vs-fbx-vs-step-best-export-format-for-revit-rhino-amp-blender-watertight-guide)
- [Alpha3D — Mastering FBX in Blender for game development](https://www.alpha3d.io/kb/3d-modelling/blender-fbx/)
- [Tripo3D — Multi-format export strategy: FBX, OBJ, GLB, USDZ, USD](https://www.tripo3d.ai/blog/explore/multi-format-export-strategy-fbx-obj-glb-usdz-usd)
- [Blender Studio — Asset Browser Fundamentals 4.5 LTS](https://studio.blender.org/training/blender-fundamentals-45-lts/blender_4-5_lts_asset-browser/)
- [Khronos glTF 2.0 Spec](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0)
---
## Outstanding
- [ ] Unreal Engine specific export checklist
- [ ] Unity-specific quirks (axis conversion, scale)
- [ ] USDZ for Apple AR (specific texture limits)
- [ ] Roundtrip workflow: Blender → Substance → back
SKILL.md
---
name: blender-export
description: Export Blender scenes to glTF/GLB (web/AR), FBX (game engines), OBJ (universal), USD (VFX pipelines), STL (3D printing). Includes per-format settings, embed/unpack textures, axis conversion, polygon optimization (Decimate), and target-platform validation. Use whenever the user asks to "export this", "save as glTF / FBX / OBJ / STL / USD", "package for Unity / Unreal / Three.js / web / AR / 3D print", or any output format conversion. Make sure to use this skill even if the user does not say "export" — also covers "package this for the web", "make it work in Unity", "send to Unreal", "save for 3D printing".
when_to_use: Any export to a non-.blend format, packaging for game engines, web, AR, or 3D printing.
allowed-tools: Read Bash mcp__blender__execute_blender_code mcp__blender__get_scene_info mcp__blender__get_object_info
---
# Blender Export
Export to the right format with the right settings. Wrong format choice = days of debugging in the target platform.
## Format decision tree
```
Where is this going?
├── Web (Three.js, Babylon.js, model-viewer, AR Quick Look) → glTF / GLB
├── Game engine (Unity, Unreal, Godot)
│ ├── Animated/rigged → FBX (or glTF for modern engines)
│ └── Static → OBJ or FBX or glTF
├── Apple AR (USDZ) → USDZ (special, see Recipe 6)
├── 3D printing → STL (geometry only, must be watertight)
├── VFX pipeline (Maya, Houdini, Nuke) → USD
└── DCC roundtrip → FBX (industry standard)
```
**Quick rule for unknown target**: glTF / GLB. Open standard, modern, universally supported.
## Recipes
### Recipe 1 — glTF / GLB export (web / AR / general)
```python
import bpy
bpy.ops.export_scene.gltf(
filepath='/tmp/output.glb',
export_format='GLB', # single-file binary; preferred
export_apply=True, # apply modifiers before export
export_materials='EXPORT',
export_image_format='AUTO', # PNG; AUTO falls back to JPEG for opaque images
export_yup=True, # Y-up convention (most engines / web expect this)
export_animations=True, # toggle off for static models
export_morph=True, # shape keys
export_skins=True, # armatures + weights
export_normals=True,
export_tangents=False, # skip unless target uses tangent-space normals beyond standard
)
# Verify
import os
size_mb = os.path.getsize('/tmp/output.glb') / (1024 * 1024)
print(f"export:gltf {size_mb:.2f} MB")
```
**glTF caveats**:
- Only Principled BSDF materials export cleanly. Procedural shaders are dropped or simplified.
- Hard cap: 15 MB; soft target: 8 MB.
- No KTX2 / Draco compression (unless target supports those loaders).
- PNG textures only (max 1024×1024 typical).
### Recipe 2 — Decimate before export (if too large)
```python
import bpy
obj = bpy.data.objects['GEO-target']
bpy.context.view_layer.objects.active = obj
mod = obj.modifiers.new('Decimate', type='DECIMATE')
mod.ratio = 0.7 # keep 70% of faces; lower = more reduction
mod.use_collapse_degenerate = True
bpy.ops.object.modifier_apply(modifier=mod.name)
print(f"decimated:{obj.name} verts:{len(obj.data.vertices)}")
```
Then re-export. Iterate ratio until file size fits target.
### Recipe 3 — FBX export (game engines)
```python
import bpy
bpy.ops.export_scene.fbx(
filepath='/tmp/output.fbx',
use_selection=False,
apply_unit_scale=True,
apply_scale_options='FBX_SCALE_ALL',
bake_space_transform=True, # critical: applies rotation to mesh
object_types={'MESH', 'ARMATURE', 'EMPTY'},
use_mesh_modifiers=True,
mesh_smooth_type='FACE',
use_armature_deform_only=True,
bake_anim=True,
bake_anim_use_all_bones=True,
bake_anim_use_nla_strips=True,
bake_anim_use_all_actions=True,
bake_anim_force_startend_keying=True,
embed_textures=True, # critical: embed textures into FBX
path_mode='COPY',
axis_forward='-Z',
axis_up='Y',
)
print('export:fbx')
```
**Common FBX gotchas**:
- Model rotated 90° in target → check `axis_up='Y', axis_forward='-Z'`
- Model 100× too large → Apply Transform on the object before export
- Textures missing → `embed_textures=True, path_mode='COPY'`
- Animation only plays one action → use NLA + bake all actions
### Recipe 4 — OBJ export (simple / universal)
```python
import bpy
bpy.ops.wm.obj_export(
filepath='/tmp/output.obj',
export_animation=False,
apply_modifiers=True,
export_eval_mode='DAG_EVAL_VIEWPORT',
export_uv=True,
export_normals=True,
export_materials=True,
export_triangulated_mesh=False,
forward_axis='NEGATIVE_Z',
up_axis='Y',
)
print('export:obj')
```
OBJ has no animation, no rigging, basic material support only. Use for simple geometry exchange.
### Recipe 5 — STL export (3D printing)
```python
import bpy
bpy.ops.wm.stl_export(
filepath='/tmp/output.stl',
ascii_format=False, # binary STL (smaller, faster)
apply_modifiers=True,
)
print('export:stl')
```
**Critical for STL**:
- Mesh must be **watertight** (no holes, no flipped normals, no internal faces).
- Pre-export: in Edit Mode, run `Mesh → Clean Up → Make Manifold`.
- STL has NO units — most slicers assume mm. Set Blender scene units to mm before modeling.
- STL has NO color/material — single-color grey only.
### Recipe 6 — USD export (VFX pipeline)
```python
import bpy
bpy.ops.wm.usd_export(
filepath='/tmp/scene.usdc',
export_animation=True,
export_uvmaps=True,
export_normals=True,
export_materials=True,
use_instancing=True,
export_textures=True,
overwrite_textures=True,
)
print('export:usd')
```
USD variants:
- `.usd` — text-based (debuggable, large)
- `.usdc` — binary (compact, fast — **default choice**)
- `.usda` — ASCII (human-readable, larger)
- `.usdz` — zipped USD with all assets (Apple AR / iOS)
### Recipe 7 — Pre-export checklist (run before any export)
```python
import bpy
# 1. Apply transforms (rotation + scale baked into geometry)
obj = bpy.data.objects['GEO-target']
bpy.context.view_layer.objects.active = obj
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
# 2. Recompute normals
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.normals_make_consistent(inside=False)
bpy.ops.mesh.remove_doubles(threshold=0.0001)
bpy.ops.object.mode_set(mode='OBJECT')
# 3. Apply modifiers (some exporters keep them, but for portability apply before export)
# Already done via export_apply=True / use_mesh_modifiers=True flags
# 4. Verify mesh stats
mesh = obj.data
print(f"export_check:{obj.name} verts:{len(mesh.vertices)} faces:{len(mesh.polygons)}")
```
### Recipe 8 — Verify exported file
```python
import os
filepath = '/tmp/output.glb'
if not os.path.exists(filepath):
print(f"ERROR:not_found {filepath}")
else:
size_mb = os.path.getsize(filepath) / (1024 * 1024)
print(f"verified:{filepath} {size_mb:.2f}MB")
```
Always verify after export. Use `Bash` tool: `ls -la /tmp/output.glb`.
## Polycount targets per platform
| Platform | Target | Notes |
|----------|--------|-------|
| Web (glTF) | ≤ 30 000 tris | Mobile-safe |
| Hero web asset | ≤ 60 000 tris | Desktop OK |
| Unity / Unreal hero | 50 000–100 000 tris | High-end |
| Mobile game | ≤ 10 000 tris | Per asset |
| AR USDZ | ≤ 50 000 tris | iOS recommendation |
| 3D print | unlimited | But export size matters |
## Common pitfalls
| Symptom | Fix |
|---------|-----|
| FBX has no textures | Set `embed_textures=True, path_mode='COPY'` |
| Procedural material missing in glTF | Bake to image textures first; use only Principled BSDF |
| Game engine: model rotated 90° | FBX: `axis_up='Y', axis_forward='-Z'`; glTF: `export_yup=True` |
| Game engine: model 100× too large | Apply Transform; check unit scale |
| OBJ won't import elsewhere | Stick to ASCII filenames |
| STL won't print | Make Manifold; recompute normals |
| GLB > 15 MB | Apply Decimate (Recipe 2); reduce textures to 1024×1024 |
| Bone count exceeded | Limit weights to 4 per vertex; reduce bone count |
| Animation didn't export | glTF: `export_animations=True`; FBX: `bake_anim=True` |
## When to load `references/overview.md`
Load when:
- Multi-format batch export needed
- Asset Browser / library override workflow
- USDZ for Apple AR specifics
- Game engine roundtrip troubleshooting (Unity/Unreal-specific quirks)
- LOD generation strategy
The reference covers: per-format pitfalls, asset browser workflow, library overrides for production, USD composition, polycount targets per platform.