diff --git a/Editor/Commands/MaterialCommandHandler.cs b/Editor/Commands/MaterialCommandHandler.cs index 0e161ca4..66f8bf04 100644 --- a/Editor/Commands/MaterialCommandHandler.cs +++ b/Editor/Commands/MaterialCommandHandler.cs @@ -2,6 +2,8 @@ using Newtonsoft.Json.Linq; using UnityEngine.Rendering.Universal; using UnityEngine.Rendering; +using UnityEditor; +using System.IO; namespace MCPServer.Editor.Commands { @@ -22,26 +24,72 @@ public static object SetMaterial(JObject @params) // Check if URP is being used bool isURP = GraphicsSettings.currentRenderPipeline is UniversalRenderPipelineAsset; - // Create material with appropriate shader based on render pipeline - Material material; - if (isURP) + Material material = null; + string materialName = (string)@params["material_name"]; + bool createIfMissing = (bool)(@params["create_if_missing"] ?? true); + string materialPath = null; + + // If material name is specified, try to find or create it + if (!string.IsNullOrEmpty(materialName)) { - material = new Material(Shader.Find("Universal Render Pipeline/Lit")); + // Ensure Materials folder exists + const string materialsFolder = "Assets/Materials"; + if (!Directory.Exists(materialsFolder)) + { + Directory.CreateDirectory(materialsFolder); + } + + materialPath = $"{materialsFolder}/{materialName}.mat"; + material = AssetDatabase.LoadAssetAtPath(materialPath); + + if (material == null && createIfMissing) + { + // Create new material with appropriate shader + material = new Material(isURP ? Shader.Find("Universal Render Pipeline/Lit") : Shader.Find("Standard")); + material.name = materialName; + + // Save the material asset + AssetDatabase.CreateAsset(material, materialPath); + AssetDatabase.SaveAssets(); + } + else if (material == null) + { + throw new System.Exception($"Material '{materialName}' not found and create_if_missing is false."); + } } else { - material = new Material(Shader.Find("Standard")); + // Create a temporary material if no name specified + material = new Material(isURP ? Shader.Find("Universal Render Pipeline/Lit") : Shader.Find("Standard")); } - if (@params.ContainsKey("material_name")) material.name = (string)@params["material_name"]; + // Apply color if specified if (@params.ContainsKey("color")) { - var colorArray = (JArray)@params["color"] ?? throw new System.Exception("Invalid color parameter."); - if (colorArray.Count != 3) throw new System.Exception("Color must be an array of 3 floats [r, g, b]."); - material.color = new Color((float)colorArray[0], (float)colorArray[1], (float)colorArray[2]); + var colorArray = (JArray)@params["color"]; + if (colorArray.Count < 3 || colorArray.Count > 4) + throw new System.Exception("Color must be an array of 3 (RGB) or 4 (RGBA) floats."); + + Color color = new Color( + (float)colorArray[0], + (float)colorArray[1], + (float)colorArray[2], + colorArray.Count > 3 ? (float)colorArray[3] : 1.0f + ); + material.color = color; + + // If this is a saved material, make sure to save the color change + if (!string.IsNullOrEmpty(materialPath)) + { + EditorUtility.SetDirty(material); + AssetDatabase.SaveAssets(); + } } + + // Apply the material to the renderer renderer.material = material; - return new { material_name = material.name }; + + return new { material_name = material.name, path = materialPath }; } } } \ No newline at end of file diff --git a/Python/tools/material_tools.py b/Python/tools/material_tools.py index 47cbf2d5..01df0f8a 100644 --- a/Python/tools/material_tools.py +++ b/Python/tools/material_tools.py @@ -1,5 +1,5 @@ from mcp.server.fastmcp import FastMCP, Context -from typing import List +from typing import List, Optional from unity_connection import get_unity_connection def register_material_tools(mcp: FastMCP): @@ -9,18 +9,22 @@ def register_material_tools(mcp: FastMCP): def set_material( ctx: Context, object_name: str, - material_name: str = None, - color: List[float] = None, + material_name: Optional[str] = None, + color: Optional[List[float]] = None, create_if_missing: bool = True ) -> str: """ - Apply or create a material for a game object. + Apply or create a material for a game object. If material_name is provided, + the material will be saved as a shared asset in the Materials folder. Args: object_name: Target game object. - material_name: Optional material name. - color: Optional [R, G, B] values (0.0-1.0). + material_name: Optional material name. If provided, creates/uses a shared material asset. + color: Optional [R, G, B] or [R, G, B, A] values (0.0-1.0). create_if_missing: Whether to create the material if it doesn't exist (default: True). + + Returns: + str: Status message indicating success or failure. """ try: unity = get_unity_connection() @@ -63,14 +67,23 @@ def set_material( return f"Error: Color {channel} value must be in the range 0.0-1.0, but got {value}." # Set up parameters for the command - params = {"object_name": object_name} + params = { + "object_name": object_name, + "create_if_missing": create_if_missing + } if material_name: params["material_name"] = material_name - params["create_if_missing"] = create_if_missing if color: params["color"] = color result = unity.send_command("SET_MATERIAL", params) - return f"Applied material to {object_name}: {result.get('material_name', 'unknown')}" + material_name = result.get("material_name", "unknown") + material_path = result.get("path") + + if material_path: + return f"Applied shared material '{material_name}' to {object_name} (saved at {material_path})" + else: + return f"Applied instance material '{material_name}' to {object_name}" + except Exception as e: return f"Error setting material: {str(e)}" \ No newline at end of file