"""
Selection Tools for Topology
MCP tools for selecting mesh elements by topology criteria.
"""
from mcp.server.fastmcp import Context
import logging
logger = logging.getLogger("BlenderMCPServer")
def select_by_valence(
ctx: Context,
blender_connection,
object_name: str = None,
min_valence: int = 5
) -> str:
"""
Select vertices with valence (edge count) >= threshold.
Parameters:
- object_name: Name of object to select in (default: active object)
- min_valence: Minimum valence to select (default: 5)
Returns selection count.
"""
try:
params = {"min_valence": min_valence}
if object_name:
params["object_name"] = object_name
result = blender_connection.send_command("select_by_valence", params)
if "error" in result:
return f"Error: {result['error']}"
count = result.get('selection_count', 0)
output = f"Selection by Valence: {result.get('object_name', 'Unknown')}\n"
output += "=" * 50 + "\n\n"
output += f"Filter: valence >= {result.get('min_valence', 5)}\n"
output += f"Selected: {count} vertices\n"
if count == 0:
output += "\nNo vertices match the criteria.\n"
else:
output += "\nVertices are now selected in Edit Mode.\n"
output += "You can now modify or inspect them.\n"
return output
except Exception as e:
logger.error(f"Error selecting by valence: {str(e)}")
return f"Error selecting by valence: {str(e)}"
def select_triangles(
ctx: Context,
blender_connection,
object_name: str = None
) -> str:
"""
Select all triangle faces (3 vertices) in the mesh.
Parameters:
- object_name: Name of object to select in (default: active object)
Returns selection count.
"""
try:
params = {}
if object_name:
params["object_name"] = object_name
result = blender_connection.send_command("select_triangles", params)
if "error" in result:
return f"Error: {result['error']}"
count = result.get('selection_count', 0)
output = f"Triangle Selection: {result.get('object_name', 'Unknown')}\n"
output += "=" * 50 + "\n\n"
output += f"Selected: {count} triangle faces\n"
if count == 0:
output += "\nNo triangles found in the mesh.\n"
else:
output += "\nTriangles are now selected in Edit Mode.\n"
output += "Use tris_to_quads to convert compatible pairs.\n"
return output
except Exception as e:
logger.error(f"Error selecting triangles: {str(e)}")
return f"Error selecting triangles: {str(e)}"
def select_ngons(
ctx: Context,
blender_connection,
object_name: str = None
) -> str:
"""
Select all n-gon faces (5+ vertices) in the mesh.
Parameters:
- object_name: Name of object to select in (default: active object)
Returns selection count.
"""
try:
params = {}
if object_name:
params["object_name"] = object_name
result = blender_connection.send_command("select_ngons", params)
if "error" in result:
return f"Error: {result['error']}"
count = result.get('selection_count', 0)
output = f"N-gon Selection: {result.get('object_name', 'Unknown')}\n"
output += "=" * 50 + "\n\n"
output += f"Selected: {count} n-gon faces (5+ vertices)\n"
if count == 0:
output += "\nNo n-gons found in the mesh.\n"
else:
output += "\nN-gons are now selected in Edit Mode.\n"
output += "Consider dissolving or triangulating these faces.\n"
return output
except Exception as e:
logger.error(f"Error selecting n-gons: {str(e)}")
return f"Error selecting n-gons: {str(e)}"
def select_edge_loop(
ctx: Context,
blender_connection,
object_name: str = None,
edge_index: int = 0
) -> str:
"""
Select the edge loop containing the specified edge.
Parameters:
- object_name: Name of object to select in (default: active object)
- edge_index: Index of edge to start loop selection from
Returns selection count.
"""
try:
params = {"edge_index": edge_index}
if object_name:
params["object_name"] = object_name
result = blender_connection.send_command("select_edge_loop", params)
if "error" in result:
return f"Error: {result['error']}"
count = result.get('selection_count', 0)
output = f"Edge Loop Selection: {result.get('object_name', 'Unknown')}\n"
output += "=" * 50 + "\n\n"
output += f"Starting edge: {result.get('start_edge', edge_index)}\n"
output += f"Loop edges selected: {count}\n"
if count == 0:
output += "\nNo edge loop found.\n"
else:
output += "\nEdge loop is now selected in Edit Mode.\n"
output += "You can dissolve, slide, or modify the loop.\n"
return output
except Exception as e:
logger.error(f"Error selecting edge loop: {str(e)}")
return f"Error selecting edge loop: {str(e)}"
def select_by_criteria(
ctx: Context,
blender_connection,
object_name: str = None,
criteria: str = "valence",
min_value: float = None,
max_value: float = None
) -> str:
"""
Select mesh elements by flexible criteria.
Parameters:
- object_name: Name of object (default: active object)
- criteria: Selection type - 'valence', 'face_sides', 'boundary', 'area', 'non_manifold'
- min_value: Minimum value for range criteria (optional)
- max_value: Maximum value for range criteria (optional)
Returns selection count.
"""
try:
valid_criteria = ['valence', 'face_sides', 'boundary', 'area', 'non_manifold']
if criteria not in valid_criteria:
return f"Error: Invalid criteria '{criteria}'. Must be one of {valid_criteria}."
params = {"criteria": criteria}
if object_name:
params["object_name"] = object_name
if min_value is not None:
params["min_value"] = min_value
if max_value is not None:
params["max_value"] = max_value
result = blender_connection.send_command("select_by_criteria", params)
if "error" in result:
return f"Error: {result['error']}"
count = result.get('selection_count', 0)
output = f"Selection by Criteria: {result.get('object_name', 'Unknown')}\n"
output += "=" * 50 + "\n\n"
output += f"Criteria: {criteria}\n"
if min_value is not None:
output += f"Min value: {min_value}\n"
if max_value is not None:
output += f"Max value: {max_value}\n"
output += f"Selected: {count} elements\n"
if count == 0:
output += "\nNo elements match the criteria.\n"
else:
output += "\nElements are now selected in Edit Mode.\n"
return output
except Exception as e:
logger.error(f"Error selecting by criteria: {str(e)}")
return f"Error selecting by criteria: {str(e)}"
def get_selected_elements(
ctx: Context,
blender_connection,
object_name: str = None,
mode: str = "VERT"
) -> str:
"""
Get list of selected mesh elements with detailed data.
Parameters:
- object_name: Name of object (default: active object)
- mode: 'VERT' | 'EDGE' | 'FACE'
Returns: {count, elements: [{index, co/verts/area, ...}]}
"""
try:
params = {"mode": mode}
if object_name:
params["object_name"] = object_name
result = blender_connection.send_command("get_selected_elements", params)
if "error" in result:
return f"Error: {result['error']}"
count = result.get('count', 0)
elements = result.get('elements', [])
obj_name = result.get('object_name', 'Unknown')
output = f"Selected Elements: {obj_name}\n"
output += "=" * 50 + "\n\n"
output += f"Mode: {mode}\n"
output += f"Selected count: {count}\n\n"
if count == 0:
output += "No elements selected.\n"
else:
output += "Elements:\n"
for elem in elements[:20]: # Limit output
if mode == "VERT":
output += f" Vertex {elem['index']}: pos={elem['co']}, valence={elem['valence']}\n"
elif mode == "EDGE":
output += f" Edge {elem['index']}: verts={elem['verts']}, length={elem['length']:.4f}\n"
elif mode == "FACE":
output += f" Face {elem['index']}: verts={elem['verts']}, area={elem['area']:.4f}\n"
if count > 20:
output += f" ... and {count - 20} more elements\n"
return output
except Exception as e:
logger.error(f"Error getting selected elements: {str(e)}")
return f"Error getting selected elements: {str(e)}"