.vs
Assets
Library
APIUpdater
Artifacts
Bee
BuildPlayerData
PackageCache
com.unity.collab-proxy@2.5.2
com.unity.editorcoroutines@1.0.0
com.unity.ext.nunit@1.0.6
com.unity.feature.development@1.0.1
com.unity.ide.rider@3.0.31
com.unity.ide.visualstudio@2.0.22
com.unity.ide.vscode@1.2.5
com.unity.modules.ai@1.0.0
com.unity.modules.androidjni@1.0.0
com.unity.modules.animation@1.0.0
com.unity.modules.assetbundle@1.0.0
com.unity.modules.audio@1.0.0
com.unity.modules.cloth@1.0.0
com.unity.modules.director@1.0.0
com.unity.modules.imageconversion@1.0.0
com.unity.modules.imgui@1.0.0
com.unity.modules.jsonserialize@1.0.0
com.unity.modules.particlesystem@1.0.0
com.unity.modules.physics2d@1.0.0
com.unity.modules.physics@1.0.0
com.unity.modules.screencapture@1.0.0
com.unity.modules.subsystems@1.0.0
com.unity.modules.terrain@1.0.0
com.unity.modules.terrainphysics@1.0.0
com.unity.modules.tilemap@1.0.0
com.unity.modules.ui@1.0.0
com.unity.modules.uielements@1.0.0
com.unity.modules.umbra@1.0.0
com.unity.modules.unityanalytics@1.0.0
com.unity.modules.unitywebrequest@1.0.0
com.unity.modules.unitywebrequestassetbundle@1.0.0
com.unity.modules.unitywebrequestaudio@1.0.0
com.unity.modules.unitywebrequesttexture@1.0.0
com.unity.modules.unitywebrequestwww@1.0.0
com.unity.modules.vehicles@1.0.0
com.unity.modules.video@1.0.0
com.unity.modules.vr@1.0.0
com.unity.modules.wind@1.0.0
com.unity.modules.xr@1.0.0
com.unity.performance.profile-analyzer@1.2.2
com.unity.settings-manager@2.0.1
com.unity.test-framework@1.1.33
com.unity.testtools.codecoverage@1.2.6
com.unity.textmeshpro@3.0.6
com.unity.timeline@1.7.6
DocCodeExamples
Documentation~
Editor
Actions
Activation
Analytics
Animation
AnimationClipActions.cs
AnimationClipActions.cs.meta
AnimationClipCurveCache.cs
AnimationClipCurveCache.cs.meta
AnimationClipExtensions.cs
AnimationClipExtensions.cs.meta
AnimationOffsetMenu.cs
AnimationOffsetMenu.cs.meta
AnimationPlayableAssetEditor.cs
AnimationPlayableAssetEditor.cs.meta
AnimationTrackActions.cs
AnimationTrackActions.cs.meta
BindingSelector.cs
BindingSelector.cs.meta
BindingTreeViewDataSource.cs
BindingTreeViewDataSource.cs.meta
BindingTreeViewDataSourceGUI.cs
BindingTreeViewDataSourceGUI.cs.meta
ClipCurveEditor.cs
ClipCurveEditor.cs.meta
CurveDataSource.cs
CurveDataSource.cs.meta
CurveTreeViewNode.cs
CurveTreeViewNode.cs.meta
CurvesProxy.cs
CurvesProxy.cs.meta
TimelineAnimationUtilities.cs
TimelineAnimationUtilities.cs.meta
Attributes
Audio
ControlTrack
CustomEditors
Extensions
Items
Localization
Manipulators
Playables
Properties
Recording
Signals
State
StyleSheets
Undo
Utilities
Window
inspectors
treeview
Actions.meta
Activation.meta
Analytics.meta
Animation.meta
Attributes.meta
Audio.meta
ControlTrack.meta
CurveEditUtility.cs
CurveEditUtility.cs.meta
CustomEditors.meta
DirectorNamedColor.cs
DirectorNamedColor.cs.meta
DirectorStyles.cs
DirectorStyles.cs.meta
Extensions.meta
Items.meta
Localization.meta
Manipulators.meta
MenuPriority.cs
MenuPriority.cs.meta
Playables.meta
Properties.meta
Recording.meta
Shortcuts.cs
Shortcuts.cs.meta
Signals.meta
State.meta
StyleSheets.meta
TimelineEditor.cs
TimelineEditor.cs.meta
TimelineHelpers.cs
TimelineHelpers.cs.meta
TimelineSelection.cs
TimelineSelection.cs.meta
TimelineUtility.cs
TimelineUtility.cs.meta
Tooltip.cs
Tooltip.cs.meta
Trackhead.cs
Trackhead.cs.meta
Undo.meta
Unity.Timeline.Editor.asmdef
Unity.Timeline.Editor.asmdef.meta
UnityEditorInternals.cs
UnityEditorInternals.cs.meta
Utilities.meta
Window.meta
inspectors.meta
treeview.meta
Runtime
Samples~
.signature
CHANGELOG.md
CHANGELOG.md.meta
DocCodeExamples.meta
Editor.meta
LICENSE.md
LICENSE.md.meta
README.md
README.md.meta
Runtime.meta
ValidationExceptions.json
ValidationExceptions.json.meta
package.json
package.json.meta
com.unity.ugui@1.0.0
com.unity.visualscripting@1.9.4
PackageManager
PlayModeViewStates
PlayerDataCache
ScriptAssemblies
Search
ShaderCache
SplashScreenCache
StateCache
UIElements
AnnotationManager
ArtifactDB
ArtifactDB-lock
BuildPlayer.prefs
BuildSettings.asset
EditorOnlyScriptingSettings.json
EditorOnlyVirtualTextureState.json
EditorSnapSettings.asset
EditorUserBuildSettings.asset
InspectorExpandedItems.asset
LastBuild.buildreport
LastSceneManagerSetup.txt
LibraryFormatVersion.txt
MonoManager.asset
SceneVisibilityState.asset
ScriptMapper
ShaderCache.db
SourceAssetDB
SourceAssetDB-lock
SpriteAtlasDatabase.asset
Style.catalog
expandedItems
ilpp.pid
Logs
Packages
ProjectSettings
UserSettings
obj
.vsconfig
Assembly-CSharp.Player.csproj
Assembly-CSharp.csproj
TM1.sln
Unity.CollabProxy.Editor.csproj
Unity.EditorCoroutines.Editor.csproj
Unity.Performance.Profile-Analyzer.Editor.csproj
Unity.PlasticSCM.Editor.csproj
Unity.Rider.Editor.csproj
Unity.Settings.Editor.csproj
Unity.TestTools.CodeCoverage.Editor.OpenCover.Model.csproj
Unity.TestTools.CodeCoverage.Editor.OpenCover.Mono.Reflection.csproj
Unity.TestTools.CodeCoverage.Editor.csproj
Unity.TextMeshPro.Editor.csproj
Unity.TextMeshPro.Player.csproj
Unity.TextMeshPro.csproj
Unity.Timeline.Editor.csproj
Unity.Timeline.Player.csproj
Unity.Timeline.csproj
Unity.VSCode.Editor.csproj
Unity.VisualScripting.Core.Editor.csproj
Unity.VisualScripting.Core.Player.csproj
Unity.VisualScripting.Core.csproj
Unity.VisualScripting.Flow.Editor.csproj
Unity.VisualScripting.Flow.Player.csproj
Unity.VisualScripting.Flow.csproj
Unity.VisualScripting.SettingsProvider.Editor.csproj
Unity.VisualScripting.Shared.Editor.csproj
Unity.VisualScripting.State.Editor.csproj
Unity.VisualScripting.State.Player.csproj
Unity.VisualScripting.State.csproj
Unity.VisualStudio.Editor.csproj
UnityEditor.TestRunner.csproj
UnityEditor.UI.csproj
UnityEngine.TestRunner.Player.csproj
UnityEngine.TestRunner.csproj
UnityEngine.UI.Player.csproj
UnityEngine.UI.csproj
428 lines
14 KiB
C#
428 lines
14 KiB
C#
![]() |
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
using UnityEngine;
|
||
|
using UnityEditor;
|
||
|
using UnityEditorInternal;
|
||
|
|
||
|
namespace UnityEditor.Timeline
|
||
|
{
|
||
|
struct CurveBindingPair
|
||
|
{
|
||
|
public EditorCurveBinding binding;
|
||
|
public AnimationCurve curve;
|
||
|
public ObjectReferenceKeyframe[] objectCurve;
|
||
|
}
|
||
|
|
||
|
class CurveBindingGroup
|
||
|
{
|
||
|
public CurveBindingPair[] curveBindingPairs { get; set; }
|
||
|
public Vector2 timeRange { get; set; }
|
||
|
public Vector2 valueRange { get; set; }
|
||
|
|
||
|
public bool isFloatCurve
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return curveBindingPairs != null && curveBindingPairs.Length > 0 &&
|
||
|
curveBindingPairs[0].curve != null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool isObjectCurve
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return curveBindingPairs != null && curveBindingPairs.Length > 0 &&
|
||
|
curveBindingPairs[0].objectCurve != null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int count
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (curveBindingPairs == null)
|
||
|
return 0;
|
||
|
return curveBindingPairs.Length;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class AnimationClipCurveInfo
|
||
|
{
|
||
|
bool m_CurveDirty = true;
|
||
|
bool m_KeysDirty = true;
|
||
|
|
||
|
public bool dirty
|
||
|
{
|
||
|
get { return m_CurveDirty; }
|
||
|
set
|
||
|
{
|
||
|
m_CurveDirty = value;
|
||
|
if (m_CurveDirty)
|
||
|
{
|
||
|
m_KeysDirty = true;
|
||
|
if (m_groupings != null)
|
||
|
m_groupings.Clear();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public AnimationCurve[] curves;
|
||
|
public EditorCurveBinding[] bindings;
|
||
|
|
||
|
public EditorCurveBinding[] objectBindings;
|
||
|
public List<ObjectReferenceKeyframe[]> objectCurves;
|
||
|
|
||
|
Dictionary<string, CurveBindingGroup> m_groupings;
|
||
|
|
||
|
// to tell whether the cache has changed
|
||
|
public int version { get; private set; }
|
||
|
|
||
|
float[] m_KeyTimes;
|
||
|
|
||
|
Dictionary<EditorCurveBinding, float[]> m_individualBindinsKey;
|
||
|
|
||
|
public float[] keyTimes
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (m_KeysDirty || m_KeyTimes == null)
|
||
|
{
|
||
|
RebuildKeyCache();
|
||
|
}
|
||
|
return m_KeyTimes;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public float[] GetCurveTimes(EditorCurveBinding curve)
|
||
|
{
|
||
|
return GetCurveTimes(new[] { curve });
|
||
|
}
|
||
|
|
||
|
public float[] GetCurveTimes(EditorCurveBinding[] curves)
|
||
|
{
|
||
|
if (m_KeysDirty || m_KeyTimes == null)
|
||
|
{
|
||
|
RebuildKeyCache();
|
||
|
}
|
||
|
|
||
|
var keyTimes = new List<float>();
|
||
|
for (int i = 0; i < curves.Length; i++)
|
||
|
{
|
||
|
var c = curves[i];
|
||
|
if (m_individualBindinsKey.ContainsKey(c))
|
||
|
{
|
||
|
keyTimes.AddRange(m_individualBindinsKey[c]);
|
||
|
}
|
||
|
}
|
||
|
return keyTimes.ToArray();
|
||
|
}
|
||
|
|
||
|
void RebuildKeyCache()
|
||
|
{
|
||
|
m_individualBindinsKey = new Dictionary<EditorCurveBinding, float[]>();
|
||
|
|
||
|
List<float> keys = curves.SelectMany(y => y.keys).Select(z => z.time).ToList();
|
||
|
for (int i = 0; i < objectCurves.Count; i++)
|
||
|
{
|
||
|
var kf = objectCurves[i];
|
||
|
keys.AddRange(kf.Select(x => x.time));
|
||
|
}
|
||
|
|
||
|
for (int b = 0; b < bindings.Count(); b++)
|
||
|
{
|
||
|
m_individualBindinsKey.Add(bindings[b], curves[b].keys.Select(k => k.time).Distinct().ToArray());
|
||
|
}
|
||
|
|
||
|
m_KeyTimes = keys.OrderBy(x => x).Distinct().ToArray();
|
||
|
m_KeysDirty = false;
|
||
|
}
|
||
|
|
||
|
public void Update(AnimationClip clip)
|
||
|
{
|
||
|
List<EditorCurveBinding> postfilter = new List<EditorCurveBinding>();
|
||
|
var clipBindings = AnimationUtility.GetCurveBindings(clip);
|
||
|
for (int i = 0; i < clipBindings.Length; i++)
|
||
|
{
|
||
|
var bind = clipBindings[i];
|
||
|
if (!bind.propertyName.Contains("LocalRotation.w"))
|
||
|
postfilter.Add(RotationCurveInterpolation.RemapAnimationBindingForRotationCurves(bind, clip));
|
||
|
}
|
||
|
bindings = postfilter.ToArray();
|
||
|
|
||
|
curves = new AnimationCurve[bindings.Length];
|
||
|
for (int i = 0; i < bindings.Length; i++)
|
||
|
{
|
||
|
curves[i] = AnimationUtility.GetEditorCurve(clip, bindings[i]);
|
||
|
}
|
||
|
|
||
|
objectBindings = AnimationUtility.GetObjectReferenceCurveBindings(clip);
|
||
|
objectCurves = new List<ObjectReferenceKeyframe[]>(objectBindings.Length);
|
||
|
for (int i = 0; i < objectBindings.Length; i++)
|
||
|
{
|
||
|
objectCurves.Add(AnimationUtility.GetObjectReferenceCurve(clip, objectBindings[i]));
|
||
|
}
|
||
|
|
||
|
m_CurveDirty = false;
|
||
|
m_KeysDirty = true;
|
||
|
|
||
|
version = version + 1;
|
||
|
}
|
||
|
|
||
|
public bool GetBindingForCurve(AnimationCurve curve, ref EditorCurveBinding binding)
|
||
|
{
|
||
|
for (int i = 0; i < curves.Length; i++)
|
||
|
{
|
||
|
if (curve == curves[i])
|
||
|
{
|
||
|
binding = bindings[i];
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public AnimationCurve GetCurveForBinding(EditorCurveBinding binding)
|
||
|
{
|
||
|
for (int i = 0; i < curves.Length; i++)
|
||
|
{
|
||
|
if (binding.Equals(bindings[i]))
|
||
|
{
|
||
|
return curves[i];
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
public ObjectReferenceKeyframe[] GetObjectCurveForBinding(EditorCurveBinding binding)
|
||
|
{
|
||
|
if (objectCurves == null)
|
||
|
return null;
|
||
|
|
||
|
for (int i = 0; i < objectCurves.Count; i++)
|
||
|
{
|
||
|
if (binding.Equals(objectBindings[i]))
|
||
|
{
|
||
|
return objectCurves[i];
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// given a groupID, get the list of curve bindings
|
||
|
public CurveBindingGroup GetGroupBinding(string groupID)
|
||
|
{
|
||
|
if (m_groupings == null)
|
||
|
m_groupings = new Dictionary<string, CurveBindingGroup>();
|
||
|
|
||
|
CurveBindingGroup result = null;
|
||
|
if (!m_groupings.TryGetValue(groupID, out result))
|
||
|
{
|
||
|
result = new CurveBindingGroup();
|
||
|
result.timeRange = new Vector2(float.MaxValue, float.MinValue);
|
||
|
result.valueRange = new Vector2(float.MaxValue, float.MinValue);
|
||
|
List<CurveBindingPair> found = new List<CurveBindingPair>();
|
||
|
for (int i = 0; i < bindings.Length; i++)
|
||
|
{
|
||
|
if (bindings[i].GetGroupID() == groupID)
|
||
|
{
|
||
|
CurveBindingPair pair = new CurveBindingPair();
|
||
|
pair.binding = bindings[i];
|
||
|
pair.curve = curves[i];
|
||
|
found.Add(pair);
|
||
|
|
||
|
for (int k = 0; k < curves[i].keys.Length; k++)
|
||
|
{
|
||
|
var key = curves[i].keys[k];
|
||
|
result.timeRange = new Vector2(Mathf.Min(key.time, result.timeRange.x), Mathf.Max(key.time, result.timeRange.y));
|
||
|
result.valueRange = new Vector2(Mathf.Min(key.value, result.valueRange.x), Mathf.Max(key.value, result.valueRange.y));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
for (int i = 0; i < objectBindings.Length; i++)
|
||
|
{
|
||
|
if (objectBindings[i].GetGroupID() == groupID)
|
||
|
{
|
||
|
CurveBindingPair pair = new CurveBindingPair();
|
||
|
pair.binding = objectBindings[i];
|
||
|
pair.objectCurve = objectCurves[i];
|
||
|
found.Add(pair);
|
||
|
|
||
|
for (int k = 0; k < objectCurves[i].Length; k++)
|
||
|
{
|
||
|
var key = objectCurves[i][k];
|
||
|
result.timeRange = new Vector2(Mathf.Min(key.time, result.timeRange.x), Mathf.Max(key.time, result.timeRange.y));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
result.curveBindingPairs = found.OrderBy(x => AnimationWindowUtility.GetComponentIndex(x.binding.propertyName)).ToArray();
|
||
|
|
||
|
m_groupings.Add(groupID, result);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Cache for storing the animation clip data
|
||
|
class AnimationClipCurveCache
|
||
|
{
|
||
|
static AnimationClipCurveCache s_Instance;
|
||
|
Dictionary<AnimationClip, AnimationClipCurveInfo> m_ClipCache = new Dictionary<AnimationClip, AnimationClipCurveInfo>();
|
||
|
bool m_IsEnabled;
|
||
|
|
||
|
|
||
|
public static AnimationClipCurveCache Instance
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (s_Instance == null)
|
||
|
{
|
||
|
s_Instance = new AnimationClipCurveCache();
|
||
|
}
|
||
|
|
||
|
return s_Instance;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void OnEnable()
|
||
|
{
|
||
|
if (!m_IsEnabled)
|
||
|
{
|
||
|
AnimationUtility.onCurveWasModified += OnCurveWasModified;
|
||
|
m_IsEnabled = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void OnDisable()
|
||
|
{
|
||
|
if (m_IsEnabled)
|
||
|
{
|
||
|
AnimationUtility.onCurveWasModified -= OnCurveWasModified;
|
||
|
m_IsEnabled = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// callback when a curve is edited. Force the cache to update next time it's accessed
|
||
|
void OnCurveWasModified(AnimationClip clip, EditorCurveBinding binding, AnimationUtility.CurveModifiedType modification)
|
||
|
{
|
||
|
AnimationClipCurveInfo data;
|
||
|
if (m_ClipCache.TryGetValue(clip, out data))
|
||
|
{
|
||
|
data.dirty = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public AnimationClipCurveInfo GetCurveInfo(AnimationClip clip)
|
||
|
{
|
||
|
AnimationClipCurveInfo data;
|
||
|
if (clip == null)
|
||
|
return null;
|
||
|
if (!m_ClipCache.TryGetValue(clip, out data))
|
||
|
{
|
||
|
data = new AnimationClipCurveInfo();
|
||
|
data.dirty = true;
|
||
|
m_ClipCache[clip] = data;
|
||
|
}
|
||
|
if (data.dirty)
|
||
|
{
|
||
|
data.Update(clip);
|
||
|
}
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
public void ClearCachedProxyClips()
|
||
|
{
|
||
|
var toRemove = new List<AnimationClip>();
|
||
|
foreach (var entry in m_ClipCache)
|
||
|
{
|
||
|
var clip = entry.Key;
|
||
|
if (clip != null && (clip.hideFlags & HideFlags.HideAndDontSave) == HideFlags.HideAndDontSave)
|
||
|
toRemove.Add(clip);
|
||
|
}
|
||
|
|
||
|
foreach (var clip in toRemove)
|
||
|
{
|
||
|
m_ClipCache.Remove(clip);
|
||
|
Object.DestroyImmediate(clip, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Clear()
|
||
|
{
|
||
|
ClearCachedProxyClips();
|
||
|
m_ClipCache.Clear();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static class EditorCurveBindingExtension
|
||
|
{
|
||
|
// identifier to generate an id thats the same for all curves in the same group
|
||
|
public static string GetGroupID(this EditorCurveBinding binding)
|
||
|
{
|
||
|
return binding.type + AnimationWindowUtility.GetPropertyGroupName(binding.propertyName);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static class CurveBindingGroupExtensions
|
||
|
{
|
||
|
// Extentions to determine curve types
|
||
|
public static bool IsEnableGroup(this CurveBindingGroup curves)
|
||
|
{
|
||
|
return curves.isFloatCurve && curves.count == 1 && curves.curveBindingPairs[0].binding.propertyName == "m_Enabled";
|
||
|
}
|
||
|
|
||
|
public static bool IsVectorGroup(this CurveBindingGroup curves)
|
||
|
{
|
||
|
if (!curves.isFloatCurve)
|
||
|
return false;
|
||
|
if (curves.count <= 1 || curves.count > 4)
|
||
|
return false;
|
||
|
char l = curves.curveBindingPairs[0].binding.propertyName.Last();
|
||
|
return l == 'x' || l == 'y' || l == 'z' || l == 'w';
|
||
|
}
|
||
|
|
||
|
public static bool IsColorGroup(this CurveBindingGroup curves)
|
||
|
{
|
||
|
if (!curves.isFloatCurve)
|
||
|
return false;
|
||
|
if (curves.count != 3 && curves.count != 4)
|
||
|
return false;
|
||
|
char l = curves.curveBindingPairs[0].binding.propertyName.Last();
|
||
|
return l == 'r' || l == 'g' || l == 'b' || l == 'a';
|
||
|
}
|
||
|
|
||
|
public static string GetDescription(this CurveBindingGroup group, float t)
|
||
|
{
|
||
|
string result = string.Empty;
|
||
|
if (group.isFloatCurve)
|
||
|
{
|
||
|
if (group.count > 1)
|
||
|
{
|
||
|
result += "(" + group.curveBindingPairs[0].curve.Evaluate(t).ToString("0.##");
|
||
|
for (int j = 1; j < group.curveBindingPairs.Length; j++)
|
||
|
{
|
||
|
result += "," + group.curveBindingPairs[j].curve.Evaluate(t).ToString("0.##");
|
||
|
}
|
||
|
result += ")";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result = group.curveBindingPairs[0].curve.Evaluate(t).ToString("0.##");
|
||
|
}
|
||
|
}
|
||
|
else if (group.isObjectCurve)
|
||
|
{
|
||
|
Object obj = null;
|
||
|
if (group.curveBindingPairs[0].objectCurve.Length > 0)
|
||
|
obj = CurveEditUtility.Evaluate(group.curveBindingPairs[0].objectCurve, t);
|
||
|
result = (obj == null ? "None" : obj.name);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
}
|