mcpforunityserver 8.7.0__py3-none-any.whl → 9.1.0__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 +87 -0
- cli/commands/asset.py +310 -0
- cli/commands/audio.py +133 -0
- cli/commands/batch.py +184 -0
- cli/commands/code.py +189 -0
- cli/commands/component.py +212 -0
- cli/commands/editor.py +487 -0
- cli/commands/gameobject.py +510 -0
- cli/commands/instance.py +101 -0
- cli/commands/lighting.py +128 -0
- cli/commands/material.py +268 -0
- cli/commands/prefab.py +144 -0
- cli/commands/scene.py +255 -0
- cli/commands/script.py +240 -0
- cli/commands/shader.py +238 -0
- cli/commands/ui.py +263 -0
- cli/commands/vfx.py +439 -0
- cli/main.py +248 -0
- cli/utils/__init__.py +31 -0
- cli/utils/config.py +58 -0
- cli/utils/connection.py +191 -0
- cli/utils/output.py +195 -0
- main.py +177 -62
- {mcpforunityserver-8.7.0.dist-info → mcpforunityserver-9.1.0.dist-info}/METADATA +4 -2
- mcpforunityserver-9.1.0.dist-info/RECORD +96 -0
- {mcpforunityserver-8.7.0.dist-info → mcpforunityserver-9.1.0.dist-info}/WHEEL +1 -1
- {mcpforunityserver-8.7.0.dist-info → mcpforunityserver-9.1.0.dist-info}/entry_points.txt +1 -0
- {mcpforunityserver-8.7.0.dist-info → mcpforunityserver-9.1.0.dist-info}/top_level.txt +1 -2
- services/custom_tool_service.py +179 -19
- services/resources/__init__.py +6 -1
- services/resources/active_tool.py +1 -1
- services/resources/custom_tools.py +2 -2
- services/resources/editor_state.py +283 -30
- services/resources/gameobject.py +243 -0
- services/resources/layers.py +1 -1
- services/resources/prefab_stage.py +1 -1
- services/resources/project_info.py +1 -1
- services/resources/selection.py +1 -1
- services/resources/tags.py +1 -1
- services/resources/unity_instances.py +1 -1
- services/resources/windows.py +1 -1
- services/state/external_changes_scanner.py +3 -4
- services/tools/__init__.py +6 -1
- services/tools/batch_execute.py +24 -9
- services/tools/debug_request_context.py +8 -2
- services/tools/execute_custom_tool.py +6 -1
- services/tools/execute_menu_item.py +6 -3
- services/tools/find_gameobjects.py +89 -0
- services/tools/find_in_file.py +26 -19
- services/tools/manage_asset.py +13 -44
- services/tools/manage_components.py +131 -0
- services/tools/manage_editor.py +9 -8
- services/tools/manage_gameobject.py +115 -79
- services/tools/manage_material.py +80 -31
- services/tools/manage_prefabs.py +7 -1
- services/tools/manage_scene.py +30 -13
- services/tools/manage_script.py +62 -19
- services/tools/manage_scriptable_object.py +22 -10
- services/tools/manage_shader.py +8 -1
- services/tools/manage_vfx.py +738 -0
- services/tools/preflight.py +15 -12
- services/tools/read_console.py +70 -17
- services/tools/refresh_unity.py +92 -29
- services/tools/run_tests.py +187 -53
- services/tools/script_apply_edits.py +15 -7
- services/tools/set_active_instance.py +12 -7
- services/tools/utils.py +60 -6
- transport/legacy/port_discovery.py +2 -2
- transport/legacy/unity_connection.py +129 -26
- transport/plugin_hub.py +85 -24
- transport/unity_instance_middleware.py +4 -3
- transport/unity_transport.py +2 -1
- utils/focus_nudge.py +321 -0
- __init__.py +0 -0
- mcpforunityserver-8.7.0.dist-info/RECORD +0 -71
- routes/__init__.py +0 -0
- services/resources/editor_state_v2.py +0 -270
- services/tools/test_jobs.py +0 -94
- {mcpforunityserver-8.7.0.dist-info → mcpforunityserver-9.1.0.dist-info}/licenses/LICENSE +0 -0
cli/commands/batch.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Batch CLI commands for executing multiple Unity operations efficiently."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import click
|
|
6
|
+
from typing import Optional, Any
|
|
7
|
+
|
|
8
|
+
from cli.utils.config import get_config
|
|
9
|
+
from cli.utils.output import format_output, print_error, print_success, print_info
|
|
10
|
+
from cli.utils.connection import run_command, UnityConnectionError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group()
|
|
14
|
+
def batch():
|
|
15
|
+
"""Batch operations - execute multiple commands efficiently."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@batch.command("run")
|
|
20
|
+
@click.argument("file", type=click.Path(exists=True))
|
|
21
|
+
@click.option("--parallel", is_flag=True, help="Execute read-only commands in parallel.")
|
|
22
|
+
@click.option("--fail-fast", is_flag=True, help="Stop on first failure.")
|
|
23
|
+
def batch_run(file: str, parallel: bool, fail_fast: bool):
|
|
24
|
+
"""Execute commands from a JSON file.
|
|
25
|
+
|
|
26
|
+
The JSON file should contain an array of command objects with 'tool' and 'params' keys.
|
|
27
|
+
|
|
28
|
+
\\b
|
|
29
|
+
File format:
|
|
30
|
+
[
|
|
31
|
+
{"tool": "manage_gameobject", "params": {"action": "create", "name": "Cube1"}},
|
|
32
|
+
{"tool": "manage_gameobject", "params": {"action": "create", "name": "Cube2"}},
|
|
33
|
+
{"tool": "manage_components", "params": {"action": "add", "target": "Cube1", "componentType": "Rigidbody"}}
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
\\b
|
|
37
|
+
Examples:
|
|
38
|
+
unity-mcp batch run commands.json
|
|
39
|
+
unity-mcp batch run setup.json --parallel
|
|
40
|
+
unity-mcp batch run critical.json --fail-fast
|
|
41
|
+
"""
|
|
42
|
+
config = get_config()
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
with open(file, 'r') as f:
|
|
46
|
+
commands = json.load(f)
|
|
47
|
+
except json.JSONDecodeError as e:
|
|
48
|
+
print_error(f"Invalid JSON in file: {e}")
|
|
49
|
+
sys.exit(1)
|
|
50
|
+
except IOError as e:
|
|
51
|
+
print_error(f"Error reading file: {e}")
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
if not isinstance(commands, list):
|
|
55
|
+
print_error("JSON file must contain an array of commands")
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
if len(commands) > 25:
|
|
59
|
+
print_error(f"Maximum 25 commands per batch, got {len(commands)}")
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
|
|
62
|
+
params: dict[str, Any] = {"commands": commands}
|
|
63
|
+
if parallel:
|
|
64
|
+
params["parallel"] = True
|
|
65
|
+
if fail_fast:
|
|
66
|
+
params["failFast"] = True
|
|
67
|
+
|
|
68
|
+
click.echo(f"Executing {len(commands)} commands...")
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
result = run_command("batch_execute", params, config)
|
|
72
|
+
click.echo(format_output(result, config.format))
|
|
73
|
+
|
|
74
|
+
if isinstance(result, dict):
|
|
75
|
+
results = result.get("data", {}).get("results", [])
|
|
76
|
+
succeeded = sum(1 for r in results if r.get("success"))
|
|
77
|
+
failed = len(results) - succeeded
|
|
78
|
+
|
|
79
|
+
if failed == 0:
|
|
80
|
+
print_success(
|
|
81
|
+
f"All {succeeded} commands completed successfully")
|
|
82
|
+
else:
|
|
83
|
+
print_info(f"{succeeded} succeeded, {failed} failed")
|
|
84
|
+
except UnityConnectionError as e:
|
|
85
|
+
print_error(str(e))
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@batch.command("inline")
|
|
90
|
+
@click.argument("commands_json")
|
|
91
|
+
@click.option("--parallel", is_flag=True, help="Execute read-only commands in parallel.")
|
|
92
|
+
@click.option("--fail-fast", is_flag=True, help="Stop on first failure.")
|
|
93
|
+
def batch_inline(commands_json: str, parallel: bool, fail_fast: bool):
|
|
94
|
+
"""Execute commands from inline JSON.
|
|
95
|
+
|
|
96
|
+
\\b
|
|
97
|
+
Examples:
|
|
98
|
+
unity-mcp batch inline '[{"tool": "manage_scene", "params": {"action": "get_active"}}]'
|
|
99
|
+
|
|
100
|
+
unity-mcp batch inline '[
|
|
101
|
+
{"tool": "manage_gameobject", "params": {"action": "create", "name": "A", "primitiveType": "Cube"}},
|
|
102
|
+
{"tool": "manage_gameobject", "params": {"action": "create", "name": "B", "primitiveType": "Sphere"}}
|
|
103
|
+
]'
|
|
104
|
+
"""
|
|
105
|
+
config = get_config()
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
commands = json.loads(commands_json)
|
|
109
|
+
except json.JSONDecodeError as e:
|
|
110
|
+
print_error(f"Invalid JSON: {e}")
|
|
111
|
+
sys.exit(1)
|
|
112
|
+
|
|
113
|
+
if not isinstance(commands, list):
|
|
114
|
+
print_error("Commands must be an array")
|
|
115
|
+
sys.exit(1)
|
|
116
|
+
|
|
117
|
+
if len(commands) > 25:
|
|
118
|
+
print_error(f"Maximum 25 commands per batch, got {len(commands)}")
|
|
119
|
+
sys.exit(1)
|
|
120
|
+
|
|
121
|
+
params: dict[str, Any] = {"commands": commands}
|
|
122
|
+
if parallel:
|
|
123
|
+
params["parallel"] = True
|
|
124
|
+
if fail_fast:
|
|
125
|
+
params["failFast"] = True
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
result = run_command("batch_execute", params, config)
|
|
129
|
+
click.echo(format_output(result, config.format))
|
|
130
|
+
except UnityConnectionError as e:
|
|
131
|
+
print_error(str(e))
|
|
132
|
+
sys.exit(1)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@batch.command("template")
|
|
136
|
+
@click.option("--output", "-o", type=click.Path(), help="Output file (default: stdout)")
|
|
137
|
+
def batch_template(output: Optional[str]):
|
|
138
|
+
"""Generate a sample batch commands file.
|
|
139
|
+
|
|
140
|
+
\\b
|
|
141
|
+
Examples:
|
|
142
|
+
unity-mcp batch template > commands.json
|
|
143
|
+
unity-mcp batch template -o my_batch.json
|
|
144
|
+
"""
|
|
145
|
+
template = [
|
|
146
|
+
{
|
|
147
|
+
"tool": "manage_scene",
|
|
148
|
+
"params": {"action": "get_active"}
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"tool": "manage_gameobject",
|
|
152
|
+
"params": {
|
|
153
|
+
"action": "create",
|
|
154
|
+
"name": "BatchCube",
|
|
155
|
+
"primitiveType": "Cube",
|
|
156
|
+
"position": [0, 1, 0]
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"tool": "manage_components",
|
|
161
|
+
"params": {
|
|
162
|
+
"action": "add",
|
|
163
|
+
"target": "BatchCube",
|
|
164
|
+
"componentType": "Rigidbody"
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"tool": "manage_gameobject",
|
|
169
|
+
"params": {
|
|
170
|
+
"action": "modify",
|
|
171
|
+
"target": "BatchCube",
|
|
172
|
+
"position": [0, 5, 0]
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
json_output = json.dumps(template, indent=2)
|
|
178
|
+
|
|
179
|
+
if output:
|
|
180
|
+
with open(output, 'w') as f:
|
|
181
|
+
f.write(json_output)
|
|
182
|
+
print_success(f"Template written to: {output}")
|
|
183
|
+
else:
|
|
184
|
+
click.echo(json_output)
|
cli/commands/code.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Code CLI commands - read source code. search might be implemented later (but can be totally achievable with AI)."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
import click
|
|
6
|
+
from typing import Optional, Any
|
|
7
|
+
|
|
8
|
+
from cli.utils.config import get_config
|
|
9
|
+
from cli.utils.output import format_output, print_error, print_info
|
|
10
|
+
from cli.utils.connection import run_command, UnityConnectionError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group()
|
|
14
|
+
def code():
|
|
15
|
+
"""Code operations - read source files."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@code.command("read")
|
|
20
|
+
@click.argument("path")
|
|
21
|
+
@click.option(
|
|
22
|
+
"--start-line", "-s",
|
|
23
|
+
default=None,
|
|
24
|
+
type=int,
|
|
25
|
+
help="Starting line number (1-based)."
|
|
26
|
+
)
|
|
27
|
+
@click.option(
|
|
28
|
+
"--line-count", "-n",
|
|
29
|
+
default=None,
|
|
30
|
+
type=int,
|
|
31
|
+
help="Number of lines to read."
|
|
32
|
+
)
|
|
33
|
+
def read(path: str, start_line: Optional[int], line_count: Optional[int]):
|
|
34
|
+
"""Read a source file.
|
|
35
|
+
|
|
36
|
+
\b
|
|
37
|
+
Examples:
|
|
38
|
+
unity-mcp code read "Assets/Scripts/Player.cs"
|
|
39
|
+
unity-mcp code read "Assets/Scripts/Player.cs" --start-line 10 --line-count 20
|
|
40
|
+
"""
|
|
41
|
+
config = get_config()
|
|
42
|
+
|
|
43
|
+
# Extract name and directory from path
|
|
44
|
+
parts = path.replace("\\", "/").split("/")
|
|
45
|
+
filename = os.path.splitext(parts[-1])[0]
|
|
46
|
+
directory = "/".join(parts[:-1]) or "Assets"
|
|
47
|
+
|
|
48
|
+
params: dict[str, Any] = {
|
|
49
|
+
"action": "read",
|
|
50
|
+
"name": filename,
|
|
51
|
+
"path": directory,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if start_line:
|
|
55
|
+
params["startLine"] = start_line
|
|
56
|
+
if line_count:
|
|
57
|
+
params["lineCount"] = line_count
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
result = run_command("manage_script", params, config)
|
|
61
|
+
# For read, output content directly if available
|
|
62
|
+
if result.get("success") and result.get("data"):
|
|
63
|
+
data = result.get("data", {})
|
|
64
|
+
if isinstance(data, dict) and "contents" in data:
|
|
65
|
+
click.echo(data["contents"])
|
|
66
|
+
else:
|
|
67
|
+
click.echo(format_output(result, config.format))
|
|
68
|
+
else:
|
|
69
|
+
click.echo(format_output(result, config.format))
|
|
70
|
+
except UnityConnectionError as e:
|
|
71
|
+
print_error(str(e))
|
|
72
|
+
sys.exit(1)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@code.command("search")
|
|
76
|
+
@click.argument("pattern")
|
|
77
|
+
@click.argument("path")
|
|
78
|
+
@click.option(
|
|
79
|
+
"--max-results", "-n",
|
|
80
|
+
default=50,
|
|
81
|
+
type=int,
|
|
82
|
+
help="Maximum number of results (default: 50)."
|
|
83
|
+
)
|
|
84
|
+
@click.option(
|
|
85
|
+
"--case-sensitive", "-c",
|
|
86
|
+
is_flag=True,
|
|
87
|
+
help="Make search case-sensitive (default: case-insensitive)."
|
|
88
|
+
)
|
|
89
|
+
def search(pattern: str, path: str, max_results: int, case_sensitive: bool):
|
|
90
|
+
"""Search for patterns in Unity scripts using regex.
|
|
91
|
+
|
|
92
|
+
PATTERN is a regex pattern to search for.
|
|
93
|
+
PATH is the script path (e.g., Assets/Scripts/Player.cs).
|
|
94
|
+
|
|
95
|
+
\\b
|
|
96
|
+
Examples:
|
|
97
|
+
unity-mcp code search "class.*Player" "Assets/Scripts/Player.cs"
|
|
98
|
+
unity-mcp code search "private.*int" "Assets/Scripts/GameManager.cs"
|
|
99
|
+
unity-mcp code search "TODO|FIXME" "Assets/Scripts/Utils.cs"
|
|
100
|
+
"""
|
|
101
|
+
import re
|
|
102
|
+
import base64
|
|
103
|
+
|
|
104
|
+
config = get_config()
|
|
105
|
+
|
|
106
|
+
# Extract name and directory from path
|
|
107
|
+
parts = path.replace("\\", "/").split("/")
|
|
108
|
+
filename = os.path.splitext(parts[-1])[0]
|
|
109
|
+
directory = "/".join(parts[:-1]) or "Assets"
|
|
110
|
+
|
|
111
|
+
# Step 1: Read the file via Unity's manage_script
|
|
112
|
+
read_params: dict[str, Any] = {
|
|
113
|
+
"action": "read",
|
|
114
|
+
"name": filename,
|
|
115
|
+
"path": directory,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
result = run_command("manage_script", read_params, config)
|
|
120
|
+
|
|
121
|
+
# Handle nested response structure: {status, result: {success, data}}
|
|
122
|
+
inner_result = result.get("result", result)
|
|
123
|
+
|
|
124
|
+
if not inner_result.get("success") and result.get("status") != "success":
|
|
125
|
+
click.echo(format_output(result, config.format))
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
# Get file contents from nested data
|
|
129
|
+
data = inner_result.get("data", {})
|
|
130
|
+
contents = data.get("contents")
|
|
131
|
+
|
|
132
|
+
# Handle base64 encoded content
|
|
133
|
+
if not contents and data.get("contentsEncoded") and data.get("encodedContents"):
|
|
134
|
+
try:
|
|
135
|
+
contents = base64.b64decode(
|
|
136
|
+
data["encodedContents"]).decode("utf-8", "replace")
|
|
137
|
+
except (ValueError, TypeError):
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
if not contents:
|
|
141
|
+
print_error(f"Could not read file content from {path}")
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
|
|
144
|
+
# Step 2: Perform regex search locally
|
|
145
|
+
flags = re.MULTILINE
|
|
146
|
+
if not case_sensitive:
|
|
147
|
+
flags |= re.IGNORECASE
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
regex = re.compile(pattern, flags)
|
|
151
|
+
except re.error as e:
|
|
152
|
+
print_error(f"Invalid regex pattern: {e}")
|
|
153
|
+
sys.exit(1)
|
|
154
|
+
|
|
155
|
+
found = list(regex.finditer(contents))
|
|
156
|
+
|
|
157
|
+
if not found:
|
|
158
|
+
print_info(f"No matches found for pattern: {pattern}")
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
results = []
|
|
162
|
+
for m in found[:max_results]:
|
|
163
|
+
start_idx = m.start()
|
|
164
|
+
|
|
165
|
+
# Calculate line number
|
|
166
|
+
line_num = contents.count('\n', 0, start_idx) + 1
|
|
167
|
+
|
|
168
|
+
# Get line content
|
|
169
|
+
line_start = contents.rfind('\n', 0, start_idx) + 1
|
|
170
|
+
line_end = contents.find('\n', start_idx)
|
|
171
|
+
if line_end == -1:
|
|
172
|
+
line_end = len(contents)
|
|
173
|
+
|
|
174
|
+
line_content = contents[line_start:line_end].strip()
|
|
175
|
+
|
|
176
|
+
results.append({
|
|
177
|
+
"line": line_num,
|
|
178
|
+
"content": line_content,
|
|
179
|
+
"match": m.group(0),
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
# Display results
|
|
183
|
+
click.echo(f"Found {len(results)} matches (total: {len(found)}):\n")
|
|
184
|
+
for match in results:
|
|
185
|
+
click.echo(f" Line {match['line']}: {match['content']}")
|
|
186
|
+
|
|
187
|
+
except UnityConnectionError as e:
|
|
188
|
+
print_error(str(e))
|
|
189
|
+
sys.exit(1)
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Component CLI commands."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import click
|
|
6
|
+
from typing import Optional, Any
|
|
7
|
+
|
|
8
|
+
from cli.utils.config import get_config
|
|
9
|
+
from cli.utils.output import format_output, print_error, print_success
|
|
10
|
+
from cli.utils.connection import run_command, UnityConnectionError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group()
|
|
14
|
+
def component():
|
|
15
|
+
"""Component operations - add, remove, modify components on GameObjects."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@component.command("add")
|
|
20
|
+
@click.argument("target")
|
|
21
|
+
@click.argument("component_type")
|
|
22
|
+
@click.option(
|
|
23
|
+
"--search-method",
|
|
24
|
+
type=click.Choice(["by_id", "by_name", "by_path"]),
|
|
25
|
+
default=None,
|
|
26
|
+
help="How to find the target GameObject."
|
|
27
|
+
)
|
|
28
|
+
@click.option(
|
|
29
|
+
"--properties", "-p",
|
|
30
|
+
default=None,
|
|
31
|
+
help='Initial properties as JSON (e.g., \'{"mass": 5.0}\').'
|
|
32
|
+
)
|
|
33
|
+
def add(target: str, component_type: str, search_method: Optional[str], properties: Optional[str]):
|
|
34
|
+
"""Add a component to a GameObject.
|
|
35
|
+
|
|
36
|
+
\b
|
|
37
|
+
Examples:
|
|
38
|
+
unity-mcp component add "Player" Rigidbody
|
|
39
|
+
unity-mcp component add "-81840" BoxCollider --search-method by_id
|
|
40
|
+
unity-mcp component add "Enemy" Rigidbody --properties '{"mass": 5.0, "useGravity": true}'
|
|
41
|
+
"""
|
|
42
|
+
config = get_config()
|
|
43
|
+
|
|
44
|
+
params: dict[str, Any] = {
|
|
45
|
+
"action": "add",
|
|
46
|
+
"target": target,
|
|
47
|
+
"componentType": component_type,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if search_method:
|
|
51
|
+
params["searchMethod"] = search_method
|
|
52
|
+
if properties:
|
|
53
|
+
try:
|
|
54
|
+
params["properties"] = json.loads(properties)
|
|
55
|
+
except json.JSONDecodeError as e:
|
|
56
|
+
print_error(f"Invalid JSON for properties: {e}")
|
|
57
|
+
sys.exit(1)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
result = run_command("manage_components", params, config)
|
|
61
|
+
click.echo(format_output(result, config.format))
|
|
62
|
+
if result.get("success"):
|
|
63
|
+
print_success(f"Added {component_type} to '{target}'")
|
|
64
|
+
except UnityConnectionError as e:
|
|
65
|
+
print_error(str(e))
|
|
66
|
+
sys.exit(1)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@component.command("remove")
|
|
70
|
+
@click.argument("target")
|
|
71
|
+
@click.argument("component_type")
|
|
72
|
+
@click.option(
|
|
73
|
+
"--search-method",
|
|
74
|
+
type=click.Choice(["by_id", "by_name", "by_path"]),
|
|
75
|
+
default=None,
|
|
76
|
+
help="How to find the target GameObject."
|
|
77
|
+
)
|
|
78
|
+
@click.option(
|
|
79
|
+
"--force", "-f",
|
|
80
|
+
is_flag=True,
|
|
81
|
+
help="Skip confirmation prompt."
|
|
82
|
+
)
|
|
83
|
+
def remove(target: str, component_type: str, search_method: Optional[str], force: bool):
|
|
84
|
+
"""Remove a component from a GameObject.
|
|
85
|
+
|
|
86
|
+
\b
|
|
87
|
+
Examples:
|
|
88
|
+
unity-mcp component remove "Player" Rigidbody
|
|
89
|
+
unity-mcp component remove "-81840" BoxCollider --search-method by_id --force
|
|
90
|
+
"""
|
|
91
|
+
config = get_config()
|
|
92
|
+
|
|
93
|
+
if not force:
|
|
94
|
+
click.confirm(f"Remove {component_type} from '{target}'?", abort=True)
|
|
95
|
+
|
|
96
|
+
params: dict[str, Any] = {
|
|
97
|
+
"action": "remove",
|
|
98
|
+
"target": target,
|
|
99
|
+
"componentType": component_type,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if search_method:
|
|
103
|
+
params["searchMethod"] = search_method
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
result = run_command("manage_components", params, config)
|
|
107
|
+
click.echo(format_output(result, config.format))
|
|
108
|
+
if result.get("success"):
|
|
109
|
+
print_success(f"Removed {component_type} from '{target}'")
|
|
110
|
+
except UnityConnectionError as e:
|
|
111
|
+
print_error(str(e))
|
|
112
|
+
sys.exit(1)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@component.command("set")
|
|
116
|
+
@click.argument("target")
|
|
117
|
+
@click.argument("component_type")
|
|
118
|
+
@click.argument("property_name")
|
|
119
|
+
@click.argument("value")
|
|
120
|
+
@click.option(
|
|
121
|
+
"--search-method",
|
|
122
|
+
type=click.Choice(["by_id", "by_name", "by_path"]),
|
|
123
|
+
default=None,
|
|
124
|
+
help="How to find the target GameObject."
|
|
125
|
+
)
|
|
126
|
+
def set_property(target: str, component_type: str, property_name: str, value: str, search_method: Optional[str]):
|
|
127
|
+
"""Set a single property on a component.
|
|
128
|
+
|
|
129
|
+
\b
|
|
130
|
+
Examples:
|
|
131
|
+
unity-mcp component set "Player" Rigidbody mass 5.0
|
|
132
|
+
unity-mcp component set "Enemy" Transform position "[0, 5, 0]"
|
|
133
|
+
unity-mcp component set "-81840" Light intensity 2.5 --search-method by_id
|
|
134
|
+
"""
|
|
135
|
+
config = get_config()
|
|
136
|
+
|
|
137
|
+
# Try to parse value as JSON for complex types
|
|
138
|
+
try:
|
|
139
|
+
parsed_value = json.loads(value)
|
|
140
|
+
except json.JSONDecodeError:
|
|
141
|
+
# Keep as string if not valid JSON
|
|
142
|
+
parsed_value = value
|
|
143
|
+
|
|
144
|
+
params: dict[str, Any] = {
|
|
145
|
+
"action": "set_property",
|
|
146
|
+
"target": target,
|
|
147
|
+
"componentType": component_type,
|
|
148
|
+
"property": property_name,
|
|
149
|
+
"value": parsed_value,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if search_method:
|
|
153
|
+
params["searchMethod"] = search_method
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
result = run_command("manage_components", params, config)
|
|
157
|
+
click.echo(format_output(result, config.format))
|
|
158
|
+
if result.get("success"):
|
|
159
|
+
print_success(f"Set {component_type}.{property_name} = {value}")
|
|
160
|
+
except UnityConnectionError as e:
|
|
161
|
+
print_error(str(e))
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@component.command("modify")
|
|
166
|
+
@click.argument("target")
|
|
167
|
+
@click.argument("component_type")
|
|
168
|
+
@click.option(
|
|
169
|
+
"--properties", "-p",
|
|
170
|
+
required=True,
|
|
171
|
+
help='Properties to set as JSON (e.g., \'{"mass": 5.0, "useGravity": false}\').'
|
|
172
|
+
)
|
|
173
|
+
@click.option(
|
|
174
|
+
"--search-method",
|
|
175
|
+
type=click.Choice(["by_id", "by_name", "by_path"]),
|
|
176
|
+
default=None,
|
|
177
|
+
help="How to find the target GameObject."
|
|
178
|
+
)
|
|
179
|
+
def modify(target: str, component_type: str, properties: str, search_method: Optional[str]):
|
|
180
|
+
"""Set multiple properties on a component at once.
|
|
181
|
+
|
|
182
|
+
\b
|
|
183
|
+
Examples:
|
|
184
|
+
unity-mcp component modify "Player" Rigidbody --properties '{"mass": 5.0, "useGravity": false}'
|
|
185
|
+
unity-mcp component modify "Light" Light --properties '{"intensity": 2.0, "color": [1, 0, 0, 1]}'
|
|
186
|
+
"""
|
|
187
|
+
config = get_config()
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
props_dict = json.loads(properties)
|
|
191
|
+
except json.JSONDecodeError as e:
|
|
192
|
+
print_error(f"Invalid JSON for properties: {e}")
|
|
193
|
+
sys.exit(1)
|
|
194
|
+
|
|
195
|
+
params: dict[str, Any] = {
|
|
196
|
+
"action": "set_property",
|
|
197
|
+
"target": target,
|
|
198
|
+
"componentType": component_type,
|
|
199
|
+
"properties": props_dict,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if search_method:
|
|
203
|
+
params["searchMethod"] = search_method
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
result = run_command("manage_components", params, config)
|
|
207
|
+
click.echo(format_output(result, config.format))
|
|
208
|
+
if result.get("success"):
|
|
209
|
+
print_success(f"Modified {component_type} on '{target}'")
|
|
210
|
+
except UnityConnectionError as e:
|
|
211
|
+
print_error(str(e))
|
|
212
|
+
sys.exit(1)
|