Streamingle_URP/Assets/External/StreamingleFacial/Editor/BlendShapeIntensityOverrideDrawer.cs
user 41270a34f5 Refactor: 전체 에디터 UXML 전환 + 대시보드/런타임 UI + 한글화 + NanumGothic 폰트
- 모든 컨트롤러 에디터를 IMGUI → UI Toolkit(UXML/USS)으로 전환
  (Camera, Item, Event, Avatar, System, StreamDeck, OptiTrack, Facial)
- StreamingleCommon.uss 공통 테마 + 개별 에디터 USS 스타일시트
- SystemController 서브매니저 분리 (OptiTrack, Facial, Recording, Screenshot 등)
- 런타임 컨트롤 패널 (ESC 토글, 좌측 오버레이, 150% 스케일)
- 웹 대시보드 서버 (StreamingleDashboardServer) + 리타게팅 통합
- 설정 도구(StreamingleControllerSetupTool) UXML 재작성 + 원클릭 설정
- SimplePoseTransfer UXML 에디터 추가
- 전체 UXML 한글화 + NanumGothic 폰트 적용
- Streamingle.Debug → Streamingle.Debugging 네임스페이스 변경 (Debug.Log 충돌 해결)
- 불필요 코드 제거 (rawkey.cs, RetargetingHTTPServer, OptitrackSkeletonAnimator 등)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 02:51:43 +09:00

165 lines
6.2 KiB
C#

using UnityEngine;
using UnityEditor;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using System.Collections.Generic;
[CustomPropertyDrawer(typeof(StreamingleFacialReceiver.BlendShapeIntensityOverride))]
public class BlendShapeIntensityOverrideDrawer : PropertyDrawer
{
private static readonly string[] ARKitBlendShapeNames = new string[]
{
// Eye
"EyeBlinkLeft", "EyeBlinkRight",
"EyeLookDownLeft", "EyeLookDownRight",
"EyeLookInLeft", "EyeLookInRight",
"EyeLookOutLeft", "EyeLookOutRight",
"EyeLookUpLeft", "EyeLookUpRight",
"EyeSquintLeft", "EyeSquintRight",
"EyeWideLeft", "EyeWideRight",
// Jaw
"JawForward", "JawLeft", "JawRight", "JawOpen",
// Mouth
"MouthClose", "MouthFunnel", "MouthPucker",
"MouthLeft", "MouthRight",
"MouthSmileLeft", "MouthSmileRight",
"MouthFrownLeft", "MouthFrownRight",
"MouthDimpleLeft", "MouthDimpleRight",
"MouthStretchLeft", "MouthStretchRight",
"MouthRollLower", "MouthRollUpper",
"MouthShrugLower", "MouthShrugUpper",
"MouthPressLeft", "MouthPressRight",
"MouthLowerDownLeft", "MouthLowerDownRight",
"MouthUpperUpLeft", "MouthUpperUpRight",
// Brow
"BrowDownLeft", "BrowDownRight",
"BrowInnerUp",
"BrowOuterUpLeft", "BrowOuterUpRight",
// Cheek/Nose
"CheekPuff", "CheekSquintLeft", "CheekSquintRight",
"NoseSneerLeft", "NoseSneerRight",
// Tongue
"TongueOut",
};
private static readonly List<string> DisplayNames;
private static readonly Dictionary<string, int> NameToIndex;
static BlendShapeIntensityOverrideDrawer()
{
NameToIndex = new Dictionary<string, int>(System.StringComparer.OrdinalIgnoreCase);
DisplayNames = new List<string>(ARKitBlendShapeNames.Length);
for (int i = 0; i < ARKitBlendShapeNames.Length; i++)
{
string name = ARKitBlendShapeNames[i];
string category;
if (name.StartsWith("Eye")) category = "Eye";
else if (name.StartsWith("Jaw")) category = "Jaw";
else if (name.StartsWith("Mouth")) category = "Mouth";
else if (name.StartsWith("Brow")) category = "Brow";
else if (name.StartsWith("Cheek") || name.StartsWith("Nose")) category = "Cheek-Nose";
else if (name.StartsWith("Tongue")) category = "Tongue";
else category = "";
DisplayNames.Add(category + "/" + name);
NameToIndex[name] = i;
}
}
private const string UssPath = "Assets/External/StreamingleFacial/Editor/UXML/StreamingleFacialReceiverEditor.uss";
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var row = new VisualElement();
row.AddToClassList("blendshape-override-row");
var uss = AssetDatabase.LoadAssetAtPath<StyleSheet>(UssPath);
if (uss != null) row.styleSheets.Add(uss);
var nameProp = property.FindPropertyRelative("blendShapeName");
var intensityProp = property.FindPropertyRelative("intensity");
// Default value for new entries
if (intensityProp.floatValue == 0f && string.IsNullOrEmpty(nameProp.stringValue))
{
intensityProp.floatValue = 1.0f;
nameProp.stringValue = ARKitBlendShapeNames[0];
property.serializedObject.ApplyModifiedPropertiesWithoutUndo();
}
// Dropdown for ARKit blendshape selection
int currentIndex = 0;
if (!string.IsNullOrEmpty(nameProp.stringValue) && NameToIndex.TryGetValue(nameProp.stringValue, out int idx))
currentIndex = idx;
var dropdown = new PopupField<string>(DisplayNames, currentIndex);
dropdown.AddToClassList("blendshape-override-dropdown");
dropdown.RegisterValueChangedCallback(evt =>
{
int newIdx = DisplayNames.IndexOf(evt.newValue);
if (newIdx >= 0 && newIdx < ARKitBlendShapeNames.Length)
{
nameProp.stringValue = ARKitBlendShapeNames[newIdx];
nameProp.serializedObject.ApplyModifiedProperties();
}
});
row.Add(dropdown);
// Slider for intensity
var slider = new Slider(0f, 3f);
slider.value = intensityProp.floatValue;
slider.AddToClassList("blendshape-override-slider");
row.Add(slider);
// Value label with color coding
var valueLabel = new Label($"x{intensityProp.floatValue:F1}");
valueLabel.AddToClassList("blendshape-override-value");
UpdateValueLabelStyle(valueLabel, intensityProp.floatValue);
row.Add(valueLabel);
// Bind slider <-> property
slider.RegisterValueChangedCallback(evt =>
{
intensityProp.floatValue = evt.newValue;
intensityProp.serializedObject.ApplyModifiedProperties();
valueLabel.text = $"x{evt.newValue:F1}";
UpdateValueLabelStyle(valueLabel, evt.newValue);
});
// Track external changes to intensity
row.TrackPropertyValue(intensityProp, prop =>
{
slider.SetValueWithoutNotify(prop.floatValue);
valueLabel.text = $"x{prop.floatValue:F1}";
UpdateValueLabelStyle(valueLabel, prop.floatValue);
});
// Track external changes to blendShapeName
row.TrackPropertyValue(nameProp, prop =>
{
if (!string.IsNullOrEmpty(prop.stringValue) && NameToIndex.TryGetValue(prop.stringValue, out int newIdx))
{
dropdown.SetValueWithoutNotify(DisplayNames[newIdx]);
}
});
return row;
}
private void UpdateValueLabelStyle(Label label, float value)
{
label.RemoveFromClassList("blendshape-value--low");
label.RemoveFromClassList("blendshape-value--normal");
label.RemoveFromClassList("blendshape-value--high");
if (value < 0.5f)
label.AddToClassList("blendshape-value--low");
else if (value > 1.5f)
label.AddToClassList("blendshape-value--high");
else
label.AddToClassList("blendshape-value--normal");
}
}