Files
2026-07-13 12:49:17 +08:00

862 lines
31 KiB
C#

using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using MCPForUnity.Editor.Tools.ProBuilder;
using static MCPForUnityTests.Editor.TestUtilities;
namespace MCPForUnityTests.Editor.Tools
{
public class ManageProBuilderTests
{
private readonly List<GameObject> _createdObjects = new List<GameObject>();
private bool _proBuilderInstalled;
[OneTimeSetUp]
public void OneTimeSetUp()
{
_proBuilderInstalled = Type.GetType(
"UnityEngine.ProBuilder.ProBuilderMesh, Unity.ProBuilder"
) != null;
}
[TearDown]
public void TearDown()
{
foreach (var go in _createdObjects)
{
if (go != null)
UnityEngine.Object.DestroyImmediate(go);
}
_createdObjects.Clear();
}
// =====================================================================
// Basic action validation (works regardless of ProBuilder installation)
// =====================================================================
[Test]
public void HandleCommand_MissingAction_ReturnsError()
{
var paramsObj = new JObject();
var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj));
Assert.IsFalse(result.Value<bool>("success"), result.ToString());
}
[Test]
public void HandleCommand_UnknownAction_ReturnsError()
{
var paramsObj = new JObject { ["action"] = "nonexistent_action" };
var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj));
Assert.IsFalse(result.Value<bool>("success"), result.ToString());
var errorMsg = result["error"]?.ToString() ?? result["message"]?.ToString();
Assert.That(errorMsg, Does.Contain("Unknown action").Or.Contain("not installed"));
}
[Test]
public void HandleCommand_Ping_ReturnsSuccess()
{
var paramsObj = new JObject { ["action"] = "ping" };
var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj));
if (!_proBuilderInstalled)
{
// Without ProBuilder, should return error about missing package
Assert.IsFalse(result.Value<bool>("success"), result.ToString());
Assert.That(result["error"]?.ToString(),
Does.Contain("ProBuilder").IgnoreCase);
return;
}
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
}
// =====================================================================
// Shape creation (requires ProBuilder)
// =====================================================================
[Test]
public void CreateShape_MissingShapeType_ReturnsError()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var paramsObj = new JObject
{
["action"] = "create_shape",
};
var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj));
Assert.IsFalse(result.Value<bool>("success"), result.ToString());
}
[Test]
public void CreateShape_InvalidShapeType_ReturnsError()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var paramsObj = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject { ["shapeType"] = "InvalidShape" },
};
var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj));
Assert.IsFalse(result.Value<bool>("success"), result.ToString());
}
[Test]
public void CreateShape_Cube_CreatesGameObject()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var paramsObj = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestCube",
},
};
var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
var data = result["data"] as JObject;
Assert.IsNotNull(data, "Result should contain data");
Assert.AreEqual("PBTestCube", data.Value<string>("gameObjectName"));
Assert.Greater(data.Value<int>("faceCount"), 0);
Assert.Greater(data.Value<int>("vertexCount"), 0);
// Track for cleanup
var go = GameObject.Find("PBTestCube");
if (go != null) _createdObjects.Add(go);
}
[Test]
public void CreateShape_WithPosition_SetsTransform()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var paramsObj = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestCubePos",
["position"] = new JArray(5f, 10f, 15f),
},
};
var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
var go = GameObject.Find("PBTestCubePos");
Assert.IsNotNull(go, "Created GameObject should exist");
Assert.AreEqual(new Vector3(5f, 10f, 15f), go.transform.position);
_createdObjects.Add(go);
}
[Test]
public void CreatePolyShape_CreatesFromPoints()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var paramsObj = new JObject
{
["action"] = "create_poly_shape",
["properties"] = new JObject
{
["points"] = new JArray(
new JArray(0f, 0f, 0f),
new JArray(5f, 0f, 0f),
new JArray(5f, 0f, 5f),
new JArray(0f, 0f, 5f)
),
["extrudeHeight"] = 3f,
["name"] = "PBTestPoly",
},
};
var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
var go = GameObject.Find("PBTestPoly");
if (go != null) _createdObjects.Add(go);
}
// =====================================================================
// Mesh editing (requires ProBuilder + created shape)
// =====================================================================
[Test]
public void GetMeshInfo_ReturnsDetails()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
// First create a shape
var createParams = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestInfoCube",
},
};
var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams));
Assert.IsTrue(createResult.Value<bool>("success"), createResult.ToString());
var go = GameObject.Find("PBTestInfoCube");
Assert.IsNotNull(go);
_createdObjects.Add(go);
// Now get mesh info
var infoParams = new JObject
{
["action"] = "get_mesh_info",
["target"] = "PBTestInfoCube",
["properties"] = new JObject { ["include"] = "faces" },
};
var result = ToJObject(ManageProBuilder.HandleCommand(infoParams));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
var data = result["data"] as JObject;
Assert.IsNotNull(data);
Assert.Greater(data.Value<int>("faceCount"), 0);
Assert.Greater(data.Value<int>("vertexCount"), 0);
Assert.IsNotNull(data["bounds"]);
Assert.IsNotNull(data["faces"]);
}
[Test]
public void ExtrudeFaces_IncreaseFaceCount()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
// Create a cube
var createParams = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestExtrudeCube",
},
};
var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams));
Assert.IsTrue(createResult.Value<bool>("success"), createResult.ToString());
var go = GameObject.Find("PBTestExtrudeCube");
_createdObjects.Add(go);
int initialFaceCount = createResult["data"].Value<int>("faceCount");
// Extrude face 0
var extrudeParams = new JObject
{
["action"] = "extrude_faces",
["target"] = "PBTestExtrudeCube",
["properties"] = new JObject
{
["faceIndices"] = new JArray(0),
["distance"] = 1.0f,
},
};
var result = ToJObject(ManageProBuilder.HandleCommand(extrudeParams));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
int newFaceCount = result["data"].Value<int>("faceCount");
Assert.Greater(newFaceCount, initialFaceCount,
"Face count should increase after extrusion");
}
[Test]
public void DeleteFaces_DecreasesFaceCount()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var createParams = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestDeleteCube",
},
};
var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams));
Assert.IsTrue(createResult.Value<bool>("success"), createResult.ToString());
var go = GameObject.Find("PBTestDeleteCube");
_createdObjects.Add(go);
int initialFaceCount = createResult["data"].Value<int>("faceCount");
var deleteParams = new JObject
{
["action"] = "delete_faces",
["target"] = "PBTestDeleteCube",
["properties"] = new JObject
{
["faceIndices"] = new JArray(0),
},
};
var result = ToJObject(ManageProBuilder.HandleCommand(deleteParams));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
int newFaceCount = result["data"].Value<int>("faceCount");
Assert.Less(newFaceCount, initialFaceCount,
"Face count should decrease after deletion");
}
[Test]
public void SetFaceMaterial_WithMissingTarget_ReturnsError()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var paramsObj = new JObject
{
["action"] = "set_face_material",
["target"] = "NonExistentObject999",
["properties"] = new JObject
{
["faceIndices"] = new JArray(0),
["materialPath"] = "Assets/Materials/Test.mat",
},
};
var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj));
Assert.IsFalse(result.Value<bool>("success"), result.ToString());
}
[Test]
public void FlipNormals_SucceedsOnValidMesh()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var createParams = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestFlipCube",
},
};
var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams));
Assert.IsTrue(createResult.Value<bool>("success"), createResult.ToString());
var go = GameObject.Find("PBTestFlipCube");
_createdObjects.Add(go);
var flipParams = new JObject
{
["action"] = "flip_normals",
["target"] = "PBTestFlipCube",
["properties"] = new JObject
{
["faceIndices"] = new JArray(0, 1),
},
};
var result = ToJObject(ManageProBuilder.HandleCommand(flipParams));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
}
// =====================================================================
// Enhanced get_mesh_info with include parameter
// =====================================================================
[Test]
public void GetMeshInfo_DefaultInclude_ReturnsSummaryOnly()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var createParams = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestSummaryCube",
},
};
var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams));
Assert.IsTrue(createResult.Value<bool>("success"), createResult.ToString());
var go = GameObject.Find("PBTestSummaryCube");
Assert.IsNotNull(go);
_createdObjects.Add(go);
var infoParams = new JObject
{
["action"] = "get_mesh_info",
["target"] = "PBTestSummaryCube",
};
var result = ToJObject(ManageProBuilder.HandleCommand(infoParams));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
var data = result["data"] as JObject;
Assert.IsNotNull(data);
Assert.Greater(data.Value<int>("faceCount"), 0);
// Default "summary" should NOT include faces array
Assert.IsNull(data["faces"], "Summary mode should not include faces array");
}
[Test]
public void GetMeshInfo_IncludeFaces_ReturnsFaceNormalsAndDirections()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var createParams = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestFacesCube",
},
};
var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams));
Assert.IsTrue(createResult.Value<bool>("success"), createResult.ToString());
var go = GameObject.Find("PBTestFacesCube");
Assert.IsNotNull(go);
_createdObjects.Add(go);
var infoParams = new JObject
{
["action"] = "get_mesh_info",
["target"] = "PBTestFacesCube",
["properties"] = new JObject { ["include"] = "faces" },
};
var result = ToJObject(ManageProBuilder.HandleCommand(infoParams));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
var data = result["data"] as JObject;
Assert.IsNotNull(data);
var faces = data["faces"] as JArray;
Assert.IsNotNull(faces, "Faces mode should include faces array");
Assert.Greater(faces.Count, 0);
// Each face should have normal, center, direction
var firstFace = faces[0] as JObject;
Assert.IsNotNull(firstFace);
Assert.IsNotNull(firstFace["normal"], "Face should have normal");
Assert.IsNotNull(firstFace["center"], "Face should have center");
// direction may be null for angled faces, but should exist as key
Assert.IsTrue(firstFace.ContainsKey("direction"), "Face should have direction key");
}
[Test]
public void GetMeshInfo_IncludeEdges_ReturnsEdgeData()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var createParams = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestEdgesCube",
},
};
var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams));
Assert.IsTrue(createResult.Value<bool>("success"), createResult.ToString());
var go = GameObject.Find("PBTestEdgesCube");
Assert.IsNotNull(go);
_createdObjects.Add(go);
var infoParams = new JObject
{
["action"] = "get_mesh_info",
["target"] = "PBTestEdgesCube",
["properties"] = new JObject { ["include"] = "edges" },
};
var result = ToJObject(ManageProBuilder.HandleCommand(infoParams));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
var data = result["data"] as JObject;
Assert.IsNotNull(data);
var edges = data["edges"] as JArray;
Assert.IsNotNull(edges, "Edges mode should include edges array");
Assert.Greater(edges.Count, 0);
var firstEdge = edges[0] as JObject;
Assert.IsNotNull(firstEdge);
Assert.IsTrue(firstEdge.ContainsKey("vertexA"), "Edge should have vertexA");
Assert.IsTrue(firstEdge.ContainsKey("vertexB"), "Edge should have vertexB");
}
[Test]
public void GetMeshInfo_CubeTopFace_HasUpNormal()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var createParams = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestTopNormalCube",
},
};
var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams));
Assert.IsTrue(createResult.Value<bool>("success"), createResult.ToString());
var go = GameObject.Find("PBTestTopNormalCube");
Assert.IsNotNull(go);
_createdObjects.Add(go);
var infoParams = new JObject
{
["action"] = "get_mesh_info",
["target"] = "PBTestTopNormalCube",
["properties"] = new JObject { ["include"] = "faces" },
};
var result = ToJObject(ManageProBuilder.HandleCommand(infoParams));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
var faces = result["data"]["faces"] as JArray;
Assert.IsNotNull(faces);
// At least one face should have direction "top"
bool hasTop = false;
foreach (JObject face in faces)
{
if (face["direction"]?.ToString() == "top")
{
hasTop = true;
break;
}
}
Assert.IsTrue(hasTop, "A cube should have at least one face with direction 'top'");
}
// =====================================================================
// Smoothing
// =====================================================================
[Test]
public void AutoSmooth_DefaultAngle_AssignsGroups()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var createParams = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestAutoSmoothCube",
},
};
var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams));
Assert.IsTrue(createResult.Value<bool>("success"), createResult.ToString());
var go = GameObject.Find("PBTestAutoSmoothCube");
Assert.IsNotNull(go);
_createdObjects.Add(go);
var smoothParams = new JObject
{
["action"] = "auto_smooth",
["target"] = "PBTestAutoSmoothCube",
["properties"] = new JObject { ["angleThreshold"] = 30 },
};
var result = ToJObject(ManageProBuilder.HandleCommand(smoothParams));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
}
[Test]
public void SetSmoothing_OnSpecificFaces_SetsGroup()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var createParams = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestSetSmoothCube",
},
};
var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams));
Assert.IsTrue(createResult.Value<bool>("success"), createResult.ToString());
var go = GameObject.Find("PBTestSetSmoothCube");
Assert.IsNotNull(go);
_createdObjects.Add(go);
var smoothParams = new JObject
{
["action"] = "set_smoothing",
["target"] = "PBTestSetSmoothCube",
["properties"] = new JObject
{
["faceIndices"] = new JArray(0, 1),
["smoothingGroup"] = 1,
},
};
var result = ToJObject(ManageProBuilder.HandleCommand(smoothParams));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
var data = result["data"] as JObject;
Assert.IsNotNull(data);
Assert.AreEqual(2, data.Value<int>("facesModified"));
}
// =====================================================================
// Mesh Utilities
// =====================================================================
[Test]
public void CenterPivot_MovesPivotToCenter()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var createParams = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestCenterPivot",
},
};
var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams));
Assert.IsTrue(createResult.Value<bool>("success"), createResult.ToString());
var go = GameObject.Find("PBTestCenterPivot");
Assert.IsNotNull(go);
_createdObjects.Add(go);
var pivotParams = new JObject
{
["action"] = "center_pivot",
["target"] = "PBTestCenterPivot",
};
var result = ToJObject(ManageProBuilder.HandleCommand(pivotParams));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
}
[Test]
public void FreezeTransform_ResetsTransformKeepsShape()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var createParams = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestFreeze",
["position"] = new JArray(5f, 3f, 2f),
},
};
var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams));
Assert.IsTrue(createResult.Value<bool>("success"), createResult.ToString());
var go = GameObject.Find("PBTestFreeze");
Assert.IsNotNull(go);
_createdObjects.Add(go);
var freezeParams = new JObject
{
["action"] = "freeze_transform",
["target"] = "PBTestFreeze",
};
var result = ToJObject(ManageProBuilder.HandleCommand(freezeParams));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
// Transform should be reset to identity
Assert.AreEqual(Vector3.zero, go.transform.position);
Assert.AreEqual(Quaternion.identity, go.transform.rotation);
Assert.AreEqual(Vector3.one, go.transform.localScale);
}
[Test]
public void ValidateMesh_CleanMesh_ReturnsNoIssues()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var createParams = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestValidate",
},
};
var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams));
Assert.IsTrue(createResult.Value<bool>("success"), createResult.ToString());
var go = GameObject.Find("PBTestValidate");
Assert.IsNotNull(go);
_createdObjects.Add(go);
var validateParams = new JObject
{
["action"] = "validate_mesh",
["target"] = "PBTestValidate",
};
var result = ToJObject(ManageProBuilder.HandleCommand(validateParams));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
var data = result["data"] as JObject;
Assert.IsNotNull(data);
Assert.IsTrue(data.Value<bool>("healthy"), "Fresh cube should be healthy");
Assert.AreEqual(0, data.Value<int>("degenerateTriangles"));
}
[Test]
public void RepairMesh_OnCleanMesh_ReportsNoChanges()
{
if (!_proBuilderInstalled)
{
Assert.Pass("ProBuilder not installed - skipping.");
return;
}
var createParams = new JObject
{
["action"] = "create_shape",
["properties"] = new JObject
{
["shapeType"] = "Cube",
["name"] = "PBTestRepair",
},
};
var createResult = ToJObject(ManageProBuilder.HandleCommand(createParams));
Assert.IsTrue(createResult.Value<bool>("success"), createResult.ToString());
var go = GameObject.Find("PBTestRepair");
Assert.IsNotNull(go);
_createdObjects.Add(go);
var repairParams = new JObject
{
["action"] = "repair_mesh",
["target"] = "PBTestRepair",
};
var result = ToJObject(ManageProBuilder.HandleCommand(repairParams));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
var data = result["data"] as JObject;
Assert.IsNotNull(data);
Assert.AreEqual(0, data.Value<int>("degenerateTrianglesRemoved"));
}
// =====================================================================
// ProBuilder not installed fallback
// =====================================================================
[Test]
public void AllActions_WithoutProBuilder_ReturnPackageError()
{
if (_proBuilderInstalled)
{
Assert.Pass("ProBuilder IS installed - this test verifies the not-installed path.");
return;
}
string[] testActions = {
"ping", "create_shape", "get_mesh_info", "extrude_faces",
"auto_smooth", "set_smoothing", "center_pivot", "validate_mesh",
};
foreach (var action in testActions)
{
var paramsObj = new JObject { ["action"] = action };
var result = ToJObject(ManageProBuilder.HandleCommand(paramsObj));
Assert.IsFalse(result.Value<bool>("success"),
$"Action '{action}' should fail without ProBuilder: {result}");
Assert.That(result["error"]?.ToString(),
Does.Contain("ProBuilder").IgnoreCase,
$"Error for '{action}' should mention ProBuilder");
}
}
}
}