@@ -352,12 +352,61 @@ public static object EditBlendTree(JObject @params)
352352 if ( blendTree == null ) return error ;
353353
354354 var edits = @params [ "children" ] as JArray ;
355- if ( edits == null || edits . Count == 0 )
356- return new { success = false , message = "'children' array is required: [{index, position:[x,y], threshold?, timeScale?, cycleOffset?, mirror?}]" } ;
355+
356+ Undo . RecordObject ( blendTree , "Edit Blend Tree" ) ;
357+
358+ // Tree-level properties (optional). Lets a blend tree be retyped/reparameterized
359+ // in place — needed to copy a source tree's type+params onto an existing state.
360+ int treeProps = 0 ;
361+ if ( @params [ "blendType" ] != null &&
362+ Enum . TryParse < BlendTreeType > ( @params [ "blendType" ] . ToString ( ) , true , out var bt ) )
363+ { blendTree . blendType = bt ; treeProps ++ ; }
364+ if ( @params [ "blendParameter" ] != null )
365+ { blendTree . blendParameter = @params [ "blendParameter" ] . ToString ( ) ; treeProps ++ ; }
366+ if ( @params [ "blendParameterY" ] != null )
367+ { blendTree . blendParameterY = @params [ "blendParameterY" ] . ToString ( ) ; treeProps ++ ; }
368+ if ( @params [ "useAutomaticThresholds" ] != null )
369+ { blendTree . useAutomaticThresholds = @params [ "useAutomaticThresholds" ] . ToObject < bool > ( ) ; treeProps ++ ; }
370+
371+ // Optional: REPLACE the entire clip-child list (clears then re-adds). Lets a tree be
372+ // restructured in one call — needed when extracting children into a nested sub-tree,
373+ // since there is no per-child remove. Each item: {clipInstanceId|clipPath, position?:[x,y], threshold?}
374+ int replaced = - 1 ;
375+ if ( @params [ "setChildren" ] is JArray setKids )
376+ {
377+ var fresh = new System . Collections . Generic . List < ChildMotion > ( ) ;
378+ foreach ( var kt in setKids )
379+ {
380+ if ( ! ( kt is JObject kid ) ) continue ;
381+ AnimationClip clip = null ;
382+ string cInst = kid [ "clipInstanceId" ] ? . ToString ( ) ;
383+ string cPath = kid [ "clipPath" ] ? . ToString ( ) ;
384+ if ( ! string . IsNullOrEmpty ( cInst ) )
385+ clip = UnityObjectIdCompat . InstanceIDFromString ( cInst ) as AnimationClip ;
386+ else if ( ! string . IsNullOrEmpty ( cPath ) )
387+ clip = AssetDatabase . LoadAssetAtPath < AnimationClip > ( AssetPathUtility . SanitizeAssetPath ( cPath ) ) ;
388+ if ( clip == null )
389+ return new { success = false , message = $ "setChildren clip not resolved (instanceId '{ cInst } ', path '{ cPath } ')" } ;
390+
391+ var cm = new ChildMotion { motion = clip , timeScale = 1f , directBlendParameter = blendTree . blendParameter } ;
392+ if ( kid [ "position" ] is JArray kpos && kpos . Count >= 2 )
393+ cm . position = new Vector2 ( kpos [ 0 ] . ToObject < float > ( ) , kpos [ 1 ] . ToObject < float > ( ) ) ;
394+ if ( kid [ "threshold" ] != null ) cm . threshold = kid [ "threshold" ] . ToObject < float > ( ) ;
395+ if ( kid [ "timeScale" ] != null ) cm . timeScale = kid [ "timeScale" ] . ToObject < float > ( ) ;
396+ if ( kid [ "cycleOffset" ] != null ) cm . cycleOffset = kid [ "cycleOffset" ] . ToObject < float > ( ) ;
397+ if ( kid [ "mirror" ] != null ) cm . mirror = kid [ "mirror" ] . ToObject < bool > ( ) ;
398+ fresh . Add ( cm ) ;
399+ }
400+ blendTree . children = fresh . ToArray ( ) ;
401+ replaced = fresh . Count ;
402+ }
403+
404+ if ( ( edits == null || edits . Count == 0 ) && treeProps == 0 && replaced < 0 && @params [ "addChildTree" ] == null )
405+ return new { success = false , message = "Provide tree-level props, 'children' (index edits), 'setChildren' (replace all), and/or 'addChildTree' (nested)." } ;
357406
358407 // ChildMotion is a struct; must reassign the whole array.
359408 var children = blendTree . children ;
360- Undo . RecordObject ( blendTree , "Edit Blend Tree" ) ;
409+ if ( edits == null ) edits = new JArray ( ) ;
361410
362411 int applied = 0 ;
363412 foreach ( var token in edits )
@@ -385,14 +434,75 @@ public static object EditBlendTree(JObject @params)
385434 }
386435
387436 blendTree . children = children ;
437+
438+ // Optional: create a NESTED child blend tree inside this tree and populate it.
439+ // This is the only way to reach a true 3rd blend dimension (Unity trees are max 2D);
440+ // the nested tree blends on its own parameter, independent of the parent's axes.
441+ // Shape: addChildTree: { name?, position:[x,y], threshold?, blendType, blendParameter,
442+ // blendParameterY?, useAutomaticThresholds?, children:[{clipInstanceId|clipPath, threshold?, position?:[x,y]}] }
443+ object nestedInfo = null ;
444+ if ( @params [ "addChildTree" ] is JObject ct )
445+ {
446+ BlendTree child ;
447+ if ( blendTree . blendType == BlendTreeType . Simple1D )
448+ {
449+ float thr = ct [ "threshold" ] ? . ToObject < float > ( ) ?? 0f ;
450+ child = blendTree . CreateBlendTreeChild ( thr ) ;
451+ }
452+ else
453+ {
454+ if ( ! ( ct [ "position" ] is JArray cpos ) || cpos . Count < 2 )
455+ return new { success = false , message = "addChildTree.position [x,y] is required when parent is a 2D tree" } ;
456+ child = blendTree . CreateBlendTreeChild ( new Vector2 ( cpos [ 0 ] . ToObject < float > ( ) , cpos [ 1 ] . ToObject < float > ( ) ) ) ;
457+ }
458+
459+ child . name = ct [ "name" ] ? . ToString ( ) ?? "Look Blend Tree" ;
460+ child . hideFlags = HideFlags . HideInHierarchy ;
461+ if ( ct [ "blendType" ] != null && Enum . TryParse < BlendTreeType > ( ct [ "blendType" ] . ToString ( ) , true , out var cbt ) )
462+ child . blendType = cbt ;
463+ if ( ct [ "blendParameter" ] != null ) child . blendParameter = ct [ "blendParameter" ] . ToString ( ) ;
464+ if ( ct [ "blendParameterY" ] != null ) child . blendParameterY = ct [ "blendParameterY" ] . ToString ( ) ;
465+ if ( ct [ "useAutomaticThresholds" ] != null ) child . useAutomaticThresholds = ct [ "useAutomaticThresholds" ] . ToObject < bool > ( ) ;
466+
467+ int nestedAdded = 0 ;
468+ if ( ct [ "children" ] is JArray nestedKids )
469+ {
470+ foreach ( var kt in nestedKids )
471+ {
472+ if ( ! ( kt is JObject kid ) ) continue ;
473+ AnimationClip clip = null ;
474+ string cInst = kid [ "clipInstanceId" ] ? . ToString ( ) ;
475+ string cPath = kid [ "clipPath" ] ? . ToString ( ) ;
476+ if ( ! string . IsNullOrEmpty ( cInst ) )
477+ clip = UnityObjectIdCompat . InstanceIDFromString ( cInst ) as AnimationClip ;
478+ else if ( ! string . IsNullOrEmpty ( cPath ) )
479+ clip = AssetDatabase . LoadAssetAtPath < AnimationClip > ( AssetPathUtility . SanitizeAssetPath ( cPath ) ) ;
480+ if ( clip == null )
481+ return new { success = false , message = $ "addChildTree child clip not resolved (instanceId '{ cInst } ', path '{ cPath } ')" } ;
482+
483+ if ( child . blendType == BlendTreeType . Simple1D )
484+ child . AddChild ( clip , kid [ "threshold" ] ? . ToObject < float > ( ) ?? 0f ) ;
485+ else if ( kid [ "position" ] is JArray kpos && kpos . Count >= 2 )
486+ child . AddChild ( clip , new Vector2 ( kpos [ 0 ] . ToObject < float > ( ) , kpos [ 1 ] . ToObject < float > ( ) ) ) ;
487+ else
488+ child . AddChild ( clip ) ;
489+ nestedAdded ++ ;
490+ }
491+ }
492+
493+ EditorUtility . SetDirty ( child ) ;
494+ nestedInfo = new { name = child . name , blendType = child . blendType . ToString ( ) , blendParameter = child . blendParameter , childCount = child . children . Length , added = nestedAdded } ;
495+ }
496+
388497 EditorUtility . SetDirty ( blendTree ) ;
389498 AssetDatabase . SaveAssets ( ) ;
390499
391500 return new
392501 {
393502 success = true ,
394- message = $ "Applied { applied } edit(s) to blend tree '{ blendTree . name } '",
395- data = new { name = blendTree . name , edited = applied , childCount = children . Length }
503+ nestedChildTree = nestedInfo ,
504+ message = $ "Applied { treeProps } tree prop(s), { applied } child edit(s), replaced={ replaced } on blend tree '{ blendTree . name } '",
505+ data = new { name = blendTree . name , treeProps , edited = applied , replaced , childCount = blendTree . children . Length , blendType = blendTree . blendType . ToString ( ) }
396506 } ;
397507 }
398508 }
0 commit comments