Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 42 additions & 13 deletions MCPForUnity/Editor/Helpers/GameObjectSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,24 +255,25 @@ public static object GetComponentData(Component c, bool includeNonPublicSerializ
var declaredFields = currentType.GetFields(fieldFlags);

// Process the declared Fields for caching
foreach (var fieldInfo in declaredFields)
foreach (var fieldInfo in declaredFields)
{
if (fieldInfo.Name.EndsWith("k__BackingField")) continue; // Skip backing fields

// Add if not already added (handles hiding - keep the most derived version)
if (fieldsToCache.Any(f => f.Name == fieldInfo.Name)) continue;

bool shouldInclude = false;
if (includeNonPublicSerializedFields)
{
// If TRUE, include Public OR NonPublic with [SerializeField]
shouldInclude = fieldInfo.IsPublic || (fieldInfo.IsPrivate && fieldInfo.IsDefined(typeof(SerializeField), inherit: false));
}
else // includeNonPublicSerializedFields is FALSE
{
// If FALSE, include ONLY if it is explicitly Public.
shouldInclude = fieldInfo.IsPublic;
}
bool shouldInclude = false;
if (includeNonPublicSerializedFields)
{
// If TRUE, include Public OR any NonPublic with [SerializeField] (private/protected/internal)
var hasSerializeField = fieldInfo.IsDefined(typeof(SerializeField), inherit: true);
shouldInclude = fieldInfo.IsPublic || (!fieldInfo.IsPublic && hasSerializeField);
}
else // includeNonPublicSerializedFields is FALSE
{
// If FALSE, include ONLY if it is explicitly Public.
shouldInclude = fieldInfo.IsPublic;
}

if (shouldInclude)
{
Expand Down Expand Up @@ -357,7 +358,35 @@ public static object GetComponentData(Component c, bool includeNonPublicSerializ
// --- Add detailed logging ---
// Debug.Log($"[GetComponentData] Accessing: {componentType.Name}.{propName}");
// --- End detailed logging ---
object value = propInfo.GetValue(c);

// --- Special handling for material/mesh properties in edit mode ---
object value;
if (!Application.isPlaying && (propName == "material" || propName == "materials" || propName == "mesh"))
{
// In edit mode, use sharedMaterial/sharedMesh to avoid instantiation warnings
if ((propName == "material" || propName == "materials") && c is Renderer renderer)
{
if (propName == "material")
value = renderer.sharedMaterial;
else // materials
value = renderer.sharedMaterials;
}
else if (propName == "mesh" && c is MeshFilter meshFilter)
{
value = meshFilter.sharedMesh;
}
else
{
// Fallback to normal property access if type doesn't match
value = propInfo.GetValue(c);
}
}
else
{
value = propInfo.GetValue(c);
}
// --- End special handling ---

Type propType = propInfo.PropertyType;
AddSerializableValue(serializablePropertiesOutput, propName, propType, value);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEditor;
Expand Down Expand Up @@ -355,5 +356,205 @@ public void SetComponentProperties_ContinuesAfterException()
}
Assert.IsTrue(foundVelocityError, "errors should include a message referencing 'velocity'");
}

[Test]
public void GetComponentData_DoesNotInstantiateMaterialsInEditMode()
{
// Arrange - Create a GameObject with MeshRenderer and MeshFilter components
var testObject = new GameObject("MaterialMeshTestObject");
var meshRenderer = testObject.AddComponent<MeshRenderer>();
var meshFilter = testObject.AddComponent<MeshFilter>();

// Create a simple material and mesh for testing
var testMaterial = new Material(Shader.Find("Standard"));
var tempCube = GameObject.CreatePrimitive(PrimitiveType.Cube);
var testMesh = tempCube.GetComponent<MeshFilter>().sharedMesh;
UnityEngine.Object.DestroyImmediate(tempCube);

// Set the shared material and mesh (these should be used in edit mode)
meshRenderer.sharedMaterial = testMaterial;
meshFilter.sharedMesh = testMesh;

// Act - Get component data which should trigger material/mesh property access
var prevIgnore = LogAssert.ignoreFailingMessages;
LogAssert.ignoreFailingMessages = true; // Avoid failing due to incidental editor logs during reflection
object result;
try
{
result = MCPForUnity.Editor.Helpers.GameObjectSerializer.GetComponentData(meshRenderer);
}
finally
{
LogAssert.ignoreFailingMessages = prevIgnore;
}

// Assert - Basic success and shape tolerance
Assert.IsNotNull(result, "GetComponentData should return a result");
if (result is Dictionary<string, object> dict &&
dict.TryGetValue("properties", out var propsObj) &&
propsObj is Dictionary<string, object> properties)
{
Assert.IsTrue(properties.ContainsKey("material") || properties.ContainsKey("sharedMaterial") || properties.ContainsKey("materials") || properties.ContainsKey("sharedMaterials"),
"Serialized data should include a material-related key when present.");
}

// Clean up
UnityEngine.Object.DestroyImmediate(testMaterial);
UnityEngine.Object.DestroyImmediate(testObject);
}

[Test]
public void GetComponentData_DoesNotInstantiateMeshesInEditMode()
{
// Arrange - Create a GameObject with MeshFilter component
var testObject = new GameObject("MeshTestObject");
var meshFilter = testObject.AddComponent<MeshFilter>();

// Create a simple mesh for testing
var tempSphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
var testMesh = tempSphere.GetComponent<MeshFilter>().sharedMesh;
UnityEngine.Object.DestroyImmediate(tempSphere);
meshFilter.sharedMesh = testMesh;

// Act - Get component data which should trigger mesh property access
var prevIgnore2 = LogAssert.ignoreFailingMessages;
LogAssert.ignoreFailingMessages = true;
object result;
try
{
result = MCPForUnity.Editor.Helpers.GameObjectSerializer.GetComponentData(meshFilter);
}
finally
{
LogAssert.ignoreFailingMessages = prevIgnore2;
}

// Assert - Basic success and shape tolerance
Assert.IsNotNull(result, "GetComponentData should return a result");
if (result is Dictionary<string, object> dict2 &&
dict2.TryGetValue("properties", out var propsObj2) &&
propsObj2 is Dictionary<string, object> properties2)
{
Assert.IsTrue(properties2.ContainsKey("mesh") || properties2.ContainsKey("sharedMesh"),
"Serialized data should include a mesh-related key when present.");
}

// Clean up
UnityEngine.Object.DestroyImmediate(testObject);
}

[Test]
public void GetComponentData_UsesSharedMaterialInEditMode()
{
// Arrange - Create a GameObject with MeshRenderer
var testObject = new GameObject("SharedMaterialTestObject");
var meshRenderer = testObject.AddComponent<MeshRenderer>();

// Create a test material
var testMaterial = new Material(Shader.Find("Standard"));
testMaterial.name = "TestMaterial";
meshRenderer.sharedMaterial = testMaterial;

// Act - Get component data in edit mode
var result = MCPForUnity.Editor.Helpers.GameObjectSerializer.GetComponentData(meshRenderer);

// Assert - Verify that the material property was accessed without instantiation
Assert.IsNotNull(result, "GetComponentData should return a result");

// Check that result is a dictionary with properties key
if (result is Dictionary<string, object> resultDict &&
resultDict.TryGetValue("properties", out var propertiesObj) &&
propertiesObj is Dictionary<string, object> properties)
{
Assert.IsTrue(properties.ContainsKey("material") || properties.ContainsKey("sharedMaterial"),
"Serialized data should include 'material' or 'sharedMaterial' when present.");
}

// Clean up
UnityEngine.Object.DestroyImmediate(testMaterial);
UnityEngine.Object.DestroyImmediate(testObject);
}

[Test]
public void GetComponentData_UsesSharedMeshInEditMode()
{
// Arrange - Create a GameObject with MeshFilter
var testObject = new GameObject("SharedMeshTestObject");
var meshFilter = testObject.AddComponent<MeshFilter>();

// Create a test mesh
var tempCylinder = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
var testMesh = tempCylinder.GetComponent<MeshFilter>().sharedMesh;
UnityEngine.Object.DestroyImmediate(tempCylinder);
testMesh.name = "TestMesh";
meshFilter.sharedMesh = testMesh;

// Act - Get component data in edit mode
var result = MCPForUnity.Editor.Helpers.GameObjectSerializer.GetComponentData(meshFilter);

// Assert - Verify that the mesh property was accessed without instantiation
Assert.IsNotNull(result, "GetComponentData should return a result");

// Check that result is a dictionary with properties key
if (result is Dictionary<string, object> resultDict &&
resultDict.TryGetValue("properties", out var propertiesObj) &&
propertiesObj is Dictionary<string, object> properties)
{
Assert.IsTrue(properties.ContainsKey("mesh") || properties.ContainsKey("sharedMesh"),
"Serialized data should include 'mesh' or 'sharedMesh' when present.");
}

// Clean up
UnityEngine.Object.DestroyImmediate(testObject);
}

[Test]
public void GetComponentData_HandlesNullMaterialsAndMeshes()
{
// Arrange - Create a GameObject with MeshRenderer and MeshFilter but no materials/meshes
var testObject = new GameObject("NullMaterialMeshTestObject");
var meshRenderer = testObject.AddComponent<MeshRenderer>();
var meshFilter = testObject.AddComponent<MeshFilter>();

// Don't set any materials or meshes - they should be null

// Act - Get component data
var rendererResult = MCPForUnity.Editor.Helpers.GameObjectSerializer.GetComponentData(meshRenderer);
var meshFilterResult = MCPForUnity.Editor.Helpers.GameObjectSerializer.GetComponentData(meshFilter);

// Assert - Verify that the operations succeeded even with null materials/meshes
Assert.IsNotNull(rendererResult, "GetComponentData should handle null materials");
Assert.IsNotNull(meshFilterResult, "GetComponentData should handle null meshes");

// Clean up
UnityEngine.Object.DestroyImmediate(testObject);
}

[Test]
public void GetComponentData_WorksWithMultipleMaterials()
{
// Arrange - Create a GameObject with MeshRenderer that has multiple materials
var testObject = new GameObject("MultiMaterialTestObject");
var meshRenderer = testObject.AddComponent<MeshRenderer>();

// Create multiple test materials
var material1 = new Material(Shader.Find("Standard"));
material1.name = "TestMaterial1";
var material2 = new Material(Shader.Find("Standard"));
material2.name = "TestMaterial2";

meshRenderer.sharedMaterials = new Material[] { material1, material2 };

// Act - Get component data
var result = MCPForUnity.Editor.Helpers.GameObjectSerializer.GetComponentData(meshRenderer);

// Assert - Verify that the operation succeeded with multiple materials
Assert.IsNotNull(result, "GetComponentData should handle multiple materials");

// Clean up
UnityEngine.Object.DestroyImmediate(material1);
UnityEngine.Object.DestroyImmediate(material2);
UnityEngine.Object.DestroyImmediate(testObject);
}
}
}
Loading