Skip to content

Commit 9289af3

Browse files
committed
Updates and bug fix
1 parent fb9598d commit 9289af3

8 files changed

Lines changed: 240 additions & 241 deletions

File tree

MCPForUnity/Editor/Tools/Cameras/CameraConfigure.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,22 @@ internal static object SetCinemachinePriority(JObject @params)
159159
var props = CameraHelpers.ExtractProperties(@params) ?? new JObject();
160160
int priority = ParamCoercion.CoerceInt(props["priority"], 10);
161161

162-
Undo.RecordObject(cmCamera, "Set Cinemachine Priority");
163-
CameraHelpers.SetReflectionProperty(cmCamera, "Priority", priority);
162+
// PrioritySettings is a struct with Enabled + m_Value — use SerializedProperty
163+
using var so = new SerializedObject(cmCamera);
164+
var priorityProp = so.FindProperty("Priority");
165+
if (priorityProp != null)
166+
{
167+
var enabledProp = priorityProp.FindPropertyRelative("Enabled");
168+
var valueProp = priorityProp.FindPropertyRelative("m_Value");
169+
if (enabledProp != null) enabledProp.boolValue = true;
170+
if (valueProp != null) valueProp.intValue = priority;
171+
so.ApplyModifiedProperties();
172+
}
173+
else
174+
{
175+
Undo.RecordObject(cmCamera, "Set Cinemachine Priority");
176+
CameraHelpers.SetReflectionProperty(cmCamera, "Priority", priority);
177+
}
164178
CameraHelpers.MarkDirty(cmCamera.gameObject);
165179

166180
return new

MCPForUnity/Editor/Tools/Cameras/CameraControl.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ internal static object ListCameras(JObject @params)
2727
var follow = CameraHelpers.GetReflectionProperty(cm, "Follow") as Transform;
2828
var lookAt = CameraHelpers.GetReflectionProperty(cm, "LookAt") as Transform;
2929
var isLive = CameraHelpers.GetReflectionProperty(cm, "IsLive");
30-
var priority = CameraHelpers.GetReflectionProperty(cm, "Priority");
30+
var priority = CameraHelpers.ReadCinemachinePriority(cm);
3131

3232
var body = CameraHelpers.GetPipelineComponent(cm, "Body");
3333
var aim = CameraHelpers.GetPipelineComponent(cm, "Aim");
@@ -50,7 +50,7 @@ internal static object ListCameras(JObject @params)
5050
instanceID = cm.gameObject.GetInstanceID(),
5151
name = cm.gameObject.name,
5252
isLive = isLive is bool b && b,
53-
priority = priority is int p ? p : 0,
53+
priority,
5454
follow = follow != null ? new { name = follow.gameObject.name, instanceID = follow.gameObject.GetInstanceID() } : null,
5555
lookAt = lookAt != null ? new { name = lookAt.gameObject.name, instanceID = lookAt.gameObject.GetInstanceID() } : null,
5656
body = body?.GetType().Name,

MCPForUnity/Editor/Tools/Cameras/CameraCreate.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,23 @@ internal static object CreateCinemachineCamera(JObject @params)
8989
var cmType = CameraHelpers.CinemachineCameraType;
9090
var cmCamera = go.AddComponent(cmType);
9191

92-
// Set priority via reflection
93-
CameraHelpers.SetReflectionProperty(cmCamera, "Priority", priority);
92+
// PrioritySettings is a struct with Enabled + m_Value — use SerializedProperty
93+
using (var so = new SerializedObject(cmCamera))
94+
{
95+
var priorityProp = so.FindProperty("Priority");
96+
if (priorityProp != null)
97+
{
98+
var enabledProp = priorityProp.FindPropertyRelative("Enabled");
99+
var valueProp = priorityProp.FindPropertyRelative("m_Value");
100+
if (enabledProp != null) enabledProp.boolValue = true;
101+
if (valueProp != null) valueProp.intValue = priority;
102+
so.ApplyModifiedProperties();
103+
}
104+
else
105+
{
106+
CameraHelpers.SetReflectionProperty(cmCamera, "Priority", priority);
107+
}
108+
}
94109

95110
// Add Body component
96111
string bodyName = null;

MCPForUnity/Editor/Tools/Cameras/CameraHelpers.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,19 @@ internal static object GetReflectionProperty(Component component, string propert
176176
return prop?.GetValue(component);
177177
}
178178

179+
/// <summary>Read priority int from a CinemachineCamera component via SerializedObject.</summary>
180+
internal static int ReadCinemachinePriority(Component cmCamera)
181+
{
182+
if (cmCamera == null) return 0;
183+
using var so = new SerializedObject(cmCamera);
184+
var priorityProp = so.FindProperty("Priority");
185+
if (priorityProp == null) return 0;
186+
var enabledProp = priorityProp.FindPropertyRelative("Enabled");
187+
var valueProp = priorityProp.FindPropertyRelative("m_Value");
188+
if (enabledProp != null && !enabledProp.boolValue) return 0;
189+
return valueProp?.intValue ?? 0;
190+
}
191+
179192
internal static bool SetReflectionProperty(Component component, string propertyName, object value)
180193
{
181194
if (component == null) return false;

Server/src/services/tools/manage_camera.py

Lines changed: 21 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import json
21
from typing import Annotated, Any, Literal
32

43
from fastmcp import Context
54
from fastmcp.server.server import ToolResult
6-
from mcp.types import ToolAnnotations, TextContent, ImageContent
5+
from mcp.types import ToolAnnotations
76

87
from services.registry import mcp_for_unity_tool
98
from services.tools import get_unity_instance_from_context
10-
from services.tools.utils import coerce_int, coerce_bool, normalize_vector3
9+
from services.tools.utils import build_screenshot_params, extract_screenshot_images
1110
from transport.unity_transport import send_with_unity_instance
1211
from transport.legacy.unity_connection import async_send_command_with_retry
1312

@@ -32,54 +31,6 @@
3231
ALL_ACTIONS = SETUP_ACTIONS + CREATION_ACTIONS + CONFIGURATION_ACTIONS + EXTENSION_ACTIONS + CONTROL_ACTIONS + CAPTURE_ACTIONS
3332

3433

35-
def _extract_images(response: dict[str, Any], action: str) -> ToolResult | None:
36-
"""If the Unity response contains inline base64 images, return a ToolResult
37-
with TextContent + ImageContent blocks. Returns None for normal text-only responses."""
38-
if not isinstance(response, dict) or not response.get("success"):
39-
return None
40-
41-
data = response.get("data")
42-
if not isinstance(data, dict):
43-
return None
44-
45-
# Batch images (surround/orbit mode) — multiple screenshots in one response
46-
screenshots = data.get("screenshots")
47-
if screenshots and isinstance(screenshots, list):
48-
blocks: list[TextContent | ImageContent] = []
49-
summary_screenshots = []
50-
for s in screenshots:
51-
summary_screenshots.append({k: v for k, v in s.items() if k != "imageBase64"})
52-
text_result = {
53-
"success": True,
54-
"message": response.get("message", ""),
55-
"data": {
56-
"sceneCenter": data.get("sceneCenter"),
57-
"sceneRadius": data.get("sceneRadius"),
58-
"screenshots": summary_screenshots,
59-
},
60-
}
61-
blocks.append(TextContent(type="text", text=json.dumps(text_result)))
62-
for s in screenshots:
63-
b64 = s.get("imageBase64")
64-
if b64:
65-
blocks.append(TextContent(type="text", text=f"[Angle: {s.get('angle', '?')}]"))
66-
blocks.append(ImageContent(type="image", data=b64, mimeType="image/png"))
67-
return ToolResult(content=blocks)
68-
69-
# Single image (include_image or positioned capture) or contact sheet
70-
image_b64 = data.get("imageBase64")
71-
if not image_b64:
72-
return None
73-
text_data = {k: v for k, v in data.items() if k != "imageBase64"}
74-
text_result = {"success": True, "message": response.get("message", ""), "data": text_data}
75-
return ToolResult(
76-
content=[
77-
TextContent(type="text", text=json.dumps(text_result)),
78-
ImageContent(type="image", data=image_b64, mimeType="image/png"),
79-
],
80-
)
81-
82-
8334
@mcp_for_unity_tool(
8435
group="core",
8536
description=(
@@ -196,63 +147,24 @@ async def manage_camera(
196147

197148
# Screenshot params — only relevant for screenshot/screenshot_multiview actions
198149
if action_normalized in CAPTURE_ACTIONS:
199-
if screenshot_file_name:
200-
params_dict["fileName"] = screenshot_file_name
201-
coerced_super_size = coerce_int(screenshot_super_size, default=None)
202-
if coerced_super_size is not None:
203-
params_dict["superSize"] = coerced_super_size
204-
if camera:
205-
params_dict["camera"] = camera
206-
coerced_include_image = coerce_bool(include_image, default=None)
207-
if coerced_include_image is not None:
208-
params_dict["includeImage"] = coerced_include_image
209-
coerced_max_resolution = coerce_int(max_resolution, default=None)
210-
if coerced_max_resolution is not None:
211-
if coerced_max_resolution <= 0:
212-
return {"success": False, "message": "max_resolution must be a positive integer."}
213-
params_dict["maxResolution"] = coerced_max_resolution
214-
if batch:
215-
params_dict["batch"] = batch
216-
if look_at is not None:
217-
params_dict["lookAt"] = look_at
218-
219-
# Orbit params
220-
coerced_orbit_angles = coerce_int(orbit_angles, default=None)
221-
if coerced_orbit_angles is not None:
222-
params_dict["orbitAngles"] = coerced_orbit_angles
223-
if orbit_elevations is not None:
224-
if isinstance(orbit_elevations, str):
225-
try:
226-
orbit_elevations = json.loads(orbit_elevations)
227-
except (ValueError, TypeError):
228-
return {"success": False, "message": "orbit_elevations must be a JSON array of floats."}
229-
if not isinstance(orbit_elevations, list) or not all(
230-
isinstance(v, (int, float)) for v in orbit_elevations
231-
):
232-
return {"success": False, "message": "orbit_elevations must be a list of numbers."}
233-
params_dict["orbitElevations"] = orbit_elevations
234-
if orbit_distance is not None:
235-
try:
236-
params_dict["orbitDistance"] = float(orbit_distance)
237-
except (ValueError, TypeError):
238-
return {"success": False, "message": "orbit_distance must be a number."}
239-
if orbit_fov is not None:
240-
try:
241-
params_dict["orbitFov"] = float(orbit_fov)
242-
except (ValueError, TypeError):
243-
return {"success": False, "message": "orbit_fov must be a number."}
244-
if view_position is not None:
245-
vec, err = normalize_vector3(view_position, "view_position")
246-
if err:
247-
return {"success": False, "message": err}
248-
params_dict["viewPosition"] = vec
249-
if view_rotation is not None:
250-
vec, err = normalize_vector3(view_rotation, "view_rotation")
251-
if err:
252-
return {"success": False, "message": err}
253-
params_dict["viewRotation"] = vec
254-
255-
params_dict = {k: v for k, v in params_dict.items() if v is not None}
150+
err = build_screenshot_params(
151+
params_dict,
152+
screenshot_file_name=screenshot_file_name,
153+
screenshot_super_size=screenshot_super_size,
154+
camera=camera,
155+
include_image=include_image,
156+
max_resolution=max_resolution,
157+
batch=batch,
158+
look_at=look_at,
159+
orbit_angles=orbit_angles,
160+
orbit_elevations=orbit_elevations,
161+
orbit_distance=orbit_distance,
162+
orbit_fov=orbit_fov,
163+
view_position=view_position,
164+
view_rotation=view_rotation,
165+
)
166+
if err is not None:
167+
return err
256168

257169
result = await send_with_unity_instance(
258170
async_send_command_with_retry,
@@ -266,7 +178,7 @@ async def manage_camera(
266178

267179
# For capture actions, check for inline images to return as ImageContent
268180
if action_normalized in CAPTURE_ACTIONS:
269-
image_result = _extract_images(result, "screenshot")
181+
image_result = extract_screenshot_images(result)
270182
if image_result is not None:
271183
return image_result
272184

0 commit comments

Comments
 (0)