@@ -45,8 +45,6 @@ public class AvatarSetupWizard : EditorWindow
4545 private bool patcherHubImportedThisSession ;
4646 private GUIStyle stepStyle ;
4747 private GUIStyle currentStepStyle ;
48- private GUIStyle fxLayerHeaderStyle ;
49- private GUIStyle fxGuardedLabelStyle ;
5048 private GUIStyle helpBoxPadding10_8 ;
5149 private GUIStyle helpBoxPadding8_6 ;
5250 private GUIStyle helpBoxPadding10_6 ;
@@ -55,6 +53,8 @@ public class AvatarSetupWizard : EditorWindow
5553 private GUIStyle boldLabel13 ;
5654 private bool fxCheckAnalyzed ;
5755 private readonly HashSet < int > fxExpandedLayers = new HashSet < int > ( ) ;
56+ private readonly HashSet < int > fxExpandedBlinkLayers = new HashSet < int > ( ) ;
57+ private bool fxShowAllBlinkLayers ;
5858
5959 private enum WizardStep
6060 {
@@ -2588,10 +2588,6 @@ private void EnsureStyles()
25882588 ? new Color ( 0.8f , 0.92f , 1f )
25892589 : new Color ( 0.1f , 0.35f , 0.7f ) ;
25902590
2591- fxLayerHeaderStyle = new GUIStyle ( EditorStyles . boldLabel ) { fontSize = 12 } ;
2592- fxGuardedLabelStyle = new GUIStyle ( EditorStyles . miniLabel ) { fontStyle = FontStyle . Italic } ;
2593- fxGuardedLabelStyle . normal . textColor = new Color ( 0.3f , 0.75f , 0.3f ) ;
2594-
25952591 helpBoxPadding10_8 = new GUIStyle ( EditorStyles . helpBox ) { padding = new RectOffset ( 10 , 10 , 8 , 8 ) } ;
25962592 helpBoxPadding8_6 = new GUIStyle ( EditorStyles . helpBox ) { padding = new RectOffset ( 8 , 8 , 6 , 6 ) } ;
25972593 helpBoxPadding10_6 = new GUIStyle ( EditorStyles . helpBox ) { padding = new RectOffset ( 10 , 10 , 6 , 6 ) } ;
@@ -2675,7 +2671,7 @@ private void DrawFXCheckStep()
26752671 {
26762672 PawlygonEditorUI . DrawSection (
26772673 "FX Gesture Check" ,
2678- "Analyze and guard gesture-based facial expression transitions in the FX controller." ,
2674+ "Analyze and guard gesture-based facial expression transitions and eye-blink layers in the FX controller." ,
26792675 ( ) =>
26802676 {
26812677 // Check VRChat SDK availability
@@ -2715,18 +2711,36 @@ private void DrawFXCheckStep()
27152711 EditorGUILayout . HelpBox ( entry . fxAnalysisResult . StatusMessage ,
27162712 entry . fxAnalysisResult . StatusMessageType ) ;
27172713 }
2718- else if ( entry . fxAnalysisResult . Layers == null ||
2719- entry . fxAnalysisResult . Layers . Count == 0 )
2720- {
2721- EditorGUILayout . HelpBox (
2722- "No gesture-based facial expression transitions found." ,
2723- MessageType . Info ) ;
2724- }
27252714 else
27262715 {
2727- DrawFXResultsForEntry ( entry ) ;
2728- EditorGUILayout . Space ( SectionSpacing ) ;
2729- DrawFXApplySectionForEntry ( entry ) ;
2716+ bool hasGestureLayers = entry . fxAnalysisResult . Layers != null &&
2717+ entry . fxAnalysisResult . Layers . Count > 0 ;
2718+ bool hasBlinkLayers = entry . fxAnalysisResult . BlinkLayers != null &&
2719+ entry . fxAnalysisResult . BlinkLayers . Count > 0 ;
2720+
2721+ if ( ! hasGestureLayers && ! hasBlinkLayers )
2722+ {
2723+ EditorGUILayout . HelpBox (
2724+ "No gesture-based facial expression transitions or blink layers found." ,
2725+ MessageType . Info ) ;
2726+ }
2727+ else
2728+ {
2729+ if ( hasGestureLayers )
2730+ {
2731+ DrawFXResultsForEntry ( entry ) ;
2732+ }
2733+
2734+ if ( hasBlinkLayers )
2735+ {
2736+ EditorGUILayout . Space ( SectionSpacing ) ;
2737+ FXGestureCheckerUI . DrawBlinkSection (
2738+ entry . fxAnalysisResult . BlinkLayers , fxExpandedBlinkLayers , ref fxShowAllBlinkLayers ) ;
2739+ }
2740+
2741+ EditorGUILayout . Space ( SectionSpacing ) ;
2742+ DrawFXApplySectionForEntry ( entry ) ;
2743+ }
27302744 }
27312745
27322746 // Navigation buttons
@@ -2789,10 +2803,27 @@ private void AnalyzeAllEntries()
27892803 if ( entry . fxAnalysisResult . FXController != null )
27902804 entry . originalFxControllerPath = AssetDatabase . GetAssetPath ( entry . fxAnalysisResult . FXController ) ;
27912805
2792- // Auto-skip entries with no actionable results
2793- if ( ! entry . fxAnalysisResult . Success ||
2794- entry . fxAnalysisResult . Layers == null ||
2795- entry . fxAnalysisResult . Layers . Count == 0 )
2806+ // Pre-expand gesture layers so their transitions are visible without a click.
2807+ if ( entry . fxAnalysisResult . Layers != null )
2808+ {
2809+ foreach ( FXGestureCheckerCore . LayerAnalysis l in entry . fxAnalysisResult . Layers )
2810+ fxExpandedLayers . Add ( l . LayerIndex ) ;
2811+ }
2812+
2813+ // Sort detected blink layers by confidence (most likely first) and pre-expand them.
2814+ if ( entry . fxAnalysisResult . BlinkLayers != null && entry . fxAnalysisResult . BlinkLayers . Count > 0 )
2815+ {
2816+ entry . fxAnalysisResult . BlinkLayers . Sort ( ( a , b ) => b . ConfidenceScore . CompareTo ( a . ConfidenceScore ) ) ;
2817+ foreach ( FXGestureCheckerCore . BlinkLayerAnalysis bl in entry . fxAnalysisResult . BlinkLayers )
2818+ fxExpandedBlinkLayers . Add ( bl . LayerIndex ) ;
2819+ }
2820+
2821+ // Auto-skip entries with no actionable results (no gesture layers and no blink layers)
2822+ bool hasGestureLayers = entry . fxAnalysisResult . Layers != null &&
2823+ entry . fxAnalysisResult . Layers . Count > 0 ;
2824+ bool hasBlinkLayers = entry . fxAnalysisResult . BlinkLayers != null &&
2825+ entry . fxAnalysisResult . BlinkLayers . Count > 0 ;
2826+ if ( ! entry . fxAnalysisResult . Success || ( ! hasGestureLayers && ! hasBlinkLayers ) )
27962827 {
27972828 entry . fxCheckComplete = true ;
27982829 }
@@ -2834,110 +2865,29 @@ private void DrawFXResultsForEntry(AvatarEntry entry)
28342865 MessageType . Info ) ;
28352866 EditorGUILayout . Space ( 4f ) ;
28362867
2837- // Per-layer foldouts
2838- for ( int i = 0 ; i < result . Layers . Count ; i ++ )
2839- {
2840- DrawFXLayerAnalysis ( result . Layers [ i ] ) ;
2841- EditorGUILayout . Space ( 4f ) ;
2842- }
2843- }
2844-
2845- private void DrawFXLayerAnalysis ( FXGestureCheckerCore . LayerAnalysis layer )
2846- {
2847- using ( new EditorGUILayout . VerticalScope ( PawlygonEditorUI . SectionStyle ) )
2848- {
2849- bool isExpanded = fxExpandedLayers . Contains ( layer . LayerIndex ) ;
2850- string gestureCount = $ "{ layer . GestureTransitions . Count } gesture transition{ ( layer . GestureTransitions . Count != 1 ? "s" : "" ) } ";
2851-
2852- using ( new EditorGUILayout . HorizontalScope ( ) )
2853- {
2854- bool newExpanded = EditorGUILayout . Foldout ( isExpanded , "" , true ) ;
2855- EditorGUILayout . LabelField ( $ "Layer: { layer . LayerName } ", fxLayerHeaderStyle ) ;
2856- GUILayout . FlexibleSpace ( ) ;
2857- EditorGUILayout . LabelField ( $ "({ gestureCount } )", EditorStyles . miniLabel , GUILayout . Width ( 150f ) ) ;
2858-
2859- if ( newExpanded != isExpanded )
2860- {
2861- if ( newExpanded ) fxExpandedLayers . Add ( layer . LayerIndex ) ;
2862- else fxExpandedLayers . Remove ( layer . LayerIndex ) ;
2863- }
2864- }
2865-
2866- if ( ! isExpanded ) return ;
2867-
2868- EditorGUI . indentLevel ++ ;
2869-
2870- EditorGUILayout . Space ( 4f ) ;
2871- if ( layer . AlreadyHasLayerGuard )
2872- {
2873- using ( new EditorGUILayout . HorizontalScope ( ) )
2874- {
2875- using ( new EditorGUI . DisabledScope ( true ) )
2876- {
2877- EditorGUILayout . ToggleLeft ( "Disable entire layer when FacialExpressionsDisabled" , true ) ;
2878- }
2879- EditorGUILayout . LabelField ( "[Applied]" , fxGuardedLabelStyle , GUILayout . Width ( 60f ) ) ;
2880- }
2881- }
2882- else
2883- {
2884- layer . SelectedForLayerDisable = EditorGUILayout . ToggleLeft (
2885- "Disable entire layer when FacialExpressionsDisabled" ,
2886- layer . SelectedForLayerDisable ) ;
2887- }
2888-
2889- EditorGUILayout . Space ( 4f ) ;
2890- PawlygonEditorUI . DrawSeparator ( ) ;
2891- EditorGUILayout . Space ( 4f ) ;
2892-
2893- foreach ( FXGestureCheckerCore . TransitionAnalysis transition in layer . GestureTransitions )
2894- {
2895- DrawFXTransitionRow ( transition ) ;
2896- }
2897-
2898- EditorGUI . indentLevel -- ;
2899- }
2900- }
2901-
2902- private void DrawFXTransitionRow ( FXGestureCheckerCore . TransitionAnalysis transition )
2903- {
2904- string gestureName = FXGestureCheckerCore . GetGestureName ( transition . GestureValue ) ;
2905- string label = $ "{ transition . SourceName } -> { transition . DestinationName } ({ transition . GestureParameter } ={ gestureName } )";
2906-
2907- using ( new EditorGUILayout . HorizontalScope ( ) )
2908- {
2909- if ( transition . HasDisabledGuard )
2910- {
2911- using ( new EditorGUI . DisabledScope ( true ) )
2912- {
2913- EditorGUILayout . ToggleLeft ( label , true ) ;
2914- }
2915- EditorGUILayout . LabelField ( "[Applied]" , fxGuardedLabelStyle , GUILayout . Width ( 60f ) ) ;
2916- }
2917- else
2918- {
2919- transition . SelectedForFix = EditorGUILayout . ToggleLeft ( label , transition . SelectedForFix ) ;
2920- }
2921- }
2868+ FXGestureCheckerUI . DrawGestureLayers ( result . Layers , fxExpandedLayers ) ;
29222869 }
29232870
29242871 private void DrawFXApplySectionForEntry ( AvatarEntry entry )
29252872 {
29262873 FXGestureCheckerCore . AnalysisResult result = entry . fxAnalysisResult ;
2927- List < FXGestureCheckerCore . LayerAnalysis > layers = result . Layers ;
2874+ List < FXGestureCheckerCore . LayerAnalysis > layers =
2875+ result . Layers ?? new List < FXGestureCheckerCore . LayerAnalysis > ( ) ;
2876+ List < FXGestureCheckerCore . BlinkLayerAnalysis > blinkLayers =
2877+ result . BlinkLayers ?? new List < FXGestureCheckerCore . BlinkLayerAnalysis > ( ) ;
29282878
2929- bool anySelected = layers . Any ( l =>
2930- l . SelectedForLayerDisable ||
2931- l . GestureTransitions . Any ( t => t . SelectedForFix ) ) ;
2879+ bool anySelected =
2880+ layers . Any ( l => l . SelectedForLayerDisable || l . GestureTransitions . Any ( t => t . SelectedForFix ) ) ||
2881+ blinkLayers . Any ( b => b . SelectedForGuard ) ;
29322882
2933- bool allGuarded = layers . All ( l =>
2934- l . AlreadyHasLayerGuard &&
2935- l . GestureTransitions . All ( t => t . HasDisabledGuard ) ) ;
2883+ bool allGuarded =
2884+ layers . All ( l => l . AlreadyHasLayerGuard && l . GestureTransitions . All ( t => t . HasDisabledGuard ) ) &&
2885+ blinkLayers . All ( b => b . AlreadyHasBlinkGuard ) ;
29362886
29372887 if ( allGuarded )
29382888 {
29392889 EditorGUILayout . HelpBox (
2940- "All gesture transitions and layers are already guarded." ,
2890+ "All gesture transitions, layers, and blink layers are already guarded." ,
29412891 MessageType . Info ) ;
29422892 entry . fxCheckComplete = true ;
29432893 return ;
@@ -2947,9 +2897,15 @@ private void DrawFXApplySectionForEntry(AvatarEntry entry)
29472897 {
29482898 GUILayout . FlexibleSpace ( ) ;
29492899 if ( GUILayout . Button ( "Select All Unguarded" , GUILayout . Height ( 26f ) , GUILayout . Width ( 160f ) ) )
2900+ {
29502901 FXGestureCheckerCore . SelectAllUnguarded ( layers ) ;
2902+ FXGestureCheckerCore . SelectAllUnguardedBlink ( blinkLayers ) ;
2903+ }
29512904 if ( GUILayout . Button ( "Deselect All" , GUILayout . Height ( 26f ) , GUILayout . Width ( 120f ) ) )
2905+ {
29522906 FXGestureCheckerCore . DeselectAll ( layers ) ;
2907+ FXGestureCheckerCore . DeselectAllBlink ( blinkLayers ) ;
2908+ }
29532909 }
29542910
29552911 EditorGUILayout . Space ( 4f ) ;
@@ -2965,7 +2921,7 @@ private void DrawFXApplySectionForEntry(AvatarEntry entry)
29652921 if ( ! anySelected )
29662922 {
29672923 EditorGUILayout . HelpBox (
2968- "Select transitions or layers to apply the FacialExpressionsDisabled guard ." ,
2924+ "Select gesture transitions, layers, or blink layers to apply their guards ." ,
29692925 MessageType . Info ) ;
29702926 }
29712927 }
@@ -2978,15 +2934,28 @@ private void ApplyFXFixesForEntry(AvatarEntry entry)
29782934 // Capture user selections before re-analysis resets them
29792935 var selectedTransitionKeys = new HashSet < string > ( ) ;
29802936 var selectedLayerIndices = new HashSet < int > ( ) ;
2981- foreach ( FXGestureCheckerCore . LayerAnalysis layer in result . Layers )
2937+ if ( result . Layers != null )
2938+ {
2939+ foreach ( FXGestureCheckerCore . LayerAnalysis layer in result . Layers )
2940+ {
2941+ if ( layer . SelectedForLayerDisable )
2942+ selectedLayerIndices . Add ( layer . LayerIndex ) ;
2943+ foreach ( FXGestureCheckerCore . TransitionAnalysis t in layer . GestureTransitions )
2944+ {
2945+ if ( t . SelectedForFix )
2946+ selectedTransitionKeys . Add (
2947+ $ "{ layer . LayerIndex } :{ t . SourceName } ->{ t . DestinationName } :{ t . GestureParameter } ") ;
2948+ }
2949+ }
2950+ }
2951+
2952+ var selectedBlinkIndices = new HashSet < int > ( ) ;
2953+ if ( result . BlinkLayers != null )
29822954 {
2983- if ( layer . SelectedForLayerDisable )
2984- selectedLayerIndices . Add ( layer . LayerIndex ) ;
2985- foreach ( FXGestureCheckerCore . TransitionAnalysis t in layer . GestureTransitions )
2955+ foreach ( FXGestureCheckerCore . BlinkLayerAnalysis bl in result . BlinkLayers )
29862956 {
2987- if ( t . SelectedForFix )
2988- selectedTransitionKeys . Add (
2989- $ "{ layer . LayerIndex } :{ t . SourceName } ->{ t . DestinationName } :{ t . GestureParameter } ") ;
2957+ if ( bl . SelectedForGuard )
2958+ selectedBlinkIndices . Add ( bl . LayerIndex ) ;
29902959 }
29912960 }
29922961
@@ -3001,29 +2970,46 @@ private void ApplyFXFixesForEntry(AvatarEntry entry)
30012970 }
30022971 entry . copiedFxControllerPath = AssetDatabase . GetAssetPath ( copy ) ;
30032972
3004- // 2. Re-analyze on the copy so TransitionRef references point to the new asset
2973+ // 2. Re-analyze on the copy so the analysis references point to the new asset
30052974 FXGestureCheckerCore . AnalysisResult copyResult = FXGestureCheckerCore . Analyze ( copy ) ;
3006- if ( ! copyResult . Success || copyResult . Layers == null )
2975+ if ( ! copyResult . Success )
30072976 {
30082977 statusMessage = "Failed to analyze copied FX controller." ;
30092978 return ;
30102979 }
30112980
3012- // Restore user selections on re-analyzed layers
3013- foreach ( FXGestureCheckerCore . LayerAnalysis layer in copyResult . Layers )
2981+ // Restore user selections on re-analyzed gesture layers
2982+ if ( copyResult . Layers != null )
30142983 {
3015- if ( selectedLayerIndices . Contains ( layer . LayerIndex ) )
3016- layer . SelectedForLayerDisable = true ;
3017- foreach ( FXGestureCheckerCore . TransitionAnalysis t in layer . GestureTransitions )
2984+ foreach ( FXGestureCheckerCore . LayerAnalysis layer in copyResult . Layers )
30182985 {
3019- string key = $ "{ layer . LayerIndex } :{ t . SourceName } ->{ t . DestinationName } :{ t . GestureParameter } ";
3020- if ( selectedTransitionKeys . Contains ( key ) )
3021- t . SelectedForFix = true ;
2986+ if ( selectedLayerIndices . Contains ( layer . LayerIndex ) )
2987+ layer . SelectedForLayerDisable = true ;
2988+ foreach ( FXGestureCheckerCore . TransitionAnalysis t in layer . GestureTransitions )
2989+ {
2990+ string key = $ "{ layer . LayerIndex } :{ t . SourceName } ->{ t . DestinationName } :{ t . GestureParameter } ";
2991+ if ( selectedTransitionKeys . Contains ( key ) )
2992+ t . SelectedForFix = true ;
2993+ }
30222994 }
30232995 }
30242996
3025- // 3. Apply fixes on the copy
3026- var ( tFixes , lFixes ) = FXGestureCheckerCore . ApplySelectedFixes ( copy , copyResult . Layers ) ;
2997+ // Restore user selections on re-analyzed blink layers
2998+ if ( copyResult . BlinkLayers != null )
2999+ {
3000+ foreach ( FXGestureCheckerCore . BlinkLayerAnalysis bl in copyResult . BlinkLayers )
3001+ {
3002+ if ( selectedBlinkIndices . Contains ( bl . LayerIndex ) )
3003+ bl . SelectedForGuard = true ;
3004+ }
3005+ }
3006+
3007+ // 3. Apply gesture and blink guards on the copy
3008+ var ( tFixes , lFixes ) = FXGestureCheckerCore . ApplySelectedFixes (
3009+ copy , copyResult . Layers ?? new List < FXGestureCheckerCore . LayerAnalysis > ( ) ) ;
3010+ int bFixes = copyResult . BlinkLayers != null
3011+ ? FXGestureCheckerCore . ApplySelectedBlinkGuards ( copy , copyResult . BlinkLayers )
3012+ : 0 ;
30273013
30283014 // 4. Assign copy to descriptor — must reopen prefab to get fresh descriptor
30293015 GameObject prefabRoot = null ;
@@ -3062,7 +3048,7 @@ private void ApplyFXFixesForEntry(AvatarEntry entry)
30623048 }
30633049
30643050 entry . fxCheckComplete = true ;
3065- statusMessage = $ "Applied { tFixes } transition guard(s) and { lFixes } layer guard(s).";
3051+ statusMessage = $ "Applied { tFixes } transition guard(s), { lFixes } layer guard(s), and { bFixes } blink guard(s).";
30663052
30673053 // Propagate to other entries sharing the same original FX controller
30683054 PropagateSharedFXFixes ( entry , copy ) ;
@@ -3139,6 +3125,8 @@ private void ResetWizard()
31393125 importLoadAttempts = 0 ;
31403126 fxCheckAnalyzed = false ;
31413127 fxExpandedLayers . Clear ( ) ;
3128+ fxExpandedBlinkLayers . Clear ( ) ;
3129+ fxShowAllBlinkLayers = false ;
31423130 currentStep = WizardStep . Setup ;
31433131 }
31443132 }
0 commit comments