Merge pull request #23 from specklesystems/editor2

feat: receive in editor mode
This commit is contained in:
Matteo Cominetti
2021-06-29 20:27:09 +01:00
committed by GitHub
5 changed files with 555 additions and 168 deletions
+6
View File
@@ -2,6 +2,7 @@
<project version="4">
<component name="ContentModelStore">
<e p="C:\Code\Speckle-Next\speckle-unity\Speckle Unity" t="IncludeRecursive">
<e p="Assembly-CSharp-Editor.csproj" t="IncludeRecursive" />
<e p="Assembly-CSharp.csproj" t="IncludeRecursive" />
<e p="Assets" t="Include">
<e p="Extra" t="Include">
@@ -17,9 +18,14 @@
<e p="ConverterUnity.Geometry.cs" t="Include" />
<e p="ConverterUnity.Units.cs" t="Include" />
<e p="Dispatcher.cs" t="Include" />
<e p="Editor" t="Include">
<e p="StreamManagerEditor.cs" t="Include" />
</e>
<e p="Receiver.cs" t="Include" />
<e p="RecursiveConverter.cs" t="Include" />
<e p="Sender.cs" t="Include" />
<e p="SpeckleProperties.cs" t="Include" />
<e p="StreamManager.cs" t="Include" />
<e p="Streams.cs" t="Include" />
<e p="Utils.cs" t="Include" />
</e>
@@ -0,0 +1,318 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Sentry;
using Speckle.Core.Api;
using Speckle.Core.Credentials;
using Speckle.Core.Logging;
using Speckle.Core.Transports;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Speckle.ConnectorUnity
{
[CustomEditor(typeof(StreamManager))]
[CanEditMultipleObjects]
public class StreamManagerEditor : Editor
{
private bool _foldOutAccount;
private int _totalChildrenCount = 0;
private StreamManager _streamManager;
private int SelectedAccountIndex
{
get { return _streamManager.SelectedAccountIndex; }
set { _streamManager.SelectedAccountIndex = value; }
}
private int SelectedStreamIndex
{
get { return _streamManager.SelectedStreamIndex; }
set { _streamManager.SelectedStreamIndex = value; }
}
private int SelectedBranchIndex
{
get { return _streamManager.SelectedBranchIndex; }
set { _streamManager.SelectedBranchIndex = value; }
}
private int SelectedCommitIndex
{
get { return _streamManager.SelectedCommitIndex; }
set { _streamManager.SelectedCommitIndex = value; }
}
private int OldSelectedAccountIndex
{
get { return _streamManager.OldSelectedAccountIndex; }
set { _streamManager.OldSelectedAccountIndex = value; }
}
private int OldSelectedStreamIndex
{
get { return _streamManager.OldSelectedStreamIndex; }
set { _streamManager.OldSelectedStreamIndex = value; }
}
private Client Client
{
get { return _streamManager.Client; }
set { _streamManager.Client = value; }
}
private Account SelectedAccount
{
get { return _streamManager.SelectedAccount; }
set { _streamManager.SelectedAccount = value; }
}
private Stream SelectedStream
{
get { return _streamManager.SelectedStream; }
set { _streamManager.SelectedStream = value; }
}
public List<Account> Accounts
{
get { return _streamManager.Accounts; }
set { _streamManager.Accounts = value; }
}
private List<Stream> Streams
{
get { return _streamManager.Streams; }
set { _streamManager.Streams = value; }
}
private List<Branch> Branches
{
get { return _streamManager.Branches; }
set { _streamManager.Branches = value; }
}
private async Task LoadAccounts()
{
//refresh accounts just in case
Accounts = AccountManager.GetAccounts().ToList();
if (!Accounts.Any())
{
Debug.Log("No Accounts found, please login in Manager");
}
else
{
await SelectAccount(0);
}
}
private async Task SelectAccount(int i)
{
SelectedAccountIndex = i;
OldSelectedAccountIndex = i;
SelectedAccount = Accounts[i];
Client = new Client(SelectedAccount);
await LoadStreams();
}
private async Task LoadStreams()
{
EditorUtility.DisplayProgressBar("Loading streams...", "", 0);
Streams = await Client.StreamsGet();
EditorUtility.ClearProgressBar();
if (Streams.Any())
await SelectStream(0);
}
private async Task SelectStream(int i)
{
SelectedStreamIndex = i;
OldSelectedStreamIndex = i;
SelectedStream = Streams[i];
EditorUtility.DisplayProgressBar("Loading stream details...", "", 0);
Branches = await Client.StreamGetBranches(SelectedStream.id);
if (Branches.Any())
{
SelectedBranchIndex = 0;
if (Branches[SelectedBranchIndex].commits.items.Any())
{
SelectedCommitIndex = 0;
}
}
EditorUtility.ClearProgressBar();
}
private async Task Receive()
{
EditorUtility.DisplayProgressBar("Receving data...", "", 0);
try
{
Tracker.TrackPageview(Tracker.RECEIVE);
var transport = new ServerTransport(SelectedAccount, SelectedStream.id);
var @base = await Operations.Receive(
Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].referencedObject,
remoteTransport: transport,
onProgressAction: dict =>
{
EditorUtility.DisplayProgressBar("Receving data...", "",
Convert.ToSingle(dict.Values.Average() / _totalChildrenCount));
},
onTotalChildrenCountKnown: count => { _totalChildrenCount = count; }
);
var rc = new RecursiveConverter();
var go = rc.ConvertRecursivelyToNative(@base,
Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].id);
}
catch (Exception e)
{
throw new SpeckleException(e.Message, e, true, SentryLevel.Error);
}
EditorUtility.ClearProgressBar();
}
public override async void OnInspectorGUI()
{
_streamManager = (StreamManager) target;
#region Account GUI
if (Accounts == null)
{
await LoadAccounts();
return;
}
EditorGUILayout.BeginHorizontal();
SelectedAccountIndex = EditorGUILayout.Popup("Accounts", SelectedAccountIndex,
Accounts.Select(x => x.userInfo.name + " | " + x.serverInfo.name).ToArray(),
GUILayout.ExpandWidth(true), GUILayout.Height(20));
if (OldSelectedAccountIndex != SelectedAccountIndex)
{
await SelectAccount(SelectedAccountIndex);
return;
}
if (GUILayout.Button("Refresh", GUILayout.Width(60), GUILayout.Height(20)))
{
await LoadAccounts();
return;
}
EditorGUILayout.EndHorizontal();
#region Speckle Account Info
_foldOutAccount = EditorGUILayout.BeginFoldoutHeaderGroup(_foldOutAccount, "Account Info");
if (_foldOutAccount)
{
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.TextField("Name", SelectedAccount.userInfo.name,
GUILayout.Height(20),
GUILayout.ExpandWidth(true));
EditorGUILayout.TextField("Server", SelectedAccount.serverInfo.name,
GUILayout.Height(20),
GUILayout.ExpandWidth(true));
EditorGUILayout.TextField("URL", SelectedAccount.serverInfo.url,
GUILayout.Height(20),
GUILayout.ExpandWidth(true));
EditorGUI.EndDisabledGroup();
}
EditorGUILayout.EndFoldoutHeaderGroup();
#endregion
#endregion
#region Stream List
if (Streams == null)
return;
EditorGUILayout.BeginHorizontal();
SelectedStreamIndex = EditorGUILayout.Popup("Streams",
SelectedStreamIndex, Streams.Select(x => x.name).ToArray(), GUILayout.Height(20),
GUILayout.ExpandWidth(true));
if (OldSelectedStreamIndex != SelectedStreamIndex)
{
await SelectStream(SelectedStreamIndex);
return;
}
if (GUILayout.Button("Refresh", GUILayout.Width(60), GUILayout.Height(20)))
{
await LoadStreams();
return;
}
EditorGUILayout.EndHorizontal();
#endregion
#region Branch List
if (Branches == null)
return;
EditorGUILayout.BeginHorizontal();
SelectedBranchIndex = EditorGUILayout.Popup("Branches",
SelectedBranchIndex, Branches.Select(x => x.name).ToArray(), GUILayout.Height(20),
GUILayout.ExpandWidth(true));
EditorGUILayout.EndHorizontal();
if (!Branches[SelectedBranchIndex].commits.items.Any())
return;
EditorGUILayout.BeginHorizontal();
SelectedCommitIndex = EditorGUILayout.Popup("Commits",
SelectedCommitIndex,
Branches[SelectedBranchIndex].commits.items.Select(x => x.message).ToArray(),
GUILayout.Height(20),
GUILayout.ExpandWidth(true));
EditorGUILayout.EndHorizontal();
#endregion
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Receive!"))
{
await Receive();
}
GUILayout.EndHorizontal();
}
}
}
+6 -168
View File
@@ -21,6 +21,7 @@ namespace Speckle.ConnectorUnity
/// A Speckle Receiver, it's a wrapper around a basic Speckle Client
/// that handles conversions and subscriptions for you
/// </summary>
[RequireComponent( typeof( RecursiveConverter ) )]
public class Receiver : MonoBehaviour
{
public string StreamId;
@@ -37,7 +38,6 @@ namespace Speckle.ConnectorUnity
private Action<GameObject> OnDataReceivedAction;
private ConverterUnity _converter = new ConverterUnity();
private Client Client { get; set; }
@@ -69,12 +69,7 @@ namespace Speckle.ConnectorUnity
OnTotalChildrenCountKnown = onTotalChildrenCountKnown;
Client = new Client(account ?? AccountManager.GetDefaultAccount());
//using the ApplicationPlaceholderObject to pass materials
//available in Assets/Materials to the converters
var materials = Resources.LoadAll("Materials", typeof(Material)).Cast<Material>()
.Select(x => new ApplicationPlaceholderObject {NativeObject = x}).ToList();
_converter.SetContextObjects(materials);
if (AutoReceive)
{
@@ -145,7 +140,9 @@ namespace Speckle.ConnectorUnity
);
Dispatcher.Instance().Enqueue(() =>
{
var go = ConvertRecursivelyToNative(@base, commitId);
var rc = GetComponent<RecursiveConverter>();
var go = rc.ConvertRecursivelyToNative(@base, commitId);
//remove previously received object
if (DeleteOld && ReceivedData != null)
Destroy(ReceivedData);
@@ -160,166 +157,7 @@ namespace Speckle.ConnectorUnity
}
/// <summary>
/// Converts a Base object to a GameObject Recursively
/// </summary>
/// <param name="base"></param>
/// <returns></returns>
private GameObject ConvertRecursivelyToNative(Base @base, string name)
{
// case 1: it's an item that has a direct conversion method, eg a point
if (_converter.CanConvertToNative(@base))
{
var go = TryConvertItemToNative(@base);
return go;
}
// case 2: it's a wrapper Base
// 2a: if there's only one member unpack it
// 2b: otherwise return dictionary of unpacked members
var members = @base.GetMemberNames().ToList();
if (members.Count() == 1)
{
var go = RecurseTreeToNative(@base[members.First()]);
go.name = members.First();
return go;
}
else
{
//empty game object with the commit id as name, used to contain all the rest
var go = new GameObject();
go.name = name;
foreach (var member in members)
{
var goo = RecurseTreeToNative(@base[member]);
if (goo != null)
{
goo.name = member;
goo.transform.parent = go.transform;
}
}
return go;
}
}
/// <summary>
/// Converts an object recursively to a list of GameObjects
/// </summary>
/// <param name="object"></param>
/// <returns></returns>
private GameObject RecurseTreeToNative(object @object)
{
if (IsList(@object))
{
var list = ((IEnumerable) @object).Cast<object>();
var objects = list.Select(x => RecurseTreeToNative(x)).Where(x => x != null).ToList();
if (objects.Any())
{
var go = new GameObject();
go.name = "List";
objects.ForEach(x => x.transform.parent = go.transform);
return go;
}
}
else
{
return TryConvertItemToNative(@object);
}
return null;
}
private GameObject TryConvertItemToNative(object value)
{
if (value == null)
return null;
//it's a simple type or not a Base
if (value.GetType().IsSimpleType() || !(value is Base))
{
return null;
}
var @base = (Base) value;
//it's an unsupported Base, go through each of its property and try convert that
if (!_converter.CanConvertToNative(@base))
{
var members = @base.GetMemberNames().ToList();
//empty game object with the commit id as name, used to contain all the rest
var go = new GameObject();
go.name = @base.speckle_type;
var goos = new List<GameObject>();
foreach (var member in members)
{
var goo = RecurseTreeToNative(@base[member]);
if (goo != null)
{
goo.name = member;
goo.transform.parent = go.transform;
goos.Add(goo);
}
}
//if no children is valid, return null
if (!goos.Any())
{
Destroy(go);
return null;
}
return go;
}
else
{
try
{
var go = _converter.ConvertToNative(@base) as GameObject;
// Some revit elements have nested elements in a "elements" property
// for instance hosted families on a wall
if (go != null && @base["elements"] is List<Base> l && l.Any())
{
var goo = RecurseTreeToNative(l);
if (goo != null)
{
goo.name = "elements";
goo.transform.parent = go.transform;
}
}
return go;
}
catch (Exception e)
{
throw new SpeckleException(e.Message, e, true, SentryLevel.Error);
}
}
return null;
}
private static bool IsList(object @object)
{
if (@object == null)
return false;
var type = @object.GetType();
return (typeof(IEnumerable).IsAssignableFrom(type) && !typeof(IDictionary).IsAssignableFrom(type) &&
type != typeof(string));
}
private static bool IsDictionary(object @object)
{
if (@object == null)
return false;
Type type = @object.GetType();
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>);
}
private void OnDestroy()
{
@@ -0,0 +1,191 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Objects.Converter.Unity;
using Sentry;
using Speckle.Core.Logging;
using Speckle.Core.Models;
using UnityEngine;
namespace Speckle.ConnectorUnity
{
public class RecursiveConverter : MonoBehaviour
{
private ConverterUnity _converter = new ConverterUnity();
public RecursiveConverter()
{
}
/// <summary>
/// Converts a Base object to a GameObject Recursively
/// </summary>
/// <param name="base"></param>
/// <returns></returns>
public GameObject ConvertRecursivelyToNative(Base @base, string name)
{
//using the ApplicationPlaceholderObject to pass materials
//available in Assets/Materials to the converters
var materials = Resources.LoadAll("Materials", typeof(Material)).Cast<Material>()
.Select(x => new ApplicationPlaceholderObject {NativeObject = x}).ToList();
_converter.SetContextObjects(materials);
// case 1: it's an item that has a direct conversion method, eg a point
if (_converter.CanConvertToNative(@base))
{
var go = TryConvertItemToNative(@base);
return go;
}
// case 2: it's a wrapper Base
// 2a: if there's only one member unpack it
// 2b: otherwise return dictionary of unpacked members
var members = @base.GetMemberNames().ToList();
if (members.Count() == 1)
{
var go = RecurseTreeToNative(@base[members.First()]);
go.name = members.First();
return go;
}
else
{
//empty game object with the commit id as name, used to contain all the rest
var go = new GameObject();
go.name = name;
foreach (var member in members)
{
var goo = RecurseTreeToNative(@base[member]);
if (goo != null)
{
goo.name = member;
goo.transform.parent = go.transform;
}
}
return go;
}
}
/// <summary>
/// Converts an object recursively to a list of GameObjects
/// </summary>
/// <param name="object"></param>
/// <returns></returns>
private GameObject RecurseTreeToNative(object @object)
{
if (IsList(@object))
{
var list = ((IEnumerable) @object).Cast<object>();
var objects = list.Select(x => RecurseTreeToNative(x)).Where(x => x != null).ToList();
if (objects.Any())
{
var go = new GameObject();
go.name = "List";
objects.ForEach(x => x.transform.parent = go.transform);
return go;
}
}
else
{
return TryConvertItemToNative(@object);
}
return null;
}
private GameObject TryConvertItemToNative(object value)
{
if (value == null)
return null;
//it's a simple type or not a Base
if (value.GetType().IsSimpleType() || !(value is Base))
{
return null;
}
var @base = (Base) value;
//it's an unsupported Base, go through each of its property and try convert that
if (!_converter.CanConvertToNative(@base))
{
var members = @base.GetMemberNames().ToList();
//empty game object with the commit id as name, used to contain all the rest
var go = new GameObject();
go.name = @base.speckle_type;
var goos = new List<GameObject>();
foreach (var member in members)
{
var goo = RecurseTreeToNative(@base[member]);
if (goo != null)
{
goo.name = member;
goo.transform.parent = go.transform;
goos.Add(goo);
}
}
//if no children is valid, return null
if (!goos.Any())
{
Destroy(go);
return null;
}
return go;
}
else
{
try
{
var go = _converter.ConvertToNative(@base) as GameObject;
// Some revit elements have nested elements in a "elements" property
// for instance hosted families on a wall
if (go != null && @base["elements"] is List<Base> l && l.Any())
{
var goo = RecurseTreeToNative(l);
if (goo != null)
{
goo.name = "elements";
goo.transform.parent = go.transform;
}
}
return go;
}
catch (Exception e)
{
throw new SpeckleException(e.Message, e, true, SentryLevel.Error);
}
}
return null;
}
private static bool IsList(object @object)
{
if (@object == null)
return false;
var type = @object.GetType();
return (typeof(IEnumerable).IsAssignableFrom(type) && !typeof(IDictionary).IsAssignableFrom(type) &&
type != typeof(string));
}
private static bool IsDictionary(object @object)
{
if (@object == null)
return false;
Type type = @object.GetType();
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>);
}
}
}
+34
View File
@@ -0,0 +1,34 @@
using System;
using System.Collections;
using Speckle.Core.Api;
using Speckle.Core.Credentials;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using UnityEngine.Events;
namespace Speckle.ConnectorUnity
{
[ExecuteAlways]
public class StreamManager : MonoBehaviour
{
public int SelectedAccountIndex = -1;
public int SelectedStreamIndex = -1;
public int SelectedBranchIndex = -1;
public int SelectedCommitIndex = -1;
public int OldSelectedAccountIndex = -1;
public int OldSelectedStreamIndex = -1;
public Client Client;
public Account SelectedAccount;
public Stream SelectedStream;
public List<Account> Accounts;
public List<Stream> Streams;
public List<Branch> Branches;
}
}