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
18 changes: 16 additions & 2 deletions data/tool-guides/export-guide.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
title: "Export & File Format Guide"
category: "export"
tags: ["export", "GLB", "GLTF", "FBX", "OBJ", "STL", "file format", "3D printing", "game engine", "web", "UV", "prepare_uv_layout", "validate_export_readiness", "export_object"]
triggered_by: ["prepare_uv_layout", "validate_export_readiness", "export_object"]
tags: ["export", "GLB", "GLTF", "FBX", "OBJ", "STL", "file format", "3D printing", "game engine", "web", "UV", "prepare_uv_layout", "validate_export_readiness", "export_asset_package", "export_object"]
triggered_by: ["prepare_uv_layout", "validate_export_readiness", "export_asset_package", "export_object"]
description: "Domain knowledge for 3D model export, file format selection, pre-export preparation, and format-specific considerations."
blender_version: "4.0+"
---
Expand Down Expand Up @@ -48,6 +48,20 @@ blender_version: "4.0+"
- Packed images are acceptable for Blender-internal work, but exported interchange assets should still be validated in the target viewer.

### Recommended Tool Sequence
Use `export_asset_package` for normal user-requested exports. It runs UV prep, optional rotation/scale application, readiness validation, export, and expected-file reporting in one deterministic flow:

```text
export_asset_package(
names=["HeroMesh"],
filepath="C:/exports/hero.glb",
file_format="GLB",
uv_mode="preserve_original",
apply_transforms=true
)
```

Use the lower-level sequence only when a validation report needs manual repair:

1. `prepare_uv_layout(names, mode: "preserve_original")`
2. If missing UVs on a generated mesh, `prepare_uv_layout(names, mode: "smart_project")`
3. `validate_export_readiness(names, filepath, file_format)`
Expand Down
146 changes: 146 additions & 0 deletions desktop/assets/vipermesh-addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ def _execute_command_internal(self, command):
"set_visibility": self.set_visibility,
"prepare_uv_layout": self.prepare_uv_layout,
"validate_export_readiness": self.validate_export_readiness,
"export_asset_package": self.export_asset_package,
"export_object": self.export_object,
"list_installed_addons": self.list_installed_addons,
"create_material": self.create_material,
Expand Down Expand Up @@ -5626,6 +5627,151 @@ def validate_export_readiness(
except Exception as e:
return {"error": f"Failed to validate export readiness: {str(e)}"}

def export_asset_package(
self,
names,
filepath,
file_format="GLB",
uv_mode="preserve_original",
apply_transforms=False,
required_uvs=True,
require_materials=False,
check_textures=True,
check_texture_formats=True,
make_dirs=True,
force_export=False,
):
"""Run UV prep, export readiness validation, optional transform apply, and export in one safe step."""
previous_active = bpy.context.view_layer.objects.active
previous_selection = [obj for obj in bpy.context.scene.objects if obj.select_get()]
previous_mode = previous_active.mode if previous_active else "OBJECT"
try:
if not filepath:
return {"error": "filepath is required"}

def coerce_bool(value, default=False):
if value is None:
return default
if isinstance(value, bool):
return value
if isinstance(value, str):
return value.strip().lower() in {"1", "true", "yes", "on"}
return bool(value)

objects, missing, non_mesh = self._resolve_mesh_objects(names)
if missing:
return {"error": f"Object not found: {', '.join(missing)}"}
if not objects:
return {"error": "No mesh objects provided"}

absolute_filepath = bpy.path.abspath(str(filepath))
output_dir = os.path.dirname(absolute_filepath)
if output_dir and not os.path.exists(output_dir):
if coerce_bool(make_dirs, True):
os.makedirs(output_dir, exist_ok=True)
else:
return {"error": f"Output directory does not exist: {output_dir}"}

uv_report = None
mode_key = str(uv_mode or "none").lower().replace("-", "_")
if mode_key not in {"none", "skip", "preserve_original", "smart_project", "lightmap_pack", "pack_existing"}:
return {"error": "uv_mode must be none, preserve_original, smart_project, lightmap_pack, or pack_existing"}
if mode_key not in {"none", "skip"}:
uv_report = self.prepare_uv_layout(
[obj.name for obj in objects],
mode=mode_key,
)
if uv_report.get("error"):
return {"error": uv_report["error"], "uv_report": uv_report}

transform_report = {"applied": False, "objects": []}
if coerce_bool(apply_transforms, False):
if bpy.context.object and bpy.context.object.mode != "OBJECT":
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.select_all(action="DESELECT")
for obj in objects:
obj.select_set(True)
transform_report["objects"].append(obj.name)
bpy.context.view_layer.objects.active = objects[0]
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
transform_report["applied"] = True

validation = self.validate_export_readiness(
[obj.name for obj in objects],
filepath=absolute_filepath,
file_format=file_format,
required_uvs=coerce_bool(required_uvs, True),
require_materials=coerce_bool(require_materials, False),
require_applied_transforms=not transform_report["applied"],
check_textures=coerce_bool(check_textures, True),
check_texture_formats=coerce_bool(check_texture_formats, True),
require_absolute_path=True,
)
if validation.get("error"):
return {"error": validation["error"], "validation": validation, "uv_report": uv_report}

should_export = validation.get("ready", False) or coerce_bool(force_export, False)
if not should_export:
return {
"success": True,
"exported": False,
"filepath": absolute_filepath,
"format": validation.get("format"),
"uv_report": uv_report,
"transform_report": transform_report,
"validation": validation,
"errors": validation.get("errors", []),
"warnings": validation.get("warnings", []),
"next_safe_action": "fix validation errors or call export_asset_package with force_export=true",
}

export_result = self.export_object(
[obj.name for obj in objects],
absolute_filepath,
validation.get("format") or file_format,
)
if export_result.get("error"):
return {
"error": export_result["error"],
"uv_report": uv_report,
"transform_report": transform_report,
"validation": validation,
}

expected_files = validation.get("expected_files") or [absolute_filepath]
existing_files = [path for path in expected_files if os.path.exists(path)]
missing_expected_files = [path for path in expected_files if not os.path.exists(path)]

return {
"success": True,
"exported": True,
"filepath": absolute_filepath,
"format": validation.get("format"),
"exported_objects": export_result.get("exported_objects", []),
"file_size_bytes": export_result.get("file_size_bytes", 0),
"expected_files": expected_files,
"existing_files": existing_files,
"missing_expected_files": missing_expected_files,
"uv_report": uv_report,
"transform_report": transform_report,
"validation": validation,
"warnings": validation.get("warnings", []),
}
except Exception as e:
return {"error": f"Failed to export asset package: {str(e)}"}
finally:
with suppress(Exception):
if bpy.context.object and bpy.context.object.mode != "OBJECT":
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.select_all(action="DESELECT")
for obj in previous_selection:
if obj.name in bpy.data.objects:
obj.select_set(True)
if previous_active and previous_active.name in bpy.data.objects:
bpy.context.view_layer.objects.active = previous_active
if previous_mode != "OBJECT":
bpy.ops.object.mode_set(mode=previous_mode)

def export_object(self, names, filepath, file_format='GLB'):
"""Export selected objects to a file. Supports GLB, GLTF, FBX, OBJ, STL."""
try:
Expand Down
3 changes: 2 additions & 1 deletion docs/blender-mcp-capability-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
## Summary

- Official Blender MCP tools found: 26
- ViperMesh addon commands found: 88
- ViperMesh addon commands found: 89
- Decision: keep ViperMesh addon as the product connector, use official MCP as a benchmark/reference, and build deterministic ViperMesh tools where both sides currently rely on Python.

## Capability Matrix
Expand Down Expand Up @@ -84,6 +84,7 @@
- `duplicate_object`
- `duplicate_object_pattern`
- `execute_code`
- `export_asset_package`
- `export_object`
- `focus_viewport_on_objects`
- `get_all_object_info`
Expand Down
64 changes: 64 additions & 0 deletions lib/ai/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2165,6 +2165,69 @@ const validateExportReadiness = tool(
}
)

const exportAssetPackage = tool(
async ({
names,
filepath,
file_format,
uv_mode,
apply_transforms,
required_uvs,
require_materials,
check_textures,
check_texture_formats,
make_dirs,
force_export,
}: {
names: string[]
filepath: string
file_format?: string
uv_mode?: "none" | "preserve_original" | "smart_project" | "lightmap_pack" | "pack_existing"
apply_transforms?: boolean
required_uvs?: boolean
require_materials?: boolean
check_textures?: boolean
check_texture_formats?: boolean
make_dirs?: boolean
force_export?: boolean
}) =>
executeMcpCommand("export_asset_package", {
names,
filepath,
file_format,
uv_mode,
apply_transforms,
required_uvs,
require_materials,
check_textures,
check_texture_formats,
make_dirs,
force_export,
}),
{
name: "export_asset_package",
description:
"Run a safe export package flow: optional UV preparation, optional rotation/scale transform apply, export readiness validation, export_object, and expected-file reporting. " +
"Prefer this for user-requested GLB/GLTF/FBX/OBJ/STL exports unless the workflow needs manual step-by-step repair.",
schema: z.object({
names: z.array(z.string()).min(1).describe("Mesh object names to export"),
filepath: z.string().describe("Absolute output filepath including extension"),
file_format: z.string().optional().describe("Target format: GLB, GLTF, FBX, OBJ, or STL. Defaults to GLB."),
uv_mode: z
.enum(["none", "preserve_original", "smart_project", "lightmap_pack", "pack_existing"])
.optional()
.describe("Optional UV preparation mode before validation/export. Defaults to preserve_original."),
apply_transforms: z.boolean().optional().describe("Apply rotation and scale before validation/export. Location is preserved."),
required_uvs: z.boolean().optional().describe("Require active UVs for material-capable formats. Defaults true."),
require_materials: z.boolean().optional().describe("Require at least one material on each mesh. Defaults false."),
check_textures: z.boolean().optional().describe("Check image texture file paths and packed-image status. Defaults true."),
check_texture_formats: z.boolean().optional().describe("Warn about risky portable-export image formats. Defaults true."),
make_dirs: z.boolean().optional().describe("Create the output directory if needed. Defaults true."),
force_export: z.boolean().optional().describe("Export even when validation reports errors. Use only when the user explicitly wants a best-effort export."),
}),
}
)

const listInstalledAddons = tool(
async () => executeMcpCommand("list_installed_addons"),
{
Expand Down Expand Up @@ -2961,6 +3024,7 @@ const ALL_TOOLS = [
setVisibility,
prepareUvLayout,
validateExportReadiness,
exportAssetPackage,
exportObject,
listInstalledAddons,
createMaterial,
Expand Down
3 changes: 2 additions & 1 deletion lib/orchestration/prompts/blender-agent-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ You have access to the following MCP tools. **Use direct tools whenever one matc
- `shade_smooth(name, smooth?, angle?)`: Set smooth/flat shading.
- `prepare_uv_layout(names, mode?, uv_map_name?, angle_limit?, island_margin?)`: Preserve original UVs or run Smart Project/lightmap/pack modes before texturing/export.
- `validate_export_readiness(names, filepath?, file_format?, required_uvs?, require_materials?, require_applied_transforms?, check_textures?)`: Check UVs, textures, transforms, mesh validity, expected output files, and format risks before export.
- `export_asset_package(names, filepath, file_format?, uv_mode?, apply_transforms?, required_uvs?, require_materials?, check_textures?, check_texture_formats?, make_dirs?, force_export?)`: Run UV prep, optional rotation/scale apply, validation, export, and expected-file reporting in one safe package flow.

### 🎨 Material Tools
- `create_material_preset(name, preset?, object_name?, base_color?, metallic?, roughness?, texture_maps?)`: Create/update a deterministic Blender 5.x Principled BSDF material preset and optionally assign it. Prefer this for glass, emissive, metal, fabric, ceramic, rubber, plastic, and PBR map wiring.
Expand Down Expand Up @@ -171,7 +172,7 @@ You have access to the following MCP tools. **Use direct tools whenever one matc
| Simple materials (color, metallic, roughness) | `create_material_preset` or `create_material` + `assign_material` | ✓ |
| PBR maps, glass, emissive, fabric, rubber, ceramic, metal presets | `create_material_preset` + `inspect_material_node_graph` | ✓ |
| Preserve imported UVs or unwrap generated meshes | `prepare_uv_layout` | ✓ |
| Validate before GLB/FBX/OBJ/STL export | `validate_export_readiness` then `export_object` | ✓ |
| Validate and package GLB/FBX/OBJ/STL exports | `export_asset_package`; use `validate_export_readiness` then `export_object` only for manual repair flows | ✓ |
| Studio/product lighting and camera framing | `setup_studio_scene` + `validate_studio_scene` | ✓ |
| World background/HDRI/procedural sky | `set_world_environment` | ✓ |
| Lightweight thumbnail or preview artifact | `render_thumbnail_to_path` | ✓ |
Expand Down
2 changes: 1 addition & 1 deletion lib/orchestration/tool-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const CATEGORY_GROUPS: Record<string, string[]> = {
animation: ["inspect_animation_data", "set_timeline_settings", "set_keyframe_animation"],
organization: ["inspect_collection_hierarchy", "organize_collection_hierarchy", "select_scene_objects", "set_active_collection", "parent_set", "parent_clear", "move_to_collection", "set_visibility"],
materials: ["list_materials", "create_material", "assign_material", "create_material_preset", "inspect_material_node_graph", "set_texture"],
pipeline: ["prepare_uv_layout", "validate_export_readiness", "export_object"],
pipeline: ["prepare_uv_layout", "validate_export_readiness", "export_asset_package", "export_object"],
lighting: ["set_world_environment", "setup_studio_scene", "validate_studio_scene", "render_thumbnail_to_path", "add_light", "set_light_properties", "set_render_settings", "render_image"],
camera: ["inspect_viewport_areas", "focus_viewport_on_objects", "get_viewport_screenshot", "render_viewport_to_path", "setup_studio_scene", "validate_studio_scene", "render_thumbnail_to_path", "add_camera", "set_camera_properties", "aim_camera_at", "set_render_settings", "render_image"],
viewport: ["inspect_viewport_areas", "set_viewport_shading", "focus_viewport_on_objects", "select_scene_objects", "set_active_collection", "get_viewport_screenshot", "render_viewport_to_path"],
Expand Down
8 changes: 8 additions & 0 deletions lib/orchestration/tool-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,14 @@ export const TOOL_REGISTRY: ToolMetadata[] = [
category: "advanced",
parameters: "names: string[], filepath: string, file_format?: GLB|GLTF|FBX|OBJ|STL",
},
{
name: "export_asset_package",
description:
"Run UV preparation, optional transform apply, export readiness validation, export, and expected-file reporting in one safe package flow.",
category: "advanced",
parameters:
"names: string[], filepath: string, file_format?: GLB|GLTF|FBX|OBJ|STL, uv_mode?: none|preserve_original|smart_project|lightmap_pack|pack_existing, apply_transforms?: boolean, required_uvs?: boolean, require_materials?: boolean, check_textures?: boolean, check_texture_formats?: boolean, make_dirs?: boolean, force_export?: boolean",
},
{
name: "list_installed_addons",
description:
Expand Down
Loading
Loading