using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json.Linq;
using MCPForUnity.Editor.Helpers;
using UnityEditor;
using UnityEngine;
using MCPForUnity.Runtime.Helpers;
namespace MCPForUnity.Editor.Tools.ProBuilder
{
///
/// Tool for managing Unity ProBuilder meshes for in-editor 3D modeling.
/// Requires com.unity.probuilder package to be installed.
///
/// SHAPE CREATION:
/// - create_shape: Create ProBuilder primitive with real dimensions via Generate* methods
/// Shape types: Cube, Cylinder, Sphere, Plane, Cone, Torus, Pipe, Arch, Stair, CurvedStair, Door, Prism
/// Each shape accepts type-specific parameters (radius, height, steps, segments, etc.)
/// - create_poly_shape: Create from 2D polygon footprint (points, extrudeHeight, flipNormals)
///
/// MESH EDITING:
/// - extrude_faces: Extrude faces (faceIndices, distance, method: FaceNormal/VertexNormal/IndividualFaces)
/// - extrude_edges: Extrude edges (edgeIndices or edges [{a,b},...], distance, asGroup)
/// - bevel_edges: Bevel edges (edgeIndices or edges [{a,b},...], amount 0-1)
/// - subdivide: Subdivide faces (faceIndices optional)
/// - delete_faces: Delete faces (faceIndices)
/// - bridge_edges: Bridge two open edges (edgeA, edgeB as {a,b} pairs, allowNonManifold)
/// - connect_elements: Connect edges/faces (edgeIndices or faceIndices)
/// - detach_faces: Detach faces (faceIndices, deleteSourceFaces)
/// - flip_normals: Flip face normals (faceIndices)
/// - merge_faces: Merge faces into one (faceIndices)
/// - combine_meshes: Combine ProBuilder objects (targets list)
/// - merge_objects: Merge objects (auto-converts non-ProBuilder), convenience wrapper (targets, name)
/// - duplicate_and_flip: Create double-sided geometry (faceIndices)
/// - create_polygon: Connect existing vertices into a new face (vertexIndices, unordered)
///
/// VERTEX OPERATIONS:
/// - merge_vertices: Collapse vertices to single point (vertexIndices, collapseToFirst)
/// - weld_vertices: Weld vertices within proximity radius (vertexIndices, radius)
/// - split_vertices: Split shared vertices (vertexIndices)
/// - move_vertices: Translate vertices (vertexIndices, offset [x,y,z])
/// - insert_vertex: Insert vertex on edge or face (edge {a,b} or faceIndex + point [x,y,z])
/// - append_vertices_to_edge: Insert evenly-spaced points on edges (edgeIndices or edges, count)
///
/// SELECTION:
/// - select_faces: Select faces by criteria (direction, growAngle, floodAngle, loop, ring)
///
/// UV & MATERIALS:
/// - set_face_material: Assign material to faces (faceIndices, materialPath)
/// - set_face_color: Set vertex color (faceIndices, color [r,g,b,a])
/// - set_face_uvs: Set UV params (faceIndices, scale, offset, rotation, flipU, flipV)
///
/// QUERY:
/// - get_mesh_info: Get mesh details (face count, vertex count, bounds, materials, edges with positions)
/// - convert_to_probuilder: Convert standard mesh to ProBuilder
///
[McpForUnityTool("manage_probuilder", AutoRegister = false, Group = "probuilder")]
public static class ManageProBuilder
{
// ProBuilder types resolved via reflection (optional package)
internal static Type _proBuilderMeshType;
private static Type _shapeGeneratorType;
internal static Type _shapeTypeEnum;
private static Type _extrudeMethodEnum;
private static Type _extrudeElementsType;
private static Type _bevelType;
private static Type _deleteElementsType;
private static Type _appendElementsType;
private static Type _connectElementsType;
private static Type _mergeElementsType;
private static Type _combineMeshesType;
private static Type _surfaceTopologyType;
internal static Type _faceType;
internal static Type _edgeType;
private static Type _editorMeshUtilityType;
private static Type _meshImporterType;
internal static Type _smoothingType;
internal static Type _meshValidationType;
private static Type _pivotLocationType;
private static Type _vertexEditingType;
private static Type _elementSelectionType;
private static Type _axisEnum;
private static bool _typesResolved;
private static bool _proBuilderAvailable;
private static bool EnsureProBuilder()
{
if (_typesResolved) return _proBuilderAvailable;
_typesResolved = true;
_proBuilderMeshType = Type.GetType("UnityEngine.ProBuilder.ProBuilderMesh, Unity.ProBuilder");
if (_proBuilderMeshType == null)
{
_proBuilderAvailable = false;
return false;
}
_shapeGeneratorType = Type.GetType("UnityEngine.ProBuilder.ShapeGenerator, Unity.ProBuilder");
_shapeTypeEnum = Type.GetType("UnityEngine.ProBuilder.ShapeType, Unity.ProBuilder");
_faceType = Type.GetType("UnityEngine.ProBuilder.Face, Unity.ProBuilder");
_edgeType = Type.GetType("UnityEngine.ProBuilder.Edge, Unity.ProBuilder");
// MeshOperations
_extrudeElementsType = Type.GetType("UnityEngine.ProBuilder.MeshOperations.ExtrudeElements, Unity.ProBuilder");
_extrudeMethodEnum = Type.GetType("UnityEngine.ProBuilder.ExtrudeMethod, Unity.ProBuilder");
_bevelType = Type.GetType("UnityEngine.ProBuilder.MeshOperations.Bevel, Unity.ProBuilder");
_deleteElementsType = Type.GetType("UnityEngine.ProBuilder.MeshOperations.DeleteElements, Unity.ProBuilder");
_appendElementsType = Type.GetType("UnityEngine.ProBuilder.MeshOperations.AppendElements, Unity.ProBuilder");
_connectElementsType = Type.GetType("UnityEngine.ProBuilder.MeshOperations.ConnectElements, Unity.ProBuilder");
_mergeElementsType = Type.GetType("UnityEngine.ProBuilder.MeshOperations.MergeElements, Unity.ProBuilder");
_combineMeshesType = Type.GetType("UnityEngine.ProBuilder.MeshOperations.CombineMeshes, Unity.ProBuilder");
_surfaceTopologyType = Type.GetType("UnityEngine.ProBuilder.MeshOperations.SurfaceTopology, Unity.ProBuilder");
_vertexEditingType = Type.GetType("UnityEngine.ProBuilder.MeshOperations.VertexEditing, Unity.ProBuilder");
_elementSelectionType = Type.GetType("UnityEngine.ProBuilder.MeshOperations.ElementSelection, Unity.ProBuilder");
// Enums & structs
_pivotLocationType = Type.GetType("UnityEngine.ProBuilder.PivotLocation, Unity.ProBuilder");
_axisEnum = Type.GetType("UnityEngine.ProBuilder.Axis, Unity.ProBuilder");
// Editor utilities
_editorMeshUtilityType = Type.GetType("UnityEditor.ProBuilder.EditorMeshUtility, Unity.ProBuilder.Editor");
_meshImporterType = Type.GetType("UnityEngine.ProBuilder.MeshOperations.MeshImporter, Unity.ProBuilder");
_smoothingType = Type.GetType("UnityEngine.ProBuilder.Smoothing, Unity.ProBuilder");
_meshValidationType = Type.GetType("UnityEngine.ProBuilder.MeshOperations.MeshValidation, Unity.ProBuilder");
_proBuilderAvailable = true;
PatchProBuilderDefaultMaterial();
return true;
}
///
/// Patches ProBuilderDefault.mat in memory to suppress unintended emission in URP projects.
///
///
/// Root cause: The ProBuilder default material was authored in an HDRP context and ships
/// with _EmissionColor = {1,1,1,1} (full white) and
/// m_LightmapFlags = RealtimeEmissive | BakedEmissive.
/// In a URP project Unity's GI system reads these material properties directly,
/// bypassing the shader's own Emission block (which is correctly wired to black).
/// The result is that every fresh ProBuilder mesh is treated as a full-white emitter,
/// and any URP Bloom volume in the scene amplifies this into a visible glow artefact.
///
/// Fix: Zero all emission colour properties and set
/// globalIlluminationFlags = EmissiveIsBlack on the loaded
/// object. The change is in-memory only — package assets are read-only on disk — but
/// the GI system and Bloom post-process both re-query the material each frame, so the
/// patch is effective for the entire session. It is re-applied automatically on every
/// domain reload because is called on the first MCP
/// ProBuilder command of each session.
///
private static void PatchProBuilderDefaultMaterial()
{
const string defaultMatPath =
"Packages/com.unity.probuilder/Content/Resources/Materials/ProBuilderDefault.mat";
var mat = AssetDatabase.LoadAssetAtPath(defaultMatPath);
if (mat == null) return;
bool changed = false;
foreach (var prop in new[] { "_EmissionColor", "_EmissionColorUI", "_EmissionColorWithMapUI" })
{
if (mat.HasProperty(prop) && mat.GetColor(prop) != Color.black)
{
mat.SetColor(prop, Color.black);
changed = true;
}
}
if (mat.globalIlluminationFlags != MaterialGlobalIlluminationFlags.EmissiveIsBlack)
{
mat.globalIlluminationFlags = MaterialGlobalIlluminationFlags.EmissiveIsBlack;
changed = true;
}
if (changed)
Debug.Log("[MCP] Patched ProBuilderDefault material: zeroed emission and set GI flags to EmissiveIsBlack.");
}
public static object HandleCommand(JObject @params)
{
if (!EnsureProBuilder())
{
return new ErrorResponse(
"ProBuilder package is not installed. Install com.unity.probuilder via Package Manager."
);
}
var p = new ToolParams(@params);
string action = p.Get("action");
if (string.IsNullOrEmpty(action))
return new ErrorResponse("Action is required");
try
{
switch (action.ToLowerInvariant())
{
case "ping":
return new SuccessResponse("ProBuilder tool is available", new { tool = "manage_probuilder" });
// Shape creation
case "create_shape": return CreateShape(@params);
case "create_poly_shape": return CreatePolyShape(@params);
// Mesh editing
case "extrude_faces": return ExtrudeFaces(@params);
case "extrude_edges": return ExtrudeEdges(@params);
case "bevel_edges": return BevelEdges(@params);
case "subdivide": return Subdivide(@params);
case "delete_faces": return DeleteFaces(@params);
case "bridge_edges": return BridgeEdges(@params);
case "connect_elements": return ConnectElements(@params);
case "detach_faces": return DetachFaces(@params);
case "flip_normals": return FlipNormals(@params);
case "merge_faces": return MergeFaces(@params);
case "combine_meshes": return CombineMeshes(@params);
case "merge_objects": return MergeObjects(@params);
case "duplicate_and_flip": return DuplicateAndFlip(@params);
case "create_polygon": return CreatePolygon(@params);
// Vertex operations
case "merge_vertices": return MergeVertices(@params);
case "weld_vertices": return WeldVertices(@params);
case "split_vertices": return SplitVertices(@params);
case "move_vertices": return MoveVertices(@params);
case "insert_vertex": return InsertVertex(@params);
case "append_vertices_to_edge": return AppendVerticesToEdge(@params);
// Selection
case "select_faces": return SelectFaces(@params);
// UV & materials
case "set_face_material": return SetFaceMaterial(@params);
case "set_face_color": return SetFaceColor(@params);
case "set_face_uvs": return SetFaceUVs(@params);
// Query
case "get_mesh_info": return GetMeshInfo(@params);
case "convert_to_probuilder": return ConvertToProBuilder(@params);
// Smoothing
case "set_smoothing": return ProBuilderSmoothing.SetSmoothing(@params);
case "auto_smooth": return ProBuilderSmoothing.AutoSmooth(@params);
// Mesh utilities
case "center_pivot": return ProBuilderMeshUtils.CenterPivot(@params);
case "freeze_transform": return ProBuilderMeshUtils.FreezeTransform(@params);
case "set_pivot": return ProBuilderMeshUtils.SetPivot(@params);
case "validate_mesh": return ProBuilderMeshUtils.ValidateMesh(@params);
case "repair_mesh": return ProBuilderMeshUtils.RepairMesh(@params);
default:
return new ErrorResponse($"Unknown action: {action}");
}
}
catch (Exception ex)
{
return new ErrorResponse(ex.Message, new { stackTrace = ex.StackTrace });
}
}
// =====================================================================
// Helpers
// =====================================================================
internal static GameObject FindTarget(JObject @params)
{
return ObjectResolver.ResolveGameObject(@params["target"], @params["searchMethod"]?.ToString());
}
private static Component GetProBuilderMesh(GameObject go)
{
return go.GetComponent(_proBuilderMeshType);
}
internal static Component RequireProBuilderMesh(JObject @params)
{
var go = FindTarget(@params);
if (go == null)
throw new Exception("Target GameObject not found.");
var pbMesh = GetProBuilderMesh(go);
if (pbMesh == null)
throw new Exception($"GameObject '{go.name}' does not have a ProBuilderMesh component.");
return pbMesh;
}
internal static void RefreshMesh(Component pbMesh)
{
// ToMesh and Refresh have optional parameters (MeshTopology, RefreshMask) —
// Type.EmptyTypes won't find them. Use name-only lookup with default args.
var toMeshMethod = _proBuilderMeshType.GetMethod("ToMesh", Type.EmptyTypes)
?? _proBuilderMeshType.GetMethod("ToMesh", BindingFlags.Instance | BindingFlags.Public);
toMeshMethod?.Invoke(pbMesh, toMeshMethod.GetParameters().Length > 0
? new object[toMeshMethod.GetParameters().Length]
: null);
var refreshMethod = _proBuilderMeshType.GetMethod("Refresh", Type.EmptyTypes)
?? _proBuilderMeshType.GetMethod("Refresh", BindingFlags.Instance | BindingFlags.Public);
refreshMethod?.Invoke(pbMesh, refreshMethod.GetParameters().Length > 0
? new object[refreshMethod.GetParameters().Length]
: null);
if (_editorMeshUtilityType != null)
{
var optimizeMethod = _editorMeshUtilityType.GetMethod("Optimize",
BindingFlags.Static | BindingFlags.Public,
null,
new[] { _proBuilderMeshType },
null);
optimizeMethod?.Invoke(null, new object[] { pbMesh });
}
}
internal static object GetFacesArray(Component pbMesh)
{
var facesProperty = _proBuilderMeshType.GetProperty("faces");
return facesProperty?.GetValue(pbMesh);
}
internal static Array GetFacesByIndices(Component pbMesh, JToken faceIndicesToken)
{
var allFaces = GetFacesArray(pbMesh);
if (allFaces == null)
throw new Exception("Could not read faces from ProBuilderMesh.");
var facesList = (System.Collections.IList)allFaces;
if (faceIndicesToken == null)
{
// Return all faces when no indices specified
var allResult = Array.CreateInstance(_faceType, facesList.Count);
for (int i = 0; i < facesList.Count; i++)
allResult.SetValue(facesList[i], i);
return allResult;
}
var indices = faceIndicesToken.ToObject();
var result = Array.CreateInstance(_faceType, indices.Length);
for (int i = 0; i < indices.Length; i++)
{
if (indices[i] < 0 || indices[i] >= facesList.Count)
throw new Exception($"Face index {indices[i]} out of range (0-{facesList.Count - 1}).");
result.SetValue(facesList[indices[i]], i);
}
return result;
}
internal static JObject ExtractProperties(JObject @params)
{
var propsToken = @params["properties"];
if (propsToken is JObject jObj) return jObj;
if (propsToken is JValue jVal && jVal.Type == JTokenType.String)
{
var parsed = JObject.Parse(jVal.ToString());
if (parsed != null) return parsed;
}
// Fallback: properties might be at the top level
return @params;
}
private static Vector3 ParseVector3(JToken token)
{
return VectorParsing.ParseVector3OrDefault(token);
}
internal static int GetFaceCount(Component pbMesh)
{
var faceCount = _proBuilderMeshType.GetProperty("faceCount");
return faceCount != null ? (int)faceCount.GetValue(pbMesh) : -1;
}
internal static int GetVertexCount(Component pbMesh)
{
var vertexCount = _proBuilderMeshType.GetProperty("vertexCount");
return vertexCount != null ? (int)vertexCount.GetValue(pbMesh) : -1;
}
private static object GetPivotCenter()
{
if (_pivotLocationType == null) return null;
// PivotLocation.Center = 0
return Enum.ToObject(_pivotLocationType, 0);
}
private static Component InvokeGenerator(string methodName, Type[] paramTypes, object[] args)
{
if (_shapeGeneratorType == null) return null;
var method = _shapeGeneratorType.GetMethod(methodName,
BindingFlags.Static | BindingFlags.Public,
null, paramTypes, null);
return method?.Invoke(null, args) as Component;
}
// =====================================================================
// Edge Helpers
// =====================================================================
private static int GetEdgeVertexA(object edge)
{
var f = _edgeType.GetField("a");
if (f != null) return (int)f.GetValue(edge);
var p = _edgeType.GetProperty("a");
return p != null ? (int)p.GetValue(edge) : -1;
}
private static int GetEdgeVertexB(object edge)
{
var f = _edgeType.GetField("b");
if (f != null) return (int)f.GetValue(edge);
var p = _edgeType.GetProperty("b");
return p != null ? (int)p.GetValue(edge) : -1;
}
private static object CreateEdge(int a, int b)
{
var ctor = _edgeType.GetConstructor(new[] { typeof(int), typeof(int) });
return ctor?.Invoke(new object[] { a, b });
}
///
/// Create a typed List<Face> from a Face[] array for reflection calls
/// that require IEnumerable<Face>.
///
private static System.Collections.IList ToTypedFaceList(Array faces)
{
var faceListType = typeof(List<>).MakeGenericType(_faceType);
var faceList = Activator.CreateInstance(faceListType) as System.Collections.IList;
foreach (var f in faces)
faceList.Add(f);
return faceList;
}
///
/// Collect unique (deduplicated) edges from the mesh.
/// Edges shared between faces appear only once.
///
internal static List