diff --git a/SpeckleRevitPlugin/Speckle/ISpeckleClient.cs b/SpeckleRevitPlugin/Speckle/ISpeckleClient.cs
new file mode 100644
index 0000000..74e5915
--- /dev/null
+++ b/SpeckleRevitPlugin/Speckle/ISpeckleClient.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Runtime.Serialization;
+
+
+namespace SpeckleRhino
+{
+ ///
+ /// Generalises some methhods for both senders and receivers.
+ ///
+ public interface ISpeckleClient : IDisposable, ISerializable
+ {
+ SpeckleCore.ClientRole GetRole();
+
+ string GetClientId();
+
+ void TogglePaused(bool status);
+
+ void ToggleVisibility(bool status);
+
+ void ToggleLayerVisibility(string layerId, bool status);
+
+ void ToggleLayerHover(string layerId, bool status);
+
+ void Dispose(bool delete = false);
+ }
+}
diff --git a/SpeckleRevitPlugin/Speckle/Interop.cs b/SpeckleRevitPlugin/Speckle/Interop.cs
new file mode 100644
index 0000000..809a02a
--- /dev/null
+++ b/SpeckleRevitPlugin/Speckle/Interop.cs
@@ -0,0 +1,542 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+
+using CefSharp;
+using Newtonsoft.Json;
+using System.IO;
+
+using SpeckleCore;
+
+using Newtonsoft.Json.Serialization;
+using CefSharp.Wpf;
+
+namespace SpeckleRhino
+{
+ // CEF Bound object.
+ // If CEF will be removed, porting to url hacks will be necessary,
+ // so let's keep the methods as simple as possible.
+
+ public class Interop : IDisposable
+ {
+ public ChromiumWebBrowser Browser;
+
+ private List UserAccounts;
+ public List UserClients;
+
+ public Dictionary SpeckleObjectCache;
+
+ public bool SpeckleIsReady = false;
+
+ public bool SelectionInfoNeedsToBeSentYeMighty = false; // should be false
+
+ public Interop( ChromiumWebBrowser _originalBrowser )
+ {
+ // Makes sure we always get some camelCaseLove
+ JsonConvert.DefaultSettings = ( ) => new JsonSerializerSettings()
+ {
+ ContractResolver = new CamelCasePropertyNamesContractResolver()
+ };
+
+ Browser = _originalBrowser;
+
+ UserAccounts = new List();
+
+ UserClients = new List();
+
+ SpeckleObjectCache = new Dictionary();
+
+ ReadUserAccounts();
+
+ //RhinoDoc.NewDocument += RhinoDoc_NewDocument;
+
+ //RhinoDoc.EndOpenDocument += RhinoDoc_EndOpenDocument;
+
+ //RhinoDoc.BeginSaveDocument += RhinoDoc_BeginSaveDocument;
+
+ //RhinoDoc.SelectObjects += RhinoDoc_SelectObjects;
+
+ //RhinoDoc.DeselectObjects += RhinoDoc_DeselectObjects;
+
+ //RhinoDoc.DeselectAllObjects += RhinoDoc_DeselectAllObjects;
+
+ //RhinoApp.Idle += RhinoApp_Idle;
+ }
+
+ private void RhinoApp_Idle( object sender, EventArgs e )
+ {
+ //System.Diagnostics.Debug.WriteLine( "I am idle... " + SelectionInfoNeedsToBeSentYeMighty );
+ if ( SelectionInfoNeedsToBeSentYeMighty )
+ {
+ NotifySpeckleFrame( "object-selection", "", this.getLayersAndObjectsInfo() );
+ SelectionInfoNeedsToBeSentYeMighty = false;
+ }
+ }
+
+ public void SetBrowser( ChromiumWebBrowser _Browser )
+ {
+ Browser = _Browser;
+ }
+
+ public void Dispose( )
+ {
+
+ //this.RemoveAllClients();
+
+ //RhinoDoc.NewDocument -= RhinoDoc_NewDocument;
+
+ //RhinoDoc.EndOpenDocument -= RhinoDoc_EndOpenDocument;
+
+ //RhinoDoc.BeginSaveDocument -= RhinoDoc_BeginSaveDocument;
+
+ //RhinoDoc.SelectObjects -= RhinoDoc_SelectObjects;
+
+ //RhinoDoc.DeselectObjects -= RhinoDoc_DeselectObjects;
+
+ //RhinoDoc.DeselectAllObjects -= RhinoDoc_DeselectAllObjects;
+
+ //RhinoApp.Idle -= RhinoApp_Idle;
+ }
+ /*
+ #region Global Events
+
+ private void RhinoDoc_NewDocument( object sender, DocumentEventArgs e )
+ {
+ Debug.WriteLine( "New document event" );
+ NotifySpeckleFrame( "purge-clients", "", "" );
+ RemoveAllClients();
+ }
+
+ private void RhinoDoc_DeselectAllObjects( object sender, RhinoDeselectAllObjectsEventArgs e )
+ {
+ Debug.WriteLine( "Deselect all event" );
+ SelectionInfoNeedsToBeSentYeMighty = true;
+ return;
+ }
+
+ private void RhinoDoc_DeselectObjects( object sender, RhinoObjectSelectionEventArgs e )
+ {
+ Debug.WriteLine( "Deselect event" );
+ SelectionInfoNeedsToBeSentYeMighty = true;
+ return;
+ }
+
+ private void RhinoDoc_SelectObjects( object sender, RhinoObjectSelectionEventArgs e )
+ {
+ Debug.WriteLine( "Select objs event" );
+ SelectionInfoNeedsToBeSentYeMighty = true;
+ return;
+ }
+
+ private void RhinoDoc_EndOpenDocument( object sender, DocumentOpenEventArgs e )
+ {
+ Debug.WriteLine( "END OPEN DOC" );
+ // this seems to cover the copy paste issues
+ if ( e.Merge ) return;
+ // purge clients from ui
+ NotifySpeckleFrame( "client-purge", "", "" );
+ // purge clients from here
+ RemoveAllClients();
+ // read clients from document strings
+ InstantiateFileClients();
+ }
+
+ private void RhinoDoc_BeginSaveDocument( object sender, DocumentSaveEventArgs e )
+ {
+ Debug.WriteLine( "BEGIN SAVE DOC" );
+ SaveFileClients();
+ }
+
+
+ #endregion
+
+ */
+ #region General Utils
+
+ public void ShowDev( )
+ {
+ Browser.ShowDevTools();
+ }
+
+ public string GetDocumentName( )
+ {
+ throw new NotImplementedException();
+ // return Rhino.RhinoDoc.ActiveDoc.Name;
+ }
+
+ public string GetDocumentGuid( )
+ {
+ throw new NotImplementedException();
+ // return Rhino.RhinoDoc.ActiveDoc.DocumentId.ToString();
+ }
+ #endregion
+
+ #region Serialisation & Init.
+
+ ///
+ /// Do not call this from the constructor as you'll get confilcts with
+ /// browser load, etc.
+ ///
+ public void AppReady( )
+ {
+ SpeckleIsReady = true;
+ InstantiateFileClients();
+ }
+
+ public void SaveFileClients( )
+ {
+ RhinoDoc myDoc = RhinoDoc.ActiveDoc;
+ foreach ( ISpeckleClient rhinoClient in UserClients )
+ {
+ using ( var ms = new MemoryStream() )
+ {
+ var formatter = new BinaryFormatter();
+ formatter.Serialize( ms, rhinoClient );
+ string section = rhinoClient.GetRole() == ClientRole.Receiver ? "speckle-client-receivers" : "speckle-client-senders";
+ var client = Convert.ToBase64String( ms.ToArray() );
+ var clientId = rhinoClient.GetClientId();
+ RhinoDoc.ActiveDoc.Strings.SetString( section, clientId, client );
+ }
+ }
+ }
+
+ public void InstantiateFileClients( )
+ {
+ if ( !SpeckleIsReady ) return;
+
+ Debug.WriteLine( "Instantiate file clients." );
+
+ string[ ] receiverKeys = RhinoDoc.ActiveDoc.Strings.GetEntryNames( "speckle-client-receivers" );
+
+ foreach ( string rec in receiverKeys )
+ {
+ //if ( UserClients.Any( cl => cl.GetClientId() == rec ) )
+ // continue;
+
+ byte[ ] serialisedClient = Convert.FromBase64String( RhinoDoc.ActiveDoc.Strings.GetValue( "speckle-client-receivers", rec ) );
+ using ( var ms = new MemoryStream() )
+ {
+ ms.Write( serialisedClient, 0, serialisedClient.Length );
+ ms.Seek( 0, SeekOrigin.Begin );
+ RhinoReceiver client = ( RhinoReceiver ) new BinaryFormatter().Deserialize( ms );
+ client.Context = this;
+ }
+ }
+
+ string[ ] senderKeys = RhinoDoc.ActiveDoc.Strings.GetEntryNames( "speckle-client-senders" );
+
+ foreach ( string sen in senderKeys )
+ {
+ byte[ ] serialisedClient = Convert.FromBase64String( RhinoDoc.ActiveDoc.Strings.GetValue( "speckle-client-senders", sen ) );
+
+ using ( var ms = new MemoryStream() )
+ {
+ ms.Write( serialisedClient, 0, serialisedClient.Length );
+ ms.Seek( 0, SeekOrigin.Begin );
+ RhinoSender client = ( RhinoSender ) new BinaryFormatter().Deserialize( ms );
+ client.CompleteDeserialisation( this );
+ }
+ }
+ }
+ #endregion
+
+ #region Account Management
+
+ public string GetUserAccounts( )
+ {
+ ReadUserAccounts();
+ return JsonConvert.SerializeObject( UserAccounts );
+ }
+
+ private void ReadUserAccounts( )
+ {
+ UserAccounts = new List();
+ string strPath = System.Environment.GetFolderPath( System.Environment.SpecialFolder.LocalApplicationData );
+ strPath = strPath + @"\SpeckleSettings";
+
+ if ( Directory.Exists( strPath ) && Directory.EnumerateFiles( strPath, "*.txt" ).Count() > 0 )
+ foreach ( string file in Directory.EnumerateFiles( strPath, "*.txt" ) )
+ {
+ string content = File.ReadAllText( file );
+ string[ ] pieces = content.TrimEnd( '\r', '\n' ).Split( ',' );
+ UserAccounts.Add( new SpeckleAccount() { email = pieces[ 0 ], apiToken = pieces[ 1 ], serverName = pieces[ 2 ], restApi = pieces[ 3 ], rootUrl = pieces[ 4 ], fileName = file } );
+ }
+ }
+
+ public void AddAccount( string payload )
+ {
+ var pieces = payload.Split( ',' );
+
+ string strPath = System.Environment.GetFolderPath( System.Environment.SpecialFolder.LocalApplicationData );
+ System.IO.Directory.CreateDirectory( strPath + @"\SpeckleSettings" );
+
+ strPath = strPath + @"\SpeckleSettings\";
+
+ string fileName = pieces[ 0 ] + "." + pieces[ 2 ] + ".txt";
+
+ System.IO.StreamWriter file = new System.IO.StreamWriter( strPath + fileName );
+ file.WriteLine( payload );
+ file.Close();
+ }
+
+ public void RemoveAccount( string payload )
+ {
+ var x = UserAccounts.RemoveAll( account => { return account.fileName == payload; } );
+ if ( File.Exists( payload ) )
+ File.Delete( payload );
+ }
+ #endregion
+
+ #region Client Management
+ public bool AddReceiverClient( string _payload )
+ {
+ var myReceiver = new RhinoReceiver( _payload, this );
+ return true;
+ }
+
+ public bool AddSenderClientFromSelection( string _payload )
+ {
+ var mySender = new RhinoSender( _payload, this );
+ return true;
+ }
+
+ public bool RemoveClient( string _payload )
+ {
+ var myClient = UserClients.FirstOrDefault( client => client.GetClientId() == _payload );
+ if ( myClient == null ) return false;
+
+ RhinoDoc.ActiveDoc.Strings.Delete( myClient.GetRole() == ClientRole.Receiver ? "speckle-client-receivers" : "speckle-client-senders", myClient.GetClientId() );
+
+ myClient.Dispose( true );
+
+ return UserClients.Remove( myClient );
+ }
+
+ public bool RemoveAllClients( )
+ {
+ foreach ( var uc in UserClients )
+ {
+ uc.Dispose();
+ }
+ UserClients.RemoveAll( c => true );
+ return true;
+ }
+
+ public string GetAllClients( )
+ {
+ return JsonConvert.SerializeObject( UserClients );
+ }
+
+ #endregion
+
+ #region To UI (Generic)
+ public void NotifySpeckleFrame( string EventType, string StreamId, string EventInfo )
+ {
+ if ( !SpeckleIsReady )
+ {
+ Debug.WriteLine( "Speckle wwas not ready, trying to send " + EventType );
+ return;
+ }
+
+ var script = string.Format( "window.EventBus.$emit('{0}', '{1}', '{2}')", EventType, StreamId, EventInfo );
+ try
+ {
+ Browser.GetMainFrame().EvaluateScriptAsync( script );
+ }
+ catch
+ {
+ Debug.WriteLine( "For some reason, this browser was not initialised." );
+ }
+ }
+ #endregion
+
+ #region From UI (..)
+
+ public void bakeClient( string clientId )
+ {
+ var myClient = UserClients.FirstOrDefault( c => c.GetClientId() == clientId );
+ if ( myClient != null || myClient is RhinoReceiver )
+ ( ( RhinoReceiver ) myClient ).Bake();
+
+ }
+
+ public void bakeLayer( string clientId, string layerGuid )
+ {
+ var myClient = UserClients.FirstOrDefault( c => c.GetClientId() == clientId );
+ if ( myClient != null || myClient is RhinoReceiver )
+ ( ( RhinoReceiver ) myClient ).BakeLayer( layerGuid );
+ }
+
+ public void setClientPause( string clientId, bool status )
+ {
+ var myClient = UserClients.FirstOrDefault( c => c.GetClientId() == clientId );
+ if ( myClient != null )
+ myClient.TogglePaused( status );
+ }
+
+ public void setClientVisibility( string clientId, bool status )
+ {
+ var myClient = UserClients.FirstOrDefault( c => c.GetClientId() == clientId );
+ if ( myClient != null )
+ myClient.ToggleVisibility( status );
+ }
+
+ public void setClientHover( string clientId, bool status )
+ {
+ var myClient = UserClients.FirstOrDefault( c => c.GetClientId() == clientId );
+ if ( myClient != null )
+ myClient.ToggleVisibility( status );
+ }
+
+ public void setLayerVisibility( string clientId, string layerId, bool status )
+ {
+ var myClient = UserClients.FirstOrDefault( c => c.GetClientId() == clientId );
+ if ( myClient != null )
+ myClient.ToggleLayerVisibility( layerId, status );
+ }
+
+ public void setLayerHover( string clientId, string layerId, bool status )
+ {
+ var myClient = UserClients.FirstOrDefault( c => c.GetClientId() == clientId );
+ if ( myClient != null )
+ myClient.ToggleLayerHover( layerId, status );
+ }
+
+ public void setObjectHover( string clientId, string layerId, bool status )
+ {
+
+ }
+
+ public void AddRemoveObjects( string clientId, string _guids, bool remove )
+ {
+ string[ ] guids = JsonConvert.DeserializeObject( _guids );
+
+ var myClient = UserClients.FirstOrDefault( c => c.GetClientId() == clientId );
+ if ( myClient != null )
+ try
+ {
+ if ( !remove )
+ ( ( RhinoSender ) myClient ).AddTrackedObjects( guids );
+ else ( ( RhinoSender ) myClient ).RemoveTrackedObjects( guids );
+
+ }
+ catch { throw new Exception( "Force send client was not a sender. whoopsie poopsiee." ); }
+ }
+
+ public void refreshClient( string clientId )
+ {
+ var myClient = UserClients.FirstOrDefault( c => c.GetClientId() == clientId );
+ if ( myClient != null )
+ try
+ {
+ ( ( RhinoReceiver ) myClient ).UpdateGlobal();
+ }
+ catch { throw new Exception( "Refresh client was not a receiver. whoopsie poopsiee." ); }
+ }
+
+ public void forceSend( string clientId )
+ {
+ var myClient = UserClients.FirstOrDefault( c => c.GetClientId() == clientId );
+ if ( myClient != null )
+ try
+ {
+ ( ( RhinoSender ) myClient ).ForceUpdate();
+ }
+ catch { throw new Exception( "Force send client was not a sender. whoopsie poopsiee." ); }
+ }
+
+ public void openUrl( string url )
+ {
+ System.Diagnostics.Process.Start( url );
+ }
+
+ public void setName( string clientId, string name )
+ {
+ var myClient = UserClients.FirstOrDefault( c => c.GetClientId() == clientId );
+ if ( myClient != null && myClient is RhinoSender )
+ {
+ ( ( RhinoSender ) myClient ).Client.Stream.Name = name;
+ ( ( RhinoSender ) myClient ).Client.BroadcastMessage( new { eventType = "update-name" } );
+ }
+ }
+
+ #endregion
+
+ #region Sender Helpers
+
+ public string getLayersAndObjectsInfo( bool ignoreSelection = false )
+ {
+ List SelectedObjects;
+ List layerInfoList = new List();
+
+ if ( !ignoreSelection )
+ {
+ SelectedObjects = RhinoDoc.ActiveDoc.Objects.GetSelectedObjects( false, false ).ToList();
+ if ( SelectedObjects.Count == 0 || SelectedObjects[ 0 ] == null )
+ return JsonConvert.SerializeObject( layerInfoList );
+ }
+ else
+ {
+ SelectedObjects = RhinoDoc.ActiveDoc.Objects.ToList();
+ if ( SelectedObjects.Count == 0 || SelectedObjects[ 0 ] == null )
+ return JsonConvert.SerializeObject( layerInfoList );
+
+ foreach ( Layer ll in RhinoDoc.ActiveDoc.Layers )
+ {
+ layerInfoList.Add( new LayerSelection()
+ {
+ objectCount = 0,
+ layerName = ll.FullPath,
+ color = System.Drawing.ColorTranslator.ToHtml( ll.Color ),
+ ObjectGuids = new List(),
+ ObjectTypes = new List()
+ } );
+ }
+ }
+
+ SelectedObjects = SelectedObjects.OrderBy( o => o.Attributes.LayerIndex ).ToList();
+
+ foreach ( var obj in SelectedObjects )
+ {
+ var layer = RhinoDoc.ActiveDoc.Layers[ obj.Attributes.LayerIndex ];
+ var myLInfo = layerInfoList.FirstOrDefault( l => l.layerName == layer.FullPath );
+
+ if ( myLInfo != null )
+ {
+ myLInfo.objectCount++;
+ myLInfo.ObjectGuids.Add( obj.Id.ToString() );
+ myLInfo.ObjectTypes.Add( obj.Geometry.GetType().ToString() );
+ }
+ else
+ {
+ var myNewLinfo = new LayerSelection()
+ {
+ objectCount = 1,
+ layerName = layer.FullPath,
+ color = System.Drawing.ColorTranslator.ToHtml( layer.Color ),
+ ObjectGuids = new List( new string[ ] { obj.Id.ToString() } ),
+ ObjectTypes = new List( new string[ ] { obj.Geometry.GetType().ToString() } )
+ };
+ layerInfoList.Add( myNewLinfo );
+ }
+ }
+
+ return Convert.ToBase64String( System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject( layerInfoList )));
+ }
+ #endregion
+
+ }
+
+ [Serializable]
+ public class LayerSelection
+ {
+ public string layerName;
+ public int objectCount;
+ public string color;
+ public List ObjectGuids;
+ public List ObjectTypes;
+ }
+}
diff --git a/SpeckleRevitPlugin/Speckle/SpeckleAccount.cs b/SpeckleRevitPlugin/Speckle/SpeckleAccount.cs
new file mode 100644
index 0000000..33149d0
--- /dev/null
+++ b/SpeckleRevitPlugin/Speckle/SpeckleAccount.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SpeckleRhino
+{
+ class SpeckleAccount
+ {
+ public string email { get; set; }
+ public string apiToken { get; set; }
+ public string serverName { get; set; }
+ public string restApi { get; set; }
+ public string rootUrl { get; set; }
+ public string fileName { get; set; }
+ }
+}
+