Skip to content

Commit 23dd9d4

Browse files
authored
Merge branch 'CoplayDev:beta' into beta
2 parents 4f52fd5 + 9081fd5 commit 23dd9d4

4 files changed

Lines changed: 265 additions & 35 deletions

File tree

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/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "com.coplaydev.unity-mcp",
3-
"version": "9.6.3-beta.5",
3+
"version": "9.6.3-beta.6",
44
"displayName": "MCP for Unity",
55
"description": "A bridge that connects AI assistants to Unity via the MCP (Model Context Protocol). Allows AI clients like Claude Code, Cursor, and VSCode to directly control your Unity Editor for enhanced development workflows.\n\nFeatures automated setup wizard, cross-platform support, and seamless integration with popular AI development tools.\n\nJoin Our Discord: https://discord.gg/y4p8KfzrN4",
66
"unity": "2021.3",

Server/src/cli/commands/texture.py

Lines changed: 137 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -481,29 +481,113 @@ def sprite(path: str, width: int, height: int, image_path: Optional[str], color:
481481
print_success(f"Created sprite: {path}")
482482

483483

484+
def _build_import_settings_from_flags(
485+
texture_type: Optional[str],
486+
sprite_mode: Optional[str],
487+
sprite_ppu: Optional[float],
488+
max_size: Optional[str],
489+
compression: Optional[str],
490+
generate_mipmaps: Optional[bool],
491+
srgb: Optional[bool],
492+
readable: Optional[bool],
493+
) -> dict[str, Any]:
494+
"""Build importSettings dict from CLI flags. Returns empty dict if no flags set."""
495+
import_settings: dict[str, Any] = {}
496+
if texture_type:
497+
import_settings["textureType"] = _TEXTURE_TYPES[texture_type]
498+
if sprite_mode:
499+
import_settings["spriteImportMode"] = _SPRITE_MODES[sprite_mode]
500+
if sprite_ppu is not None:
501+
import_settings["spritePixelsPerUnit"] = sprite_ppu
502+
if max_size:
503+
import_settings["maxTextureSize"] = int(max_size)
504+
if compression:
505+
import_settings["textureCompression"] = _COMPRESSIONS[compression]
506+
if generate_mipmaps is not None:
507+
import_settings["mipmapEnabled"] = generate_mipmaps
508+
if srgb is not None:
509+
import_settings["sRGBTexture"] = srgb
510+
if readable is not None:
511+
import_settings["isReadable"] = readable
512+
return import_settings
513+
514+
515+
def _apply_import_flags_to_params(
516+
params: dict[str, Any],
517+
texture_type: Optional[str],
518+
sprite_mode: Optional[str],
519+
sprite_ppu: Optional[float],
520+
max_size: Optional[str],
521+
compression: Optional[str],
522+
generate_mipmaps: Optional[bool],
523+
srgb: Optional[bool],
524+
readable: Optional[bool],
525+
as_sprite: bool,
526+
) -> bool:
527+
"""Validate and apply import-setting flags to params dict. Returns True if any import setting present."""
528+
has_other_flags = any(v is not None for v in (
529+
texture_type, sprite_mode, sprite_ppu, max_size, compression, generate_mipmaps, srgb, readable))
530+
531+
if as_sprite:
532+
if has_other_flags:
533+
print_error("--as-sprite cannot be combined with other import-setting flags")
534+
sys.exit(1)
535+
params["spriteSettings"] = {"pivot": [0.5, 0.5], "pixelsPerUnit": 100}
536+
return True
537+
538+
if has_other_flags:
539+
import_settings = _build_import_settings_from_flags(
540+
texture_type, sprite_mode, sprite_ppu, max_size, compression,
541+
generate_mipmaps, srgb, readable)
542+
if import_settings:
543+
params["importSettings"] = import_settings
544+
return True
545+
546+
return False
547+
548+
484549
@texture.command("modify")
485550
@click.argument("path")
486-
@click.option("--set-pixels", required=True, help="Modification args as JSON")
551+
@click.option("--set-pixels", default=None, help="Modification args as JSON")
552+
@click.option("--texture-type", type=click.Choice(list(_TEXTURE_TYPES.keys())), help="Texture type")
553+
@click.option("--sprite-mode", type=click.Choice(list(_SPRITE_MODES.keys())), help="Sprite import mode")
554+
@click.option("--sprite-ppu", type=float, help="Sprite pixels per unit")
555+
@click.option("--max-size", type=click.Choice(["32", "64", "128", "256", "512", "1024", "2048", "4096", "8192", "16384"]), help="Max texture size")
556+
@click.option("--compression", type=click.Choice(list(_COMPRESSIONS.keys())), help="Compression quality")
557+
@click.option("--generate-mipmaps/--no-mipmaps", default=None, help="Generate mipmaps")
558+
@click.option("--srgb/--linear", default=None, help="sRGB color texture")
559+
@click.option("--readable/--no-readable", default=None, help="Read/Write enabled")
560+
@click.option("--as-sprite", is_flag=True, help="Shorthand: set texture type to Sprite with defaults")
487561
@handle_unity_errors
488-
def modify(path: str, set_pixels: str):
562+
def modify(path: str, set_pixels: Optional[str], texture_type: Optional[str], sprite_mode: Optional[str],
563+
sprite_ppu: Optional[float], max_size: Optional[str], compression: Optional[str],
564+
generate_mipmaps: Optional[bool], srgb: Optional[bool], readable: Optional[bool],
565+
as_sprite: bool):
489566
"""Modify an existing texture.
490567
491568
\b
492569
Examples:
493570
unity-mcp texture modify Assets/Tex.png --set-pixels '{"x":0,"y":0,"width":10,"height":10,"color":[255,0,0]}'
494571
unity-mcp texture modify Assets/Tex.png --set-pixels '{"x":0,"y":0,"width":2,"height":2,"pixels":[[255,0,0,255],[0,255,0,255],[0,0,255,255],[255,255,0,255]]}'
572+
unity-mcp texture modify Assets/UI/icon.png --as-sprite
573+
unity-mcp texture modify Assets/UI/bg.png --texture-type sprite --max-size 2048
495574
"""
496575
config = get_config()
497576

498-
params: dict[str, Any] = {
499-
"action": "modify",
500-
"path": path,
501-
}
577+
params: dict[str, Any] = {"action": "modify", "path": path}
502578

503-
try:
504-
params["setPixels"] = _normalize_set_pixels(set_pixels)
505-
except ValueError as e:
506-
print_error(str(e))
579+
has_import = _apply_import_flags_to_params(
580+
params, texture_type, sprite_mode, sprite_ppu, max_size,
581+
compression, generate_mipmaps, srgb, readable, as_sprite)
582+
583+
if set_pixels is not None:
584+
try:
585+
params["setPixels"] = _normalize_set_pixels(set_pixels)
586+
except ValueError as e:
587+
print_error(str(e))
588+
sys.exit(1)
589+
elif not has_import:
590+
print_error("At least one of --set-pixels or an import-setting flag must be provided")
507591
sys.exit(1)
508592

509593
result = run_command("manage_texture", params, config)
@@ -538,3 +622,46 @@ def delete(path: str, force: bool):
538622
click.echo(format_output(result, config.format))
539623
if result.get("success"):
540624
print_success(f"Deleted texture: {path}")
625+
626+
627+
@texture.command("set-import-settings")
628+
@click.argument("path")
629+
@click.option("--texture-type", type=click.Choice(list(_TEXTURE_TYPES.keys())), help="Texture type")
630+
@click.option("--sprite-mode", type=click.Choice(list(_SPRITE_MODES.keys())), help="Sprite import mode")
631+
@click.option("--sprite-ppu", type=float, help="Sprite pixels per unit")
632+
@click.option("--max-size", type=click.Choice(["32", "64", "128", "256", "512", "1024", "2048", "4096", "8192", "16384"]), help="Max texture size")
633+
@click.option("--compression", type=click.Choice(list(_COMPRESSIONS.keys())), help="Compression quality")
634+
@click.option("--generate-mipmaps/--no-mipmaps", default=None, help="Generate mipmaps")
635+
@click.option("--srgb/--linear", default=None, help="sRGB color texture")
636+
@click.option("--readable/--no-readable", default=None, help="Read/Write enabled")
637+
@click.option("--as-sprite", is_flag=True, help="Shorthand: set texture type to Sprite with defaults")
638+
@handle_unity_errors
639+
def set_import_settings(path: str, texture_type: Optional[str], sprite_mode: Optional[str],
640+
sprite_ppu: Optional[float], max_size: Optional[str],
641+
compression: Optional[str], generate_mipmaps: Optional[bool],
642+
srgb: Optional[bool], readable: Optional[bool], as_sprite: bool):
643+
"""Change import settings on an existing texture.
644+
645+
\b
646+
Examples:
647+
unity-mcp texture set-import-settings Assets/UI/icon.png --texture-type sprite
648+
unity-mcp texture set-import-settings Assets/UI/icon.png --as-sprite
649+
unity-mcp texture set-import-settings Assets/UI/icon.png --texture-type sprite --sprite-mode single --sprite-ppu 100
650+
unity-mcp texture set-import-settings Assets/UI/bg.png --max-size 2048 --compression high_quality
651+
"""
652+
config = get_config()
653+
654+
params: dict[str, Any] = {"action": "set_import_settings", "path": path}
655+
656+
has_import = _apply_import_flags_to_params(
657+
params, texture_type, sprite_mode, sprite_ppu, max_size,
658+
compression, generate_mipmaps, srgb, readable, as_sprite)
659+
660+
if not has_import:
661+
print_error("At least one import setting must be specified")
662+
sys.exit(1)
663+
664+
result = run_command("manage_texture", params, config)
665+
click.echo(format_output(result, config.format))
666+
if result.get("success"):
667+
print_success(f"Import settings updated: {path}")

Server/src/services/tools/manage_texture.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,8 @@ def _normalize_import_settings(value: Any) -> tuple[dict | None, str | None]:
378378
description=(
379379
"Procedural texture generation for Unity. Creates textures with solid fills, "
380380
"patterns (checkerboard, stripes, dots, grid, brick), gradients, and noise. "
381-
"Actions: create, modify, delete, create_sprite, apply_pattern, apply_gradient, apply_noise"
381+
"Actions: create, modify, delete, create_sprite, apply_pattern, apply_gradient, apply_noise, "
382+
"set_import_settings"
382383
),
383384
annotations=ToolAnnotations(
384385
title="Manage Texture",
@@ -394,7 +395,8 @@ async def manage_texture(
394395
"create_sprite",
395396
"apply_pattern",
396397
"apply_gradient",
397-
"apply_noise"
398+
"apply_noise",
399+
"set_import_settings"
398400
], "Action to perform."],
399401

400402
# Required for most actions

0 commit comments

Comments
 (0)