mcpforunityserver 9.4.0b20260203025228__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cli/__init__.py +3 -0
- cli/commands/__init__.py +3 -0
- cli/commands/animation.py +84 -0
- cli/commands/asset.py +280 -0
- cli/commands/audio.py +125 -0
- cli/commands/batch.py +171 -0
- cli/commands/code.py +182 -0
- cli/commands/component.py +190 -0
- cli/commands/editor.py +447 -0
- cli/commands/gameobject.py +487 -0
- cli/commands/instance.py +93 -0
- cli/commands/lighting.py +123 -0
- cli/commands/material.py +239 -0
- cli/commands/prefab.py +248 -0
- cli/commands/scene.py +231 -0
- cli/commands/script.py +222 -0
- cli/commands/shader.py +226 -0
- cli/commands/texture.py +540 -0
- cli/commands/tool.py +58 -0
- cli/commands/ui.py +258 -0
- cli/commands/vfx.py +421 -0
- cli/main.py +281 -0
- cli/utils/__init__.py +31 -0
- cli/utils/config.py +58 -0
- cli/utils/confirmation.py +37 -0
- cli/utils/connection.py +254 -0
- cli/utils/constants.py +23 -0
- cli/utils/output.py +195 -0
- cli/utils/parsers.py +112 -0
- cli/utils/suggestions.py +34 -0
- core/__init__.py +0 -0
- core/config.py +67 -0
- core/constants.py +4 -0
- core/logging_decorator.py +37 -0
- core/telemetry.py +551 -0
- core/telemetry_decorator.py +164 -0
- main.py +845 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/METADATA +328 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/RECORD +105 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/WHEEL +5 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/entry_points.txt +3 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/licenses/LICENSE +21 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/top_level.txt +7 -0
- models/__init__.py +4 -0
- models/models.py +56 -0
- models/unity_response.py +70 -0
- services/__init__.py +0 -0
- services/api_key_service.py +235 -0
- services/custom_tool_service.py +499 -0
- services/registry/__init__.py +22 -0
- services/registry/resource_registry.py +53 -0
- services/registry/tool_registry.py +51 -0
- services/resources/__init__.py +86 -0
- services/resources/active_tool.py +48 -0
- services/resources/custom_tools.py +57 -0
- services/resources/editor_state.py +304 -0
- services/resources/gameobject.py +243 -0
- services/resources/layers.py +30 -0
- services/resources/menu_items.py +35 -0
- services/resources/prefab.py +191 -0
- services/resources/prefab_stage.py +40 -0
- services/resources/project_info.py +40 -0
- services/resources/selection.py +56 -0
- services/resources/tags.py +31 -0
- services/resources/tests.py +88 -0
- services/resources/unity_instances.py +125 -0
- services/resources/windows.py +48 -0
- services/state/external_changes_scanner.py +245 -0
- services/tools/__init__.py +83 -0
- services/tools/batch_execute.py +93 -0
- services/tools/debug_request_context.py +86 -0
- services/tools/execute_custom_tool.py +43 -0
- services/tools/execute_menu_item.py +32 -0
- services/tools/find_gameobjects.py +110 -0
- services/tools/find_in_file.py +181 -0
- services/tools/manage_asset.py +119 -0
- services/tools/manage_components.py +131 -0
- services/tools/manage_editor.py +64 -0
- services/tools/manage_gameobject.py +260 -0
- services/tools/manage_material.py +111 -0
- services/tools/manage_prefabs.py +209 -0
- services/tools/manage_scene.py +111 -0
- services/tools/manage_script.py +645 -0
- services/tools/manage_scriptable_object.py +87 -0
- services/tools/manage_shader.py +71 -0
- services/tools/manage_texture.py +581 -0
- services/tools/manage_vfx.py +120 -0
- services/tools/preflight.py +110 -0
- services/tools/read_console.py +151 -0
- services/tools/refresh_unity.py +153 -0
- services/tools/run_tests.py +317 -0
- services/tools/script_apply_edits.py +1006 -0
- services/tools/set_active_instance.py +120 -0
- services/tools/utils.py +348 -0
- transport/__init__.py +0 -0
- transport/legacy/port_discovery.py +329 -0
- transport/legacy/stdio_port_registry.py +65 -0
- transport/legacy/unity_connection.py +910 -0
- transport/models.py +68 -0
- transport/plugin_hub.py +787 -0
- transport/plugin_registry.py +182 -0
- transport/unity_instance_middleware.py +262 -0
- transport/unity_transport.py +94 -0
- utils/focus_nudge.py +589 -0
- utils/module_discovery.py +55 -0
cli/commands/texture.py
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
"""Texture CLI commands."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import click
|
|
5
|
+
from typing import Optional, Any
|
|
6
|
+
|
|
7
|
+
from cli.utils.config import get_config
|
|
8
|
+
from cli.utils.output import format_output, print_error, print_success
|
|
9
|
+
from cli.utils.connection import run_command, handle_unity_errors
|
|
10
|
+
from cli.utils.parsers import parse_json_or_exit as try_parse_json
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_TEXTURE_TYPES = {
|
|
14
|
+
"default": "Default",
|
|
15
|
+
"normal_map": "NormalMap",
|
|
16
|
+
"editor_gui": "GUI",
|
|
17
|
+
"sprite": "Sprite",
|
|
18
|
+
"cursor": "Cursor",
|
|
19
|
+
"cookie": "Cookie",
|
|
20
|
+
"lightmap": "Lightmap",
|
|
21
|
+
"directional_lightmap": "DirectionalLightmap",
|
|
22
|
+
"shadow_mask": "Shadowmask",
|
|
23
|
+
"single_channel": "SingleChannel",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_TEXTURE_SHAPES = {"2d": "Texture2D", "cube": "TextureCube"}
|
|
27
|
+
|
|
28
|
+
_ALPHA_SOURCES = {
|
|
29
|
+
"none": "None",
|
|
30
|
+
"from_input": "FromInput",
|
|
31
|
+
"from_gray_scale": "FromGrayScale",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_WRAP_MODES = {
|
|
35
|
+
"repeat": "Repeat",
|
|
36
|
+
"clamp": "Clamp",
|
|
37
|
+
"mirror": "Mirror",
|
|
38
|
+
"mirror_once": "MirrorOnce",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
_FILTER_MODES = {"point": "Point",
|
|
42
|
+
"bilinear": "Bilinear", "trilinear": "Trilinear"}
|
|
43
|
+
|
|
44
|
+
_COMPRESSIONS = {
|
|
45
|
+
"none": "Uncompressed",
|
|
46
|
+
"low_quality": "CompressedLQ",
|
|
47
|
+
"normal_quality": "Compressed",
|
|
48
|
+
"high_quality": "CompressedHQ",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_SPRITE_MODES = {"single": "Single",
|
|
52
|
+
"multiple": "Multiple", "polygon": "Polygon"}
|
|
53
|
+
|
|
54
|
+
_SPRITE_MESH_TYPES = {"full_rect": "FullRect", "tight": "Tight"}
|
|
55
|
+
|
|
56
|
+
_MIPMAP_FILTERS = {"box": "BoxFilter", "kaiser": "KaiserFilter"}
|
|
57
|
+
|
|
58
|
+
_MAX_TEXTURE_DIMENSION = 1024
|
|
59
|
+
_MAX_TEXTURE_PIXELS = 1024 * 1024
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _validate_texture_dimensions(width: int, height: int) -> list[str]:
|
|
63
|
+
if width <= 0 or height <= 0:
|
|
64
|
+
raise ValueError("width and height must be positive")
|
|
65
|
+
warnings: list[str] = []
|
|
66
|
+
if width > _MAX_TEXTURE_DIMENSION or height > _MAX_TEXTURE_DIMENSION:
|
|
67
|
+
warnings.append(
|
|
68
|
+
f"width and height should be <= {_MAX_TEXTURE_DIMENSION} (got {width}x{height})")
|
|
69
|
+
total_pixels = width * height
|
|
70
|
+
if total_pixels > _MAX_TEXTURE_PIXELS:
|
|
71
|
+
warnings.append(
|
|
72
|
+
f"width*height should be <= {_MAX_TEXTURE_PIXELS} (got {width}x{height})")
|
|
73
|
+
return warnings
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _is_normalized_color(values: list[Any]) -> bool:
|
|
77
|
+
if not values:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
numeric_values = [float(v) for v in values]
|
|
82
|
+
except (TypeError, ValueError):
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
all_small = all(0 <= v <= 1.0 for v in numeric_values)
|
|
86
|
+
if not all_small:
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
has_fractional = any(0 < v < 1 for v in numeric_values)
|
|
90
|
+
all_binary = all(v in (0, 1, 0.0, 1.0) for v in numeric_values)
|
|
91
|
+
|
|
92
|
+
return has_fractional or all_binary
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _parse_hex_color(value: str) -> list[int]:
|
|
96
|
+
h = value.lstrip("#")
|
|
97
|
+
if len(h) == 6:
|
|
98
|
+
return [int(h[i:i + 2], 16) for i in (0, 2, 4)] + [255]
|
|
99
|
+
if len(h) == 8:
|
|
100
|
+
return [int(h[i:i + 2], 16) for i in (0, 2, 4, 6)]
|
|
101
|
+
raise ValueError(f"Invalid hex color: {value}")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _normalize_color(value: Any, context: str) -> list[int]:
|
|
105
|
+
if value is None:
|
|
106
|
+
raise ValueError(f"{context} is required")
|
|
107
|
+
|
|
108
|
+
if isinstance(value, str):
|
|
109
|
+
if value.startswith("#"):
|
|
110
|
+
return _parse_hex_color(value)
|
|
111
|
+
value = try_parse_json(value, context)
|
|
112
|
+
|
|
113
|
+
# Handle dict with r/g/b keys (e.g., {"r": 1, "g": 0, "b": 0} or {"r": 1, "g": 0, "b": 0, "a": 1})
|
|
114
|
+
if isinstance(value, dict):
|
|
115
|
+
if all(k in value for k in ("r", "g", "b")):
|
|
116
|
+
try:
|
|
117
|
+
color = [value["r"], value["g"], value["b"]]
|
|
118
|
+
if "a" in value:
|
|
119
|
+
color.append(value["a"])
|
|
120
|
+
else:
|
|
121
|
+
color.append(1.0 if _is_normalized_color(color) else 255)
|
|
122
|
+
if _is_normalized_color(color):
|
|
123
|
+
return [int(round(float(c) * 255)) for c in color]
|
|
124
|
+
return [int(c) for c in color]
|
|
125
|
+
except (TypeError, ValueError):
|
|
126
|
+
raise ValueError(f"{context} dict values must be numeric, got {value}")
|
|
127
|
+
raise ValueError(f"{context} dict must have 'r', 'g', 'b' keys, got {list(value.keys())}")
|
|
128
|
+
|
|
129
|
+
if isinstance(value, (list, tuple)):
|
|
130
|
+
if len(value) == 3:
|
|
131
|
+
value = list(value) + [1.0 if _is_normalized_color(value) else 255]
|
|
132
|
+
if len(value) == 4:
|
|
133
|
+
try:
|
|
134
|
+
if _is_normalized_color(value):
|
|
135
|
+
return [int(round(float(c) * 255)) for c in value]
|
|
136
|
+
return [int(c) for c in value]
|
|
137
|
+
except (TypeError, ValueError):
|
|
138
|
+
raise ValueError(
|
|
139
|
+
f"{context} values must be numeric, got {value}")
|
|
140
|
+
raise ValueError(
|
|
141
|
+
f"{context} must have 3 or 4 components, got {len(value)}")
|
|
142
|
+
|
|
143
|
+
raise ValueError(f"{context} must be a list or hex string")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _normalize_palette(value: Any, context: str) -> list[list[int]]:
|
|
147
|
+
if value is None:
|
|
148
|
+
return []
|
|
149
|
+
if isinstance(value, str):
|
|
150
|
+
value = try_parse_json(value, context)
|
|
151
|
+
if not isinstance(value, list):
|
|
152
|
+
raise ValueError(f"{context} must be a list of colors")
|
|
153
|
+
return [_normalize_color(color, f"{context} item") for color in value]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _normalize_pixels(value: Any, width: int, height: int, context: str) -> list[list[int]] | str:
|
|
157
|
+
if value is None:
|
|
158
|
+
raise ValueError(f"{context} is required")
|
|
159
|
+
if isinstance(value, str):
|
|
160
|
+
if value.startswith("base64:"):
|
|
161
|
+
return value
|
|
162
|
+
trimmed = value.strip()
|
|
163
|
+
if trimmed.startswith("[") and trimmed.endswith("]"):
|
|
164
|
+
value = try_parse_json(trimmed, context)
|
|
165
|
+
else:
|
|
166
|
+
return f"base64:{value}"
|
|
167
|
+
if isinstance(value, list):
|
|
168
|
+
expected_count = width * height
|
|
169
|
+
if len(value) != expected_count:
|
|
170
|
+
raise ValueError(
|
|
171
|
+
f"{context} must have {expected_count} entries, got {len(value)}")
|
|
172
|
+
return [_normalize_color(pixel, f"{context} pixel") for pixel in value]
|
|
173
|
+
raise ValueError(f"{context} must be a list or base64 string")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _normalize_set_pixels(value: Any) -> dict[str, Any]:
|
|
177
|
+
if value is None:
|
|
178
|
+
raise ValueError("set-pixels is required")
|
|
179
|
+
if isinstance(value, str):
|
|
180
|
+
value = try_parse_json(value, "set-pixels")
|
|
181
|
+
if not isinstance(value, dict):
|
|
182
|
+
raise ValueError("set-pixels must be a JSON object")
|
|
183
|
+
|
|
184
|
+
result: dict[str, Any] = dict(value)
|
|
185
|
+
|
|
186
|
+
if "pixels" in value:
|
|
187
|
+
width = value.get("width")
|
|
188
|
+
height = value.get("height")
|
|
189
|
+
if width is None or height is None:
|
|
190
|
+
raise ValueError(
|
|
191
|
+
"set-pixels requires width and height when pixels are provided")
|
|
192
|
+
width = int(width)
|
|
193
|
+
height = int(height)
|
|
194
|
+
if width <= 0 or height <= 0:
|
|
195
|
+
raise ValueError("set-pixels width and height must be positive")
|
|
196
|
+
result["width"] = width
|
|
197
|
+
result["height"] = height
|
|
198
|
+
result["pixels"] = _normalize_pixels(
|
|
199
|
+
value["pixels"], width, height, "set-pixels pixels")
|
|
200
|
+
|
|
201
|
+
if "color" in value:
|
|
202
|
+
result["color"] = _normalize_color(value["color"], "set-pixels color")
|
|
203
|
+
|
|
204
|
+
if "pixels" not in value and "color" not in value:
|
|
205
|
+
raise ValueError("set-pixels requires 'color' or 'pixels'")
|
|
206
|
+
|
|
207
|
+
if "x" in value:
|
|
208
|
+
result["x"] = int(value["x"])
|
|
209
|
+
if "y" in value:
|
|
210
|
+
result["y"] = int(value["y"])
|
|
211
|
+
|
|
212
|
+
if "width" in value and "pixels" not in value:
|
|
213
|
+
result["width"] = int(value["width"])
|
|
214
|
+
if "height" in value and "pixels" not in value:
|
|
215
|
+
result["height"] = int(value["height"])
|
|
216
|
+
|
|
217
|
+
return result
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _map_enum(value: Any, mapping: dict[str, str]) -> Any:
|
|
221
|
+
if isinstance(value, str):
|
|
222
|
+
key = value.lower()
|
|
223
|
+
return mapping.get(key, value)
|
|
224
|
+
return value
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
_TRUE_STRINGS = {"true", "1", "yes", "on"}
|
|
228
|
+
_FALSE_STRINGS = {"false", "0", "no", "off"}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _coerce_bool(value: Any, name: str) -> bool:
|
|
232
|
+
if isinstance(value, bool):
|
|
233
|
+
return value
|
|
234
|
+
if isinstance(value, (int, float)) and value in (0, 1, 0.0, 1.0):
|
|
235
|
+
return bool(value)
|
|
236
|
+
if isinstance(value, str):
|
|
237
|
+
lowered = value.strip().lower()
|
|
238
|
+
if lowered in _TRUE_STRINGS:
|
|
239
|
+
return True
|
|
240
|
+
if lowered in _FALSE_STRINGS:
|
|
241
|
+
return False
|
|
242
|
+
raise ValueError(f"{name} must be a boolean")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _normalize_import_settings(value: Any) -> dict[str, Any]:
|
|
246
|
+
if value is None:
|
|
247
|
+
return {}
|
|
248
|
+
if isinstance(value, str):
|
|
249
|
+
value = try_parse_json(value, "import_settings")
|
|
250
|
+
if not isinstance(value, dict):
|
|
251
|
+
raise ValueError("import_settings must be a JSON object")
|
|
252
|
+
|
|
253
|
+
result: dict[str, Any] = {}
|
|
254
|
+
|
|
255
|
+
if "texture_type" in value:
|
|
256
|
+
result["textureType"] = _map_enum(
|
|
257
|
+
value["texture_type"], _TEXTURE_TYPES)
|
|
258
|
+
if "texture_shape" in value:
|
|
259
|
+
result["textureShape"] = _map_enum(
|
|
260
|
+
value["texture_shape"], _TEXTURE_SHAPES)
|
|
261
|
+
|
|
262
|
+
for snake, camel in [
|
|
263
|
+
("srgb", "sRGBTexture"),
|
|
264
|
+
("alpha_is_transparency", "alphaIsTransparency"),
|
|
265
|
+
("readable", "isReadable"),
|
|
266
|
+
("generate_mipmaps", "mipmapEnabled"),
|
|
267
|
+
("compression_crunched", "crunchedCompression"),
|
|
268
|
+
]:
|
|
269
|
+
if snake in value:
|
|
270
|
+
result[camel] = _coerce_bool(value[snake], snake)
|
|
271
|
+
|
|
272
|
+
if "alpha_source" in value:
|
|
273
|
+
result["alphaSource"] = _map_enum(
|
|
274
|
+
value["alpha_source"], _ALPHA_SOURCES)
|
|
275
|
+
|
|
276
|
+
for snake, camel in [("wrap_mode", "wrapMode"), ("wrap_mode_u", "wrapModeU"), ("wrap_mode_v", "wrapModeV")]:
|
|
277
|
+
if snake in value:
|
|
278
|
+
result[camel] = _map_enum(value[snake], _WRAP_MODES)
|
|
279
|
+
|
|
280
|
+
if "filter_mode" in value:
|
|
281
|
+
result["filterMode"] = _map_enum(value["filter_mode"], _FILTER_MODES)
|
|
282
|
+
if "mipmap_filter" in value:
|
|
283
|
+
result["mipmapFilter"] = _map_enum(
|
|
284
|
+
value["mipmap_filter"], _MIPMAP_FILTERS)
|
|
285
|
+
if "compression" in value:
|
|
286
|
+
result["textureCompression"] = _map_enum(
|
|
287
|
+
value["compression"], _COMPRESSIONS)
|
|
288
|
+
|
|
289
|
+
if "aniso_level" in value:
|
|
290
|
+
result["anisoLevel"] = int(value["aniso_level"])
|
|
291
|
+
if "max_texture_size" in value:
|
|
292
|
+
result["maxTextureSize"] = int(value["max_texture_size"])
|
|
293
|
+
if "compression_quality" in value:
|
|
294
|
+
result["compressionQuality"] = int(value["compression_quality"])
|
|
295
|
+
|
|
296
|
+
if "sprite_mode" in value:
|
|
297
|
+
result["spriteImportMode"] = _map_enum(
|
|
298
|
+
value["sprite_mode"], _SPRITE_MODES)
|
|
299
|
+
if "sprite_pixels_per_unit" in value:
|
|
300
|
+
result["spritePixelsPerUnit"] = float(value["sprite_pixels_per_unit"])
|
|
301
|
+
if "sprite_pivot" in value:
|
|
302
|
+
result["spritePivot"] = value["sprite_pivot"]
|
|
303
|
+
if "sprite_mesh_type" in value:
|
|
304
|
+
result["spriteMeshType"] = _map_enum(
|
|
305
|
+
value["sprite_mesh_type"], _SPRITE_MESH_TYPES)
|
|
306
|
+
if "sprite_extrude" in value:
|
|
307
|
+
result["spriteExtrude"] = int(value["sprite_extrude"])
|
|
308
|
+
|
|
309
|
+
for key, val in value.items():
|
|
310
|
+
if key in result:
|
|
311
|
+
continue
|
|
312
|
+
if key in (
|
|
313
|
+
"textureType", "textureShape", "sRGBTexture", "alphaSource",
|
|
314
|
+
"alphaIsTransparency", "isReadable", "mipmapEnabled", "wrapMode",
|
|
315
|
+
"wrapModeU", "wrapModeV", "filterMode", "mipmapFilter", "anisoLevel",
|
|
316
|
+
"maxTextureSize", "textureCompression", "crunchedCompression",
|
|
317
|
+
"compressionQuality", "spriteImportMode", "spritePixelsPerUnit",
|
|
318
|
+
"spritePivot", "spriteMeshType", "spriteExtrude",
|
|
319
|
+
):
|
|
320
|
+
result[key] = val
|
|
321
|
+
|
|
322
|
+
return result
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@click.group()
|
|
326
|
+
def texture():
|
|
327
|
+
"""Texture operations - create, modify, generate sprites."""
|
|
328
|
+
pass
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@texture.command("create")
|
|
332
|
+
@click.argument("path")
|
|
333
|
+
@click.option("--width", default=64, help="Texture width (default: 64)")
|
|
334
|
+
@click.option("--height", default=64, help="Texture height (default: 64)")
|
|
335
|
+
@click.option("--image-path", help="Source image path (PNG/JPG) to import.")
|
|
336
|
+
@click.option("--color", help="Fill color (e.g., '#FF0000' or '[1,0,0,1]')")
|
|
337
|
+
@click.option("--pattern", type=click.Choice([
|
|
338
|
+
"checkerboard", "stripes", "stripes_h", "stripes_v", "stripes_diag",
|
|
339
|
+
"dots", "grid", "brick"
|
|
340
|
+
]), help="Pattern type")
|
|
341
|
+
@click.option("--palette", help="Color palette for pattern (JSON array of colors)")
|
|
342
|
+
@click.option("--import-settings", help="TextureImporter settings (JSON)")
|
|
343
|
+
@handle_unity_errors
|
|
344
|
+
def create(path: str, width: int, height: int, image_path: Optional[str], color: Optional[str],
|
|
345
|
+
pattern: Optional[str], palette: Optional[str], import_settings: Optional[str]):
|
|
346
|
+
"""Create a new procedural texture.
|
|
347
|
+
|
|
348
|
+
\b
|
|
349
|
+
Examples:
|
|
350
|
+
unity-mcp texture create Assets/Red.png --color '[255,0,0]'
|
|
351
|
+
unity-mcp texture create Assets/Check.png --pattern checkerboard
|
|
352
|
+
unity-mcp texture create Assets/UI.png --import-settings '{"texture_type": "sprite"}'
|
|
353
|
+
"""
|
|
354
|
+
config = get_config()
|
|
355
|
+
if image_path:
|
|
356
|
+
if color or pattern or palette:
|
|
357
|
+
print_error(
|
|
358
|
+
"image-path cannot be combined with color, pattern, or palette.")
|
|
359
|
+
sys.exit(1)
|
|
360
|
+
else:
|
|
361
|
+
try:
|
|
362
|
+
warnings = _validate_texture_dimensions(width, height)
|
|
363
|
+
except ValueError as e:
|
|
364
|
+
print_error(str(e))
|
|
365
|
+
sys.exit(1)
|
|
366
|
+
for warning in warnings:
|
|
367
|
+
click.echo(f"⚠️ Warning: {warning}")
|
|
368
|
+
|
|
369
|
+
params: dict[str, Any] = {
|
|
370
|
+
"action": "create",
|
|
371
|
+
"path": path,
|
|
372
|
+
"width": width,
|
|
373
|
+
"height": height,
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if color:
|
|
377
|
+
try:
|
|
378
|
+
params["fillColor"] = _normalize_color(color, "color")
|
|
379
|
+
except ValueError as e:
|
|
380
|
+
print_error(str(e))
|
|
381
|
+
sys.exit(1)
|
|
382
|
+
elif not pattern and not image_path:
|
|
383
|
+
# Default to white if no color or pattern specified
|
|
384
|
+
params["fillColor"] = [255, 255, 255, 255]
|
|
385
|
+
|
|
386
|
+
if pattern:
|
|
387
|
+
params["pattern"] = pattern
|
|
388
|
+
|
|
389
|
+
if palette:
|
|
390
|
+
try:
|
|
391
|
+
params["palette"] = _normalize_palette(palette, "palette")
|
|
392
|
+
except ValueError as e:
|
|
393
|
+
print_error(str(e))
|
|
394
|
+
sys.exit(1)
|
|
395
|
+
|
|
396
|
+
if import_settings:
|
|
397
|
+
try:
|
|
398
|
+
params["importSettings"] = _normalize_import_settings(
|
|
399
|
+
import_settings)
|
|
400
|
+
except ValueError as e:
|
|
401
|
+
print_error(str(e))
|
|
402
|
+
sys.exit(1)
|
|
403
|
+
|
|
404
|
+
if image_path:
|
|
405
|
+
params["imagePath"] = image_path
|
|
406
|
+
|
|
407
|
+
result = run_command("manage_texture", params, config)
|
|
408
|
+
click.echo(format_output(result, config.format))
|
|
409
|
+
if result.get("success"):
|
|
410
|
+
print_success(f"Created texture: {path}")
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
@texture.command("sprite")
|
|
414
|
+
@click.argument("path")
|
|
415
|
+
@click.option("--width", default=64, help="Texture width (default: 64)")
|
|
416
|
+
@click.option("--height", default=64, help="Texture height (default: 64)")
|
|
417
|
+
@click.option("--image-path", help="Source image path (PNG/JPG) to import.")
|
|
418
|
+
@click.option("--color", help="Fill color (e.g., '#FF0000' or '[1,0,0,1]')")
|
|
419
|
+
@click.option("--pattern", type=click.Choice([
|
|
420
|
+
"checkerboard", "stripes", "dots", "grid"
|
|
421
|
+
]), help="Pattern type (defaults to checkerboard if no color specified)")
|
|
422
|
+
@click.option("--ppu", default=100.0, help="Pixels Per Unit")
|
|
423
|
+
@click.option("--pivot", help="Pivot as [x,y] (default: [0.5, 0.5])")
|
|
424
|
+
@handle_unity_errors
|
|
425
|
+
def sprite(path: str, width: int, height: int, image_path: Optional[str], color: Optional[str], pattern: Optional[str], ppu: float, pivot: Optional[str]):
|
|
426
|
+
"""Quickly create a sprite texture.
|
|
427
|
+
|
|
428
|
+
\b
|
|
429
|
+
Examples:
|
|
430
|
+
unity-mcp texture sprite Assets/Sprites/Player.png
|
|
431
|
+
unity-mcp texture sprite Assets/Sprites/Coin.png --pattern dots
|
|
432
|
+
unity-mcp texture sprite Assets/Sprites/Solid.png --color '[0,255,0]'
|
|
433
|
+
"""
|
|
434
|
+
config = get_config()
|
|
435
|
+
if image_path:
|
|
436
|
+
if color or pattern:
|
|
437
|
+
print_error("image-path cannot be combined with color or pattern.")
|
|
438
|
+
sys.exit(1)
|
|
439
|
+
else:
|
|
440
|
+
try:
|
|
441
|
+
warnings = _validate_texture_dimensions(width, height)
|
|
442
|
+
except ValueError as e:
|
|
443
|
+
print_error(str(e))
|
|
444
|
+
sys.exit(1)
|
|
445
|
+
for warning in warnings:
|
|
446
|
+
click.echo(f"⚠️ Warning: {warning}")
|
|
447
|
+
|
|
448
|
+
sprite_settings: dict[str, Any] = {"pixelsPerUnit": ppu}
|
|
449
|
+
if pivot:
|
|
450
|
+
sprite_settings["pivot"] = try_parse_json(pivot, "pivot")
|
|
451
|
+
else:
|
|
452
|
+
sprite_settings["pivot"] = [0.5, 0.5]
|
|
453
|
+
|
|
454
|
+
params: dict[str, Any] = {
|
|
455
|
+
"action": "create_sprite",
|
|
456
|
+
"path": path,
|
|
457
|
+
"width": width,
|
|
458
|
+
"height": height,
|
|
459
|
+
"spriteSettings": sprite_settings
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if color:
|
|
463
|
+
try:
|
|
464
|
+
params["fillColor"] = _normalize_color(color, "color")
|
|
465
|
+
except ValueError as e:
|
|
466
|
+
print_error(str(e))
|
|
467
|
+
sys.exit(1)
|
|
468
|
+
|
|
469
|
+
# Only default pattern if no color is specified
|
|
470
|
+
if pattern:
|
|
471
|
+
params["pattern"] = pattern
|
|
472
|
+
elif not color and not image_path:
|
|
473
|
+
params["pattern"] = "checkerboard"
|
|
474
|
+
|
|
475
|
+
if image_path:
|
|
476
|
+
params["imagePath"] = image_path
|
|
477
|
+
|
|
478
|
+
result = run_command("manage_texture", params, config)
|
|
479
|
+
click.echo(format_output(result, config.format))
|
|
480
|
+
if result.get("success"):
|
|
481
|
+
print_success(f"Created sprite: {path}")
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
@texture.command("modify")
|
|
485
|
+
@click.argument("path")
|
|
486
|
+
@click.option("--set-pixels", required=True, help="Modification args as JSON")
|
|
487
|
+
@handle_unity_errors
|
|
488
|
+
def modify(path: str, set_pixels: str):
|
|
489
|
+
"""Modify an existing texture.
|
|
490
|
+
|
|
491
|
+
\b
|
|
492
|
+
Examples:
|
|
493
|
+
unity-mcp texture modify Assets/Tex.png --set-pixels '{"x":0,"y":0,"width":10,"height":10,"color":[255,0,0]}'
|
|
494
|
+
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]]}'
|
|
495
|
+
"""
|
|
496
|
+
config = get_config()
|
|
497
|
+
|
|
498
|
+
params: dict[str, Any] = {
|
|
499
|
+
"action": "modify",
|
|
500
|
+
"path": path,
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
try:
|
|
504
|
+
params["setPixels"] = _normalize_set_pixels(set_pixels)
|
|
505
|
+
except ValueError as e:
|
|
506
|
+
print_error(str(e))
|
|
507
|
+
sys.exit(1)
|
|
508
|
+
|
|
509
|
+
result = run_command("manage_texture", params, config)
|
|
510
|
+
click.echo(format_output(result, config.format))
|
|
511
|
+
if result.get("success"):
|
|
512
|
+
print_success(f"Modified texture: {path}")
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
@texture.command("delete")
|
|
516
|
+
@click.argument("path")
|
|
517
|
+
@click.option(
|
|
518
|
+
"--force", "-f",
|
|
519
|
+
is_flag=True,
|
|
520
|
+
help="Skip confirmation prompt."
|
|
521
|
+
)
|
|
522
|
+
@handle_unity_errors
|
|
523
|
+
def delete(path: str, force: bool):
|
|
524
|
+
"""Delete a texture.
|
|
525
|
+
|
|
526
|
+
\\b
|
|
527
|
+
Examples:
|
|
528
|
+
unity-mcp texture delete "Assets/Textures/Old.png"
|
|
529
|
+
unity-mcp texture delete "Assets/Textures/Old.png" --force
|
|
530
|
+
"""
|
|
531
|
+
from cli.utils.confirmation import confirm_destructive_action
|
|
532
|
+
config = get_config()
|
|
533
|
+
|
|
534
|
+
confirm_destructive_action("Delete", "texture", path, force)
|
|
535
|
+
|
|
536
|
+
result = run_command("manage_texture", {
|
|
537
|
+
"action": "delete", "path": path}, config)
|
|
538
|
+
click.echo(format_output(result, config.format))
|
|
539
|
+
if result.get("success"):
|
|
540
|
+
print_success(f"Deleted texture: {path}")
|
cli/commands/tool.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Tool CLI commands for listing custom tools."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from cli.utils.config import get_config
|
|
6
|
+
from cli.utils.output import format_output, print_error
|
|
7
|
+
from cli.utils.connection import run_list_custom_tools, handle_unity_errors
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _list_custom_tools() -> None:
|
|
11
|
+
config = get_config()
|
|
12
|
+
result = run_list_custom_tools(config)
|
|
13
|
+
if config.format != "text":
|
|
14
|
+
click.echo(format_output(result, config.format))
|
|
15
|
+
return
|
|
16
|
+
|
|
17
|
+
if not isinstance(result, dict) or not result.get("success", True):
|
|
18
|
+
click.echo(format_output(result, config.format))
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
tools = result.get("tools")
|
|
22
|
+
if tools is None:
|
|
23
|
+
data = result.get("data", {})
|
|
24
|
+
tools = data.get("tools") if isinstance(data, dict) else None
|
|
25
|
+
if not isinstance(tools, list):
|
|
26
|
+
click.echo(format_output(result, config.format))
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
click.echo(f"Custom tools ({len(tools)}):")
|
|
30
|
+
for i, t in enumerate(tools):
|
|
31
|
+
name = t.get("name") if isinstance(t, dict) else str(t)
|
|
32
|
+
click.echo(f" [{i}] {name}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@click.group("tool")
|
|
36
|
+
def tool():
|
|
37
|
+
"""Tool management - list custom tools for the active Unity project."""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@tool.command("list")
|
|
42
|
+
@handle_unity_errors
|
|
43
|
+
def list_tools():
|
|
44
|
+
"""List custom tools registered for the active Unity project."""
|
|
45
|
+
_list_custom_tools()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@click.group("custom_tool")
|
|
49
|
+
def custom_tool():
|
|
50
|
+
"""Alias for tool management (custom tools)."""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@custom_tool.command("list")
|
|
55
|
+
@handle_unity_errors
|
|
56
|
+
def list_custom_tools():
|
|
57
|
+
"""List custom tools registered for the active Unity project."""
|
|
58
|
+
_list_custom_tools()
|