Skip to content

Commit da1b700

Browse files
authored
Merge branch 'CoplayDev:beta' into pr-980
2 parents fd8a6da + c348a3d commit da1b700

39 files changed

Lines changed: 6074 additions & 49 deletions

MCPForUnity/Editor/Services/Server/TerminalLauncher.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,13 @@ public System.Diagnostics.ProcessStartInfo CreateTerminalProcessStartInfo(string
7070
CreateNoWindow = true
7171
};
7272
#else
73-
// Linux: Try common terminal emulators
74-
// We use bash -c to execute the command, so we must properly quote/escape for bash
75-
// Escape single quotes for the inner bash string
76-
string escapedCommandLinux = command.Replace("'", "'\\''");
77-
// Wrap the command in single quotes for bash -c
78-
string script = $"'{escapedCommandLinux}; exec bash'";
79-
// Escape double quotes for the outer Process argument string
80-
string escapedScriptForArg = script.Replace("\"", "\\\"");
73+
// Linux: Try common terminal emulators.
74+
// ProcessStartInfo passes the argument string directly to the terminal, so we only
75+
// need to escape for the double-quoted bash -c payload — no inner single quotes.
76+
string script = $"{command}; exec bash";
77+
string escapedScriptForArg = script
78+
.Replace("\\", "\\\\")
79+
.Replace("\"", "\\\"");
8180
string bashCmdArgs = $"bash -c \"{escapedScriptForArg}\"";
8281

8382
string[] terminals = { "gnome-terminal", "xterm", "konsole", "xfce4-terminal" };

MCPForUnity/Editor/Tools/Build/BuildRunner.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ private static void RunBuildCore(BuildJob job, Func<BuildReport> buildFunc)
111111
else
112112
{
113113
job.State = BuildJobState.Failed;
114-
#if UNITY_2022_3_OR_NEWER
114+
#if UNITY_2023_1_OR_NEWER
115115
job.ErrorMessage = report.SummarizeErrors();
116116
#else
117117
job.ErrorMessage = $"Build failed with result: {summary.result}";

MCPForUnity/Editor/Tools/ManageTexture.cs

Lines changed: 123 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ public static class ManageTexture
2727
"create_sprite",
2828
"apply_pattern",
2929
"apply_gradient",
30-
"apply_noise"
30+
"apply_noise",
31+
"set_import_settings"
3132
};
3233

3334
private static ErrorResponse ValidateDimensions(int width, int height, List<string> warnings)
@@ -78,6 +79,8 @@ public static object HandleCommand(JObject @params)
7879
return ApplyGradient(@params);
7980
case "apply_noise":
8081
return ApplyNoise(@params);
82+
case "set_import_settings":
83+
return SetImportSettings(@params);
8184
default:
8285
return new ErrorResponse($"Unknown action: '{action}'");
8386
}
@@ -254,20 +257,36 @@ private static object ModifyTexture(JObject @params)
254257

255258
try
256259
{
257-
Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(fullPath);
258-
if (texture == null)
259-
return new ErrorResponse($"Failed to load texture at path: {fullPath}");
260+
var setPixelsToken = @params["setPixels"] as JObject;
261+
bool hasImportSettings = HasImportSettingsParams(@params);
260262

261-
// Make the texture readable
262-
string absolutePath = GetAbsolutePath(fullPath);
263-
byte[] fileData = File.ReadAllBytes(absolutePath);
264-
Texture2D editableTexture = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false);
265-
editableTexture.LoadImage(fileData);
263+
// Validate import settings before any writes
264+
if (hasImportSettings)
265+
{
266+
var validationError = ValidateImportSettingsParams(@params);
267+
if (validationError != null) return validationError;
268+
}
266269

267-
// Apply modifications
268-
var setPixelsToken = @params["setPixels"] as JObject;
270+
// Fast path: only import settings, no pixel changes
271+
if (setPixelsToken == null && hasImportSettings)
272+
{
273+
var error = ApplyImportSettingsParams(fullPath, @params);
274+
if (error != null) return error;
275+
return new SuccessResponse($"Texture modified: {fullPath}");
276+
}
277+
278+
// Pixel modification path
269279
if (setPixelsToken != null)
270280
{
281+
Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(fullPath);
282+
if (texture == null)
283+
return new ErrorResponse($"Failed to load texture at path: {fullPath}");
284+
285+
string absolutePath = GetAbsolutePath(fullPath);
286+
byte[] fileData = File.ReadAllBytes(absolutePath);
287+
Texture2D editableTexture = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false);
288+
editableTexture.LoadImage(fileData);
289+
271290
int x = setPixelsToken["x"]?.ToObject<int>() ?? 0;
272291
int y = setPixelsToken["y"]?.ToObject<int>() ?? 0;
273292
int w = setPixelsToken["width"]?.ToObject<int>() ?? 1;
@@ -307,22 +326,25 @@ private static object ModifyTexture(JObject @params)
307326
UnityEngine.Object.DestroyImmediate(editableTexture);
308327
return new ErrorResponse("setPixels requires 'color' or 'pixels'.");
309328
}
310-
}
311329

312-
editableTexture.Apply();
330+
editableTexture.Apply();
313331

314-
// Save back to disk
315-
byte[] imageData = TextureOps.EncodeTexture(editableTexture, fullPath);
316-
if (imageData == null || imageData.Length == 0)
317-
{
332+
byte[] imageData = TextureOps.EncodeTexture(editableTexture, fullPath);
333+
if (imageData == null || imageData.Length == 0)
334+
{
335+
UnityEngine.Object.DestroyImmediate(editableTexture);
336+
return new ErrorResponse($"Failed to encode texture for '{fullPath}'");
337+
}
338+
File.WriteAllBytes(absolutePath, imageData);
339+
AssetDatabase.ImportAsset(fullPath, ImportAssetOptions.ForceUpdate);
318340
UnityEngine.Object.DestroyImmediate(editableTexture);
319-
return new ErrorResponse($"Failed to encode texture for '{fullPath}'");
320341
}
321-
File.WriteAllBytes(absolutePath, imageData);
322342

323-
AssetDatabase.ImportAsset(fullPath, ImportAssetOptions.ForceUpdate);
324-
325-
UnityEngine.Object.DestroyImmediate(editableTexture);
343+
if (hasImportSettings)
344+
{
345+
var importError = ApplyImportSettingsParams(fullPath, @params);
346+
if (importError != null) return importError;
347+
}
326348

327349
return new SuccessResponse($"Texture modified: {fullPath}");
328350
}
@@ -703,6 +725,85 @@ private static void ApplyPerlinNoise(Texture2D texture, List<Color32> palette, f
703725
}
704726
}
705727

728+
private static bool HasImportSettingsParams(JObject @params)
729+
{
730+
JToken importSettingsToken = @params["import_settings"] ?? @params["importSettings"];
731+
JToken asSpriteToken = @params["as_sprite"] ?? @params["spriteSettings"];
732+
733+
bool hasImportSettings = importSettingsToken is JObject importObject && importObject.HasValues;
734+
bool hasSpriteSettings = (asSpriteToken is JObject spriteObject && spriteObject.HasValues)
735+
|| (asSpriteToken?.Type == JTokenType.Boolean && asSpriteToken.ToObject<bool>());
736+
737+
return hasImportSettings || hasSpriteSettings;
738+
}
739+
740+
private static object ValidateImportSettingsParams(JObject @params)
741+
{
742+
JToken importSettingsToken = @params["import_settings"] ?? @params["importSettings"];
743+
JToken asSpriteToken = @params["as_sprite"] ?? @params["spriteSettings"];
744+
745+
if (importSettingsToken != null && asSpriteToken != null)
746+
{
747+
return new ErrorResponse("Cannot specify both 'import_settings' and 'as_sprite'.");
748+
}
749+
return null;
750+
}
751+
752+
private static object ApplyImportSettingsParams(string fullPath, JObject @params)
753+
{
754+
JToken importSettingsToken = @params["import_settings"] ?? @params["importSettings"];
755+
JToken asSpriteToken = @params["as_sprite"] ?? @params["spriteSettings"];
756+
757+
if (importSettingsToken != null && asSpriteToken != null)
758+
{
759+
return new ErrorResponse(
760+
"Cannot specify both 'import_settings' and 'as_sprite'. " +
761+
"Use 'import_settings' with textureType='Sprite' instead.");
762+
}
763+
764+
if (importSettingsToken != null)
765+
{
766+
ConfigureTextureImporter(fullPath, importSettingsToken);
767+
}
768+
else if (asSpriteToken != null &&
769+
(asSpriteToken.Type == JTokenType.Boolean ? asSpriteToken.ToObject<bool>() : true))
770+
{
771+
ConfigureAsSprite(fullPath, asSpriteToken.Type == JTokenType.Object ? asSpriteToken : null);
772+
}
773+
774+
return null;
775+
}
776+
777+
private static object SetImportSettings(JObject @params)
778+
{
779+
var toolParams = new MCPForUnity.Editor.Helpers.ToolParams(@params);
780+
var pathResult = toolParams.GetRequired("path", "'path' is required for set_import_settings.");
781+
if (!pathResult.IsSuccess)
782+
return new ErrorResponse(pathResult.ErrorMessage);
783+
string path = pathResult.Value;
784+
785+
string fullPath = AssetPathUtility.SanitizeAssetPath(path);
786+
if (!AssetExists(fullPath))
787+
return new ErrorResponse($"Texture not found at path: {fullPath}");
788+
789+
try
790+
{
791+
if (!HasImportSettingsParams(@params))
792+
{
793+
return new ErrorResponse("Either 'import_settings' or 'as_sprite' is required.");
794+
}
795+
796+
var error = ApplyImportSettingsParams(fullPath, @params);
797+
if (error != null) return error;
798+
799+
return new SuccessResponse($"Import settings updated for: {fullPath}", new { path = fullPath });
800+
}
801+
catch (Exception e)
802+
{
803+
return new ErrorResponse($"Failed to set import settings: {e.Message}");
804+
}
805+
}
806+
706807
private static void ConfigureAsSprite(string path, JToken spriteSettings)
707808
{
708809
TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter;

MCPForUnity/Editor/Tools/Physics.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using System.Collections.Generic;
2+
using Newtonsoft.Json.Linq;
3+
using UnityEditor;
4+
using UnityEngine;
5+
using MCPForUnity.Editor.Helpers;
6+
7+
namespace MCPForUnity.Editor.Tools.Physics
8+
{
9+
internal static class CollisionMatrixOps
10+
{
11+
public static object GetCollisionMatrix(JObject @params)
12+
{
13+
var p = new ToolParams(@params);
14+
string dimension = (p.Get("dimension") ?? "3d").ToLowerInvariant();
15+
16+
if (dimension != "3d" && dimension != "2d")
17+
return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'.");
18+
19+
var layers = new List<object>();
20+
var populatedIndices = new List<int>();
21+
22+
for (int i = 0; i < 32; i++)
23+
{
24+
string name = LayerMask.LayerToName(i);
25+
if (string.IsNullOrEmpty(name)) continue;
26+
layers.Add(new { index = i, name });
27+
populatedIndices.Add(i);
28+
}
29+
30+
var matrix = new Dictionary<string, Dictionary<string, bool>>();
31+
32+
foreach (int i in populatedIndices)
33+
{
34+
string nameA = LayerMask.LayerToName(i);
35+
var row = new Dictionary<string, bool>();
36+
37+
foreach (int j in populatedIndices)
38+
{
39+
if (j > i) continue;
40+
string nameB = LayerMask.LayerToName(j);
41+
bool collides = dimension == "2d"
42+
? !Physics2D.GetIgnoreLayerCollision(i, j)
43+
: !UnityEngine.Physics.GetIgnoreLayerCollision(i, j);
44+
row[nameB] = collides;
45+
}
46+
47+
matrix[nameA] = row;
48+
}
49+
50+
return new
51+
{
52+
success = true,
53+
message = $"Collision matrix retrieved ({dimension}).",
54+
data = new { layers, matrix }
55+
};
56+
}
57+
58+
public static object SetCollisionMatrix(JObject @params)
59+
{
60+
var p = new ToolParams(@params);
61+
string dimension = (p.Get("dimension") ?? "3d").ToLowerInvariant();
62+
63+
if (dimension != "3d" && dimension != "2d")
64+
return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'.");
65+
66+
var layerAToken = p.GetRaw("layer_a");
67+
var layerBToken = p.GetRaw("layer_b");
68+
69+
if (layerAToken == null)
70+
return new ErrorResponse("'layer_a' parameter is required.");
71+
if (layerBToken == null)
72+
return new ErrorResponse("'layer_b' parameter is required.");
73+
74+
int layerA = ResolveLayer(layerAToken);
75+
int layerB = ResolveLayer(layerBToken);
76+
77+
if (layerA < 0 || layerA >= 32)
78+
return new ErrorResponse($"Invalid layer_a: '{layerAToken}'. Layer not found or out of range.");
79+
if (layerB < 0 || layerB >= 32)
80+
return new ErrorResponse($"Invalid layer_b: '{layerBToken}'. Layer not found or out of range.");
81+
82+
bool collide = p.GetBool("collide", true);
83+
84+
if (dimension == "2d")
85+
{
86+
Physics2D.IgnoreLayerCollision(layerA, layerB, !collide);
87+
MarkSettingsDirty("ProjectSettings/Physics2DSettings.asset");
88+
}
89+
else
90+
{
91+
UnityEngine.Physics.IgnoreLayerCollision(layerA, layerB, !collide);
92+
MarkSettingsDirty("ProjectSettings/DynamicsManager.asset");
93+
}
94+
95+
string nameA = LayerMask.LayerToName(layerA);
96+
string nameB = LayerMask.LayerToName(layerB);
97+
if (string.IsNullOrEmpty(nameA)) nameA = layerA.ToString();
98+
if (string.IsNullOrEmpty(nameB)) nameB = layerB.ToString();
99+
100+
return new
101+
{
102+
success = true,
103+
message = $"Collision between '{nameA}' and '{nameB}' set to {(collide ? "enabled" : "disabled")} ({dimension}).",
104+
data = new { layer_a = nameA, layer_b = nameB, collide, dimension }
105+
};
106+
}
107+
108+
private static void MarkSettingsDirty(string assetPath)
109+
{
110+
var assets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
111+
if (assets != null && assets.Length > 0)
112+
EditorUtility.SetDirty(assets[0]);
113+
}
114+
115+
private static int ResolveLayer(JToken token)
116+
{
117+
if (token.Type == JTokenType.Integer)
118+
{
119+
int idx = token.Value<int>();
120+
return idx >= 0 && idx < 32 ? idx : -1;
121+
}
122+
string name = token.ToString();
123+
if (int.TryParse(name, out int parsed))
124+
return parsed >= 0 && parsed < 32 ? parsed : -1;
125+
return LayerMask.NameToLayer(name);
126+
}
127+
}
128+
}

MCPForUnity/Editor/Tools/Physics/CollisionMatrixOps.cs.meta

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)