"""
Mesh Analysis Tools
Tools for analyzing mesh topology, detecting issues, and gathering statistics.
"""
from mcp.server.fastmcp import Context
import logging
logger = logging.getLogger("BlenderMCPServer")
def mesh_stats(ctx: Context, blender_connection, active_only: bool = True) -> str:
"""
Get detailed topology statistics for mesh objects.
Returns counts of vertices/edges/faces, tri/quad/ngon breakdown,
non-manifold counts, sharp edges, and surface area/volume metrics.
Parameters:
- active_only: Analyze only the active object (true) or all mesh objects (false)
Returns detailed mesh statistics including topology metrics.
"""
try:
result = blender_connection.send_command("mesh_stats", {
"active_only": active_only
})
if "error" in result:
return f"Error: {result['error']}"
# Handler returns {"success": True, "stats": {...}} - extract stats
stats = result.get('stats', result)
# Format the statistics in a readable way
output = "Mesh Statistics:\n\n"
if active_only:
# Get object name from objects_analyzed list if available
objects_analyzed = stats.get('objects_analyzed', [])
if objects_analyzed and len(objects_analyzed) > 0:
output += f"Active Object: {objects_analyzed[0].get('name', 'Unknown')}\n\n"
else:
output += f"Active Object: Unknown\n\n"
else:
output += f"Total Objects Analyzed: {len(stats.get('objects_analyzed', []))}\n\n"
output += f"Vertices: {stats.get('vertex_count', 0)}\n"
output += f"Edges: {stats.get('edge_count', 0)}\n"
output += f"Faces: {stats.get('face_count', 0)}\n\n"
output += "Face Types:\n"
output += f" Triangles: {stats.get('tri_count', 0)}\n"
output += f" Quads: {stats.get('quad_count', 0)}\n"
output += f" N-gons: {stats.get('ngon_count', 0)}\n\n"
output += "Topology Issues:\n"
output += f" Non-manifold edges: {stats.get('non_manifold_edge_count', 0)}\n"
output += f" Sharp edges (>30°): {stats.get('sharp_edge_count', 0)}\n\n"
output += f"Surface Area: {stats.get('total_surface_area', 0):.4f}\n"
if stats.get('total_volume') is not None:
output += f"Volume: {stats.get('total_volume'):.4f}\n"
else:
output += "Volume: N/A (mesh not closed/manifold)\n"
# Add per-object breakdown if analyzing all objects
if not active_only and 'objects_analyzed' in stats:
output += "\n--- Per-Object Breakdown ---\n"
for obj_data in stats['objects_analyzed']:
output += f"\n{obj_data['name']}:\n"
output += f" Verts: {obj_data.get('vertex_count', 0)}, "
output += f"Edges: {obj_data.get('edge_count', 0)}, "
output += f"Faces: {obj_data.get('face_count', 0)}\n"
return output
except Exception as e:
logger.error(f"Error getting mesh stats: {str(e)}")
return f"Error getting mesh stats: {str(e)}"
def detect_topology_issues(
ctx: Context,
blender_connection,
angle_sharp: float = 30.0,
distance_doubles: float = 0.0001
) -> str:
"""
Detect topology issues in the active mesh object.
Reports non-manifold edges, loose geometry, duplicate vertices,
inverted normals, and provides suggested fixes.
Parameters:
- angle_sharp: Threshold angle in degrees for detecting sharp edges (default: 30)
- distance_doubles: Merge distance for detecting duplicate vertices (default: 0.0001)
Returns a detailed report of topology issues with suggestions for fixes.
"""
try:
result = blender_connection.send_command("detect_topology_issues", {
"angle_sharp": angle_sharp,
"distance_doubles": distance_doubles
})
if "error" in result:
return f"Error: {result['error']}"
output = f"Topology Issue Report for: {result.get('object_name', 'Unknown')}\n\n"
total_issues = result.get('total_issues_count', 0)
if total_issues == 0:
output += "✓ No topology issues detected! Mesh is clean.\n"
return output
output += f"Total Issues Found: {total_issues}\n\n"
issues = result.get('issues', [])
for issue in issues:
issue_type = issue.get('type', 'Unknown')
count = issue.get('count', 0)
indices = issue.get('indices', [])
suggestion = issue.get('suggestion', '')
output += f"Issue: {issue_type}\n"
output += f" Count: {count}\n"
if indices:
# Show first few indices
indices_str = ', '.join(map(str, indices[:10]))
if len(indices) > 10:
indices_str += f" ... ({len(indices)} total)"
output += f" Element indices: {indices_str}\n"
output += f" Suggestion: {suggestion}\n\n"
return output
except Exception as e:
logger.error(f"Error detecting topology issues: {str(e)}")
return f"Error detecting topology issues: {str(e)}"