您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

567 行
23 KiB

#define ENABLED
#if ENABLED
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using Unity.Entities;
using UnityEditor.UIElements;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.Perception.GroundTruth;
using UnityEngine.PlayerLoop;
using UnityEngine.Rendering.UI;
using UnityEngine.UI;
using UnityEngine.UIElements;
using Button = UnityEngine.UIElements.Button;
namespace UnityEditor.Perception.GroundTruth
{
[CustomEditor(typeof(Labeling)), CanEditMultipleObjects]
class LabelingEditor : Editor
{
private Labeling m_Labeling;
private SerializedProperty m_SerializedLabelsArray;
private VisualElement m_Root;
private BindableElement m_OuterElement;
private ListView m_CurrentLabelsListView;
private ListView m_SuggestLabelsListView_FromName;
private ListView m_SuggestLabelsListView_FromPath;
private ScrollView m_LabelConfigsScrollView;
private Button m_AddButton;
private string m_UxmlDir = "Packages/com.unity.perception/Editor/GroundTruth/Uxml/";
private string m_UxmlPath;
private string[] m_NameSeparators = {".","-", "_"};
private string[] m_PathSeparators = {"/"};
private List<string> m_SuggestedLabelsBasedOnName = new List<string>();
private List<string> m_SuggestedLabelsBasedOnPath = new List<string>();
private List<string> m_CommonLabels = new List<string>(); //labels that are common between all selected Labeling objects (for multi editing)
public List<string> CommonLabels => m_CommonLabels;
private List<Type> m_LabelConfigTypes = new List<Type>();
private List<ScriptableObject> m_AllLabelConfigsInProject = new List<ScriptableObject>();
private void OnEnable()
{
m_LabelConfigTypes = AddToConfigWindow.FindAllSubTypes(typeof(LabelConfig<>));
var mySerializedObject = new SerializedObject(serializedObject.targetObjects[0]);
m_SerializedLabelsArray = mySerializedObject.FindProperty("labels");
m_Labeling = mySerializedObject.targetObject as Labeling;
m_UxmlPath = m_UxmlDir + "Labeling_Main.uxml";
m_Root = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(m_UxmlPath).CloneTree();
m_CurrentLabelsListView = m_Root.Q<ListView>("current-labels-listview");
m_SuggestLabelsListView_FromName = m_Root.Q<ListView>("suggested-labels-name-listview");
m_SuggestLabelsListView_FromPath = m_Root.Q<ListView>("suggested-labels-path-listview");
m_LabelConfigsScrollView = m_Root.Q<ScrollView>("label-configs-scrollview");
m_AddButton = m_Root.Q<Button>("add-label");
if (serializedObject.targetObjects.Length > 1)
{
var addedTitle = m_Root.Q<Label>("added-labels-title");
addedTitle.text = "Common Labels of Selected Items";
var suggestedOnNamePanel = m_Root.Q<VisualElement>("suggested-labels-from-name");
suggestedOnNamePanel.RemoveFromHierarchy();
}
m_AddButton.clicked += () =>
{
var labelsUnion = CreateUnionOfAllLabels();
string newLabel = FindNewLabelValue(labelsUnion);
foreach (var targetObject in targets)
{
if (targetObject is Labeling labeling)
{
var serializedLabelingObject2 = new SerializedObject(labeling);
var serializedLabelArray2 = serializedLabelingObject2.FindProperty("labels");
serializedLabelArray2.InsertArrayElementAtIndex(serializedLabelArray2.arraySize);
serializedLabelArray2.GetArrayElementAtIndex(serializedLabelArray2.arraySize-1).stringValue = newLabel;
serializedLabelingObject2.ApplyModifiedProperties();
serializedLabelingObject2.SetIsDifferentCacheDirty();
serializedObject.SetIsDifferentCacheDirty();
}
}
RefreshData();
};
}
HashSet<string> CreateUnionOfAllLabels()
{
HashSet<String> result = new HashSet<string>();
foreach (var obj in targets)
{
if (obj is Labeling labeling)
{
result.UnionWith(labeling.labels);
}
}
return result;
}
string FindNewLabelValue(HashSet<string> labels)
{
string baseLabel = "New Label";
string label = baseLabel;
int count = 1;
while (labels.Contains(label))
{
label = baseLabel + "_" + count++;
}
return label;
}
public override VisualElement CreateInspectorGUI()
{
serializedObject.Update();
var mySerializedObject = new SerializedObject(serializedObject.targetObjects[0]);
m_Labeling = serializedObject.targetObject as Labeling;
m_SerializedLabelsArray = mySerializedObject.FindProperty("labels");
RefreshCommonLabels();
RefreshSuggestedLabelLists();
RefreshLabelConfigsList();
SetupListsAndScrollers();
return m_Root;
}
void RefreshLabelConfigsList()
{
List<string> labelConfigGuids = new List<string>();
foreach (var type in m_LabelConfigTypes)
{
labelConfigGuids.AddRange(AssetDatabase.FindAssets("t:"+type.Name));
}
m_AllLabelConfigsInProject.Clear();
foreach (var configGuid in labelConfigGuids)
{
var asset = AssetDatabase.LoadAssetAtPath<ScriptableObject>(AssetDatabase.GUIDToAssetPath(configGuid));
m_AllLabelConfigsInProject.Add(asset);
}
}
public void RemoveAddedLabelsFromSuggestedLists()
{
m_SuggestedLabelsBasedOnName.RemoveAll(s => m_CommonLabels.Contains(s));
m_SuggestedLabelsBasedOnPath.RemoveAll(s => m_CommonLabels.Contains(s));
}
public void RefreshSuggestedLabelLists()
{
m_SuggestedLabelsBasedOnName.Clear();
m_SuggestedLabelsBasedOnPath.Clear();
//based on name
if (serializedObject.targetObjects.Length == 1)
{
string assetName = serializedObject.targetObject.name;
m_SuggestedLabelsBasedOnName.Add(assetName);
m_SuggestedLabelsBasedOnName.AddRange(assetName.Split(m_NameSeparators, StringSplitOptions.RemoveEmptyEntries).ToList());
}
//based on path
var prefabObject = PrefabUtility.GetCorrespondingObjectFromSource(m_Labeling.gameObject);
if (prefabObject)
{
string assetPath = AssetDatabase.GetAssetPath(prefabObject);
var stringList = assetPath.Split(m_PathSeparators, StringSplitOptions.RemoveEmptyEntries).ToList();
stringList.Reverse();
m_SuggestedLabelsBasedOnPath.AddRange(stringList);
}
foreach (var targetObject in targets)
{
if (targetObject == target)
continue; //we have already taken care of this one above
prefabObject = PrefabUtility.GetCorrespondingObjectFromSource(((Labeling)targetObject).gameObject);
if (prefabObject)
{
string assetPath = AssetDatabase.GetAssetPath(prefabObject);
var stringList = assetPath.Split(m_PathSeparators, StringSplitOptions.RemoveEmptyEntries).ToList();
m_SuggestedLabelsBasedOnPath = m_SuggestedLabelsBasedOnPath.Intersect(stringList).ToList();
}
}
RemoveAddedLabelsFromSuggestedLists();
//Debug.Log("list update, source list count is:" + m_SuggestedLabelsBasedOnPath.Count);
}
public void RefreshData()
{
serializedObject.SetIsDifferentCacheDirty();
serializedObject.Update();
var mySerializedObject = new SerializedObject(serializedObject.targetObjects[0]);
m_SerializedLabelsArray = mySerializedObject.FindProperty("labels");
RefreshCommonLabels();
RefreshSuggestedLabelLists();
SetupSuggestedLabelsListViews();
SetupCurrentLabelsListView();
}
void SetupListsAndScrollers()
{
//Labels that have already been added to the target Labeling component
SetupCurrentLabelsListView();
//Labels suggested by the system, which the user can add
SetupSuggestedLabelsListViews();
//Add labels from Label Configs present in project
SetupLabelConfigsScrollView();
}
void RefreshCommonLabels()
{
m_CommonLabels.Clear();
m_CommonLabels.AddRange(((Labeling)serializedObject.targetObjects[0]).labels);
foreach (var obj in serializedObject.targetObjects)
{
m_CommonLabels = m_CommonLabels.Intersect(((Labeling) obj).labels).ToList();
}
}
void SetupCurrentLabelsListView()
{
m_CurrentLabelsListView.itemsSource = m_CommonLabels;
var mySerializedObject = new SerializedObject(serializedObject.targetObjects[0]);
VisualElement MakeItem() =>
new AddedLabelEditor(this, m_CurrentLabelsListView, mySerializedObject, m_SerializedLabelsArray);
void BindItem(VisualElement e, int i)
{
if (e is AddedLabelEditor addedLabel)
{
addedLabel.m_IndexInList = i;
addedLabel.m_LabelTextField.value = m_CommonLabels[i];
}
}
const int itemHeight = 35;
m_CurrentLabelsListView.bindItem = BindItem;
m_CurrentLabelsListView.makeItem = MakeItem;
m_CurrentLabelsListView.itemHeight = itemHeight;
m_CurrentLabelsListView.itemsSource = m_CommonLabels;
m_CurrentLabelsListView.selectionType = SelectionType.None;
// #if UNITY_2020_1_OR_NEWER
// m_CurrentLabelsListView.selectionType = SelectionType.Single;
// m_CurrentLabelsListView.reorderable = true;
// m_CurrentLabelsListView.RegisterCallback<DragPerformEvent>(evt =>
// {
//
// });
// #endif
}
void SetupSuggestedLabelsListViews()
{
SetupSuggestedLabelsBasedOnFlatList(m_SuggestLabelsListView_FromName, m_SuggestedLabelsBasedOnName);
SetupSuggestedLabelsBasedOnFlatList(m_SuggestLabelsListView_FromPath, m_SuggestedLabelsBasedOnPath);
//SetupSuggestedBasedOnNameLabelsListView();
//SetupSuggestedBasedOnPathLabelsListView();
}
void SetupSuggestedLabelsBasedOnFlatList(ListView labelsListView, List<string> stringList)
{
labelsListView.itemsSource = stringList;
VisualElement MakeItem() => new SuggestedLabelElement(this);
void BindItem(VisualElement e, int i)
{
if (e is SuggestedLabelElement suggestedLabel)
{
suggestedLabel.m_Label.text = stringList[i];
}
}
const int itemHeight = 32;
labelsListView.bindItem = BindItem;
labelsListView.makeItem = MakeItem;
labelsListView.itemHeight = itemHeight;
labelsListView.selectionType = SelectionType.None;
}
void SetupLabelConfigsScrollView()
{
m_LabelConfigsScrollView.Clear();
foreach (var config in m_AllLabelConfigsInProject)
{
VisualElement configElement = new LabelConfigElement(this, config);
m_LabelConfigsScrollView.Add(configElement);
}
}
}
class AddedLabelEditor : VisualElement
{
private string m_UxmlDir = "Packages/com.unity.perception/Editor/GroundTruth/Uxml/";
private string m_UxmlPath;
private Button m_RemoveButton;
private Button m_AddToConfigButton;
public TextField m_LabelTextField;
public int m_IndexInList;
public AddedLabelEditor(LabelingEditor editor, ListView listView, SerializedObject serializedLabelingObject, SerializedProperty labelsArrayProperty)
{
m_UxmlPath = m_UxmlDir + "AddedLabelElement.uxml";
AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(m_UxmlPath).CloneTree(this);
m_LabelTextField = this.Q<TextField>("label-value");
m_RemoveButton = this.Q<Button>("remove-button");
m_AddToConfigButton = this.Q<Button>("add-to-config-button");
m_LabelTextField.isDelayed = true;
ScrollView tmp = listView.Q<ScrollView>();
tmp.verticalScroller.slider.RegisterCallback<MouseDownEvent>(evt =>
{
Debug.Log("mouse down");
});
listView.RegisterCallback<FocusInEvent>(evt =>
{
Debug.Log("list focused");
});
m_LabelTextField.RegisterCallback<FocusOutEvent>(evt =>
{
Debug.Log("focus out");
});
m_LabelTextField.RegisterCallback<FocusInEvent>(evt =>
{
Debug.Log("focus in");
});
m_LabelTextField.RegisterValueChangedCallback<string>((cEvent) =>
{
//Do not let the user define a duplicate label
if (editor.CommonLabels.Contains(cEvent.newValue) && editor.CommonLabels.IndexOf(cEvent.newValue) != m_IndexInList)
{
//The listview recycles child visual elements and that causes the RegisterValueChangedCallback event to be called when scrolling.
//Therefore, we need to make sure we are not in this code block just because of scrolling, but because the user is actively changing one of the labels.
//The editor.CommonLabels.IndexOf(cEvent.newValue) != m_IndexInList check is for this purpose.
Debug.LogError("A label with the string " + cEvent.newValue + " has already been added to selected objects.");
editor.RefreshData();
return;
}
bool shouldRefresh = false;
foreach (var targetObject in editor.targets)
{
if (targetObject is Labeling labeling)
{
var indexToModifyInTargetLabelList =
labeling.labels.IndexOf(editor.CommonLabels[m_IndexInList]);
var serializedLabelingObject2 = new SerializedObject(labeling);
var serializedLabelArray2 = serializedLabelingObject2.FindProperty("labels");
serializedLabelArray2.GetArrayElementAtIndex(indexToModifyInTargetLabelList).stringValue = cEvent.newValue;
shouldRefresh = shouldRefresh || serializedLabelArray2.serializedObject.hasModifiedProperties;
serializedLabelingObject2.ApplyModifiedProperties();
serializedLabelingObject2.SetIsDifferentCacheDirty();
}
}
//the value change event is called even when the listview recycles its child elements for re-use during scrolling, therefore, we should check to make sure there are modified properties, otherwise we would be doing the refresh for no reason (reduces scrolling performance)
if (shouldRefresh)
editor.RefreshData();
});
m_AddToConfigButton.clicked += () =>
{
AddToConfigWindow.ShowWindow(m_LabelTextField.value);
};
m_RemoveButton.clicked += () =>
{
List<string> m_CommonLabels = new List<string>();
m_CommonLabels.Clear();
var firstTarget = editor.targets[0] as Labeling;
if (firstTarget)
{
m_CommonLabels.AddRange(firstTarget.labels);
foreach (var obj in editor.targets)
{
m_CommonLabels = m_CommonLabels.Intersect(((Labeling) obj).labels).ToList();
}
foreach (var targetObject in editor.targets)
{
if (targetObject is Labeling labeling)
{
RemoveLabelFromLabelingSerObj(labeling, m_CommonLabels);
}
}
editor.serializedObject.SetIsDifferentCacheDirty();
editor.RefreshData();
}
};
}
void RemoveLabelFromLabelingSerObj(Labeling labeling, List<string> commonLabels)
{
Dictionary<int, int> commonsIndexToLabelsIndex = new Dictionary<int, int>();
for (int i = 0; i < labeling.labels.Count; i++)
{
string label = labeling.labels[i];
for (int j = 0; j < commonLabels.Count; j++)
{
string label2 = commonLabels[j];
if (String.Equals(label, label2) && !commonsIndexToLabelsIndex.ContainsKey(j))
{
commonsIndexToLabelsIndex.Add(j, i);
}
}
}
var serializedLabelingObject2 = new SerializedObject(labeling);
var serializedLabelArray2 = serializedLabelingObject2.FindProperty("labels");
serializedLabelArray2.DeleteArrayElementAtIndex(commonsIndexToLabelsIndex[m_IndexInList]);
serializedLabelingObject2.ApplyModifiedProperties();
serializedLabelingObject2.SetIsDifferentCacheDirty();
}
}
class SuggestedLabelElement : VisualElement
{
private string m_UxmlDir = "Packages/com.unity.perception/Editor/GroundTruth/Uxml/";
private string m_UxmlPath;
private Button m_AddButton;
public Label m_Label;
public SuggestedLabelElement(LabelingEditor editor)
{
m_UxmlPath = m_UxmlDir + "SuggestedLabelElement.uxml";
AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(m_UxmlPath).CloneTree(this);
m_Label = this.Q<Label>("label-value");
m_AddButton = this.Q<Button>("add-button");
m_AddButton.clicked += () =>
{
foreach (var targetObject in editor.serializedObject.targetObjects)
{
if (targetObject is Labeling labeling)
{
if (labeling.labels.Contains(m_Label.text))
continue; //Do not allow duplicate labels in one asset. Duplicate labels have no use and cause other operations (especially mutlt asset editing) to get messed up
var serializedLabelingObject2 = new SerializedObject(targetObject);
var serializedLabelArray2 = serializedLabelingObject2.FindProperty("labels");
serializedLabelArray2.InsertArrayElementAtIndex(serializedLabelArray2.arraySize);
serializedLabelArray2.GetArrayElementAtIndex(serializedLabelArray2.arraySize-1).stringValue = m_Label.text;
serializedLabelingObject2.ApplyModifiedProperties();
serializedLabelingObject2.SetIsDifferentCacheDirty();
editor.serializedObject.SetIsDifferentCacheDirty();
}
}
editor.RefreshData();
//editor.RefreshUi();
};
}
}
class LabelConfigElement : VisualElement
{
private string m_UxmlDir = "Packages/com.unity.perception/Editor/GroundTruth/Uxml/";
private string m_UxmlPath;
private bool m_Collapsed = true;
private ListView m_LabelsListView;
private Label m_ConfigName;
private VisualElement m_CollapseToggle;
//private Toggle m_HiddenCollapsedToggle;
public LabelConfigElement(LabelingEditor editor, ScriptableObject config)
{
m_UxmlPath = m_UxmlDir + "ConfigElementForAddingLabelsFrom.uxml";
AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(m_UxmlPath).CloneTree(this);
m_LabelsListView = this.Q<ListView>("label-config-contents-listview");
m_ConfigName = this.Q<Label>("config-name");
m_ConfigName.text = config.name;
m_CollapseToggle = this.Q<VisualElement>("collapse-toggle");
var propertyInfo = config.GetType().GetProperty(IdLabelConfig.publicLabelEntriesFieldName);
if (propertyInfo != null)
{
var objectList = (IEnumerable) propertyInfo.GetValue(config);
var labelEntryList = objectList.Cast<ILabelEntry>().ToList();
var labelList = labelEntryList.Select(entry => entry.label).ToList();
m_LabelsListView.itemsSource = labelList;
VisualElement MakeItem()
{
var element = new SuggestedLabelElement(editor);
element.AddToClassList("label_add_from_config");
return element;
}
void BindItem(VisualElement e, int i)
{
if (e is SuggestedLabelElement suggestedLabel)
{
suggestedLabel.m_Label.text = labelList[i];
}
}
const int itemHeight = 27;
m_LabelsListView.bindItem = BindItem;
m_LabelsListView.makeItem = MakeItem;
m_LabelsListView.itemHeight = itemHeight;
m_LabelsListView.selectionType = SelectionType.None;
}
m_CollapseToggle.RegisterCallback<MouseUpEvent>(evt =>
{
m_Collapsed = !m_Collapsed;
ApplyCollapseState();
});
ApplyCollapseState();
}
void ApplyCollapseState()
{
if (m_Collapsed)
{
m_CollapseToggle.AddToClassList("collapsed-toggle-state");
m_LabelsListView.AddToClassList("collapsed");
}
else
{
m_CollapseToggle.RemoveFromClassList("collapsed-toggle-state");
m_LabelsListView.RemoveFromClassList("collapsed");
}
}
}
}
#endif