From fd2f4cf1756e465944ac40dd6257624406fc39c7 Mon Sep 17 00:00:00 2001 From: Matteo Cominetti Date: Tue, 29 Jun 2021 20:26:36 +0100 Subject: [PATCH] feat: receive in editor mode --- .../.idea/contentModel.xml | 6 + .../Editor/StreamManagerEditor.cs | 318 ++++++++++++++++++ Assets/Speckle Connector/Receiver.cs | 174 +--------- .../Speckle Connector/RecursiveConverter.cs | 191 +++++++++++ Assets/Speckle Connector/StreamManager.cs | 34 ++ 5 files changed, 555 insertions(+), 168 deletions(-) create mode 100644 Assets/Speckle Connector/Editor/StreamManagerEditor.cs create mode 100644 Assets/Speckle Connector/RecursiveConverter.cs create mode 100644 Assets/Speckle Connector/StreamManager.cs diff --git a/.idea/.idea.Speckle Unity/.idea/contentModel.xml b/.idea/.idea.Speckle Unity/.idea/contentModel.xml index 8fd952e..3acf793 100644 --- a/.idea/.idea.Speckle Unity/.idea/contentModel.xml +++ b/.idea/.idea.Speckle Unity/.idea/contentModel.xml @@ -2,6 +2,7 @@ + @@ -17,9 +18,14 @@ + + + + + diff --git a/Assets/Speckle Connector/Editor/StreamManagerEditor.cs b/Assets/Speckle Connector/Editor/StreamManagerEditor.cs new file mode 100644 index 0000000..e4b7333 --- /dev/null +++ b/Assets/Speckle Connector/Editor/StreamManagerEditor.cs @@ -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 Accounts + { + get { return _streamManager.Accounts; } + set { _streamManager.Accounts = value; } + } + + private List Streams + { + get { return _streamManager.Streams; } + set { _streamManager.Streams = value; } + } + + private List 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(); + } + + + } +} \ No newline at end of file diff --git a/Assets/Speckle Connector/Receiver.cs b/Assets/Speckle Connector/Receiver.cs index 30b2257..49f4f6f 100644 --- a/Assets/Speckle Connector/Receiver.cs +++ b/Assets/Speckle Connector/Receiver.cs @@ -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 /// + [RequireComponent( typeof( RecursiveConverter ) )] public class Receiver : MonoBehaviour { public string StreamId; @@ -37,7 +38,6 @@ namespace Speckle.ConnectorUnity private Action 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() - .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(); + var go = rc.ConvertRecursivelyToNative(@base, commitId); //remove previously received object if (DeleteOld && ReceivedData != null) Destroy(ReceivedData); @@ -160,166 +157,7 @@ namespace Speckle.ConnectorUnity } - /// - /// Converts a Base object to a GameObject Recursively - /// - /// - /// - 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; - } - } - - - /// - /// Converts an object recursively to a list of GameObjects - /// - /// - /// - private GameObject RecurseTreeToNative(object @object) - { - if (IsList(@object)) - { - var list = ((IEnumerable) @object).Cast(); - 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(); - 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 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() { diff --git a/Assets/Speckle Connector/RecursiveConverter.cs b/Assets/Speckle Connector/RecursiveConverter.cs new file mode 100644 index 0000000..3ea1764 --- /dev/null +++ b/Assets/Speckle Connector/RecursiveConverter.cs @@ -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() + { + + } + /// + /// Converts a Base object to a GameObject Recursively + /// + /// + /// + 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() + .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; + } + } + + + /// + /// Converts an object recursively to a list of GameObjects + /// + /// + /// + private GameObject RecurseTreeToNative(object @object) + { + if (IsList(@object)) + { + var list = ((IEnumerable) @object).Cast(); + 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(); + 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 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<,>); + } + } +} \ No newline at end of file diff --git a/Assets/Speckle Connector/StreamManager.cs b/Assets/Speckle Connector/StreamManager.cs new file mode 100644 index 0000000..4ddaab3 --- /dev/null +++ b/Assets/Speckle Connector/StreamManager.cs @@ -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 Accounts; + public List Streams; + public List Branches; + + } +} \ No newline at end of file