Skip to content

get/set animation node positions#1206

Open
Thaina wants to merge 2 commits into
CoplayDev:betafrom
Thaina:animation-node-graph-position-MCP
Open

get/set animation node positions#1206
Thaina wants to merge 2 commits into
CoplayDev:betafrom
Thaina:animation-node-graph-position-MCP

Conversation

@Thaina

@Thaina Thaina commented Jun 15, 2026

Copy link
Copy Markdown

Description

adding get_state_positions and set_state_positions to the mcp managed animation controller so that AI can modified the node graph position in addition to create and set its transition

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • Test update

Changes Made

Compatibility / Package Source

  • Unity version(s) tested: 6000.3.13f
  • Package source used : file:
  • Resolved commit hash from Packages/packages-lock.json: 4d8cf5d

Testing/Screenshots/Recordings

  • Python tests (cd Server && uv run pytest tests/ -v)
  • Unity EditMode tests
  • Unity PlayMode tests
  • Package import/compile check
  • Not applicable (explain why in Additional Notes)

Documentation Updates

  • I have added/removed/modified tools or resources
  • If yes, I have updated all documentation files using:
    • The LLM prompt at tools/UPDATE_DOCS_PROMPT.md (recommended)
    • Manual review of the generated changes

Related Issues

Additional Notes

Summary by CodeRabbit

  • New Features
    • Added commands to retrieve Animator state graph node positions (name, x/y coordinates, layer), including recursive sub-state-machines with optional layer filtering and paging.
    • Added commands to update Animator state graph positions using provided state name/position mappings, supporting layer scoping and persistence on the controller asset.
  • Enhancements
    • Extended controller action routing/validation to recognize the new state position actions and improved “unknown action” messaging to include them.
  • Tests
    • Added coverage for dispatching and payload forwarding for get/set state positions, including paging and layer scoping.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Two new static methods, GetStatePositions and SetStatePositions, are added to ControllerCreate.cs with recursive helpers to read and write AnimatorController state graph node positions across all layers and sub-state-machines. ManageAnimation.cs routes the new actions through its controller switch, manage_animation.py registers the two new action names as valid controller actions, and new tests validate the integration.

Changes

Animator State Position Endpoints

Layer / File(s) Summary
Core position read/write implementation
MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs
GetStatePositions loads the AnimatorController and uses CollectPositions to recursively gather each state's name, layer, and graph coordinates with pagination support. SetStatePositions validates the input array, builds a name-to-coordinate lookup, and uses ApplyPositions to recursively update state positions (reassigning sm.states to persist edits and recording undo steps), returning matched and unmatched counts.
Dispatch routing and action registration
MCPForUnity/Editor/Tools/Animation/ManageAnimation.cs, Server/src/services/tools/manage_animation.py
ManageAnimation adds two switch cases routing set_state_positions and get_state_positions to ControllerCreate handlers, and updates the unknown-action error message to include the new actions. CONTROLLER_ACTIONS in the Python server gains controller_set_state_positions and controller_get_state_positions for validation and action suggestions.
Integration tests for state position actions
Server/tests/test_manage_animation.py
New TestStatePositionActions class verifies that manage_animation correctly dispatches the two controller state position actions and forwards their properties including layerIndex, pagination fields, and position arrays. Expected controller actions list is reformatted.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐇 Hop through state machines, left and right,
Each node's position shines so bright,
X and Y coordinates stored with care,
Recursive paths dance everywhere,
The animator graph's pure delight! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main changes—adding get/set animation node position functionality—accurately summarizing the primary purpose of the changeset.
Description check ✅ Passed The description covers the main purpose and includes required sections (Type of Change, Compatibility/Package Source, Testing, Documentation Updates), though Changes Made section is empty and Python tests checkbox is unchecked despite changes to Server code.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Thaina Thaina force-pushed the animation-node-graph-position-MCP branch from 4d8cf5d to 95b9c2f Compare June 15, 2026 10:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs`:
- Around line 434-443: The GetStatePositions method currently collects and
returns all nodes in a single response without pagination, which violates coding
guidelines for endpoints that could return large datasets. Modify the method to
accept page_size and cursor parameters in the method signature, implement
pagination logic to slice the nodes list collected by CollectPositions based on
these parameters, and update the return object to include a next_cursor field
that indicates the position for the next page of results (or null if this is the
final page). This ensures large AnimatorControllers do not create oversized
payloads.
- Around line 474-481: The foreach loop iterating through positions assumes
every token item is a JObject that supports property indexing, but malformed
entries (nulls, primitives, or incompatible types) will throw exceptions. Before
accessing token["name"], token["x"], and token["y"], add a guard clause to
verify that token is a JObject instance; if not, skip that iteration using
continue or collect the error appropriately. This prevents crashes on malformed
position data while maintaining the existing logic for valid entries.
- Around line 473-503: The SetStatePositions method currently matches states by
name only, which is ambiguous since duplicate names are valid in Animator graphs
and could cause unintended updates across layers. Replace the name-only key in
the want dictionary with a unique state identifier that combines the layer
index, state-machine path, and state name (for example
"layer0/RootStateMachine/StateName"). Update the contract to expect this unique
identifier format instead of just "name", and modify the ApplyPositions method
(and any other matching logic around lines 507-521) to construct and match
against this same unique identifier when traversing the state machine hierarchy,
so that only the intended state is updated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 10199cd6-3ddb-40ef-aa2e-d236839cdc09

📥 Commits

Reviewing files that changed from the base of the PR and between dccecd6 and 4d8cf5d.

📒 Files selected for processing (3)
  • MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs
  • MCPForUnity/Editor/Tools/Animation/ManageAnimation.cs
  • Server/src/services/tools/manage_animation.py

Comment thread MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs
Comment thread MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs
Comment thread MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs (2)

499-506: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard positions items before property indexing to avoid runtime exceptions.

On Line 501–506, the loop indexes token["..."] without verifying object shape. Non-object entries in positions can throw and convert client input errors into internal failures. Add an if (token is not JObject item) continue/collect-error guard before field access.

Minimal fix
 foreach (var token in positions)
 {
-    string name = token["name"]?.ToString();
+    if (token is not JObject item)
+        continue;
+
+    string name = item["name"]?.ToString();
     if (string.IsNullOrEmpty(name))
         continue;
-    float x = token["x"]?.ToObject<float>() ?? 0f;
-    float y = token["y"]?.ToObject<float>() ?? 0f;
-    int? layer = token["layer"]?.ToObject<int>() ?? defaultLayer;
+    float x = item["x"]?.ToObject<float>() ?? 0f;
+    float y = item["y"]?.ToObject<float>() ?? 0f;
+    int? layer = item["layer"]?.ToObject<int>() ?? defaultLayer;
     want[$"{(layer.HasValue ? layer.Value.ToString() : "*")}:{name}"] = new Vector2(x, y);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs` around lines 499 -
506, In the foreach loop that iterates over positions, add a guard clause at the
beginning to verify that each token is a JObject before attempting to access its
properties using the indexer (e.g., token["name"]). If the token is not a
JObject, use continue to skip it or handle the error appropriately. This
prevents runtime exceptions when non-object entries exist in the positions
collection and converts potential client input errors into graceful handling
rather than internal failures.

497-507: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

State key is still ambiguous for duplicate names within the same layer.

On Line 497 and Line 541, matching is keyed by {layer}:{name} (or wildcard), which is not unique across sub-state-machines in that layer. A single request can still move multiple unintended nodes when names repeat. The read/write contract needs a unique identifier (e.g., layer + state-machine path + state name) in both GetStatePositions output and SetStatePositions matching.

Suggested direction
- // Returns [{ name, x, y, layer }]
+ // Returns [{ id, name, x, y, layer, path }]

- want[$"{(layer.HasValue ? layer.Value.ToString() : "*")}:{name}"] = new Vector2(x, y);
+ // Expect `id` from GetStatePositions; use that as the only write key.
+ want[id] = new Vector2(x, y);

- string scopedKey = $"{layer}:{name}";
- string anyKey = $"*:{name}";
+ string scopedKey = BuildStateId(layer, stateMachinePath, name);

Also applies to: 539-545

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs` around lines 497 -
507, The state positioning key format using only layer and name is ambiguous
when duplicate state names exist in different sub-state-machines within the same
layer. Update the key structure to include the state-machine path in addition to
layer and name to ensure uniqueness. In the section around line 497-507 where
the want dictionary is built, modify the key format from {layer}:{name} to
include the full state-machine path (e.g., {layer}:{stateMachinePath}:{name}).
Apply the same key format change in the matching logic around lines 539-545 to
ensure consistency between how positions are stored and retrieved. This requires
coordinating the GetStatePositions output format with the SetStatePositions
matching logic to use the same unique identifier scheme.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs`:
- Around line 499-506: In the foreach loop that iterates over positions, add a
guard clause at the beginning to verify that each token is a JObject before
attempting to access its properties using the indexer (e.g., token["name"]). If
the token is not a JObject, use continue to skip it or handle the error
appropriately. This prevents runtime exceptions when non-object entries exist in
the positions collection and converts potential client input errors into
graceful handling rather than internal failures.
- Around line 497-507: The state positioning key format using only layer and
name is ambiguous when duplicate state names exist in different
sub-state-machines within the same layer. Update the key structure to include
the state-machine path in addition to layer and name to ensure uniqueness. In
the section around line 497-507 where the want dictionary is built, modify the
key format from {layer}:{name} to include the full state-machine path (e.g.,
{layer}:{stateMachinePath}:{name}). Apply the same key format change in the
matching logic around lines 539-545 to ensure consistency between how positions
are stored and retrieved. This requires coordinating the GetStatePositions
output format with the SetStatePositions matching logic to use the same unique
identifier scheme.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a2f12fc9-d9c0-495a-9b3f-ef909f5fbfe1

📥 Commits

Reviewing files that changed from the base of the PR and between 95b9c2f and e4fbd3a.

📒 Files selected for processing (2)
  • MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs
  • Server/tests/test_manage_animation.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant