44using System . Linq ;
55using Newtonsoft . Json . Linq ;
66using MCPForUnity . Editor . Helpers ;
7+ using MCPForUnity . Runtime . Helpers ;
78using UnityEditor ;
89using UnityEditor . Animations ;
910using UnityEngine ;
@@ -423,9 +424,11 @@ public static object AssignToGameObject(JObject @params)
423424 }
424425
425426 // Reads node graph positions for every state (recurses into sub-state-machines).
426- // Returns [{ name, x, y, layer }] so a caller can analyze the current layout
427- // before sending back a revised one. Pass 'layerIndex' to scope to one layer;
428- // results are paged (page_size/cursor) since controllers can have many states.
427+ // Returns [{ name, instanceId, x, y, layer }] so a caller can analyze the current
428+ // layout before sending back a revised one. 'instanceId' round-trips into
429+ // set_state_positions for an unambiguous match (duplicate names are fine).
430+ // Pass 'layerIndex' to scope to one layer; results are paged (page_size/cursor)
431+ // since controllers can have many states.
429432 public static object GetStatePositions ( JObject @params )
430433 {
431434 var controller = LoadController ( @params ) ;
@@ -470,6 +473,7 @@ private static void CollectPositions(AnimatorStateMachine sm, int layer, List<ob
470473 outList . Add ( new
471474 {
472475 name = children [ i ] . state . name ,
476+ instanceId = children [ i ] . state . GetInstanceIDLongCompat ( ) ,
473477 x = children [ i ] . position . x ,
474478 y = children [ i ] . position . y ,
475479 layer
@@ -478,41 +482,39 @@ private static void CollectPositions(AnimatorStateMachine sm, int layer, List<ob
478482 CollectPositions ( sub . stateMachine , layer , outList ) ;
479483 }
480484
481- // Sets node graph positions from a 'positions' array of { name , x, y, layer? }.
482- // Each entry's optional 'layer ' (falling back to a top-level 'layerIndex') scopes
483- // the match to one layer, so a name reused across layers is no longer ambiguous;
484- // entries with no layer match that name on any layer. Recurses into sub-state-
485- // machines and reassigns stateMachine.states so the edits persist on the asset.
485+ // Sets node graph positions from a 'positions' array of { instanceId , x, y }.
486+ // States are matched by 'instanceId ' (from get_state_positions) for an exact,
487+ // unambiguous hit even when names repeat across layers or sub-state-machines.
488+ // Recurses into sub-state-machines and reassigns stateMachine.states so the
489+ // edits persist on the asset.
486490 public static object SetStatePositions ( JObject @params )
487491 {
488492 var controller = LoadController ( @params ) ;
489493 if ( controller == null )
490494 return ControllerNotFoundError ( @params ) ;
491495
492496 if ( ! ( @params [ "positions" ] is JArray positions ) || positions . Count == 0 )
493- return new { success = false , message = "'positions' array is required: [{ name , x, y, layer? }, ...]" } ;
497+ return new { success = false , message = "'positions' array is required: [{ instanceId , x, y }, ...]" } ;
494498
495- int ? defaultLayer = @params [ "layerIndex" ] ? . ToObject < int > ( ) ;
496-
497- // Key is "layer:name" when scoped to a layer, else "*:name" to match any layer.
498- var want = new Dictionary < string , Vector2 > ( ) ;
499+ var want = new Dictionary < ulong , Vector2 > ( ) ;
499500 foreach ( var token in positions )
500501 {
501- string name = token [ "name" ] ? . ToString ( ) ;
502- if ( string . IsNullOrEmpty ( name ) )
502+ if ( ! ( token is JObject entry ) )
503+ continue ;
504+ ulong ? instanceId = entry [ "instanceId" ] ? . ToObject < ulong > ( ) ;
505+ if ( ! instanceId . HasValue )
503506 continue ;
504- float x = token [ "x" ] ? . ToObject < float > ( ) ?? 0f ;
505- float y = token [ "y" ] ? . ToObject < float > ( ) ?? 0f ;
506- int ? layer = token [ "layer" ] ? . ToObject < int > ( ) ?? defaultLayer ;
507- want [ $ "{ ( layer . HasValue ? layer . Value . ToString ( ) : "*" ) } :{ name } "] = new Vector2 ( x , y ) ;
507+ float x = entry [ "x" ] ? . ToObject < float > ( ) ?? 0f ;
508+ float y = entry [ "y" ] ? . ToObject < float > ( ) ?? 0f ;
509+ want [ instanceId . Value ] = new Vector2 ( x , y ) ;
508510 }
509511 if ( want . Count == 0 )
510- return new { success = false , message = "No valid entries in 'positions' (each needs a 'name ')." } ;
512+ return new { success = false , message = "No valid entries in 'positions' (each needs an 'instanceId ')." } ;
511513
512- var matched = new HashSet < string > ( ) ;
514+ var matched = new HashSet < ulong > ( ) ;
513515 Undo . RecordObject ( controller , "Set State Positions" ) ;
514516 for ( int li = 0 ; li < controller . layers . Length ; li ++ )
515- ApplyPositions ( controller . layers [ li ] . stateMachine , li , want , matched ) ;
517+ ApplyPositions ( controller . layers [ li ] . stateMachine , want , matched ) ;
516518
517519 EditorUtility . SetDirty ( controller ) ;
518520 AssetDatabase . SaveAssets ( ) ;
@@ -521,7 +523,7 @@ public static object SetStatePositions(JObject @params)
521523 return new
522524 {
523525 success = true ,
524- message = $ "Positioned { matched . Count } state(s); { unmatched . Count } key (s) unmatched.",
526+ message = $ "Positioned { matched . Count } state(s); { unmatched . Count } id (s) unmatched.",
525527 data = new
526528 {
527529 matched = matched . Count ,
@@ -531,27 +533,22 @@ public static object SetStatePositions(JObject @params)
531533 } ;
532534 }
533535
534- private static void ApplyPositions ( AnimatorStateMachine sm , int layer , Dictionary < string , Vector2 > want , HashSet < string > matched )
536+ private static void ApplyPositions ( AnimatorStateMachine sm , Dictionary < ulong , Vector2 > want , HashSet < ulong > matched )
535537 {
536538 var children = sm . states ;
537539 for ( int i = 0 ; i < children . Length ; i ++ )
538540 {
539- string name = children [ i ] . state . name ;
540- // Prefer a layer-scoped entry; fall back to the any-layer entry.
541- string scopedKey = $ "{ layer } :{ name } ";
542- string anyKey = $ "*:{ name } ";
543- string key = want . ContainsKey ( scopedKey ) ? scopedKey
544- : want . ContainsKey ( anyKey ) ? anyKey : null ;
545- if ( key != null )
541+ ulong ? id = children [ i ] . state . GetInstanceIDLongCompat ( ) ;
542+ if ( id . HasValue && want . TryGetValue ( id . Value , out var p ) )
546543 {
547- children [ i ] . position = new Vector3 ( want [ key ] . x , want [ key ] . y , 0f ) ;
548- matched . Add ( key ) ;
544+ children [ i ] . position = new Vector3 ( p . x , p . y , 0f ) ;
545+ matched . Add ( id . Value ) ;
549546 }
550547 }
551548 sm . states = children ; // reassign so position edits persist
552549
553550 foreach ( var sub in sm . stateMachines )
554- ApplyPositions ( sub . stateMachine , layer , want , matched ) ;
551+ ApplyPositions ( sub . stateMachine , want , matched ) ;
555552 }
556553
557554 private static AnimatorController LoadController ( JObject @params )
0 commit comments