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
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
"""GameObject CLI commands."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import click
|
|
6
|
+
from typing import Optional, Tuple, Any
|
|
7
|
+
|
|
8
|
+
from cli.utils.config import get_config
|
|
9
|
+
from cli.utils.output import format_output, print_error, print_success, print_warning
|
|
10
|
+
from cli.utils.connection import run_command, handle_unity_errors, UnityConnectionError
|
|
11
|
+
from cli.utils.constants import SEARCH_METHOD_CHOICE_FULL, SEARCH_METHOD_CHOICE_TAGGED
|
|
12
|
+
from cli.utils.confirmation import confirm_destructive_action
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group()
|
|
16
|
+
def gameobject():
|
|
17
|
+
"""GameObject operations - create, find, modify, delete GameObjects."""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@gameobject.command("find")
|
|
22
|
+
@click.argument("search_term")
|
|
23
|
+
@click.option(
|
|
24
|
+
"--method", "-m",
|
|
25
|
+
type=SEARCH_METHOD_CHOICE_FULL,
|
|
26
|
+
default="by_name",
|
|
27
|
+
help="Search method."
|
|
28
|
+
)
|
|
29
|
+
@click.option(
|
|
30
|
+
"--include-inactive", "-i",
|
|
31
|
+
is_flag=True,
|
|
32
|
+
help="Include inactive GameObjects."
|
|
33
|
+
)
|
|
34
|
+
@click.option(
|
|
35
|
+
"--limit", "-l",
|
|
36
|
+
default=50,
|
|
37
|
+
type=int,
|
|
38
|
+
help="Maximum results to return."
|
|
39
|
+
)
|
|
40
|
+
@click.option(
|
|
41
|
+
"--cursor", "-c",
|
|
42
|
+
default=0,
|
|
43
|
+
type=int,
|
|
44
|
+
help="Pagination cursor (offset)."
|
|
45
|
+
)
|
|
46
|
+
@handle_unity_errors
|
|
47
|
+
def find(search_term: str, method: str, include_inactive: bool, limit: int, cursor: int):
|
|
48
|
+
"""Find GameObjects by search criteria.
|
|
49
|
+
|
|
50
|
+
\b
|
|
51
|
+
Examples:
|
|
52
|
+
unity-mcp gameobject find "Player"
|
|
53
|
+
unity-mcp gameobject find "Enemy" --method by_tag
|
|
54
|
+
unity-mcp gameobject find "-81840" --method by_id
|
|
55
|
+
unity-mcp gameobject find "Rigidbody" --method by_component
|
|
56
|
+
unity-mcp gameobject find "/Canvas/Panel" --method by_path
|
|
57
|
+
"""
|
|
58
|
+
config = get_config()
|
|
59
|
+
result = run_command("find_gameobjects", {
|
|
60
|
+
"searchMethod": method,
|
|
61
|
+
"searchTerm": search_term,
|
|
62
|
+
"includeInactive": include_inactive,
|
|
63
|
+
"pageSize": limit,
|
|
64
|
+
"cursor": cursor,
|
|
65
|
+
}, config)
|
|
66
|
+
click.echo(format_output(result, config.format))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@gameobject.command("create")
|
|
70
|
+
@click.argument("name")
|
|
71
|
+
@click.option(
|
|
72
|
+
"--primitive", "-p",
|
|
73
|
+
type=click.Choice(["Cube", "Sphere", "Cylinder",
|
|
74
|
+
"Plane", "Capsule", "Quad"]),
|
|
75
|
+
help="Create a primitive type."
|
|
76
|
+
)
|
|
77
|
+
@click.option(
|
|
78
|
+
"--position", "-pos",
|
|
79
|
+
nargs=3,
|
|
80
|
+
type=float,
|
|
81
|
+
default=None,
|
|
82
|
+
help="Position as X Y Z."
|
|
83
|
+
)
|
|
84
|
+
@click.option(
|
|
85
|
+
"--rotation", "-rot",
|
|
86
|
+
nargs=3,
|
|
87
|
+
type=float,
|
|
88
|
+
default=None,
|
|
89
|
+
help="Rotation as X Y Z (euler angles)."
|
|
90
|
+
)
|
|
91
|
+
@click.option(
|
|
92
|
+
"--scale", "-s",
|
|
93
|
+
nargs=3,
|
|
94
|
+
type=float,
|
|
95
|
+
default=None,
|
|
96
|
+
help="Scale as X Y Z."
|
|
97
|
+
)
|
|
98
|
+
@click.option(
|
|
99
|
+
"--parent",
|
|
100
|
+
default=None,
|
|
101
|
+
help="Parent GameObject name or path."
|
|
102
|
+
)
|
|
103
|
+
@click.option(
|
|
104
|
+
"--tag", "-t",
|
|
105
|
+
default=None,
|
|
106
|
+
help="Tag to assign."
|
|
107
|
+
)
|
|
108
|
+
@click.option(
|
|
109
|
+
"--layer",
|
|
110
|
+
default=None,
|
|
111
|
+
help="Layer to assign."
|
|
112
|
+
)
|
|
113
|
+
@click.option(
|
|
114
|
+
"--components",
|
|
115
|
+
default=None,
|
|
116
|
+
help="Comma-separated list of components to add."
|
|
117
|
+
)
|
|
118
|
+
@click.option(
|
|
119
|
+
"--save-prefab",
|
|
120
|
+
is_flag=True,
|
|
121
|
+
help="Save as prefab after creation."
|
|
122
|
+
)
|
|
123
|
+
@click.option(
|
|
124
|
+
"--prefab-path",
|
|
125
|
+
default=None,
|
|
126
|
+
help="Path for prefab (e.g., Assets/Prefabs/MyPrefab.prefab)."
|
|
127
|
+
)
|
|
128
|
+
@handle_unity_errors
|
|
129
|
+
def create(
|
|
130
|
+
name: str,
|
|
131
|
+
primitive: Optional[str],
|
|
132
|
+
position: Optional[Tuple[float, float, float]],
|
|
133
|
+
rotation: Optional[Tuple[float, float, float]],
|
|
134
|
+
scale: Optional[Tuple[float, float, float]],
|
|
135
|
+
parent: Optional[str],
|
|
136
|
+
tag: Optional[str],
|
|
137
|
+
layer: Optional[str],
|
|
138
|
+
components: Optional[str],
|
|
139
|
+
save_prefab: bool,
|
|
140
|
+
prefab_path: Optional[str],
|
|
141
|
+
):
|
|
142
|
+
"""Create a new GameObject.
|
|
143
|
+
|
|
144
|
+
\b
|
|
145
|
+
Examples:
|
|
146
|
+
unity-mcp gameobject create "MyCube" --primitive Cube
|
|
147
|
+
unity-mcp gameobject create "Player" --position 0 1 0
|
|
148
|
+
unity-mcp gameobject create "Enemy" --primitive Sphere --tag Enemy
|
|
149
|
+
unity-mcp gameobject create "Child" --parent "ParentObject"
|
|
150
|
+
unity-mcp gameobject create "Item" --components "Rigidbody,BoxCollider"
|
|
151
|
+
"""
|
|
152
|
+
config = get_config()
|
|
153
|
+
|
|
154
|
+
params: dict[str, Any] = {
|
|
155
|
+
"action": "create",
|
|
156
|
+
"name": name,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if primitive:
|
|
160
|
+
params["primitiveType"] = primitive
|
|
161
|
+
if position:
|
|
162
|
+
params["position"] = list(position)
|
|
163
|
+
if rotation:
|
|
164
|
+
params["rotation"] = list(rotation)
|
|
165
|
+
if scale:
|
|
166
|
+
params["scale"] = list(scale)
|
|
167
|
+
if parent:
|
|
168
|
+
params["parent"] = parent
|
|
169
|
+
if tag:
|
|
170
|
+
params["tag"] = tag
|
|
171
|
+
if layer:
|
|
172
|
+
params["layer"] = layer
|
|
173
|
+
if save_prefab:
|
|
174
|
+
params["saveAsPrefab"] = True
|
|
175
|
+
if prefab_path:
|
|
176
|
+
params["prefabPath"] = prefab_path
|
|
177
|
+
|
|
178
|
+
result = run_command("manage_gameobject", params, config)
|
|
179
|
+
|
|
180
|
+
# Add components separately since componentsToAdd doesn't work
|
|
181
|
+
if components and (result.get("success") or result.get("data") or result.get("result")):
|
|
182
|
+
component_list = [c.strip() for c in components.split(",")]
|
|
183
|
+
failed_components = []
|
|
184
|
+
for component in component_list:
|
|
185
|
+
try:
|
|
186
|
+
run_command("manage_components", {
|
|
187
|
+
"action": "add",
|
|
188
|
+
"target": name,
|
|
189
|
+
"componentType": component,
|
|
190
|
+
}, config)
|
|
191
|
+
except UnityConnectionError:
|
|
192
|
+
failed_components.append(component)
|
|
193
|
+
if failed_components:
|
|
194
|
+
print_warning(f"Failed to add components: {', '.join(failed_components)}")
|
|
195
|
+
|
|
196
|
+
click.echo(format_output(result, config.format))
|
|
197
|
+
if result.get("success") or result.get("result"):
|
|
198
|
+
print_success(f"Created GameObject '{name}'")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@gameobject.command("modify")
|
|
202
|
+
@click.argument("target")
|
|
203
|
+
@click.option(
|
|
204
|
+
"--name", "-n",
|
|
205
|
+
default=None,
|
|
206
|
+
help="New name for the GameObject."
|
|
207
|
+
)
|
|
208
|
+
@click.option(
|
|
209
|
+
"--position", "-pos",
|
|
210
|
+
nargs=3,
|
|
211
|
+
type=float,
|
|
212
|
+
default=None,
|
|
213
|
+
help="New position as X Y Z."
|
|
214
|
+
)
|
|
215
|
+
@click.option(
|
|
216
|
+
"--rotation", "-rot",
|
|
217
|
+
nargs=3,
|
|
218
|
+
type=float,
|
|
219
|
+
default=None,
|
|
220
|
+
help="New rotation as X Y Z (euler angles)."
|
|
221
|
+
)
|
|
222
|
+
@click.option(
|
|
223
|
+
"--scale", "-s",
|
|
224
|
+
nargs=3,
|
|
225
|
+
type=float,
|
|
226
|
+
default=None,
|
|
227
|
+
help="New scale as X Y Z."
|
|
228
|
+
)
|
|
229
|
+
@click.option(
|
|
230
|
+
"--parent",
|
|
231
|
+
default=None,
|
|
232
|
+
help="New parent GameObject."
|
|
233
|
+
)
|
|
234
|
+
@click.option(
|
|
235
|
+
"--tag", "-t",
|
|
236
|
+
default=None,
|
|
237
|
+
help="New tag."
|
|
238
|
+
)
|
|
239
|
+
@click.option(
|
|
240
|
+
"--layer",
|
|
241
|
+
default=None,
|
|
242
|
+
help="New layer."
|
|
243
|
+
)
|
|
244
|
+
@click.option(
|
|
245
|
+
"--active/--inactive",
|
|
246
|
+
default=None,
|
|
247
|
+
help="Set active state."
|
|
248
|
+
)
|
|
249
|
+
@click.option(
|
|
250
|
+
"--add-components",
|
|
251
|
+
default=None,
|
|
252
|
+
help="Comma-separated list of components to add."
|
|
253
|
+
)
|
|
254
|
+
@click.option(
|
|
255
|
+
"--remove-components",
|
|
256
|
+
default=None,
|
|
257
|
+
help="Comma-separated list of components to remove."
|
|
258
|
+
)
|
|
259
|
+
@click.option(
|
|
260
|
+
"--search-method",
|
|
261
|
+
type=SEARCH_METHOD_CHOICE_TAGGED,
|
|
262
|
+
default=None,
|
|
263
|
+
help="How to find the target GameObject."
|
|
264
|
+
)
|
|
265
|
+
@handle_unity_errors
|
|
266
|
+
def modify(
|
|
267
|
+
target: str,
|
|
268
|
+
name: Optional[str],
|
|
269
|
+
position: Optional[Tuple[float, float, float]],
|
|
270
|
+
rotation: Optional[Tuple[float, float, float]],
|
|
271
|
+
scale: Optional[Tuple[float, float, float]],
|
|
272
|
+
parent: Optional[str],
|
|
273
|
+
tag: Optional[str],
|
|
274
|
+
layer: Optional[str],
|
|
275
|
+
active: Optional[bool],
|
|
276
|
+
add_components: Optional[str],
|
|
277
|
+
remove_components: Optional[str],
|
|
278
|
+
search_method: Optional[str],
|
|
279
|
+
):
|
|
280
|
+
"""Modify an existing GameObject.
|
|
281
|
+
|
|
282
|
+
TARGET can be a name, path, instance ID, or tag depending on --search-method.
|
|
283
|
+
|
|
284
|
+
\b
|
|
285
|
+
Examples:
|
|
286
|
+
unity-mcp gameobject modify "Player" --position 0 5 0
|
|
287
|
+
unity-mcp gameobject modify "Enemy" --name "Boss" --tag "Boss"
|
|
288
|
+
unity-mcp gameobject modify "-81840" --search-method by_id --active
|
|
289
|
+
unity-mcp gameobject modify "/Canvas/Panel" --search-method by_path --inactive
|
|
290
|
+
unity-mcp gameobject modify "Cube" --add-components "Rigidbody,BoxCollider"
|
|
291
|
+
"""
|
|
292
|
+
config = get_config()
|
|
293
|
+
|
|
294
|
+
params: dict[str, Any] = {
|
|
295
|
+
"action": "modify",
|
|
296
|
+
"target": target,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if name:
|
|
300
|
+
params["name"] = name
|
|
301
|
+
if position:
|
|
302
|
+
params["position"] = list(position)
|
|
303
|
+
if rotation:
|
|
304
|
+
params["rotation"] = list(rotation)
|
|
305
|
+
if scale:
|
|
306
|
+
params["scale"] = list(scale)
|
|
307
|
+
if parent:
|
|
308
|
+
params["parent"] = parent
|
|
309
|
+
if tag:
|
|
310
|
+
params["tag"] = tag
|
|
311
|
+
if layer:
|
|
312
|
+
params["layer"] = layer
|
|
313
|
+
if active is not None:
|
|
314
|
+
params["setActive"] = active
|
|
315
|
+
if add_components:
|
|
316
|
+
params["componentsToAdd"] = [c.strip() for c in add_components.split(",")]
|
|
317
|
+
if remove_components:
|
|
318
|
+
params["componentsToRemove"] = [c.strip() for c in remove_components.split(",")]
|
|
319
|
+
if search_method:
|
|
320
|
+
params["searchMethod"] = search_method
|
|
321
|
+
|
|
322
|
+
result = run_command("manage_gameobject", params, config)
|
|
323
|
+
click.echo(format_output(result, config.format))
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@gameobject.command("delete")
|
|
327
|
+
@click.argument("target")
|
|
328
|
+
@click.option(
|
|
329
|
+
"--search-method",
|
|
330
|
+
type=SEARCH_METHOD_CHOICE_TAGGED,
|
|
331
|
+
default=None,
|
|
332
|
+
help="How to find the target GameObject."
|
|
333
|
+
)
|
|
334
|
+
@click.option(
|
|
335
|
+
"--force", "-f",
|
|
336
|
+
is_flag=True,
|
|
337
|
+
help="Skip confirmation prompt."
|
|
338
|
+
)
|
|
339
|
+
@handle_unity_errors
|
|
340
|
+
def delete(target: str, search_method: Optional[str], force: bool):
|
|
341
|
+
"""Delete a GameObject.
|
|
342
|
+
|
|
343
|
+
\b
|
|
344
|
+
Examples:
|
|
345
|
+
unity-mcp gameobject delete "OldObject"
|
|
346
|
+
unity-mcp gameobject delete "-81840" --search-method by_id
|
|
347
|
+
unity-mcp gameobject delete "TempObjects" --search-method by_tag --force
|
|
348
|
+
"""
|
|
349
|
+
config = get_config()
|
|
350
|
+
|
|
351
|
+
confirm_destructive_action("Delete", "GameObject", target, force)
|
|
352
|
+
|
|
353
|
+
params = {
|
|
354
|
+
"action": "delete",
|
|
355
|
+
"target": target,
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if search_method:
|
|
359
|
+
params["searchMethod"] = search_method
|
|
360
|
+
|
|
361
|
+
result = run_command("manage_gameobject", params, config)
|
|
362
|
+
click.echo(format_output(result, config.format))
|
|
363
|
+
if result.get("success"):
|
|
364
|
+
print_success(f"Deleted GameObject '{target}'")
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
@gameobject.command("duplicate")
|
|
368
|
+
@click.argument("target")
|
|
369
|
+
@click.option(
|
|
370
|
+
"--name", "-n",
|
|
371
|
+
default=None,
|
|
372
|
+
help="Name for the duplicate (default: OriginalName_Copy)."
|
|
373
|
+
)
|
|
374
|
+
@click.option(
|
|
375
|
+
"--offset",
|
|
376
|
+
nargs=3,
|
|
377
|
+
type=float,
|
|
378
|
+
default=None,
|
|
379
|
+
help="Position offset from original as X Y Z."
|
|
380
|
+
)
|
|
381
|
+
@click.option(
|
|
382
|
+
"--search-method",
|
|
383
|
+
type=SEARCH_METHOD_CHOICE_TAGGED,
|
|
384
|
+
default=None,
|
|
385
|
+
help="How to find the target GameObject."
|
|
386
|
+
)
|
|
387
|
+
@handle_unity_errors
|
|
388
|
+
def duplicate(
|
|
389
|
+
target: str,
|
|
390
|
+
name: Optional[str],
|
|
391
|
+
offset: Optional[Tuple[float, float, float]],
|
|
392
|
+
search_method: Optional[str],
|
|
393
|
+
):
|
|
394
|
+
"""Duplicate a GameObject.
|
|
395
|
+
|
|
396
|
+
\b
|
|
397
|
+
Examples:
|
|
398
|
+
unity-mcp gameobject duplicate "Player"
|
|
399
|
+
unity-mcp gameobject duplicate "Enemy" --name "Enemy2" --offset 5 0 0
|
|
400
|
+
unity-mcp gameobject duplicate "-81840" --search-method by_id
|
|
401
|
+
"""
|
|
402
|
+
config = get_config()
|
|
403
|
+
|
|
404
|
+
params: dict[str, Any] = {
|
|
405
|
+
"action": "duplicate",
|
|
406
|
+
"target": target,
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if name:
|
|
410
|
+
params["new_name"] = name
|
|
411
|
+
if offset:
|
|
412
|
+
params["offset"] = list(offset)
|
|
413
|
+
if search_method:
|
|
414
|
+
params["searchMethod"] = search_method
|
|
415
|
+
|
|
416
|
+
result = run_command("manage_gameobject", params, config)
|
|
417
|
+
click.echo(format_output(result, config.format))
|
|
418
|
+
if result.get("success"):
|
|
419
|
+
print_success(f"Duplicated GameObject '{target}'")
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@gameobject.command("move")
|
|
423
|
+
@click.argument("target")
|
|
424
|
+
@click.option(
|
|
425
|
+
"--reference", "-r",
|
|
426
|
+
required=True,
|
|
427
|
+
help="Reference object for relative movement."
|
|
428
|
+
)
|
|
429
|
+
@click.option(
|
|
430
|
+
"--direction", "-d",
|
|
431
|
+
type=click.Choice(["left", "right", "up", "down", "forward",
|
|
432
|
+
"back", "front", "backward", "behind"]),
|
|
433
|
+
required=True,
|
|
434
|
+
help="Direction to move."
|
|
435
|
+
)
|
|
436
|
+
@click.option(
|
|
437
|
+
"--distance",
|
|
438
|
+
type=float,
|
|
439
|
+
default=1.0,
|
|
440
|
+
help="Distance to move (default: 1.0)."
|
|
441
|
+
)
|
|
442
|
+
@click.option(
|
|
443
|
+
"--local",
|
|
444
|
+
is_flag=True,
|
|
445
|
+
help="Use reference object's local space instead of world space."
|
|
446
|
+
)
|
|
447
|
+
@click.option(
|
|
448
|
+
"--search-method",
|
|
449
|
+
type=SEARCH_METHOD_CHOICE_TAGGED,
|
|
450
|
+
default=None,
|
|
451
|
+
help="How to find the target GameObject."
|
|
452
|
+
)
|
|
453
|
+
@handle_unity_errors
|
|
454
|
+
def move(
|
|
455
|
+
target: str,
|
|
456
|
+
reference: str,
|
|
457
|
+
direction: str,
|
|
458
|
+
distance: float,
|
|
459
|
+
local: bool,
|
|
460
|
+
search_method: Optional[str],
|
|
461
|
+
):
|
|
462
|
+
"""Move a GameObject relative to another object.
|
|
463
|
+
|
|
464
|
+
\b
|
|
465
|
+
Examples:
|
|
466
|
+
unity-mcp gameobject move "Chair" --reference "Table" --direction right --distance 2
|
|
467
|
+
unity-mcp gameobject move "Light" --reference "Player" --direction up --distance 3
|
|
468
|
+
unity-mcp gameobject move "NPC" --reference "Player" --direction forward --local
|
|
469
|
+
"""
|
|
470
|
+
config = get_config()
|
|
471
|
+
|
|
472
|
+
params: dict[str, Any] = {
|
|
473
|
+
"action": "move_relative",
|
|
474
|
+
"target": target,
|
|
475
|
+
"reference_object": reference,
|
|
476
|
+
"direction": direction,
|
|
477
|
+
"distance": distance,
|
|
478
|
+
"world_space": not local,
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if search_method:
|
|
482
|
+
params["searchMethod"] = search_method
|
|
483
|
+
|
|
484
|
+
result = run_command("manage_gameobject", params, config)
|
|
485
|
+
click.echo(format_output(result, config.format))
|
|
486
|
+
if result.get("success"):
|
|
487
|
+
print_success(f"Moved '{target}' {direction} of '{reference}' by {distance} units")
|
cli/commands/instance.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Instance CLI commands for managing Unity instances."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from cli.utils.config import get_config
|
|
7
|
+
from cli.utils.output import format_output, print_error, print_success, print_info
|
|
8
|
+
from cli.utils.connection import run_command, run_list_instances, handle_unity_errors
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group()
|
|
12
|
+
def instance():
|
|
13
|
+
"""Unity instance management - list, select, and view instances."""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@instance.command("list")
|
|
18
|
+
@handle_unity_errors
|
|
19
|
+
def list_instances():
|
|
20
|
+
"""List available Unity instances.
|
|
21
|
+
|
|
22
|
+
\\b
|
|
23
|
+
Examples:
|
|
24
|
+
unity-mcp instance list
|
|
25
|
+
"""
|
|
26
|
+
config = get_config()
|
|
27
|
+
|
|
28
|
+
result = run_list_instances(config)
|
|
29
|
+
instances = result.get("instances", []) if isinstance(
|
|
30
|
+
result, dict) else []
|
|
31
|
+
|
|
32
|
+
if not instances:
|
|
33
|
+
print_info("No Unity instances currently connected")
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
click.echo("Available Unity instances:")
|
|
37
|
+
for inst in instances:
|
|
38
|
+
project = inst.get("project", "Unknown")
|
|
39
|
+
version = inst.get("unity_version", "Unknown")
|
|
40
|
+
hash_id = inst.get("hash", "")
|
|
41
|
+
session_id = inst.get("session_id", "")
|
|
42
|
+
|
|
43
|
+
# Format: ProjectName@hash (Unity version)
|
|
44
|
+
display_id = f"{project}@{hash_id}" if hash_id else project
|
|
45
|
+
click.echo(f" • {display_id} (Unity {version})")
|
|
46
|
+
if session_id:
|
|
47
|
+
click.echo(f" Session: {session_id[:8]}...")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@instance.command("set")
|
|
51
|
+
@click.argument("instance_id")
|
|
52
|
+
@handle_unity_errors
|
|
53
|
+
def set_instance(instance_id: str):
|
|
54
|
+
"""Set the active Unity instance.
|
|
55
|
+
|
|
56
|
+
INSTANCE_ID can be Name@hash or just a hash prefix.
|
|
57
|
+
|
|
58
|
+
\\b
|
|
59
|
+
Examples:
|
|
60
|
+
unity-mcp instance set "MyProject@abc123"
|
|
61
|
+
unity-mcp instance set abc123
|
|
62
|
+
"""
|
|
63
|
+
config = get_config()
|
|
64
|
+
|
|
65
|
+
result = run_command("set_active_instance", {
|
|
66
|
+
"instance": instance_id,
|
|
67
|
+
}, config)
|
|
68
|
+
click.echo(format_output(result, config.format))
|
|
69
|
+
if result.get("success"):
|
|
70
|
+
data = result.get("data", {})
|
|
71
|
+
active = data.get("instance", instance_id)
|
|
72
|
+
print_success(f"Active instance set to: {active}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@instance.command("current")
|
|
76
|
+
def current_instance():
|
|
77
|
+
"""Show the currently selected Unity instance.
|
|
78
|
+
|
|
79
|
+
\\b
|
|
80
|
+
Examples:
|
|
81
|
+
unity-mcp instance current
|
|
82
|
+
"""
|
|
83
|
+
config = get_config()
|
|
84
|
+
|
|
85
|
+
# The current instance is typically shown in telemetry or needs to be tracked
|
|
86
|
+
# For now, we can show the configured instance from CLI options
|
|
87
|
+
if config.unity_instance:
|
|
88
|
+
click.echo(f"Configured instance: {config.unity_instance}")
|
|
89
|
+
else:
|
|
90
|
+
print_info(
|
|
91
|
+
"No instance explicitly set. Using default (auto-select single instance).")
|
|
92
|
+
print_info("Use 'unity-mcp instance list' to see available instances.")
|
|
93
|
+
print_info("Use 'unity-mcp instance set <id>' to select one.")
|
cli/commands/lighting.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Lighting CLI commands."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from typing import Optional, Tuple
|
|
5
|
+
|
|
6
|
+
from cli.utils.config import get_config
|
|
7
|
+
from cli.utils.output import format_output, print_error, print_success
|
|
8
|
+
from cli.utils.connection import run_command, handle_unity_errors
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group()
|
|
12
|
+
def lighting():
|
|
13
|
+
"""Lighting operations - create, modify lights and lighting settings."""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@lighting.command("create")
|
|
18
|
+
@click.argument("name")
|
|
19
|
+
@click.option(
|
|
20
|
+
"--type", "-t",
|
|
21
|
+
"light_type",
|
|
22
|
+
type=click.Choice(["Directional", "Point", "Spot", "Area"]),
|
|
23
|
+
default="Point",
|
|
24
|
+
help="Type of light to create."
|
|
25
|
+
)
|
|
26
|
+
@click.option(
|
|
27
|
+
"--position", "-pos",
|
|
28
|
+
nargs=3,
|
|
29
|
+
type=float,
|
|
30
|
+
default=(0, 3, 0),
|
|
31
|
+
help="Position as X Y Z."
|
|
32
|
+
)
|
|
33
|
+
@click.option(
|
|
34
|
+
"--color", "-c",
|
|
35
|
+
nargs=3,
|
|
36
|
+
type=float,
|
|
37
|
+
default=None,
|
|
38
|
+
help="Color as R G B (0-1)."
|
|
39
|
+
)
|
|
40
|
+
@click.option(
|
|
41
|
+
"--intensity", "-i",
|
|
42
|
+
default=None,
|
|
43
|
+
type=float,
|
|
44
|
+
help="Light intensity."
|
|
45
|
+
)
|
|
46
|
+
@handle_unity_errors
|
|
47
|
+
def create(name: str, light_type: str, position: Tuple[float, float, float], color: Optional[Tuple[float, float, float]], intensity: Optional[float]):
|
|
48
|
+
"""Create a new light.
|
|
49
|
+
|
|
50
|
+
\b
|
|
51
|
+
Examples:
|
|
52
|
+
unity-mcp lighting create "MainLight" --type Directional
|
|
53
|
+
unity-mcp lighting create "PointLight1" --position 0 5 0 --intensity 2
|
|
54
|
+
unity-mcp lighting create "RedLight" --type Spot --color 1 0 0
|
|
55
|
+
"""
|
|
56
|
+
config = get_config()
|
|
57
|
+
|
|
58
|
+
# Step 1: Create empty GameObject with position
|
|
59
|
+
create_result = run_command("manage_gameobject", {
|
|
60
|
+
"action": "create",
|
|
61
|
+
"name": name,
|
|
62
|
+
"position": list(position),
|
|
63
|
+
}, config)
|
|
64
|
+
|
|
65
|
+
if not (create_result.get("success")):
|
|
66
|
+
click.echo(format_output(create_result, config.format))
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
# Step 2: Add Light component using manage_components
|
|
70
|
+
add_result = run_command("manage_components", {
|
|
71
|
+
"action": "add",
|
|
72
|
+
"target": name,
|
|
73
|
+
"componentType": "Light",
|
|
74
|
+
}, config)
|
|
75
|
+
|
|
76
|
+
if not add_result.get("success"):
|
|
77
|
+
click.echo(format_output(add_result, config.format))
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
# Step 3: Set light type using manage_components set_property
|
|
81
|
+
type_result = run_command("manage_components", {
|
|
82
|
+
"action": "set_property",
|
|
83
|
+
"target": name,
|
|
84
|
+
"componentType": "Light",
|
|
85
|
+
"property": "type",
|
|
86
|
+
"value": light_type,
|
|
87
|
+
}, config)
|
|
88
|
+
|
|
89
|
+
if not type_result.get("success"):
|
|
90
|
+
click.echo(format_output(type_result, config.format))
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
# Step 4: Set color if provided
|
|
94
|
+
if color:
|
|
95
|
+
color_result = run_command("manage_components", {
|
|
96
|
+
"action": "set_property",
|
|
97
|
+
"target": name,
|
|
98
|
+
"componentType": "Light",
|
|
99
|
+
"property": "color",
|
|
100
|
+
"value": {"r": color[0], "g": color[1], "b": color[2], "a": 1},
|
|
101
|
+
}, config)
|
|
102
|
+
|
|
103
|
+
if not color_result.get("success"):
|
|
104
|
+
click.echo(format_output(color_result, config.format))
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
# Step 5: Set intensity if provided
|
|
108
|
+
if intensity is not None:
|
|
109
|
+
intensity_result = run_command("manage_components", {
|
|
110
|
+
"action": "set_property",
|
|
111
|
+
"target": name,
|
|
112
|
+
"componentType": "Light",
|
|
113
|
+
"property": "intensity",
|
|
114
|
+
"value": intensity,
|
|
115
|
+
}, config)
|
|
116
|
+
|
|
117
|
+
if not intensity_result.get("success"):
|
|
118
|
+
click.echo(format_output(intensity_result, config.format))
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
# Output the result
|
|
122
|
+
click.echo(format_output(create_result, config.format))
|
|
123
|
+
print_success(f"Created {light_type} light: {name}")
|