"""
Texture baking tools for retopology workflows.
Provides safe, parameterized tools for baking normal maps and other textures.
"""
from typing import Dict, Any, Optional
from mcp.server.fastmcp import Context
import logging
logger = logging.getLogger("BlenderMCPServer")
# ============================================================================
# BAKE NORMAL MAP
# ============================================================================
def bake_normals(
ctx: Context,
blender_connection,
high_poly: str,
low_poly: str,
map_size: int = 2048,
space: str = "TANGENT",
cage_object: Optional[str] = None,
max_ray_distance: float = 0.1
) -> str:
"""
Bake a normal map from high-poly mesh to low-poly mesh.
Transfers surface detail from a high-resolution source mesh to a
low-resolution target mesh by baking a normal map. The low-poly mesh
must have UV coordinates. The result is saved to //textures/ directory
and automatically assigned to the low-poly material.
Parameters:
- high_poly: Name of the high-poly source mesh
- low_poly: Name of the low-poly target mesh
- map_size: Texture resolution (512, 1024, 2048, 4096) (default: 2048)
- space: Normal space - 'TANGENT' or 'OBJECT' (default: 'TANGENT')
- cage_object: Optional cage object for ray-casting (default: None)
- max_ray_distance: Maximum distance for ray-casting (default: 0.1)
Returns file path to the baked normal map.
"""
try:
valid_sizes = [512, 1024, 2048, 4096, 8192]
if map_size not in valid_sizes:
return f"Error: Invalid map_size. Must be one of {valid_sizes}."
valid_spaces = ['TANGENT', 'OBJECT']
if space not in valid_spaces:
return f"Error: Invalid space '{space}'. Must be one of {valid_spaces}."
if max_ray_distance <= 0:
return f"Error: max_ray_distance must be greater than 0."
result = blender_connection.send_command("bake_normals", {
"high_poly": high_poly,
"low_poly": low_poly,
"map_size": map_size,
"space": space,
"cage_object": cage_object,
"max_ray_distance": max_ray_distance
})
if "error" in result:
error_msg = f"Error: {result['error']}\n\n"
# Add helpful remediation suggestions
if "UV" in result['error'] or "unwrap" in result['error'].lower():
error_msg += "The low-poly mesh needs UV coordinates. To fix:\n"
error_msg += "1. Select the low-poly mesh in Edit Mode\n"
error_msg += "2. Use 'mark_seams_by_angle()' to mark UV seams\n"
error_msg += "3. Use Smart UV Project or Unwrap in Blender\n"
error_msg += "4. Then retry baking\n"
return error_msg
output = "Normal Map Baked Successfully!\n\n"
output += f"High-Poly Source: {high_poly}\n"
output += f"Low-Poly Target: {low_poly}\n"
output += f"Resolution: {map_size}x{map_size}\n"
output += f"Normal Space: {space}\n"
if cage_object:
output += f"Cage Object: {cage_object}\n"
output += f"Max Ray Distance: {max_ray_distance}\n\n"
if 'file_path' in result:
output += f"š Saved to: {result['file_path']}\n"
if 'bake_time' in result:
output += f"ā± Bake Time: {result['bake_time']:.2f}s\n"
output += "\nā Normal map has been assigned to the low-poly material.\n"
return output
except Exception as e:
logger.error(f"Error baking normals: {str(e)}")
return f"Error baking normals: {str(e)}"
# ============================================================================
# BAKE AMBIENT OCCLUSION
# ============================================================================
def bake_ambient_occlusion(
ctx: Context,
blender_connection,
target: str,
map_size: int = 2048,
samples: int = 128
) -> str:
"""
Bake an ambient occlusion map for the target mesh.
Creates an ambient occlusion map that captures how exposed each point
is to ambient lighting. Useful for adding depth to textures.
The target mesh must have UV coordinates.
Parameters:
- target: Name of the target mesh
- map_size: Texture resolution (512, 1024, 2048, 4096) (default: 2048)
- samples: Number of AO samples for quality (default: 128)
Returns file path to the baked AO map.
"""
try:
valid_sizes = [512, 1024, 2048, 4096, 8192]
if map_size not in valid_sizes:
return f"Error: Invalid map_size. Must be one of {valid_sizes}."
if samples < 1:
return f"Error: samples must be at least 1."
result = blender_connection.send_command("bake_ambient_occlusion", {
"target": target,
"map_size": map_size,
"samples": samples
})
if "error" in result:
return f"Error: {result['error']}"
output = "Ambient Occlusion Baked Successfully!\n\n"
output += f"Target: {target}\n"
output += f"Resolution: {map_size}x{map_size}\n"
output += f"Samples: {samples}\n\n"
if 'file_path' in result:
output += f"š Saved to: {result['file_path']}\n"
if 'bake_time' in result:
output += f"ā± Bake Time: {result['bake_time']:.2f}s\n"
return output
except Exception as e:
logger.error(f"Error baking ambient occlusion: {str(e)}")
return f"Error baking ambient occlusion: {str(e)}"