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

940 行
39 KiB

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.Perception.GroundTruth;
using UnityEngine.UIElements;
using Button = UnityEngine.UIElements.Button;
using Toggle = UnityEngine.UIElements.Toggle;
namespace UnityEditor.Perception.GroundTruth
{
[CustomEditor(typeof(Labeling)), CanEditMultipleObjects]
class LabelingEditor : Editor
{
private VisualElement m_Root;
private VisualElement m_ManualLabelingContainer;
private VisualElement m_AutoLabelingContainer;
private VisualElement m_FromLabelConfigsContainer;
private VisualElement m_SuggestedLabelsContainer;
private VisualElement m_SuggestedOnNamePanel;
private VisualElement m_SuggestedOnPathPanel;
private ListView m_CurrentLabelsListView;
private ListView m_SuggestLabelsListView_FromName;
private ListView m_SuggestLabelsListView_FromPath;
private ScrollView m_LabelConfigsScrollView;
private PopupField<string> m_LabelingSchemesPopup;
private Button m_AddButton;
private Button m_AddAutoLabelToConfButton;
private Toggle m_AutoLabelingToggle;
private Label m_CurrentAutoLabel;
private Label m_CurrentAutoLabelTitle;
private Label m_AddManualLabelsTitle;
private Labeling m_Labeling;
private string m_UxmlDir = "Packages/com.unity.perception/Editor/GroundTruth/Uxml/";
private string m_UxmlPath;
private List<string> m_SuggestedLabelsBasedOnName = new List<string>();
private List<string> m_SuggestedLabelsBasedOnPath = new List<string>();
public List<string> CommonLabels { get; private set; } = new List<string>();
private List<Type> m_LabelConfigTypes;
private readonly List<ScriptableObject> m_AllLabelConfigsInProject = new List<ScriptableObject>();
private readonly List<AssetLabelingScheme> m_LabelingSchemes = new List<AssetLabelingScheme>();
/// <summary>
/// List of separator characters used for parsing asset names for auto labeling or label suggestion purposes
/// </summary>
public static readonly string[] NameSeparators = {".", "-", "_"};
/// <summary>
/// List of separator characters used for parsing asset paths for auto labeling or label suggestion purposes
/// </summary>
public static readonly string[] PathSeparators = {"/"};
private void OnEnable()
{
m_LabelConfigTypes = AddToConfigWindow.FindAllSubTypes(typeof(LabelConfig<>));
var mySerializedObject = new SerializedObject(serializedObject.targetObjects[0]);
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_SuggestedOnNamePanel = m_Root.Q<VisualElement>("suggested-labels-from-name");
m_SuggestedOnPathPanel = m_Root.Q<VisualElement>("suggested-labels-from-path");
m_AddButton = m_Root.Q<Button>("add-label");
m_CurrentAutoLabel = m_Root.Q<Label>("current-auto-label");
m_CurrentAutoLabelTitle = m_Root.Q<Label>("current-auto-label-title");
m_AutoLabelingToggle = m_Root.Q<Toggle>("auto-or-manual-toggle");
m_ManualLabelingContainer = m_Root.Q<VisualElement>("manual-labeling");
m_AutoLabelingContainer = m_Root.Q<VisualElement>("automatic-labeling");
m_FromLabelConfigsContainer = m_Root.Q<VisualElement>("from-label-configs");
m_SuggestedLabelsContainer = m_Root.Q<VisualElement>("suggested-labels");
m_AddAutoLabelToConfButton = m_Root.Q<Button>("add-auto-label-to-config");
m_AddManualLabelsTitle = m_Root.Q<Label>("add-manual-labels-title");
var dropdownParent = m_Root.Q<VisualElement>("drop-down-parent");
m_ItIsPossibleToAddMultipleAutoLabelsToConfig = false;
InitializeLabelingSchemes(dropdownParent);
AssesAutoLabelingStatus();
m_FirstItemLabelsArray = serializedObject.FindProperty(nameof(Labeling.labels));
if (serializedObject.targetObjects.Length > 1)
{
var addedTitle = m_Root.Q<Label>("added-labels-title");
addedTitle.text = "Common Labels of Selected Items";
m_SuggestedOnNamePanel.style.display = DisplayStyle.None;
m_AddAutoLabelToConfButton.text = "Add Automatic Labels of All Selected Assets to Config...";
}
else
{
m_AddAutoLabelToConfButton.text = "Add to Label Config...";
}
m_AddAutoLabelToConfButton.clicked += () =>
{
AddToConfigWindow.ShowWindow(CreateUnionOfAllLabels().ToList());
};
m_AddButton.clicked += () =>
{
var labelsUnion = CreateUnionOfAllLabels();
var newLabel = FindNewLabelValue(labelsUnion);
foreach (var targetObject in targets)
{
if (targetObject is Labeling labeling)
{
var serializedLabelingObject2 = new SerializedObject(labeling);
var serializedLabelArray2 = serializedLabelingObject2.FindProperty(nameof(Labeling.labels));
serializedLabelArray2.InsertArrayElementAtIndex(serializedLabelArray2.arraySize);
serializedLabelArray2.GetArrayElementAtIndex(serializedLabelArray2.arraySize-1).stringValue = newLabel;
serializedLabelingObject2.ApplyModifiedProperties();
serializedLabelingObject2.SetIsDifferentCacheDirty();
serializedObject.SetIsDifferentCacheDirty();
}
}
ChangesHappeningInForeground = true;
RefreshManualLabelingData();
};
m_AutoLabelingToggle.RegisterValueChangedCallback(evt =>
{
AutoLabelToggleChanged();
});
ChangesHappeningInForeground = true;
m_Root.schedule.Execute(CheckForModelChanges).Every(30);
}
private int m_PreviousLabelsArraySize = -1;
/// <summary>
/// This boolean is used to signify when changes in the model are triggered directly from the inspector UI by the user.
/// In these cases, the scheduled model checker does not need to update the UI again.
/// </summary>
public bool ChangesHappeningInForeground { get; set; }
private SerializedProperty m_FirstItemLabelsArray;
private void CheckForModelChanges()
{
if (ChangesHappeningInForeground)
{
ChangesHappeningInForeground = false;
m_PreviousLabelsArraySize = m_FirstItemLabelsArray.arraySize;
return;
}
if (m_FirstItemLabelsArray.arraySize != m_PreviousLabelsArraySize)
{
AssesAutoLabelingStatus();
RefreshManualLabelingData();
m_PreviousLabelsArraySize = m_FirstItemLabelsArray.arraySize;
}
}
private bool SerializedObjectHasValidLabelingScheme(SerializedObject serObj)
{
var schemeName = serObj.FindProperty(nameof(Labeling.autoLabelingSchemeType)).stringValue;
return IsValidLabelingSchemeName(schemeName);
}
private bool IsValidLabelingSchemeName(string schemeName)
{
return schemeName != string.Empty &&
m_LabelingSchemes.FindAll(scheme => scheme.GetType().Name == schemeName).Count > 0;
}
private bool m_ItIsPossibleToAddMultipleAutoLabelsToConfig;
private void UpdateUiAspects()
{
m_ManualLabelingContainer.SetEnabled(!m_AutoLabelingToggle.value);
m_AutoLabelingContainer.SetEnabled(m_AutoLabelingToggle.value);
m_AddManualLabelsTitle.style.display = m_AutoLabelingToggle.value ? DisplayStyle.None : DisplayStyle.Flex;
m_FromLabelConfigsContainer.style.display = m_AutoLabelingToggle.value ? DisplayStyle.None : DisplayStyle.Flex;
m_SuggestedLabelsContainer.style.display = m_AutoLabelingToggle.value ? DisplayStyle.None : DisplayStyle.Flex;
m_CurrentLabelsListView.style.minHeight = m_AutoLabelingToggle.value ? 70 : 120;
if (!m_AutoLabelingToggle.value || serializedObject.targetObjects.Length > 1 ||
!SerializedObjectHasValidLabelingScheme(new SerializedObject(serializedObject.targetObjects[0])))
{
m_CurrentAutoLabel.style.display = DisplayStyle.None;
m_AddAutoLabelToConfButton.SetEnabled(false);
}
else
{
m_CurrentAutoLabel.style.display = DisplayStyle.Flex;
m_AddAutoLabelToConfButton.SetEnabled(true);
}
if(m_AutoLabelingToggle.value && serializedObject.targetObjects.Length > 1 && m_ItIsPossibleToAddMultipleAutoLabelsToConfig)
{
m_AddAutoLabelToConfButton.SetEnabled(true);
}
if (serializedObject.targetObjects.Length == 1)
{
m_AutoLabelingToggle.text = "Use Automatic Labeling";
}
else
{
m_CurrentAutoLabelTitle.text = "Select assets individually to inspect their automatic labels.";
m_AutoLabelingToggle.text = "Use Automatic Labeling for All Selected Items";
}
}
private void UpdateCurrentAutoLabelValue(SerializedObject serObj)
{
var array = serObj.FindProperty(nameof(Labeling.labels));
if (array.arraySize > 0)
{
m_CurrentAutoLabel.text = array.GetArrayElementAtIndex(0).stringValue;
}
}
private bool AreSelectedAssetsCompatibleWithAutoLabelScheme(AssetLabelingScheme scheme)
{
foreach (var asset in serializedObject.targetObjects)
{
string label = scheme.GenerateLabel(asset);
if (label == null)
{
return false;
}
}
return true;
}
private void InitializeLabelingSchemes(VisualElement parent)
{
//this function should be called only once during the lifecycle of the editor element
AssetLabelingScheme labelingScheme = new AssetNameLabelingScheme();
if (AreSelectedAssetsCompatibleWithAutoLabelScheme(labelingScheme)) m_LabelingSchemes.Add(labelingScheme);
labelingScheme = new AssetFileNameLabelingScheme();
if (AreSelectedAssetsCompatibleWithAutoLabelScheme(labelingScheme)) m_LabelingSchemes.Add(labelingScheme);
labelingScheme = new CurrentOrParentsFolderNameLabelingScheme();
if (AreSelectedAssetsCompatibleWithAutoLabelScheme(labelingScheme)) m_LabelingSchemes.Add(labelingScheme);
var descriptions = m_LabelingSchemes.Select(scheme => scheme.Description).ToList();
descriptions.Insert(0, "<Select Scheme>");
m_LabelingSchemesPopup = new PopupField<string>(descriptions, 0) {label = "Labeling Scheme"};
m_LabelingSchemesPopup.style.marginLeft = 0;
parent.Add(m_LabelingSchemesPopup);
m_LabelingSchemesPopup.RegisterValueChangedCallback(evt => AssignAutomaticLabelToSelectedAssets());
}
private void AutoLabelToggleChanged()
{
UpdateUiAspects();
if (!m_AutoLabelingToggle.value)
{
m_ItIsPossibleToAddMultipleAutoLabelsToConfig = false;
foreach (var targetObj in serializedObject.targetObjects)
{
var serObj = new SerializedObject(targetObj);
serObj.FindProperty(nameof(Labeling.useAutoLabeling)).boolValue = false;
if (SerializedObjectHasValidLabelingScheme(serObj))
{
//asset already had a labeling scheme before auto labeling was disabled, which means it has auto label(s) attached. these should be cleared now.
serObj.FindProperty(nameof(Labeling.labels)).ClearArray();
}
serObj.FindProperty(nameof(Labeling.autoLabelingSchemeType)).stringValue = string.Empty;
m_LabelingSchemesPopup.index = 0;
serObj.ApplyModifiedProperties();
serObj.SetIsDifferentCacheDirty();
}
}
ChangesHappeningInForeground = true;
RefreshManualLabelingData();
}
private void AssignAutomaticLabelToSelectedAssets()
{
//the 0th index of this popup is "<Select Scheme>" and should not do anything
if (m_LabelingSchemesPopup.index == 0)
{
return;
}
m_ItIsPossibleToAddMultipleAutoLabelsToConfig = true;
var labelingScheme = m_LabelingSchemes[m_LabelingSchemesPopup.index - 1];
foreach (var targetObj in serializedObject.targetObjects)
{
var serObj = new SerializedObject(targetObj);
serObj.FindProperty(nameof(Labeling.useAutoLabeling)).boolValue = true; //only set this flag once the user has actually chosen a scheme, otherwise, we will not touch the flag
serObj.FindProperty(nameof(Labeling.autoLabelingSchemeType)).stringValue = labelingScheme.GetType().Name;
var serLabelsArray = serObj.FindProperty(nameof(Labeling.labels));
serLabelsArray.ClearArray();
serLabelsArray.InsertArrayElementAtIndex(0);
var label = labelingScheme.GenerateLabel(targetObj);
serLabelsArray.GetArrayElementAtIndex(0).stringValue = label;
if (targetObj == serializedObject.targetObjects[0] && serializedObject.targetObjects.Length == 1)
{
UpdateCurrentAutoLabelValue(serObj);
}
serObj.ApplyModifiedProperties();
serObj.SetIsDifferentCacheDirty();
}
UpdateUiAspects();
ChangesHappeningInForeground = true;
RefreshManualLabelingData();
}
private void AssesAutoLabelingStatus()
{
var enabledOrNot = true;
if (serializedObject.targetObjects.Length == 1)
{
var serObj = new SerializedObject(serializedObject.targetObjects[0]);
var enabled = serObj.FindProperty(nameof(Labeling.useAutoLabeling)).boolValue;
m_AutoLabelingToggle.value = enabled;
var currentLabelingSchemeName = serObj.FindProperty(nameof(Labeling.autoLabelingSchemeType)).stringValue;
if (IsValidLabelingSchemeName(currentLabelingSchemeName))
{
m_LabelingSchemesPopup.index =
m_LabelingSchemes.FindIndex(scheme => scheme.GetType().Name.ToString() == currentLabelingSchemeName) + 1;
}
UpdateCurrentAutoLabelValue(serObj);
}
else
{
string unifiedLabelingScheme = null;
var allAssetsUseSameLabelingScheme = true;
foreach (var targetObj in serializedObject.targetObjects)
{
var serObj = new SerializedObject(targetObj);
var enabled = serObj.FindProperty(nameof(Labeling.useAutoLabeling)).boolValue;
enabledOrNot &= enabled;
var schemeName = serObj.FindProperty(nameof(Labeling.autoLabelingSchemeType)).stringValue;
if (schemeName == string.Empty)
{
//if any of the selected assets does not have a labeling scheme, they can't all have the same valid scheme
allAssetsUseSameLabelingScheme = false;
}
if (allAssetsUseSameLabelingScheme)
{
if (unifiedLabelingScheme == null)
{
unifiedLabelingScheme = schemeName;
}
else if (unifiedLabelingScheme != schemeName)
{
allAssetsUseSameLabelingScheme = false;
}
}
}
m_AutoLabelingToggle.value = enabledOrNot;
if (allAssetsUseSameLabelingScheme)
{
//all selected assets have the same scheme recorded in their serialized objects
m_LabelingSchemesPopup.index =
m_LabelingSchemes.FindIndex(scheme => scheme.GetType().Name.ToString() == unifiedLabelingScheme) + 1;
m_ItIsPossibleToAddMultipleAutoLabelsToConfig = enabledOrNot;
//if all selected assets have the same scheme recorded in their serialized objects, and they all
//have auto labeling enabled, we can now add all auto labels to a config
}
else
{
//the selected DO NOT have the same scheme recorded in their serialized objects
m_LabelingSchemesPopup.index = 0;
}
}
UpdateUiAspects();
}
private 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;
}
private 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();
m_Labeling = serializedObject.targetObject as Labeling;
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 => CommonLabels.Contains(s));
m_SuggestedLabelsBasedOnPath.RemoveAll(s => 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;
var pieces = assetName.Split(NameSeparators, StringSplitOptions.RemoveEmptyEntries).ToList();
if (pieces.Count > 1)
{
//means the asset name was actually split
m_SuggestedLabelsBasedOnName.Add(assetName);
}
m_SuggestedLabelsBasedOnName.AddRange(pieces);
}
//based on path
string assetPath = GetAssetOrPrefabPath(m_Labeling.gameObject);
//var prefabObject = PrefabUtility.GetCorrespondingObjectFromSource(m_Labeling.gameObject);
if (assetPath != null)
{
var stringList = assetPath.Split(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
assetPath = GetAssetOrPrefabPath(((Labeling)targetObject).gameObject);
//prefabObject = PrefabUtility.GetCorrespondingObjectFromSource(((Labeling)targetObject).gameObject);
if (assetPath != null)
{
var stringList = assetPath.Split(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 RefreshManualLabelingData()
{
serializedObject.SetIsDifferentCacheDirty();
serializedObject.Update();
RefreshCommonLabels();
RefreshSuggestedLabelLists();
SetupSuggestedLabelsListViews();
SetupCurrentLabelsListView();
UpdateSuggestedPanelVisibility();
}
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();
UpdateSuggestedPanelVisibility();
}
void UpdateSuggestedPanelVisibility()
{
m_SuggestedOnNamePanel.style.display = m_SuggestedLabelsBasedOnName.Count == 0 ? DisplayStyle.None : DisplayStyle.Flex;
m_SuggestedOnPathPanel.style.display = m_SuggestedLabelsBasedOnPath.Count == 0 ? DisplayStyle.None : DisplayStyle.Flex;
if (m_SuggestedLabelsBasedOnPath.Count == 0 && m_SuggestedLabelsBasedOnName.Count == 0)
{
m_SuggestedLabelsContainer.style.display = DisplayStyle.None;
}
}
void RefreshCommonLabels()
{
CommonLabels.Clear();
CommonLabels.AddRange(((Labeling)serializedObject.targetObjects[0]).labels);
foreach (var obj in serializedObject.targetObjects)
{
CommonLabels = CommonLabels.Intersect(((Labeling) obj).labels).ToList();
}
}
void SetupCurrentLabelsListView()
{
m_CurrentLabelsListView.itemsSource = CommonLabels;
VisualElement MakeItem() =>
new AddedLabelEditor(this, m_CurrentLabelsListView);
void BindItem(VisualElement e, int i)
{
if (e is AddedLabelEditor addedLabel)
{
addedLabel.m_IndexInList = i;
addedLabel.m_LabelTextField.value = CommonLabels[i];
}
}
const int itemHeight = 35;
m_CurrentLabelsListView.bindItem = BindItem;
m_CurrentLabelsListView.makeItem = MakeItem;
m_CurrentLabelsListView.itemHeight = itemHeight;
m_CurrentLabelsListView.itemsSource = CommonLabels;
m_CurrentLabelsListView.selectionType = SelectionType.None;
}
void SetupSuggestedLabelsListViews()
{
SetupSuggestedLabelsBasedOnFlatList(m_SuggestLabelsListView_FromName, m_SuggestedLabelsBasedOnName);
SetupSuggestedLabelsBasedOnFlatList(m_SuggestLabelsListView_FromPath, m_SuggestedLabelsBasedOnPath);
}
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);
}
}
/// <summary>
/// Get the path of the given asset in the project, or get the path of the given Scene GameObject's source prefab if any
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static string GetAssetOrPrefabPath(UnityEngine.Object obj)
{
string assetPath = AssetDatabase.GetAssetPath(obj);
if (assetPath == string.Empty)
{
//this indicates that gObj is a scene object and not a prefab directly selected from the Project tab
var prefabObject = PrefabUtility.GetCorrespondingObjectFromSource(obj);
if (prefabObject)
{
assetPath = AssetDatabase.GetAssetPath(prefabObject);
}
}
return assetPath;
}
}
internal class AddedLabelEditor : VisualElement
{
private string m_UxmlDir = "Packages/com.unity.perception/Editor/GroundTruth/Uxml/";
public TextField m_LabelTextField;
public int m_IndexInList;
public AddedLabelEditor(LabelingEditor editor, ListView listView)
{
var uxmlPath = m_UxmlDir + "AddedLabelElement.uxml";
AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(uxmlPath).CloneTree(this);
m_LabelTextField = this.Q<TextField>("label-value");
var removeButton = this.Q<Button>("remove-button");
var addToConfigButton = this.Q<Button>("add-to-config-button");
m_LabelTextField.isDelayed = true;
m_LabelTextField.RegisterValueChangedCallback((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.ChangesHappeningInForeground = true;
editor.RefreshManualLabelingData();
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(nameof(Labeling.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.ChangesHappeningInForeground = true;
editor.RefreshManualLabelingData();
}
});
addToConfigButton.clicked += () =>
{
AddToConfigWindow.ShowWindow(m_LabelTextField.value);
};
removeButton.clicked += () =>
{
List<string> commonLabels = new List<string>();
commonLabels.Clear();
var firstTarget = editor.targets[0] as Labeling;
if (firstTarget != null)
{
commonLabels.AddRange(firstTarget.labels);
foreach (var obj in editor.targets)
{
commonLabels = commonLabels.Intersect(((Labeling) obj).labels).ToList();
}
foreach (var targetObject in editor.targets)
{
if (targetObject is Labeling labeling)
{
RemoveLabelFromLabelingSerObj(labeling, commonLabels);
}
}
editor.serializedObject.SetIsDifferentCacheDirty();
editor.RefreshManualLabelingData();
}
};
}
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();
}
}
internal class SuggestedLabelElement : VisualElement
{
private string m_UxmlDir = "Packages/com.unity.perception/Editor/GroundTruth/Uxml/";
public Label m_Label;
public SuggestedLabelElement(LabelingEditor editor)
{
var uxmlPath = m_UxmlDir + "SuggestedLabelElement.uxml";
AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(uxmlPath).CloneTree(this);
m_Label = this.Q<Label>("label-value");
var addButton = this.Q<Button>("add-button");
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.ChangesHappeningInForeground = true;
editor.RefreshManualLabelingData();
};
}
}
internal class LabelConfigElement : VisualElement
{
private string m_UxmlDir = "Packages/com.unity.perception/Editor/GroundTruth/Uxml/";
private bool m_Collapsed = true;
private ListView m_LabelsListView;
private VisualElement m_CollapseToggle;
public LabelConfigElement(LabelingEditor editor, ScriptableObject config)
{
var uxmlPath = m_UxmlDir + "ConfigElementForAddingLabelsFrom.uxml";
AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(uxmlPath).CloneTree(this);
m_LabelsListView = this.Q<ListView>("label-config-contents-listview");
var openButton = this.Q<Button>("open-config-button");
var configName = this.Q<Label>("config-name");
configName.text = config.name;
m_CollapseToggle = this.Q<VisualElement>("collapse-toggle");
openButton.clicked += () =>
{
Selection.SetActiveObjectWithContext(config, null);
};
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");
}
}
}
/// <summary>
/// A labeling scheme based on which an automatic label can be produced for a given asset. E.g. based on asset name, asset path, etc.
/// </summary>
internal abstract class AssetLabelingScheme
{
/// <summary>
/// The description of how this scheme generates labels. Used in the dropdown menu in the UI.
/// </summary>
public abstract string Description { get; }
/// <summary>
/// Generate a label for the given asset
/// </summary>
/// <param name="asset"></param>
/// <returns></returns>
public abstract string GenerateLabel(UnityEngine.Object asset);
}
/// <summary>
/// Asset labeling scheme that outputs the given asset's name as its automatic label
/// </summary>
internal class AssetNameLabelingScheme : AssetLabelingScheme
{
///<inheritdoc/>
public override string Description => "Use asset name";
///<inheritdoc/>
public override string GenerateLabel(UnityEngine.Object asset)
{
return asset.name;
}
}
/// <summary>
/// Asset labeling scheme that outputs the given asset's file name, including extension, as its automatic label
/// </summary>
internal class AssetFileNameLabelingScheme : AssetLabelingScheme
{
///<inheritdoc/>
public override string Description => "Use file name with extension";
///<inheritdoc/>
public override string GenerateLabel(UnityEngine.Object asset)
{
string assetPath = LabelingEditor.GetAssetOrPrefabPath(asset);
var stringList = assetPath.Split(LabelingEditor.PathSeparators, StringSplitOptions.RemoveEmptyEntries)
.ToList();
return stringList.Count > 0 ? stringList.Last() : null;
}
}
/// <summary>
/// Asset labeling scheme that outputs the given asset's folder name as its automatic label
/// </summary>
internal class CurrentOrParentsFolderNameLabelingScheme : AssetLabelingScheme
{
///<inheritdoc/>
public override string Description => "Use the asset's folder name";
///<inheritdoc/>
public override string GenerateLabel(UnityEngine.Object asset)
{
string assetPath = LabelingEditor.GetAssetOrPrefabPath(asset);
var stringList = assetPath.Split(LabelingEditor.PathSeparators, StringSplitOptions.RemoveEmptyEntries)
.ToList();
return stringList.Count > 1 ? stringList[stringList.Count-2] : null;
}
}
}