diff --git a/.idea/SpeckleRevit.iml b/.idea/SpeckleRevit.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/SpeckleRevit.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..56d9dce --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..ca3bd8a --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1526389059417 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SpeckleCore b/SpeckleCore index a8e4f98..53332c9 160000 --- a/SpeckleCore +++ b/SpeckleCore @@ -1 +1 @@ -Subproject commit a8e4f98984e02c983c954dca0c6a47cc1fc76822 +Subproject commit 53332c9fd2c281a6d16d9e37e7488db8a826c74d diff --git a/SpeckleRevit.sln.DotSettings.user b/SpeckleRevit.sln.DotSettings.user new file mode 100644 index 0000000..ef2654a --- /dev/null +++ b/SpeckleRevit.sln.DotSettings.user @@ -0,0 +1,6 @@ + + <AssemblyExplorer> + <Assembly Path="C:\Users\konrad.sobon\.nuget\packages\cefsharp.wpf\63.0.2\CefSharp\x64\CefSharp.Wpf.dll" /> + <Assembly Path="C:\Users\konrad.sobon\.nuget\packages\newtonsoft.json\11.0.1\lib\net45\Newtonsoft.Json.dll" /> + <Assembly Path="C:\Windows\Microsoft.NET\assembly\GAC_MSIL\PresentationFramework\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.dll" /> +</AssemblyExplorer> \ No newline at end of file diff --git a/SpeckleRevitConverter/SpeckleRevitConverter.csproj.user b/SpeckleRevitConverter/SpeckleRevitConverter.csproj.user new file mode 100644 index 0000000..e5e922d --- /dev/null +++ b/SpeckleRevitConverter/SpeckleRevitConverter.csproj.user @@ -0,0 +1,7 @@ + + + + Program + C:\Program Files\Autodesk\Revit 2018\Revit.exe + + \ No newline at end of file diff --git a/SpeckleRevitPlugin/Classes/ISpeckleRevitClient.cs b/SpeckleRevitPlugin/Classes/ISpeckleRevitClient.cs new file mode 100644 index 0000000..b8e154d --- /dev/null +++ b/SpeckleRevitPlugin/Classes/ISpeckleRevitClient.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.Serialization; +using SpeckleCore; + +namespace SpeckleRevitPlugin.Classes +{ + /// + /// Generalises some methods for both senders and receivers. + /// + public interface ISpeckleRevitClient : IDisposable, ISerializable + { + 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/Classes/Interop.cs b/SpeckleRevitPlugin/Classes/Interop.cs new file mode 100644 index 0000000..9f53f10 --- /dev/null +++ b/SpeckleRevitPlugin/Classes/Interop.cs @@ -0,0 +1,531 @@ +#region Namespaces +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization.Formatters.Binary; +using Autodesk.Revit.DB; +using Autodesk.Revit.DB.Events; +using CefSharp; +using CefSharp.Wpf; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using SpeckleCore; +using SpeckleRevitPlugin.Entry; +using SpeckleRevitPlugin.Utilities; +#endregion + +namespace SpeckleRevitPlugin.Classes +{ + /// + /// 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 + { + private List _userAccounts; + public List UserClients; + public Dictionary SpeckleObjectCache; + public ChromiumWebBrowser Browser; + public bool SpeckleIsReady; + public bool SelectionInfoNeedsToBeSentYeMighty = false; // should be false + + public Interop(ChromiumWebBrowser originalBrowser) + { + // (Luis) Makes sure we always get some camelCaseLove + JsonConvert.DefaultSettings = () => new JsonSerializerSettings() + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + + _userAccounts = new List(); + UserClients = new List(); + SpeckleObjectCache = new Dictionary(); + Browser = originalBrowser; + ReadUserAccounts(); + + AppMain.uiApp.ControlledApplication.DocumentCreated += Revit_DocumentCreated; + AppMain.uiApp.ControlledApplication.DocumentOpened += Revit_DocumentOpened; + AppMain.uiApp.ControlledApplication.DocumentSaved += Revit_DocumentSaved; + AppMain.uiApp.ControlledApplication.DocumentSynchronizedWithCentral += Revit_DocumentSynchronized; + SpeckleRequestHandler.OnClientsRetrieved += OnClientsRetrieved; + } + + public void Dispose() + { + AppMain.uiApp.ControlledApplication.DocumentCreated -= Revit_DocumentCreated; + AppMain.uiApp.ControlledApplication.DocumentOpened -= Revit_DocumentOpened; + AppMain.uiApp.ControlledApplication.DocumentSaved -= Revit_DocumentSaved; + AppMain.uiApp.ControlledApplication.DocumentSynchronizedWithCentral -= Revit_DocumentSynchronized; + SpeckleRequestHandler.OnClientsRetrieved -= OnClientsRetrieved; + + RemoveAllClients(); + } + + #region Global Events + + private void Revit_DocumentSaved(object sender, DocumentSavedEventArgs e) + { + SaveFileClients(); + } + + private void Revit_DocumentSynchronized(object sender, DocumentSynchronizedWithCentralEventArgs e) + { + SaveFileClients(); + } + + private void Revit_DocumentCreated(object sender, DocumentCreatedEventArgs e) + { + var doc = e.Document; + if (doc == null || doc.IsFamilyDocument) return; + + NotifySpeckleFrame("purge-clients", "", ""); + RemoveAllClients(); + } + + private void Revit_DocumentOpened(object sender, DocumentOpenedEventArgs e) + { + var doc = e.Document; + if (doc == null || doc.IsFamilyDocument) return; + + NotifySpeckleFrame("purge-clients", "", ""); + RemoveAllClients(); + InstantiateFileClients(); + } + + #endregion + + public void SetBrowser(ChromiumWebBrowser browser) + { + Browser = browser; + } + + public void ShowDev() + { + Browser.ShowDevTools(); + } + + public string GetDocumentName() + { + //TODO: Fix this! + return "Revit doc name."; + //return Rhino.RhinoDoc.ActiveDoc.Name; + } + + public string GetDocumentGuid() + { + //TODO: Fix this! + return "Revit GUID"; + //return Rhino.RhinoDoc.ActiveDoc.DocumentId.ToString(); + } + + /// + /// Do not call this from the constructor as you'll get confilcts with + /// browser load, etc. + /// + public void AppReady() + { + SpeckleIsReady = true; + InstantiateFileClients(); + } + + #region Account Management + + public string GetUserAccounts( ) + { + ReadUserAccounts(); + return JsonConvert.SerializeObject(_userAccounts, new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }); + } + + /// + /// + /// + private void ReadUserAccounts( ) + { + _userAccounts = new List(); + var strPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + strPath = strPath + @"\SpeckleSettings"; + + if (!Directory.Exists(strPath) || !Directory.EnumerateFiles(strPath, "*.txt").Any()) return; + + foreach (var file in Directory.EnumerateFiles(strPath, "*.txt")) + { + var content = File.ReadAllText(file); + var 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(','); + var strPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + + Directory.CreateDirectory(strPath + @"\SpeckleSettings"); + strPath = strPath + @"\SpeckleSettings\"; + + var fileName = pieces[0] + "." + pieces[2] + ".txt"; + var file = new StreamWriter(strPath + fileName); + file.WriteLine(payload); + file.Close(); + } + + public void RemoveAccount( string payload ) + { + var unused = _userAccounts.RemoveAll(account => account.fileName == payload); + if (File.Exists(payload)) File.Delete(payload); + } + + #endregion + + #region Client Management + + /// + /// Called by SpeckleView when new Receiver was added. + /// + /// Info needed to create RevitReceiver + /// + public bool AddReceiverClient( string payload ) + { + var unused = new RevitReceiver(payload, this); + return true; + } + + /// + /// + /// + public void InstantiateFileClients() + { + // (Konrad) This is the thread safe way of interacting with Revit. + // Also it's possible that the Speckle app initiates before Revit + // Document is open/ready making Extensible Storage inaccessible. + AppMain.SpeckleHandler.Request.Make(SpeckleCommandType.GetClients); + AppMain.SpeckleEvent.Raise(); + } + + /// + /// Handler for an event called by Revit when Clients have been retrived from Schema. + /// + /// Dictionary of Revit Receivers serialized into string. + /// Dictionary of Revit Senders serialized into string. + private void OnClientsRetrieved(IDictionary receivers, IDictionary senders) + { + var assembly = Assembly.GetExecutingAssembly(); + foreach (var kv in receivers) + { + var serialisedClient = Convert.FromBase64String(kv.Value); + var client = BinaryFormatterUtilities.Read(serialisedClient, assembly); + client.Context = this; + } + + //TODO: Let's deal with senders later! + //foreach (var kv in senders) + //{ + // var serialisedClient = Convert.FromBase64String(kv.Value); + // var client = BinaryFormatterUtilities.Read(serialisedClient, assembly); + // if (client.Client == null) return; + + // client.CompleteDeserialisation(this); + //} + } + + /// + /// It's used by Speckle View to trigger Client storage. + /// + public void SaveFileClients() + { + var senders = new Dictionary(); + var receivers = new Dictionary(); + foreach (var rClient in UserClients) + { + using (var ms = new MemoryStream()) + { + var formatter = new BinaryFormatter(); + formatter.Serialize(ms, rClient); + var client = Convert.ToBase64String(ms.ToArray()); + var clientId = rClient.GetClientId(); + + if (rClient.GetRole() == ClientRole.Receiver) receivers.Add(clientId, client); + else senders.Add(clientId, client); + } + } + + AppMain.SpeckleHandler.Arg1 = senders; + AppMain.SpeckleHandler.Arg2 = receivers; + AppMain.SpeckleHandler.Request.Make(SpeckleCommandType.SaveClients); + AppMain.SpeckleEvent.Raise(); + } + + //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; + + myClient.Dispose(true); + var result = UserClients.Remove(myClient); + + // (Konrad) Update Revit Schema. + SaveFileClients(); + + return result; + } + + public bool RemoveAllClients( ) + { + foreach (var uc in UserClients) + { + uc.Dispose(); + } + UserClients.RemoveAll(c => true); + return true; + } + + public string GetAllClients( ) + { + foreach (var client in UserClients) + { + switch (client) + { + case RevitSender rvtSender: + NotifySpeckleFrame("client-add", rvtSender.StreamId, JsonConvert.SerializeObject(new + { + stream = rvtSender.Client.Stream, + client = rvtSender.Client + })); + break; + case RevitReceiver rvtReceiver: + NotifySpeckleFrame("client-add", rvtReceiver.StreamId, JsonConvert.SerializeObject(new + { + stream = rvtReceiver.Client.Stream, + client = rvtReceiver.Client + })); + break; + } + } + + return JsonConvert.SerializeObject(UserClients); + } + + #endregion + + #region To UI (Generic) + + public void NotifySpeckleFrame( string eventType, string streamId, string eventInfo ) + { + if (!SpeckleIsReady) + { + Debug.WriteLine("Speckle was not ready, trying to send " + eventType); + return; + } + + var script = $"window.EventBus.$emit('{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) + { + //TODO: Implement client baking + } + + public void BakeLayer(string clientId, string layerGuid) + { + //TODO: Implement baking + } + + public void SetClientPause(string clientId, bool status) + { + var myClient = UserClients.FirstOrDefault(c => c.GetClientId() == clientId); + myClient?.TogglePaused(status); + } + + public void SetClientVisibility(string clientId, bool status) + { + var myClient = UserClients.FirstOrDefault(c => c.GetClientId() == clientId); + myClient?.ToggleVisibility(status); + } + + public void SetClientHover(string clientId, bool status) + { + var myClient = UserClients.FirstOrDefault(c => c.GetClientId() == clientId); + myClient?.ToggleVisibility(status); + } + + public void SetLayerVisibility(string clientId, string layerId, bool status) + { + //TODO: create geometry previews + } + + public void SetLayerHover(string clientId, string layerId, bool status) + { + //TODO: highlight geometry previews + } + + public void SetObjectHover(string clientId, string layerId, bool status) + { + //TODO: implement object hover + } + + //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) return; + + try + { + ((RevitReceiver) 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) + { + 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 ( Rhino.DocObjects.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/Classes/RevitReceiver.cs b/SpeckleRevitPlugin/Classes/RevitReceiver.cs new file mode 100644 index 0000000..0d4a8d2 --- /dev/null +++ b/SpeckleRevitPlugin/Classes/RevitReceiver.cs @@ -0,0 +1,308 @@ +#region Namespaces +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using SpeckleCore; +using SpeckleRevitPlugin.Utilities; +#pragma warning disable 4014 +#endregion + +namespace SpeckleRevitPlugin.Classes +{ + /// + /// Class that holds a rhino receiver client warpped around the SpeckleApiClient. + /// + [Serializable] + public class RevitReceiver : ISpeckleRevitClient + { + public Interop Context { get; set; } + public SpeckleApiClient Client { get; set; } + public List Objects { get; set; } + public string StreamId { get; private set; } + public bool Paused { get; set; } + public bool Visible { get; set; } = true; + + public RevitReceiver() + { + } + + public RevitReceiver(string payload, Interop parent) + { + Context = parent; + dynamic p = JsonConvert.DeserializeObject(payload); + + StreamId = (string) p.streamId; + Client = new SpeckleApiClient((string) p.account.restApi, true); + + Client.OnReady += Client_OnReady; + Client.OnLogData += Client_OnLogData; + Client.OnWsMessage += Client_OnWsMessage; + Client.OnError += Client_OnError; + + Client.IntializeReceiver((string) p.streamId, + Context.GetDocumentName(), + "Revit", + Context.GetDocumentGuid(), + (string) p.account.apiToken); + + Objects = new List(); + } + + protected RevitReceiver(SerializationInfo info, StreamingContext context) + { + JsonConvert.DefaultSettings = () => new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + + Objects = new List(); + + var serialisedClient = Convert.FromBase64String(info.GetString("client")); + + using (var ms = new MemoryStream()) + { + ms.Write(serialisedClient, 0, serialisedClient.Length); + ms.Seek(0, SeekOrigin.Begin); + var bf = new BinaryFormatter + { + Binder = new SearchAssembliesBinder(Assembly.GetExecutingAssembly(), true) + }; + Client = (SpeckleApiClient)bf.Deserialize(ms); + StreamId = Client.StreamId; + } + + Client.OnReady += Client_OnReady; + Client.OnLogData += Client_OnLogData; + Client.OnWsMessage += Client_OnWsMessage; + Client.OnError += Client_OnError; + } + + public string GetClientId() + { + return Client.ClientId; + } + + public ClientRole GetRole() + { + return ClientRole.Receiver; + } + + #region Events + + private void Client_OnError( object source, SpeckleEventArgs e ) + { + Context.NotifySpeckleFrame( "client-error", StreamId, JsonConvert.SerializeObject( e.EventData ) ); + } + + public virtual void Client_OnLogData( object source, SpeckleEventArgs e ) + { + Context.NotifySpeckleFrame( "client-log", StreamId, JsonConvert.SerializeObject( e.EventData ) ); + } + + public virtual void Client_OnReady(object source, SpeckleEventArgs e) + { + Context.NotifySpeckleFrame("client-add", StreamId, JsonConvert.SerializeObject(new + { + client = Client, + stream = Client.Stream + })); + + Context.UserClients.Add(this); + UpdateGlobal(); + } + + public virtual void Client_OnWsMessage(object source, SpeckleEventArgs e) + { + if (Paused) + { + Context.NotifySpeckleFrame("client-expired", StreamId, ""); + return; + } + + switch ((string)e.EventObject.args.eventType) + { + case "update-global": + UpdateGlobal(); + break; + case "update-meta": + UpdateMeta(); + break; + case "update-name": + UpdateName(); + break; + case "update-object": + break; + case "update-children": + UpdateChildren(); + break; + default: + Context.NotifySpeckleFrame("client-log", StreamId, JsonConvert.SerializeObject("Unkown event: " + (string)e.EventObject.args.eventType)); + break; + } + } + #endregion + + #region Updates + + public void UpdateName() + { + try + { + var response = Client.StreamGetAsync(StreamId, "fields=name"); + Client.Stream.Name = response.Result.Resource.Name; + Context.NotifySpeckleFrame("client-metadata-update", StreamId, Client.Stream.ToJson()); + } + catch (Exception err) + { + Context.NotifySpeckleFrame("client-error", Client.Stream.StreamId, JsonConvert.SerializeObject(err.Message)); + Context.NotifySpeckleFrame("client-done-loading", StreamId, ""); + } + } + + public void UpdateMeta() + { + Context.NotifySpeckleFrame("client-log", StreamId, JsonConvert.SerializeObject("Metadata update received.")); + + try + { + var streamGetResponse = Client.StreamGetAsync(StreamId, null).Result; + + if (streamGetResponse.Success == false) + { + Context.NotifySpeckleFrame("client-error", StreamId, streamGetResponse.Message); + Context.NotifySpeckleFrame("client-log", StreamId, JsonConvert.SerializeObject("Failed to retrieve global update.")); + } + + Client.Stream = streamGetResponse.Resource; + + Context.NotifySpeckleFrame("client-metadata-update", StreamId, Client.Stream.ToJson()); + } + catch (Exception err) + { + Context.NotifySpeckleFrame("client-error", Client.Stream.StreamId, JsonConvert.SerializeObject(err.Message)); + Context.NotifySpeckleFrame("client-done-loading", StreamId, ""); + } + } + + public void UpdateGlobal() + { + Context.NotifySpeckleFrame("client-log", StreamId, JsonConvert.SerializeObject("Global update received.")); + + try + { + var streamGetResponse = Client.StreamGetAsync(StreamId, null).Result; + if (streamGetResponse.Success == false) + { + Context.NotifySpeckleFrame("client-error", StreamId, streamGetResponse.Message); + Context.NotifySpeckleFrame("client-log", StreamId, JsonConvert.SerializeObject("Failed to retrieve global update.")); + } + + Client.Stream = streamGetResponse.Resource; + Context.NotifySpeckleFrame("client-metadata-update", StreamId, Client.Stream.ToJson()); + Context.NotifySpeckleFrame("client-is-loading", StreamId, ""); + + // prepare payload + var payload = Client.Stream.Objects.Where(o => !Context.SpeckleObjectCache.ContainsKey(o._id)).Select(obj => obj._id).ToArray(); + var getObjectsResult = Client.ObjectGetBulkAsync(payload, "omit=displayValue").Result; + + if (getObjectsResult.Success == false) + Context.NotifySpeckleFrame("client-error", StreamId, streamGetResponse.Message); + + // add to cache + foreach (var obj in getObjectsResult.Resources) + { + Context.SpeckleObjectCache[obj._id] = obj; + } + + // populate real objects + Objects.Clear(); + foreach (var obj in Client.Stream.Objects) + { + Objects.Add(Context.SpeckleObjectCache[obj._id]); + } + + //DisplayContents(); + Context.NotifySpeckleFrame("client-done-loading", StreamId, ""); + } + catch (Exception err) + { + Context.NotifySpeckleFrame("client-error", Client.Stream.StreamId, JsonConvert.SerializeObject(err.Message)); + Context.NotifySpeckleFrame("client-done-loading", StreamId, ""); + } + } + + public void UpdateChildren() + { + try + { + var getStream = Client.StreamGetAsync(StreamId, null).Result; + Client.Stream = getStream.Resource; + + Context.NotifySpeckleFrame("client-children", StreamId, Client.Stream.ToJson()); + } + catch (Exception err) + { + Context.NotifySpeckleFrame("client-error", Client.Stream.StreamId, JsonConvert.SerializeObject(err.Message)); + Context.NotifySpeckleFrame("client-done-loading", StreamId, ""); + } + } + + #endregion + + #region Toggles + + public void TogglePaused(bool status) + { + Paused = status; + } + + public void ToggleVisibility(bool status) + { + //TODO: Implement + } + + public void ToggleLayerVisibility(string layerId, bool status) + { + //TODO: Implement + } + + public void ToggleLayerHover(string layerId, bool status) + { + //TODO: Implement + } + + #endregion + + #region Serialisation & Dispose + + public void Dispose() + { + Client.Dispose(); + } + + public void Dispose(bool delete = false) + { + Client.Dispose(delete); + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + using (var ms = new MemoryStream()) + { + var formatter = new BinaryFormatter(); + formatter.Serialize(ms, Client); + info.AddValue("client", Convert.ToBase64String(ms.ToArray())); + info.AddValue("paused", Paused); + info.AddValue("visible", Visible); + } + } + + #endregion + } +} diff --git a/SpeckleRevitPlugin/Classes/RevitSender.cs b/SpeckleRevitPlugin/Classes/RevitSender.cs new file mode 100644 index 0000000..3245713 --- /dev/null +++ b/SpeckleRevitPlugin/Classes/RevitSender.cs @@ -0,0 +1,573 @@ +#region Namespaces +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Dynamic; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Timers; +using Newtonsoft.Json; +using SpeckleCore; +#endregion + +namespace SpeckleRevitPlugin.Classes +{ + /// + /// Rhino Sender Client + /// + [Serializable] + public class RevitSender : ISpeckleRevitClient + { + public Interop Context { get; set; } + public SpeckleApiClient Client { get; private set; } + public List Objects { get; set; } + //public SpeckleDisplayConduit Display; + public string StreamId { get; set; } + public bool Paused { get; set; } = false; + public bool Visible { get; set; } = true; + Timer DataSender, MetadataSender; + public string StreamName; + public bool IsSendingUpdate = false, Expired = false; + + public RevitSender() + { + } + + public RevitSender(string _payload, Interop _Context) + { + Context = _Context; + + dynamic InitPayload = JsonConvert.DeserializeObject(_payload); + + Client = new SpeckleApiClient((string)InitPayload.account.restApi, true); + + StreamName = (string)InitPayload.streamName; + + SetClientEvents(); + //SetRhinoEvents(); + SetTimers(); + + //Display = new SpeckleDisplayConduit(); + //Display.Enabled = true; + + Context.NotifySpeckleFrame("set-gl-load", "", "true"); + + Client.IntializeSender((string)InitPayload.account.apiToken, Context.GetDocumentName(), "Rhino", Context.GetDocumentGuid()) + .ContinueWith(res => + { + StreamId = Client.Stream.StreamId; + Client.Stream.Name = StreamName; + + Context.NotifySpeckleFrame("set-gl-load", "", "false"); + Context.NotifySpeckleFrame("client-add", StreamId, JsonConvert.SerializeObject(new { stream = Client.Stream, client = Client })); + Context.UserClients.Add(this); + + InitTrackedObjects(InitPayload); + DataSender.Start(); + }); + + } + + public void InitTrackedObjects(dynamic payload) + { + //TODO: implement + //foreach (string guid in payload.selection) + // RhinoDoc.ActiveDoc.Objects.Find(new Guid(guid)).Attributes.SetUserString("spk_" + StreamId, StreamId); + } + + //public void AddTrackedObjects( string[ ] guids ) + //{ + // foreach ( string guid in guids ) + // RhinoDoc.ActiveDoc.Objects.Find( new Guid( guid ) ).Attributes.SetUserString( "spk_" + StreamId, StreamId ); + + // DataSender.Start(); + //} + + //public void RemoveTrackedObjects( string[ ] guids ) + //{ + // foreach ( string guid in guids ) + // RhinoDoc.ActiveDoc.Objects.Find( new Guid( guid ) ).Attributes.SetUserString( "spk_" + StreamId, null ); + + // DataSender.Start(); + //} + + //public void SetRhinoEvents( ) + //{ + // RhinoDoc.ModifyObjectAttributes += RhinoDoc_ModifyObjectAttributes; + // RhinoDoc.DeleteRhinoObject += RhinoDoc_DeleteRhinoObject; + // RhinoDoc.AddRhinoObject += RhinoDoc_AddRhinoObject; + // RhinoDoc.UndeleteRhinoObject += RhinoDoc_UndeleteRhinoObject; + // RhinoDoc.LayerTableEvent += RhinoDoc_LayerTableEvent; + //} + + //public void UnsetRhinoEvents( ) + //{ + // RhinoDoc.ModifyObjectAttributes -= RhinoDoc_ModifyObjectAttributes; + // RhinoDoc.DeleteRhinoObject -= RhinoDoc_DeleteRhinoObject; + // RhinoDoc.AddRhinoObject -= RhinoDoc_AddRhinoObject; + // RhinoDoc.UndeleteRhinoObject -= RhinoDoc_UndeleteRhinoObject; + // RhinoDoc.LayerTableEvent -= RhinoDoc_LayerTableEvent; + //} + + //private void RhinoDoc_LayerTableEvent( object sender, Rhino.DocObjects.Tables.LayerTableEventArgs e ) + //{ + // DataSender.Start(); + //} + + //private void RhinoDoc_UndeleteRhinoObject( object sender, RhinoObjectEventArgs e ) + //{ + // //Debug.WriteLine("UNDELETE Event"); + // if ( Paused ) + // { + // Context.NotifySpeckleFrame( "client-expired", StreamId, "" ); + // return; + // } + // if ( e.TheObject.Attributes.GetUserString( "spk_" + StreamId ) == StreamId ) + // { + // DataSender.Start(); + // } + //} + + //private void RhinoDoc_AddRhinoObject( object sender, RhinoObjectEventArgs e ) + //{ + // //Debug.WriteLine("ADD Event"); + // if ( Paused ) + // { + // Context.NotifySpeckleFrame( "client-expired", StreamId, "" ); + // return; + // } + // if ( e.TheObject.Attributes.GetUserString( "spk_" + StreamId ) == StreamId ) + // { + // DataSender.Start(); + // } + //} + + //private void RhinoDoc_DeleteRhinoObject( object sender, RhinoObjectEventArgs e ) + //{ + // if ( Paused ) + // { + // Context.NotifySpeckleFrame( "client-expired", StreamId, "" ); + // return; + // } + // if ( e.TheObject.Attributes.GetUserString( "spk_" + StreamId ) == StreamId ) + // { + // DataSender.Start(); + // } + //} + + //private void RhinoDoc_ModifyObjectAttributes( object sender, RhinoModifyObjectAttributesEventArgs e ) + //{ + // //Debug.WriteLine("MODIFY Event"); + // //Prevents https://github.com/speckleworks/SpeckleRhino/issues/51 from happening + // if ( Converter.getBase64( e.NewAttributes ) == Converter.getBase64( e.OldAttributes ) ) return; + + // if ( Paused ) + // { + // Context.NotifySpeckleFrame( "client-expired", StreamId, "" ); + // return; + // } + // if ( e.RhinoObject.Attributes.GetUserString( "spk_" + StreamId ) == StreamId ) + // { + // DataSender.Start(); + // } + //} + + public void SetClientEvents( ) + { + Client.OnError += Client_OnError; + Client.OnLogData += Client_OnLogData; + Client.OnWsMessage += Client_OnWsMessage; + Client.OnReady += Client_OnReady; + } + + public void SetTimers() + { + MetadataSender = new Timer(500) { AutoReset = false, Enabled = false }; + MetadataSender.Elapsed += MetadataSender_Elapsed; + + DataSender = new Timer(2000) { AutoReset = false, Enabled = false }; + DataSender.Elapsed += DataSender_Elapsed; + } + + private void Client_OnReady( object source, SpeckleEventArgs e ) + { + Context.NotifySpeckleFrame("client-log", StreamId, JsonConvert.SerializeObject("Ready Event.")); + } + + private void DataSender_Elapsed(object sender, ElapsedEventArgs e) + { + Debug.WriteLine("Boing! Boing!"); + DataSender.Stop(); + SendStaggeredUpdate(); + Context.NotifySpeckleFrame("client-log", StreamId, JsonConvert.SerializeObject("Update Sent.")); + } + + private void MetadataSender_Elapsed( object sender, ElapsedEventArgs e ) + { + Debug.WriteLine("Ping! Ping!"); + MetadataSender.Stop(); + Context.NotifySpeckleFrame("client-log", StreamId, JsonConvert.SerializeObject("Update Sent.")); + } + + private void Client_OnWsMessage( object source, SpeckleEventArgs e ) + { + Context.NotifySpeckleFrame("client-log", StreamId, JsonConvert.SerializeObject("WS message received and ignored.")); + } + + private void Client_OnLogData( object source, SpeckleEventArgs e ) + { + Context.NotifySpeckleFrame("client-log", StreamId, JsonConvert.SerializeObject(e.EventData)); + } + + private void Client_OnError( object source, SpeckleEventArgs e ) + { + Context.NotifySpeckleFrame("client-error", StreamId, JsonConvert.SerializeObject(e.EventData)); + } + + public void ForceUpdate() + { + SendStaggeredUpdate(true); + } + + public async void SendStaggeredUpdate(bool force = false) + { + //TODO: Implement! + //if (Paused && !force) + //{ + // Context.NotifySpeckleFrame("client-expired", StreamId, ""); + // return; + //} + + //if (IsSendingUpdate) + //{ + // Expired = true; + // return; + //} + + //IsSendingUpdate = true; + + //Context.NotifySpeckleFrame("client-is-loading", StreamId, ""); + + ////TODO: I am not sure how we are going to send stuff to Speckle just yet. + ////TODO: I only know that serializing the whole damn Revit Element is not a good idea. + //var objs = new Element[]{}; + ////var objs = RhinoDoc.ActiveDoc.Objects.FindByUserString("spk_" + this.StreamId, "*", false).OrderBy(obj => obj.Attributes.LayerIndex); + + //Context.NotifySpeckleFrame("client-progress-message", StreamId, "Converting " + objs.Count() + " objects..."); + + //List pLayers = new List(); + //List convertedObjects = new List(); + //List> objectUpdatePayloads = new List>(); + + //long totalBucketSize = 0; + //long currentBucketSize = 0; + //List currentBucketObjects = new List(); + //List allObjects = new List(); + + //int lindex = -1, count = 0, orderIndex = 0; + //foreach (RhinoObject obj in objs) + //{ + // // layer list creation + // Rhino.DocObjects.Layer layer = RhinoDoc.ActiveDoc.Layers[obj.Attributes.LayerIndex]; + // if (lindex != obj.Attributes.LayerIndex) + // { + // var spkLayer = new SpeckleCore.Layer() + // { + // Name = layer.FullPath, + // Guid = layer.Id.ToString(), + // ObjectCount = 1, + // StartIndex = count, + // OrderIndex = orderIndex++, + // Properties = new LayerProperties() { Color = new SpeckleCore.SpeckleBaseColor() { A = 1, Hex = System.Drawing.ColorTranslator.ToHtml(layer.Color) }, } + // }; + + // pLayers.Add(spkLayer); + // lindex = obj.Attributes.LayerIndex; + // } + // else + // { + // var spkl = pLayers.FirstOrDefault(pl => pl.Name == layer.FullPath); + // spkl.ObjectCount++; + // } + + // count++; + + // // object conversion + // SpeckleObject convertedObject; + + // convertedObject = Converter.Serialise(obj.Geometry); + // convertedObject.ApplicationId = obj.Id.ToString(); + // allObjects.Add(convertedObject); + + // Context.NotifySpeckleFrame("client-progress-message", StreamId, "Converted " + count + " objects out of " + objs.Count() + "."); + + // // check cache and see what the response from the server is when sending placeholders + // // in the ObjectCreateBulkAsyncRoute + // if (Context.SpeckleObjectCache.ContainsKey(convertedObject.Hash)) + // { + // convertedObject = new SpecklePlaceholder() { Hash = convertedObject.Hash, _id = Context.SpeckleObjectCache[convertedObject.Hash]._id, ApplicationId = Context.SpeckleObjectCache[convertedObject.Hash].ApplicationId }; + // } + + // // size checking & bulk object creation payloads creation + // long size = Converter.getBytes(convertedObject).Length; + // currentBucketSize += size; + // totalBucketSize += size; + // currentBucketObjects.Add(convertedObject); + + // if (currentBucketSize > 2e6) + // { + // // means we're around fooking bazillion mb of an upload. FAIL FAIL FAIL + // Context.NotifySpeckleFrame("client-error", StreamId, JsonConvert.SerializeObject("This stream contains a super big object. These are not supported yet :(")); + // Context.NotifySpeckleFrame("client-done-loading", StreamId, ""); + // IsSendingUpdate = false; + // return; + // } + + // if (currentBucketSize > 5e5) // restrict max to ~500kb; should it be user config? anyway these functions should go into core. at one point. + // { + // Debug.WriteLine("Reached payload limit. Making a new one, current #: " + objectUpdatePayloads.Count); + // objectUpdatePayloads.Add(currentBucketObjects); + // currentBucketObjects = new List(); + // currentBucketSize = 0; + // } + + // // catch overflows early + // if (totalBucketSize >= 50e6) + // { + // Context.NotifySpeckleFrame("client-error", StreamId, JsonConvert.SerializeObject("This is a humongous update, in the range of ~50mb. For now, create more streams instead of just one massive one! Updates will be faster and snappier, and you can combine them back together at the other end easier. " + totalBucketSize / 1000 + "(kb)")); + // IsSendingUpdate = false; + // Context.NotifySpeckleFrame("client-done-loading", StreamId, ""); + // return; + // } + //} + + //// last bucket + //if (currentBucketObjects.Count > 0) + // objectUpdatePayloads.Add(currentBucketObjects); + + //Debug.WriteLine("Finished, payload object update count is: " + objectUpdatePayloads.Count + " total bucket size is (kb) " + totalBucketSize / 1000); + + //if (objectUpdatePayloads.Count > 100 || totalBucketSize >= 50e6) + //{ + // // means we're around fooking bazillion mb of an upload. FAIL FAIL FAIL + // Context.NotifySpeckleFrame("client-error", StreamId, JsonConvert.SerializeObject("This is a humongous update, in the range of ~50mb. For now, create more streams instead of just one massive one! Updates will be faster and snappier, and you can combine them back together at the other end easier. " + totalBucketSize / 1000 + "(kb)")); + // IsSendingUpdate = false; + // Context.NotifySpeckleFrame("client-done-loading", StreamId, ""); + // return; + //} + + //// create bulk object creation tasks + //int k = 0; + //List responses = new List(); + //foreach (var payload in objectUpdatePayloads) + //{ + // Context.NotifySpeckleFrame("client-progress-message", StreamId, String.Format("Sending payload {0} out of {1}", k++, objectUpdatePayloads.Count)); + // try + // { + // responses.Add(await Client.ObjectCreateAsync(payload)); + // } + // catch (Exception err) + // { + // Context.NotifySpeckleFrame("client-error", Client.Stream.StreamId, JsonConvert.SerializeObject(err.Message)); + // Context.NotifySpeckleFrame("client-done-loading", StreamId, ""); + // IsSendingUpdate = false; + // return; + // } + //} + + //Context.NotifySpeckleFrame("client-progress-message", StreamId, "Updating stream..."); + + //// finalise layer creation + //foreach (var layer in pLayers) + // layer.Topology = "0-" + layer.ObjectCount + " "; + + //// create placeholders for stream update payload + //List placeholders = new List(); + //int m = 0; + //foreach (var myResponse in responses) + // foreach (var obj in myResponse.Resources) placeholders.Add(new SpecklePlaceholder() { _id = obj._id, ApplicationId = allObjects[m++].ApplicationId }); + + //// create stream update payload + //SpeckleStream streamUpdatePayload = new SpeckleStream(); + //streamUpdatePayload.Layers = pLayers; + //streamUpdatePayload.Objects = placeholders; + //streamUpdatePayload.Name = Client.Stream.Name; + + //// set some base properties (will be overwritten) + //var baseProps = new Dictionary(); + //baseProps["units"] = RhinoDoc.ActiveDoc.ModelUnitSystem.ToString(); + //baseProps["tolerance"] = RhinoDoc.ActiveDoc.ModelAbsoluteTolerance; + //baseProps["angleTolerance"] = RhinoDoc.ActiveDoc.ModelAngleToleranceRadians; + //streamUpdatePayload.BaseProperties = baseProps; + + //// push it to the server yo! + //ResponseBase response = null; + //try + //{ + // response = await Client.StreamUpdateAsync(Client.Stream.StreamId, streamUpdatePayload); + //} + //catch (Exception err) + //{ + // Context.NotifySpeckleFrame("client-error", Client.Stream.StreamId, JsonConvert.SerializeObject(err.Message)); + // IsSendingUpdate = false; + // return; + //} + + //// put the objects in the cache + //int l = 0; + + //foreach (var obj in streamUpdatePayload.Objects) + //{ + // obj._id = placeholders[l]._id; + // Context.SpeckleObjectCache[allObjects[l].Hash] = placeholders[l]; + // l++; + //} + + //// emit events, etc. + //Client.Stream.Layers = streamUpdatePayload.Layers.ToList(); + //Client.Stream.Objects = placeholders; + + //Context.NotifySpeckleFrame("client-metadata-update", StreamId, Client.Stream.ToJson()); + //Context.NotifySpeckleFrame("client-done-loading", StreamId, ""); + + //Client.BroadcastMessage(new { eventType = "update-global" }); + + //IsSendingUpdate = false; + //if (Expired) + //{ + // DataSender.Start(); + //} + //Expired = false; + } + + public ClientRole GetRole( ) + { + return ClientRole.Sender; + } + + public string GetClientId( ) + { + return Client.ClientId; + } + + public void TogglePaused( bool status ) + { + Paused = status; + } + + public void ToggleVisibility( bool status ) + { + Visible = status; + } + + //public void ToggleLayerHover( string layerId, bool status ) + //{ + // Debug.WriteLine( "OHAI: " + layerId + " " + status ); + // Display.Enabled = true; + // Display.Geometry = new List(); + // if ( !status ) + // { + // Display.HoverRange = new Interval( 0, 0 ); + // RhinoDoc.ActiveDoc.Views.Redraw(); + // return; + // } + + // int myLIndex = RhinoDoc.ActiveDoc.Layers.Find( new Guid( layerId ), true ); + + // var objs1 = RhinoDoc.ActiveDoc.Objects.FindByUserString( "spk_" + this.StreamId, "*", false ); + // var cop = objs1; + // var objs = objs1.OrderBy( obj => obj.Attributes.LayerIndex ).ToList(); + // var count = objs.Count; + // foreach ( var obj in objs ) + // { + // if ( obj.Attributes.LayerIndex == myLIndex ) + // Display.Geometry.Add( obj.Geometry ); + // } + + // Display.HoverRange = new Interval( 0, Display.Geometry.Count ); + // RhinoDoc.ActiveDoc.Views.Redraw(); + + //} + + public void ToggleLayerVisibility(string layerId, bool status) + { + //TODO: Implement + } + + public void ToggleLayerHover(string layerId, bool status) + { + //TODO: Implement + } + + public void Dispose(bool delete = false) + { + if (delete) + { + //TODO: Fix this! + //var objs = RhinoDoc.ActiveDoc.Objects.FindByUserString("spk_" + StreamId, "*", false); + //foreach (var o in objs) + // o.Attributes.SetUserString("spk_" + StreamId, null); + } + + DataSender.Dispose(); + MetadataSender.Dispose(); + //UnsetRhinoEvents(); + Client.Dispose(delete); + } + + public void Dispose() + { + DataSender.Dispose(); + MetadataSender.Dispose(); + //TODO: What events do we track if any? + //UnsetRhinoEvents(); + Client.Dispose(); + } + + public void CompleteDeserialisation(Interop _Context) + { + Context = _Context; + + Context.NotifySpeckleFrame("client-add", StreamId, JsonConvert.SerializeObject(new { stream = Client.Stream, client = Client })); + Context.UserClients.Add(this); + } + + //protected RhinoSender( SerializationInfo info, StreamingContext context ) + //{ + // JsonConvert.DefaultSettings = ( ) => new JsonSerializerSettings() + // { + // ContractResolver = new CamelCasePropertyNamesContractResolver() + // }; + + // byte[ ] serialisedClient = Convert.FromBase64String( ( string ) info.GetString( "client" ) ); + + // using ( var ms = new MemoryStream() ) + // { + // ms.Write( serialisedClient, 0, serialisedClient.Length ); + // ms.Seek( 0, SeekOrigin.Begin ); + // Client = ( SpeckleApiClient ) new BinaryFormatter().Deserialize( ms ); + // StreamId = Client.StreamId; + // } + + // SetClientEvents(); + // SetRhinoEvents(); + // SetTimers(); + + // Display = new SpeckleDisplayConduit(); + // Display.Enabled = true; + //} + + public void GetObjectData( SerializationInfo info, StreamingContext context ) + { + using (var ms = new MemoryStream()) + { + var formatter = new BinaryFormatter(); + formatter.Serialize(ms, Client); + info.AddValue("client", Convert.ToBase64String(ms.ToArray())); + info.AddValue("paused", Paused); + info.AddValue("visible", Visible); + } + } + } +} diff --git a/SpeckleRevitPlugin/Classes/SettingsHelper.cs b/SpeckleRevitPlugin/Classes/SettingsHelper.cs index a8fa48e..d8812f6 100644 --- a/SpeckleRevitPlugin/Classes/SettingsHelper.cs +++ b/SpeckleRevitPlugin/Classes/SettingsHelper.cs @@ -1,112 +1,127 @@ #region Namespaces -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - using Autodesk.Revit.ApplicationServices; using Autodesk.Revit.DB; using Autodesk.Revit.UI; -using Autodesk.Revit.UI.Selection; +using SpeckleRevitPlugin.Entry; #endregion -namespace SpeckleRevitPlugin +namespace SpeckleRevitPlugin.Classes { - public class SettingsHelper - { - private ExternalCommandData _cmd; - private Application _app; - private Document _doc; - - /// - /// Constructor - /// - /// - public SettingsHelper(ExternalCommandData cmd) - { - _doc = null; - _cmd = cmd; - } - - /// - /// Constructor - from application (events) - /// - /// - /// - public SettingsHelper(Document d, Application a) - { - _app = a; - _doc = d; - } - - #region Command Data - /// - /// Revit UI application - /// - public UIApplication UiApp - { - get - { - try - { - return _cmd.Application; - } - catch { } - return null; - } - } - - /// - /// Revit Application - /// - public Application App - { - get - { - try - { - return _cmd.Application.Application; - } - catch { } - return null; - } - } - - /// - /// Revit UI Document - /// - public UIDocument UiDoc - { - get - { - try - { - return _cmd.Application.ActiveUIDocument; - } - catch { } - return null; - } - } - - /// - /// Revit Document - /// - public Document ActiveDoc - { - get - { - try - { - return UiDoc.Document; - } - catch { } - return null; - } - } - #endregion - - #region Public Properties - Modeless - internal EnumCommandType CommandType { get; set; } - #endregion - } -} \ No newline at end of file + //public class SettingsHelper + //{ + // //private readonly ExternalCommandData _cmd; + // private Application _app; + // private Document _doc; + + // ///// + // ///// Constructor + // ///// + // ///// + // //public SettingsHelper(ExternalCommandData cmd) + // //{ + // // _doc = null; + // // _cmd = cmd; + // //} + + // /// + // /// Constructor - from application (events) + // /// + // /// + // /// + // public SettingsHelper(Document d, Application a) + // { + // _app = a; + // _doc = d; + // } + + // #region Command Data + + // /// + // /// Revit UI application + // /// + // public UIApplication UiApp + // { + // get + // { + // try + // { + // return _doc.Application.; + // } + // catch + // { + // // ignored + // } + + // return null; + // } + // } + + // /// + // /// Revit Application + // /// + // public Application App + // { + // get + // { + // try + // { + // return _cmd.Application.Application; + // } + // catch + // { + // // ignored + // } + + // return null; + // } + // } + + // /// + // /// Revit UI Document + // /// + // public UIDocument UiDoc + // { + // get + // { + // try + // { + // return _cmd.Application.ActiveUIDocument; + // } + // catch + // { + // // ignored + // } + + // return null; + // } + // } + + // /// + // /// Revit Document + // /// + // public Document ActiveDoc + // { + // get + // { + // try + // { + // return UiDoc.Document; + // } + // catch + // { + // // ignored + // } + + // return null; + // } + // } + + // #endregion + + // #region Public Properties - Modeless + + // internal EnumCommandType CommandType { get; set; } + + // #endregion + //} +} diff --git a/SpeckleRevitPlugin/Classes/SpeckleAccount.cs b/SpeckleRevitPlugin/Classes/SpeckleAccount.cs new file mode 100644 index 0000000..9949c4c --- /dev/null +++ b/SpeckleRevitPlugin/Classes/SpeckleAccount.cs @@ -0,0 +1,12 @@ +namespace SpeckleRevitPlugin.Classes +{ + internal 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; } + } +} diff --git a/SpeckleRevitPlugin/Entry/AppMain.cs b/SpeckleRevitPlugin/Entry/AppMain.cs index a959ab2..46251b6 100644 --- a/SpeckleRevitPlugin/Entry/AppMain.cs +++ b/SpeckleRevitPlugin/Entry/AppMain.cs @@ -1,65 +1,134 @@ #region Namespaces -using Microsoft.VisualBasic; + using System; -using System.Collections; -using System.Collections.Generic; -using System.Data; using System.Diagnostics; -using System.Windows.Media.Imaging; using System.IO; +using System.Linq; using System.Reflection; using System.Windows.Media; - -using Autodesk.Revit.ApplicationServices; +using System.Windows.Media.Imaging; using Autodesk.Revit.Attributes; +using Autodesk.Revit.DB.Events; using Autodesk.Revit.UI; -using Autodesk.Revit.UI.Events; +using SpeckleRevitPlugin.Classes; +using SpeckleRevitPlugin.UI; + #endregion -namespace SpeckleRevitPlugin +namespace SpeckleRevitPlugin.Entry { [Transaction(TransactionMode.Manual)] - class AppMain : IExternalApplication + public class AppMain : IExternalApplication { - static string m_Path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - static UIControlledApplication uiApp; - static AppMain _thisApp; - - internal static form_MainDock MainDock; + private static readonly string m_Path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + private static AppMain _thisApp; internal DockablePaneProviderData DockData; - internal static SettingsHelper Settings { get; set; } - #region Setup + public static FormMainDock MainDock; + public static UIControlledApplication uiApp; + public static ExternalEvent SpeckleEvent; + public static SpeckleRequestHandler SpeckleHandler = new SpeckleRequestHandler(); + + public Result OnStartup(UIControlledApplication a) + { + try + { + uiApp = a; + _thisApp = this; + + if (MainDock == null) + { + MainDock = new FormMainDock(); + DockData = new DockablePaneProviderData + { + FrameworkElement = MainDock, + InitialState = new DockablePaneState + { + DockPosition = DockPosition.Right + } + }; + } + + uiApp.RegisterDockablePane(GlobalHelper.MainDockablePaneId, GlobalHelper.MainPanelName(), MainDock); + + // (Konrad) We are going to use this External Event Handler all across Speckle + // It's best to keep it on the main app, and keep it public. + SpeckleHandler = new SpeckleRequestHandler(); + SpeckleEvent = ExternalEvent.Create(SpeckleHandler); + + a.ControlledApplication.DocumentCreated += OnDocumentCreated; + a.ControlledApplication.DocumentOpened += OnDocumentOpened; + + AddRibbonPanel(a); + + return Result.Succeeded; + } + catch + { + return Result.Failed; + } + } + + private void OnDocumentOpened(object sender, DocumentOpenedEventArgs e) + { + var doc = e.Document; + if (doc == null || doc.IsFamilyDocument) + { + HideDockablePane(); + } + } + + private void OnDocumentCreated(object sender, DocumentCreatedEventArgs e) + { + var doc = e.Document; + if (doc == null || doc.IsFamilyDocument) + { + HideDockablePane(); + } + } + + public Result OnShutdown(UIControlledApplication a) + { + a.ControlledApplication.DocumentCreated -= OnDocumentCreated; + a.ControlledApplication.DocumentOpened -= OnDocumentOpened; + + return Result.Succeeded; + } + + #region Ribbon Utilities + /// /// Load an Image Source from File /// /// /// /// - private ImageSource LoadPngImgSource(string SourceName) + private static ImageSource LoadPngImgSource(string SourceName) { try { // Assembly - Assembly assembly = Assembly.GetExecutingAssembly(); + var assembly = Assembly.GetExecutingAssembly(); // Stream - Stream icon = assembly.GetManifestResourceStream(SourceName); + var icon = assembly.GetManifestResourceStream(SourceName); // Decoder - PngBitmapDecoder decoder = new PngBitmapDecoder(icon, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default); + if (icon != null) + { + var decoder = new PngBitmapDecoder(icon, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default); - // Source - ImageSource m_source = decoder.Frames[0]; - return (m_source); + // Source + ImageSource m_source = decoder.Frames[0]; + return (m_source); + } } catch { + // ignored } - // Fail return null; - } /// @@ -71,7 +140,6 @@ public void AddRibbonPanel(UIControlledApplication a) { try { - // First Create the Tab a.CreateRibbonTab("Speckle"); } catch @@ -80,152 +148,83 @@ public void AddRibbonPanel(UIControlledApplication a) } // Tools - AddButton("Speckle", - "Plugin\r\nTest", - "Plugin\r\nTest", - "SpeckleRevitPlugin.Resources.Template_16.png", - "SpeckleRevitPlugin.Resources.Template_32.png", - (m_Path + "\\SpeckleRevitPlugin.dll"), - "SpeckleRevitPlugin.ExtCmd", + AddButton("Speckle", + "Show\r\nSpeckle", + "Show\r\nSpeckle", + "SpeckleRevitPlugin.Resources.Template_16.png", + "SpeckleRevitPlugin.Resources.Template_32.png", + (m_Path + "\\SpeckleRevitPlugin.dll"), + "SpeckleRevitPlugin.Entry.ExtCmd", "Speckle connection test for Revit."); + + AddButton("Tools", + "Speckle\r\nWall", + "Speckle\r\nWall", + "SpeckleRevitPlugin.Resources.Template_16.png", + "SpeckleRevitPlugin.Resources.Template_32.png", + (m_Path + "\\SpeckleRevitPlugin.dll"), + "SpeckleRevitPlugin.Tools.WallTool.SpeckleWallCmd", + "Make Walls in Revit using Speckle Streams."); } /// /// Add a button to a Ribbon Tab /// - /// The name of the ribbon panel - /// The Name of the Button - /// Command Text - /// Small Image - /// Large Image + /// The name of the ribbon panel + /// The Name of the Button + /// Command Text + /// Small Image + /// Large Image /// Path to the DLL file /// Full qualified class descriptor - /// Tooltip to add to the button + /// Tooltip to add to the button /// /// - private bool AddButton(string Rpanel, string ButtonName, string ButtonText, string ImagePath16, string ImagePath32, string dllPath, string dllClass, string Tooltip) + private static void AddButton(string panel, + string bName, string bText, string iPath16, string iPath32, + string dllPath, string dllClass, string tooltip) { try { - // The Ribbon Panel - RibbonPanel ribbonPanel = null; - - // Find the Panel within the Case Tab - List rp = new List(); - rp = uiApp.GetRibbonPanels("Speckle"); - foreach (RibbonPanel x in rp) - { - if (x.Name.ToUpper() == Rpanel.ToUpper()) - { - ribbonPanel = x; - } - } - // Create the Panel if it doesn't Exist - if (ribbonPanel == null) - { - ribbonPanel = uiApp.CreateRibbonPanel("Speckle", Rpanel); - } + var ribbonPanel = + uiApp.GetRibbonPanels("Speckle").FirstOrDefault(x => + string.Equals(x.Name, panel, StringComparison.OrdinalIgnoreCase)) ?? + uiApp.CreateRibbonPanel("Speckle", panel); // Create the Pushbutton Data - PushButtonData pushButtonData = new PushButtonData(ButtonName, ButtonText, dllPath, dllClass); - if (!string.IsNullOrEmpty(ImagePath16)) + var pushButtonData = new PushButtonData(bName, bText, dllPath, dllClass); + if (!string.IsNullOrEmpty(iPath16)) { try { - pushButtonData.Image = LoadPngImgSource(ImagePath16); + pushButtonData.Image = LoadPngImgSource(iPath16); } catch { Debug.WriteLine("Image not found", "SPK"); } } - if (!string.IsNullOrEmpty(ImagePath32)) + if (!string.IsNullOrEmpty(iPath32)) { try { - pushButtonData.LargeImage = LoadPngImgSource(ImagePath32); + pushButtonData.LargeImage = LoadPngImgSource(iPath32); } catch { Debug.WriteLine("Image not found", "SPK"); } } - pushButtonData.ToolTip = Tooltip; + pushButtonData.ToolTip = tooltip; // Add the button to the tab - PushButton pushButtonDataAdd = (PushButton)ribbonPanel.AddItem(pushButtonData); + var unused = (PushButton)ribbonPanel.AddItem(pushButtonData); } catch { - // Quiet Fail - } - return true; - } - #endregion - - #region Startup - public Result OnStartup(UIControlledApplication a) - { - try - { - // The Shared uiApp variable - uiApp = a; - _thisApp = this; - - // Register the dockable pane - if (MainDock == null) - { - MainDock = new form_MainDock(); - DockData = new DockablePaneProviderData - { - FrameworkElement = MainDock, - InitialState = new DockablePaneState - { - DockPosition = DockPosition.Right - } - }; - } - uiApp.RegisterDockablePane(GlobalHelper.MainDockablePaneId, GlobalHelper.MainPanelName(), MainDock as IDockablePaneProvider); - - // Detect when a new model is in focus - a.ViewActivated += OnViewActivated; - - // Add the Ribbon Panel!! - AddRibbonPanel(a); - - // Return Success - return Result.Succeeded; - - } - catch { return Result.Failed; } - } - #endregion - - #region Shutdown - public Result OnShutdown(UIControlledApplication a) - { - return Result.Succeeded; - } - #endregion - - #region Internal Members - Events - /// - /// View change will detect a model change - /// - /// - /// - void OnViewActivated(object sender, ViewActivatedEventArgs e) - { - try - { - if (Settings == null) Settings = new SettingsHelper(e.Document, null); - if (Settings.App == null) - { - HideDockablePane(); - } + // ignored } - catch { } } /// @@ -235,11 +234,15 @@ internal void HideDockablePane() { try { - DockablePane m_dp = uiApp.GetDockablePane(GlobalHelper.MainDockablePaneId); + var m_dp = uiApp.GetDockablePane(GlobalHelper.MainDockablePaneId); m_dp.Hide(); } - catch { } + catch + { + // ignored + } } + #endregion } } diff --git a/SpeckleRevitPlugin/Entry/ExtCmd.cs b/SpeckleRevitPlugin/Entry/ExtCmd.cs index cfc91c6..4cdc176 100644 --- a/SpeckleRevitPlugin/Entry/ExtCmd.cs +++ b/SpeckleRevitPlugin/Entry/ExtCmd.cs @@ -1,18 +1,14 @@ #region Namespaces - using Autodesk.Revit.Attributes; using Autodesk.Revit.DB; using Autodesk.Revit.UI; - #endregion -namespace SpeckleRevitPlugin +namespace SpeckleRevitPlugin.Entry { [Transaction(TransactionMode.Manual)] public class ExtCmd : IExternalCommand { - //public static ChromiumWebBrowser Browser; - public Result Execute( ExternalCommandData commandData, ref string message, @@ -20,11 +16,11 @@ public Result Execute( { // Settings Helper Class // NOTE: You can use the AppMain.Settings throughout project to access active Revit Document, UIDocument, Application, etc. - AppMain.Settings = new SettingsHelper(commandData); + //AppMain.Settings = new SettingsHelper(commandData); // SHOW DOCKABLE WINDOW - DockablePaneId m_dpID = GlobalHelper.MainDockablePaneId; - DockablePane m_dp = commandData.Application.GetDockablePane(m_dpID); + var m_dpID = GlobalHelper.MainDockablePaneId; + var m_dp = commandData.Application.GetDockablePane(m_dpID); m_dp.Show(); diff --git a/SpeckleRevitPlugin/Entry/ExtCmdModeless.cs b/SpeckleRevitPlugin/Entry/ExtCmdModeless.cs deleted file mode 100644 index d31f859..0000000 --- a/SpeckleRevitPlugin/Entry/ExtCmdModeless.cs +++ /dev/null @@ -1,108 +0,0 @@ -#region Namespaces -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; - -using Autodesk.Revit.DB; -using Autodesk.Revit.UI; -#endregion - -namespace SpeckleRevitPlugin -{ - public enum EnumCommandType : int - { - Command1, - Command2, - Command3 - } - - /// - /// Model Updates for Modeless Dialog - /// - /// - class ExtCmdModeless : IExternalEventHandler - { - /// - /// Model Updates for Modeless Dialog - /// - /// - public void Execute(UIApplication uiapp) - { - switch (AppMain.Settings.CommandType) - { - case EnumCommandType.Command1: - SampleCommand1(uiapp); - break; - case EnumCommandType.Command2: - SampleCommand2(uiapp); - break; - case EnumCommandType.Command3: - SampleCommand3(uiapp); - break; - } - } - - /// - /// Command 1 - /// - /// - private void SampleCommand1(UIApplication uiapp) - { - try - { - using (Transaction t = new Transaction(AppMain.Settings.ActiveDoc, "Do something")) - { - t.Start(); - - t.Commit(); - } - } - catch { } - } - - /// - /// Command 2 - /// - /// - private void SampleCommand2(UIApplication uiapp) - { - try - { - using (Transaction t = new Transaction(AppMain.Settings.ActiveDoc, "Do something")) - { - t.Start(); - - t.Commit(); - } - } - catch { } - } - - /// - /// Command 3 - /// - /// - private void SampleCommand3(UIApplication uiapp) - { - try - { - using (Transaction t = new Transaction(AppMain.Settings.ActiveDoc, "Do something")) - { - t.Start(); - - t.Commit(); - } - } - catch { } - } - - /// - /// External event anme - /// - /// - public string GetName() { return "ExtCmdModeless"; } - } -} diff --git a/SpeckleRevitPlugin/Entry/SpeckleRequestHandler.cs b/SpeckleRevitPlugin/Entry/SpeckleRequestHandler.cs new file mode 100644 index 0000000..2e27567 --- /dev/null +++ b/SpeckleRevitPlugin/Entry/SpeckleRequestHandler.cs @@ -0,0 +1,130 @@ +#region Namespaces +using System; +using System.Collections.Generic; +using System.Threading; +using Autodesk.Revit.DB; +using Autodesk.Revit.UI; +using SpeckleRevitPlugin.Utilities; +#endregion + +namespace SpeckleRevitPlugin.Entry +{ + public class SpeckleRequestHandler : IExternalEventHandler + { + public SpeckleRequest Request { get; set; } = new SpeckleRequest(); + public object Arg1 { get; set; } + public object Arg2 { get; set; } + + public delegate void ClientsRetrieved(IDictionary receivers, IDictionary senders); + public static event ClientsRetrieved OnClientsRetrieved; + + public void Execute(UIApplication uiapp) + { + try + { + switch (Request.Take()) + { + case SpeckleCommandType.None: + return; + case SpeckleCommandType.GetClients: + GetClients(uiapp); + break; + case SpeckleCommandType.SaveClients: + SaveClients(uiapp); + break; + case SpeckleCommandType.Command3: + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + + /// + /// + /// + /// + private static void GetClients(UIApplication app) + { + if (!SchemaUtilities.SchemaExist(Properties.Resources.SchemaName)) return; + + var doc = app.ActiveUIDocument.Document; + var schema = SchemaUtilities.GetSchema(Properties.Resources.SchemaName); + var pInfo = SchemaUtilities.GetProjectInfo(doc); + var receivers = pInfo.GetEntity(schema).Get>(schema.GetField("receivers")); + var senders = pInfo.GetEntity(schema).Get>(schema.GetField("senders")); + + OnClientsRetrieved?.Invoke(receivers, senders); + } + + /// + /// + /// + /// + private void SaveClients(UIApplication app) + { + // (Konrad) Extract the variables used by this method + var senders = Arg1 as Dictionary; + var receivers = Arg2 as Dictionary; + + var doc = app.ActiveUIDocument.Document; + var pInfo = SchemaUtilities.GetProjectInfo(doc); + var schemaExists = SchemaUtilities.SchemaExist(Properties.Resources.SchemaName); + var schema = schemaExists + ? SchemaUtilities.GetSchema(Properties.Resources.SchemaName) + : SchemaUtilities.CreateSchema(); + + using (var trans = new Transaction(doc, "Store Clients")) + { + trans.Start(); + + if (schemaExists) + { + SchemaUtilities.UpdateSchemaEntity(schema, pInfo, "senders", senders); + SchemaUtilities.UpdateSchemaEntity(schema, pInfo, "receivers", receivers); + } + else + { + SchemaUtilities.AddSchemaEntity(schema, pInfo, "senders", senders); + SchemaUtilities.AddSchemaEntity(schema, pInfo, "receivers", receivers); + } + + trans.Commit(); + } + } + + /// + /// External event name + /// + /// + public string GetName() { return "SpeckleRequestHandler"; } + } + + public class SpeckleRequest + { + private int _request = (int)SpeckleCommandType.None; + + public SpeckleCommandType Take() + { + return (SpeckleCommandType)Interlocked.Exchange(ref _request, (int)SpeckleCommandType.None); + } + + public void Make(SpeckleCommandType request) + { + Interlocked.Exchange(ref _request, (int)request); + } + } + + public enum SpeckleCommandType + { + None, + GetClients, + SaveClients, + Command3 + } +} diff --git a/SpeckleRevitPlugin/Properties/Resources.Designer.cs b/SpeckleRevitPlugin/Properties/Resources.Designer.cs index 23f25c5..8eb7081 100644 --- a/SpeckleRevitPlugin/Properties/Resources.Designer.cs +++ b/SpeckleRevitPlugin/Properties/Resources.Designer.cs @@ -22,7 +22,7 @@ namespace SpeckleRevitPlugin.Properties { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { + public class Resources { private static global::System.Resources.ResourceManager resourceMan; @@ -36,7 +36,7 @@ internal Resources() { /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SpeckleRevitPlugin.Properties.Resources", typeof(Resources).Assembly); @@ -51,7 +51,7 @@ internal Resources() { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -61,22 +61,20 @@ internal Resources() { } /// - /// Looks up a localized resource of type System.Drawing.Bitmap. + /// Looks up a localized string similar to a2666a4c-0eed-4f9c-b4da-7a9d97e1ce40. /// - internal static System.Drawing.Bitmap Template_16 { + public static string SchemaId { get { - object obj = ResourceManager.GetObject("Template_16", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); + return ResourceManager.GetString("SchemaId", resourceCulture); } } /// - /// Looks up a localized resource of type System.Drawing.Bitmap. + /// Looks up a localized string similar to SpeckleSchema. /// - internal static System.Drawing.Bitmap Template_32 { + public static string SchemaName { get { - object obj = ResourceManager.GetObject("Template_32", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); + return ResourceManager.GetString("SchemaName", resourceCulture); } } } diff --git a/SpeckleRevitPlugin/Properties/Resources.resx b/SpeckleRevitPlugin/Properties/Resources.resx index 1af7de1..573c813 100644 --- a/SpeckleRevitPlugin/Properties/Resources.resx +++ b/SpeckleRevitPlugin/Properties/Resources.resx @@ -117,4 +117,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + a2666a4c-0eed-4f9c-b4da-7a9d97e1ce40 + + + SpeckleSchema + \ No newline at end of file diff --git a/SpeckleRevitPlugin/SpeckleRevitPlugin.addin b/SpeckleRevitPlugin/SpeckleRevitPlugin.addin index 7d1d9ec..1b05284 100644 --- a/SpeckleRevitPlugin/SpeckleRevitPlugin.addin +++ b/SpeckleRevitPlugin/SpeckleRevitPlugin.addin @@ -1,18 +1,9 @@  - - Command SpeckleRevitPlugin - Speckle plugin for Revit - .\Speckle\SpeckleRevitPlugin.dll - SpeckleRevitPlugin.ExtCmd - 76028dcb-3f9b-4a5b-9e8b-36b7a5564a56 - Speckle - http://speckle.works/ - Application SpeckleRevitPlugin - .\Speckle\SpeckleRevitPlugin.dll - SpeckleRevitPlugin.AppMain + Speckle\SpeckleRevitPlugin.dll + SpeckleRevitPlugin.Entry.AppMain 0a03f52e-0ee0-4c19-82c4-8181626ec816 Speckle http://speckle.works/ diff --git a/SpeckleRevitPlugin/SpeckleRevitPlugin.csproj b/SpeckleRevitPlugin/SpeckleRevitPlugin.csproj index 17d0bbd..23cfe1f 100644 --- a/SpeckleRevitPlugin/SpeckleRevitPlugin.csproj +++ b/SpeckleRevitPlugin/SpeckleRevitPlugin.csproj @@ -5,6 +5,7 @@ Debug AnyCPU {EA37FC7B-0CD7-407B-A6AD-C594E4BD2043} + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Library Properties SpeckleRevitPlugin @@ -51,17 +52,59 @@ + + + + + - + + + True + True + Resources.resx + + + + BooleanSelector.xaml + + + + + + ElementSelector.xaml + + + + + + InputControl.xaml + + + + + SpeckleWallView.xaml + + form_Main.xaml form_MainDock.xaml + + StreamSelector.xaml + + + + + + + + @@ -69,11 +112,32 @@ Designer - + + PublicResXFileCodeGenerator + Resources.Designer.cs + - 63.0.2 + 63.0.1 + + + 3.0.40218 + + + 1.2.4 + + + 1.1.3 + + + 2.3.0.823 + + + 0.0.10 + + + 5.3.0 11.0.1 @@ -83,6 +147,22 @@ + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -91,6 +171,16 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + + + + + {cfe27d3d-8a1a-43f9-9387-8fd9e119e174} + SpeckleCore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpeckleRevitPlugin/Tools/WallTool/SpeckleWallView.xaml.cs b/SpeckleRevitPlugin/Tools/WallTool/SpeckleWallView.xaml.cs new file mode 100644 index 0000000..dba78fc --- /dev/null +++ b/SpeckleRevitPlugin/Tools/WallTool/SpeckleWallView.xaml.cs @@ -0,0 +1,29 @@ +using MaterialDesignThemes.Wpf; +using MaterialDesignColors; +using MaterialDesignThemes.MahApps; +using MahApps.Metro; +using MahApps.Metro.Controls; + +namespace SpeckleRevitPlugin.Tools.WallTool +{ + /// + /// Interaction logic for SpeckleWallView.xaml + /// + public partial class SpeckleWallView : MetroWindow + { + public SpeckleWallView() + { + InitializeComponent(); + + //TODO: How do we force these assemblies to be resolved properly and loaded properly? + //TODO: We can get an installer and copy these to locations that would enforce this. + // (Konrad) We need to make sure that MaterialDesignThemes and MahApps + // are actually loaded into the context. These DLLs are missing if they are not explicitly + // loaded into the app via using statement. + var unused1 = typeof(FlyoutAssist); + var unused2 = typeof(ThemeManager); + var unused3 = typeof(Hue); + var unused4 = typeof(ShadowAssist); + } + } +} diff --git a/SpeckleRevitPlugin/Tools/WallTool/SpeckleWallViewModel.cs b/SpeckleRevitPlugin/Tools/WallTool/SpeckleWallViewModel.cs new file mode 100644 index 0000000..a6ff6ed --- /dev/null +++ b/SpeckleRevitPlugin/Tools/WallTool/SpeckleWallViewModel.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Autodesk.Revit.DB; +using GalaSoft.MvvmLight; +using GalaSoft.MvvmLight.Command; +using GalaSoft.MvvmLight.Messaging; +using MahApps.Metro.Controls; +using SpeckleRevitPlugin.UI; + +namespace SpeckleRevitPlugin.Tools.WallTool +{ + public class SpeckleWallViewModel : ViewModelBase + { + #region Parameters + + private SpeckleWallModel Model { get; set; } + public RelayCommand CloseFlyout { get; set; } + public RelayCommand AddInput { get; set; } + public RelayCommand WindowClosed { get; set; } + + private ObservableCollection _inputs; + public ObservableCollection Inputs + { + get { return _inputs; } + set { _inputs = value; RaisePropertyChanged(() => Inputs); } + } + + private ObservableCollection _availableInputs; + public ObservableCollection AvailableInputs + { + get { return _availableInputs; } + set { _availableInputs = value; RaisePropertyChanged(() => AvailableInputs); } + } + + private InputWrapper _selectedInput; + public InputWrapper SelectedInput + { + get { return _selectedInput; } + set { _selectedInput = value; RaisePropertyChanged(() => SelectedInput); } + } + + private bool _addInputChecked; + public bool AddInputChecked + { + get { return _addInputChecked; } + set { _addInputChecked = value; RaisePropertyChanged(() => AddInputChecked); } + } + + #endregion + + public SpeckleWallViewModel(SpeckleWallModel model) + { + Model = model; + AvailableInputs = Model.GetAllAvailableInputs(); + + CloseFlyout = new RelayCommand(OnCloseFlyout); + AddInput = new RelayCommand(OnAddInput); + WindowClosed = new RelayCommand(OnWindowClosed); + + Inputs = new ObservableCollection + { + // (Konrad) Forces the Curve input to only use Speckle Stream as source of data. + new InputViewModel(new InputModel(), new InputWrapper + { + Name = "Curve", + AcceptsLocalData = false, + IsRequired = true + }), + //new InputViewModel(new InputModel(), new InputWrapper + //{ + // Name = "Level", + // AcceptsLocalData = true, + // StorageType = LocalDataType.Element + //}), + //new InputViewModel(new InputModel(), new InputWrapper + //{ + // Name = "Structural", + // AcceptsLocalData = true, + // StorageType = LocalDataType.Boolean + //}) + }; + + Messenger.Default.Register(this, OnInputDeleted); + } + + private void OnWindowClosed(MetroWindow obj) + { + // (Konrad) Removes all Messanger bindings. + Cleanup(); + } + + private void OnInputDeleted(InputDeleted obj) + { + // (Konrad) Restore the input in the dropdown and cleanup + AvailableInputs.Add(obj.InputViewModel.Input); + Inputs.Remove(obj.InputViewModel); + } + + private void OnAddInput() + { + if (SelectedInput == null) return; + + OnCloseFlyout(); + + Inputs.Add(new InputViewModel(new InputModel(), SelectedInput)); + + // (Konrad) Cleanup Available Inputs + AvailableInputs.Remove(SelectedInput); + SelectedInput = null; + } + + private void OnCloseFlyout() + { + AddInputChecked = false; + } + } +} diff --git a/SpeckleRevitPlugin/UI/BooleanSelector/BooleanSelector.xaml b/SpeckleRevitPlugin/UI/BooleanSelector/BooleanSelector.xaml new file mode 100644 index 0000000..3096148 --- /dev/null +++ b/SpeckleRevitPlugin/UI/BooleanSelector/BooleanSelector.xaml @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/SpeckleRevitPlugin/UI/BooleanSelector/BooleanSelector.xaml.cs b/SpeckleRevitPlugin/UI/BooleanSelector/BooleanSelector.xaml.cs new file mode 100644 index 0000000..e0dac50 --- /dev/null +++ b/SpeckleRevitPlugin/UI/BooleanSelector/BooleanSelector.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace SpeckleRevitPlugin.UI.BooleanSelector +{ + /// + /// Interaction logic for BooleanSelector.xaml + /// + public partial class BooleanSelector : UserControl + { + public BooleanSelector() + { + InitializeComponent(); + } + } +} diff --git a/SpeckleRevitPlugin/UI/BooleanSelector/BooleanSelectorModel.cs b/SpeckleRevitPlugin/UI/BooleanSelector/BooleanSelectorModel.cs new file mode 100644 index 0000000..ec7fbaa --- /dev/null +++ b/SpeckleRevitPlugin/UI/BooleanSelector/BooleanSelectorModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SpeckleRevitPlugin.UI.BooleanSelector +{ + public class BooleanSelectorModel + { + } +} diff --git a/SpeckleRevitPlugin/UI/BooleanSelector/BooleanSelectorViewModel.cs b/SpeckleRevitPlugin/UI/BooleanSelector/BooleanSelectorViewModel.cs new file mode 100644 index 0000000..4ce7698 --- /dev/null +++ b/SpeckleRevitPlugin/UI/BooleanSelector/BooleanSelectorViewModel.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using GalaSoft.MvvmLight; + +namespace SpeckleRevitPlugin.UI.BooleanSelector +{ + public class BooleanSelectorViewModel : ViewModelBase + { + public BooleanSelectorModel Model { get; set; } + + public BooleanSelectorViewModel(BooleanSelectorModel model) + { + Model = model; + } + } +} diff --git a/SpeckleRevitPlugin/UI/Converters.cs b/SpeckleRevitPlugin/UI/Converters.cs new file mode 100644 index 0000000..5b668da --- /dev/null +++ b/SpeckleRevitPlugin/UI/Converters.cs @@ -0,0 +1,53 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using GalaSoft.MvvmLight.Command; + +namespace SpeckleRevitPlugin.UI +{ + /// + /// + /// + public class SelectionChangedToSpeckleStreamConverter : IEventArgsConverter + { + public object Convert(object value, object parameter) + { + var items = ((SelectionChangedEventArgs) value).AddedItems; + return items.Count > 0 ? items[0] : null; + } + } + + /// + /// Inverse of the BooleanToVisibilityConverter. Hides elements when Boolean is True. + /// + public class BooleanToVisibilityInverseConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value != null && (bool)value ? Visibility.Collapsed : Visibility.Visible; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + + /// + /// + /// + public class BooleanInverterConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value == null || !(bool) value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/SpeckleRevitPlugin/UI/ElementSelector/ElementSelector.xaml b/SpeckleRevitPlugin/UI/ElementSelector/ElementSelector.xaml new file mode 100644 index 0000000..e7e4881 --- /dev/null +++ b/SpeckleRevitPlugin/UI/ElementSelector/ElementSelector.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + diff --git a/SpeckleRevitPlugin/UI/ElementSelector/ElementSelector.xaml.cs b/SpeckleRevitPlugin/UI/ElementSelector/ElementSelector.xaml.cs new file mode 100644 index 0000000..aa4055c --- /dev/null +++ b/SpeckleRevitPlugin/UI/ElementSelector/ElementSelector.xaml.cs @@ -0,0 +1,13 @@ +namespace SpeckleRevitPlugin.UI.ElementSelector +{ + /// + /// Interaction logic for ElementSelector.xaml + /// + public partial class ElementSelector + { + public ElementSelector() + { + InitializeComponent(); + } + } +} diff --git a/SpeckleRevitPlugin/UI/ElementSelector/ElementSelectorModel.cs b/SpeckleRevitPlugin/UI/ElementSelector/ElementSelectorModel.cs new file mode 100644 index 0000000..005e8c3 --- /dev/null +++ b/SpeckleRevitPlugin/UI/ElementSelector/ElementSelectorModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SpeckleRevitPlugin.UI.ElementSelector +{ + public class ElementSelectorModel + { + } +} diff --git a/SpeckleRevitPlugin/UI/ElementSelector/ElementSelectorViewModel.cs b/SpeckleRevitPlugin/UI/ElementSelector/ElementSelectorViewModel.cs new file mode 100644 index 0000000..d4c81ba --- /dev/null +++ b/SpeckleRevitPlugin/UI/ElementSelector/ElementSelectorViewModel.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using GalaSoft.MvvmLight; + +namespace SpeckleRevitPlugin.UI.ElementSelector +{ + public class ElementSelectorViewModel : ViewModelBase + { + + } +} diff --git a/SpeckleRevitPlugin/UI/InputControl.xaml b/SpeckleRevitPlugin/UI/InputControl.xaml new file mode 100644 index 0000000..a938514 --- /dev/null +++ b/SpeckleRevitPlugin/UI/InputControl.xaml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpeckleRevitPlugin/UI/InputControl.xaml.cs b/SpeckleRevitPlugin/UI/InputControl.xaml.cs new file mode 100644 index 0000000..f250dd2 --- /dev/null +++ b/SpeckleRevitPlugin/UI/InputControl.xaml.cs @@ -0,0 +1,13 @@ +namespace SpeckleRevitPlugin.UI +{ + /// + /// Interaction logic for StreamSelectorView.xaml + /// + public partial class InputControl + { + public InputControl() + { + InitializeComponent(); + } + } +} diff --git a/SpeckleRevitPlugin/UI/InputModel.cs b/SpeckleRevitPlugin/UI/InputModel.cs new file mode 100644 index 0000000..7855b95 --- /dev/null +++ b/SpeckleRevitPlugin/UI/InputModel.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Linq; +using SpeckleCore; +using SpeckleRevitPlugin.Classes; + +namespace SpeckleRevitPlugin.UI +{ + public class InputModel + { + + } +} diff --git a/SpeckleRevitPlugin/UI/InputViewModel.cs b/SpeckleRevitPlugin/UI/InputViewModel.cs new file mode 100644 index 0000000..e785d7d --- /dev/null +++ b/SpeckleRevitPlugin/UI/InputViewModel.cs @@ -0,0 +1,126 @@ +#region Namespaces + +using System; +using Autodesk.Revit.DB; +using GalaSoft.MvvmLight; +using GalaSoft.MvvmLight.Command; +using GalaSoft.MvvmLight.Messaging; +using SpeckleRevitPlugin.Tools.WallTool; +using SpeckleRevitPlugin.UI.BooleanSelector; +using SpeckleRevitPlugin.UI.ElementSelector; +using SpeckleRevitPlugin.UI.StreamSelector; + +#endregion + +namespace SpeckleRevitPlugin.UI +{ + public class InputViewModel : ViewModelBase + { + #region Properties + + public InputModel Model { get; set; } + public RelayCommand ToggleMenu { get; set; } + public RelayCommand Delete { get; set; } + public RelayCommand ToggleSpeckleInput { get; set; } + + private InputWrapper _input; + public InputWrapper Input + { + get { return _input; } + set { _input = value; RaisePropertyChanged(() => Input); } + } + + private bool _showMenu; + public bool ShowMenu + { + get { return _showMenu; } + set { _showMenu = value; RaisePropertyChanged(() => ShowMenu); } + } + + private bool _showInputs; + public bool ShowInputs + { + get { return _showInputs; } + set { _showInputs = value; RaisePropertyChanged(() => ShowInputs); } + } + + private bool _isSpeckleInput; + public bool IsSpeckleInput + { + get { return _isSpeckleInput; } + set { _isSpeckleInput = value; RaisePropertyChanged(() => IsSpeckleInput); } + } + + private ViewModelBase _selectedDataInput; + public ViewModelBase SelectedDataInput + { + get { return _selectedDataInput; } + set { _selectedDataInput = value; RaisePropertyChanged(() => SelectedDataInput); } + } + + #endregion + + public InputViewModel(InputModel model, InputWrapper input) + { + Model = model; + Input = input; + + ToggleMenu = new RelayCommand(OnToggleMenu); + Delete = new RelayCommand(OnDelete); + ToggleSpeckleInput = new RelayCommand(OnToggleSpeckleInput); + + // (Konrad) Set defaults. In case that input doesn't accept + // locally defined data we need to ensure that Speckle Stream + // input is shown at all times. + if (!Input.AcceptsLocalData) IsSpeckleInput = true; + OnToggleSpeckleInput(IsSpeckleInput); + } + + private void OnDelete() + { + Messenger.Default.Send(new InputDeleted { InputViewModel = this }); + ShowMenu = false; + } + + private void OnToggleMenu() + { + ShowMenu = !ShowMenu; + } + + #region Events + + private void OnToggleSpeckleInput(bool isChecked) + { + if (isChecked) + { + // toggle speckle stream on + SelectedDataInput = new StreamSelectorViewModel(new StreamSelectorModel()); + } + else + { + // toggle local data input + switch (Input.DataType) + { + case LocalDataType.None: + break; + case LocalDataType.Element: + SelectedDataInput = new ElementSelectorViewModel(); + break; + case LocalDataType.Integer: + break; + case LocalDataType.Boolean: + SelectedDataInput = new BooleanSelectorViewModel(new BooleanSelectorModel()); + break; + case LocalDataType.String: + break; + case LocalDataType.Double: + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + #endregion + } +} diff --git a/SpeckleRevitPlugin/UI/InputWrapper.cs b/SpeckleRevitPlugin/UI/InputWrapper.cs new file mode 100644 index 0000000..64631e7 --- /dev/null +++ b/SpeckleRevitPlugin/UI/InputWrapper.cs @@ -0,0 +1,90 @@ +using System; +using System.ComponentModel; +using Autodesk.Revit.DB; + +namespace SpeckleRevitPlugin.UI +{ + public sealed class InputWrapper : INotifyPropertyChanged + { + public string Name { get; set; } + public int Id { get; set; } + public bool AcceptsLocalData { get; set; } + public LocalDataType DataType { get; set; } + public bool IsInstance { get; set; } + public bool IsRequired { get; set; } + + public InputWrapper() + { + } + + public InputWrapper(Parameter p, bool isInstance = true, bool isRequired = false) + { + Name = p.Definition.Name; + Id = p.Id.IntegerValue; + IsInstance = isInstance; + IsRequired = isRequired; + DataType = StorageTypeToDataType(p); + + // (Konrad) We would make an assumption that a parameter input + // always accepts a local input: int, bool, element etc. + AcceptsLocalData = true; + } + + public override bool Equals(object obj) + { + var item = obj as InputWrapper; + return item != null && Id.Equals(item.Id); + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + #region Methods + + /// + /// + /// + /// + /// + internal static LocalDataType StorageTypeToDataType(Parameter p) + { + switch (p.StorageType) + { + case StorageType.None: + return LocalDataType.None; + case StorageType.Integer: + return p.Definition.ParameterType == ParameterType.YesNo + ? LocalDataType.Boolean + : LocalDataType.Integer; + case StorageType.Double: + return LocalDataType.Double; + case StorageType.String: + return LocalDataType.String; + case StorageType.ElementId: + return LocalDataType.Element; + default: + throw new ArgumentOutOfRangeException(nameof(p.StorageType), p.StorageType, null); + } + } + + #endregion + + public event PropertyChangedEventHandler PropertyChanged; + private void RaisePropertyChanged(string propname) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname)); + } + } + + public enum LocalDataType + { + None, + Element, + Integer, + Boolean, + String, + Double + } +} diff --git a/SpeckleRevitPlugin/UI/StreamSelector/StreamSelector.xaml b/SpeckleRevitPlugin/UI/StreamSelector/StreamSelector.xaml new file mode 100644 index 0000000..e8a0150 --- /dev/null +++ b/SpeckleRevitPlugin/UI/StreamSelector/StreamSelector.xaml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/SpeckleRevitPlugin/UI/StreamSelector/StreamSelector.xaml.cs b/SpeckleRevitPlugin/UI/StreamSelector/StreamSelector.xaml.cs new file mode 100644 index 0000000..8191c93 --- /dev/null +++ b/SpeckleRevitPlugin/UI/StreamSelector/StreamSelector.xaml.cs @@ -0,0 +1,13 @@ +namespace SpeckleRevitPlugin.UI.StreamSelector +{ + /// + /// Interaction logic for StreamSelector.xaml + /// + public partial class StreamSelector + { + public StreamSelector() + { + InitializeComponent(); + } + } +} diff --git a/SpeckleRevitPlugin/UI/StreamSelector/StreamSelectorModel.cs b/SpeckleRevitPlugin/UI/StreamSelector/StreamSelectorModel.cs new file mode 100644 index 0000000..bd6d74d --- /dev/null +++ b/SpeckleRevitPlugin/UI/StreamSelector/StreamSelectorModel.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SpeckleCore; +using SpeckleRevitPlugin.Classes; + +namespace SpeckleRevitPlugin.UI.StreamSelector +{ + public class StreamSelectorModel + { + /// + /// + /// + /// + public List GetStreams() + { + var streams = FormMainDock.Store.UserClients.Where(x => x.GetRole() == ClientRole.Receiver) + .Cast() + .Select(x => x.Client.Stream) + .OrderBy(x => x.Name) + .ToList(); + return streams.Any() + ? streams + : new List(); + } + + /// + /// + /// + /// + /// + public List GetLayers(SpeckleStream stream) + { + return stream.Layers; + } + } +} diff --git a/SpeckleRevitPlugin/UI/StreamSelector/StreamSelectorViewModel.cs b/SpeckleRevitPlugin/UI/StreamSelector/StreamSelectorViewModel.cs new file mode 100644 index 0000000..e81b9ff --- /dev/null +++ b/SpeckleRevitPlugin/UI/StreamSelector/StreamSelectorViewModel.cs @@ -0,0 +1,72 @@ +#region Namespaces + +using System.Collections.Generic; +using GalaSoft.MvvmLight; +using GalaSoft.MvvmLight.Command; +using SpeckleCore; + +#endregion + +namespace SpeckleRevitPlugin.UI.StreamSelector +{ + public class StreamSelectorViewModel : ViewModelBase + { + #region Properties + + public StreamSelectorModel Model { get; set; } + public RelayCommand StreamSelected { get; set; } + + private List _streams = new List(); + public List Streams + { + get { return _streams; } + set { _streams = value; RaisePropertyChanged(() => Streams); } + } + + private SpeckleStream _selectedStream; + public SpeckleStream SelectedStream + { + get { return _selectedStream; } + set { _selectedStream = value; RaisePropertyChanged(() => SelectedStream); } + } + + private List _layers = new List(); + public List Layers + { + get { return _layers; } + set { _layers = value; RaisePropertyChanged(() => Layers); } + } + + private Layer _selectedLayer; + public Layer SelectedLayer + { + get { return _selectedLayer; } + set { _selectedLayer = value; RaisePropertyChanged(() => SelectedLayer); } + } + + #endregion + + public StreamSelectorViewModel(StreamSelectorModel model) + { + Model = model; + Streams = Model.GetStreams(); + StreamSelected = new RelayCommand(OnStreamSelected); + } + + #region Event Handlers + + /// + /// + /// + /// + private void OnStreamSelected(SpeckleStream stream) + { + // (Konrad) It's possible for this to be null since we can hide the comboboxes + if (stream == null) return; + + Layers = Model.GetLayers(stream); + } + + #endregion + } +} diff --git a/SpeckleRevitPlugin/UI/form_Main.xaml.cs b/SpeckleRevitPlugin/UI/form_Main.xaml.cs index 92647aa..55da6c4 100644 --- a/SpeckleRevitPlugin/UI/form_Main.xaml.cs +++ b/SpeckleRevitPlugin/UI/form_Main.xaml.cs @@ -1,18 +1,6 @@ #region Namespaces -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; #endregion namespace SpeckleRevitPlugin @@ -20,17 +8,17 @@ namespace SpeckleRevitPlugin /// /// Interaction logic for form_Main.xaml /// - public partial class form_Main : Window + public partial class form_Main { - private SettingsHelper _s; + //private SettingsHelper _s; - public form_Main(SettingsHelper s) + public form_Main() { InitializeComponent(); Label_Version.Content = "v." + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); //widen scope - _s = s; + //_s = s; } #region Form Events @@ -41,7 +29,7 @@ public form_Main(SettingsHelper s) /// private void Button_Close_Click(object sender, RoutedEventArgs e) { - this.Close(); + Close(); } /// @@ -61,8 +49,7 @@ private void Button_Run_Click(object sender, RoutedEventArgs e) /// private void Window_MouseDown(object sender, MouseButtonEventArgs e) { - if (e.ChangedButton == MouseButton.Left) - this.DragMove(); + if (e.ChangedButton == MouseButton.Left) DragMove(); } #endregion } diff --git a/SpeckleRevitPlugin/UI/form_MainDock.xaml b/SpeckleRevitPlugin/UI/form_MainDock.xaml index f60d2b1..b24bb8e 100644 --- a/SpeckleRevitPlugin/UI/form_MainDock.xaml +++ b/SpeckleRevitPlugin/UI/form_MainDock.xaml @@ -1,15 +1,12 @@ - - diff --git a/SpeckleRevitPlugin/UI/form_MainDock.xaml.cs b/SpeckleRevitPlugin/UI/form_MainDock.xaml.cs index 6c32071..302808e 100644 --- a/SpeckleRevitPlugin/UI/form_MainDock.xaml.cs +++ b/SpeckleRevitPlugin/UI/form_MainDock.xaml.cs @@ -1,79 +1,71 @@ #region Namespaces using System; -using System.Collections.Generic; -using System.Linq; +using System.Diagnostics; +using System.IO; using System.Reflection; -using System.Text; -using System.Threading.Tasks; using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; - -using Autodesk.Revit.DB; using Autodesk.Revit.UI; -using Autodesk.Revit.UI.Events; using CefSharp; -using Visibility = System.Windows.Visibility; -using Path = System.IO.Path; -using System.IO; -using System.Diagnostics; +using SpeckleRevitPlugin.Classes; +using SpeckleRevitPlugin.Entry; + #endregion -namespace SpeckleRevitPlugin +namespace SpeckleRevitPlugin.UI { /// /// Interaction logic for form_MainDock.xaml /// - public partial class form_MainDock : Page, IDockablePaneProvider + public partial class FormMainDock : IDockablePaneProvider { - private ExternalEvent _extEvent; - private ExtCmdModeless _handler = new ExtCmdModeless(); + public static Interop Store; /// /// Main Dockable window /// - public form_MainDock() + public FormMainDock() { InitializeCef(); InitializeComponent(); InitializeChromium(); - _extEvent = ExternalEvent.Create(_handler); - } + // initialise one store + Store = new Interop(Browser); - void InitializeCef() - { + // make them talk together + Browser.RegisterAsyncJsObject("Interop", Store); + + //_extEvent = ExternalEvent.Create(_handler); + } + private static void InitializeCef() + { Cef.EnableHighDPISupport(); var assemblyLocation = Assembly.GetExecutingAssembly().Location; var assemblyPath = Path.GetDirectoryName(assemblyLocation); + if (assemblyPath == null) return; + var pathSubprocess = Path.Combine(assemblyPath, "CefSharp.BrowserSubprocess.exe"); CefSharpSettings.LegacyJavascriptBindingEnabled = true; var settings = new CefSettings { LogSeverity = LogSeverity.Verbose, LogFile = "ceflog.txt", - BrowserSubprocessPath = pathSubprocess - }; - - // Initialize cef with the provided settings + BrowserSubprocessPath = pathSubprocess, + RemoteDebuggingPort = 8088 + }; + // Initialize cef with the provided settings + settings.CefCommandLineArgs.Add("allow-file-access-from-files", "1"); + settings.CefCommandLineArgs.Add("disable-web-security", "1"); Cef.Initialize(settings); - } public void InitializeChromium() { #if DEBUG - - Browser.Address = @"http://localhost:9090/"; + Browser.Address = @"http://localhost:2020/"; #else var path = Directory.GetParent(Assembly.GetExecutingAssembly().Location); @@ -94,33 +86,48 @@ public void InitializeChromium() FileAccessFromFileUrls = CefState.Enabled, UniversalAccessFromFileUrls = CefState.Enabled }; - - - //Browser.Dock = DockStyle.Fill; } #region Form Events + /// - /// Safely raise an external command transaction event (for modeless) + /// Can't show the Dev Tools panel until window finishes initialization. /// /// /// - private void Button_ExampleTransaction_Click(object sender, RoutedEventArgs e) + private void Form_MainDock_OnLoaded(object sender, RoutedEventArgs e) + { +#if DEBUG + //Browser.ShowDevTools(); +#endif + } + + /// + /// Safely raise an external command transaction event (for modeless) + /// + /// + /// + private void Button_ExampleTransaction_Click(object sender, RoutedEventArgs args) { try { //textBox_Comments.Text; - AppMain.Settings.CommandType = EnumCommandType.Command1; + //AppMain.Settings.CommandType = EnumCommandType.Command1; // Safe Update - _extEvent.Raise(); + //_extEvent.Raise(); + } + catch (Exception e) + { + Debug.WriteLine(e.Message); } - catch { } } + #endregion #region Dockable Pane + /// /// IDockablePaneProvider Implementation /// @@ -128,10 +135,13 @@ private void Button_ExampleTransaction_Click(object sender, RoutedEventArgs e) public void SetupDockablePane(DockablePaneProviderData data) { data.FrameworkElement = this; - DockablePaneProviderData d = new DockablePaneProviderData(); - data.InitialState = new DockablePaneState(); - data.InitialState.DockPosition = DockPosition.Right; + var unused = new DockablePaneProviderData(); + data.InitialState = new DockablePaneState + { + DockPosition = DockPosition.Right + }; } + #endregion } } diff --git a/SpeckleRevitPlugin/Utilities/BinaryFormatterUtilities.cs b/SpeckleRevitPlugin/Utilities/BinaryFormatterUtilities.cs new file mode 100644 index 0000000..959089d --- /dev/null +++ b/SpeckleRevitPlugin/Utilities/BinaryFormatterUtilities.cs @@ -0,0 +1,96 @@ +#region Namespaces +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +#endregion + +namespace SpeckleRevitPlugin.Utilities +{ + public static class BinaryFormatterUtilities + { + public static T Read(byte[] content, Assembly currentAssembly) where T : new() + { + var result = default(T); + + try + { + using (var ms = new MemoryStream()) + { + ms.Write(content, 0, content.Length); + ms.Seek(0, SeekOrigin.Begin); + var bf = new BinaryFormatter + { + Binder = new SearchAssembliesBinder(currentAssembly, true) + }; + result = (T)bf.Deserialize(ms); + } + } + catch + { + // ignored + } + + return result; + } + + //public static void Write(T obj, string filename) + //{ + // FileStream fileStream = new FileStream(filename, FileMode.Create); + // BinaryFormatter formatter = new BinaryFormatter(); + // try + // { + // formatter.Serialize(fileStream, obj); + // } + // finally + // { + // fileStream.Close(); + // } + //} + } + + internal sealed class SearchAssembliesBinder : SerializationBinder + { + private readonly bool _searchInDlls; + private readonly Assembly _currentAssembly; + + public SearchAssembliesBinder(Assembly currentAssembly, bool searchInDlls) + { + _currentAssembly = currentAssembly; + _searchInDlls = searchInDlls; + } + + public override Type BindToType(string assemblyName, string typeName) + { + var assemblyNames = new List + { + _currentAssembly.GetName() + }; + + if (_searchInDlls) + { + assemblyNames.AddRange(_currentAssembly.GetReferencedAssemblies()); + } + + foreach (var an in assemblyNames) + { + var typeToDeserialize = GetTypeToDeserialize(typeName, an); + if (typeToDeserialize != null) + { + return typeToDeserialize; // found + } + } + + return null; // not found + } + + private static Type GetTypeToDeserialize(string typeName, AssemblyName an) + { + var fullTypeName = $"{typeName}, {an.FullName}"; + var typeToDeserialize = Type.GetType(fullTypeName); + return typeToDeserialize; + } + } +} diff --git a/SpeckleRevitPlugin/Utilities/JsonUtilities.cs b/SpeckleRevitPlugin/Utilities/JsonUtilities.cs new file mode 100644 index 0000000..68be954 --- /dev/null +++ b/SpeckleRevitPlugin/Utilities/JsonUtilities.cs @@ -0,0 +1,48 @@ +using System; +using Newtonsoft.Json; + +namespace SpeckleRevitPlugin.Utilities +{ + public static class JsonUtilities + { + public static JsonSerializerSettings Settings { get; set; } + + static JsonUtilities() + { + Settings = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore, + CheckAdditionalContent = true, + Formatting = Formatting.Indented + }; + } + + public static T Deserialize(string json) where T : new() + { + try + { + var obj = JsonConvert.DeserializeObject(json, Settings); + return obj; + } + catch (Exception e) + { + throw new Exception("Failed deserializing Json: " + e.Message); + } + } + + public static string Serialize(object obj) + { + try + { + var json = JsonConvert.SerializeObject(obj, Settings); + return json; + } + catch (Exception e) + { + throw new Exception("Failed serializing Json: " + e.Message); + } + } + + } +} diff --git a/SpeckleRevitPlugin/Utilities/SchemaUtilities.cs b/SpeckleRevitPlugin/Utilities/SchemaUtilities.cs new file mode 100644 index 0000000..9725e9d --- /dev/null +++ b/SpeckleRevitPlugin/Utilities/SchemaUtilities.cs @@ -0,0 +1,146 @@ +#region Namespaces +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Autodesk.Revit.DB; +using Autodesk.Revit.DB.ExtensibleStorage; +#endregion + +namespace SpeckleRevitPlugin.Utilities +{ + public static class SchemaUtilities + { + /// + /// Retrieves Schema by its name. + /// + /// Schema name. + /// + public static Schema GetSchema(string schemaName) + { + var schemas = Schema.ListSchemas(); + if (schemas == null || !schemas.Any()) return null; + return schemas.FirstOrDefault(s => s.SchemaName == schemaName); + } + + /// + /// Checks if Schema exists in Document. + /// + /// Schema name. + /// + public static bool SchemaExist(string schemaName) + { + return GetSchema(schemaName) != null; + } + + /// + /// Creates Speckle Schema. + /// + /// + public static Schema CreateSchema() + { + var schemaGuid = new Guid(Properties.Resources.SchemaId); + var schemaBuilder = new SchemaBuilder(schemaGuid); + schemaBuilder.SetReadAccessLevel(AccessLevel.Public); + schemaBuilder.SetWriteAccessLevel(AccessLevel.Application); + + // (Konrad) These values are set in addin manifest. + schemaBuilder.SetApplicationGUID(new Guid("0a03f52e-0ee0-4c19-82c4-8181626ec816")); + schemaBuilder.SetVendorId("Speckle"); + + schemaBuilder.SetSchemaName(Properties.Resources.SchemaName); + schemaBuilder.SetDocumentation("Speckle schema."); + schemaBuilder.AddMapField("senders", typeof(string), typeof(string)); + schemaBuilder.AddMapField("receivers", typeof(string), typeof(string)); + var schema = schemaBuilder.Finish(); + + return schema; + } + + /// + /// Adds new Speckle Schema Entity to Element. + /// + /// Schema + /// Element + /// Filed name + /// Field value + public static void AddSchemaEntity(Schema s, Element e, string fName, Dictionary fValue) + { + if (s == null) + { + throw new NullReferenceException("schema"); + } + if (e == null) + { + throw new NullReferenceException("element"); + } + if (string.IsNullOrEmpty(fName)) + { + throw new NullReferenceException("fieldName"); + } + + try + { + var entity = new Entity(s); + var settingsField = s.GetField(fName); + entity.Set>(settingsField, fValue); + + e.SetEntity(entity); + } + catch (Exception ex) + { + Debug.Write(ex.Message); + } + } + + /// + /// Speckle Schema will always be stored on this Element, + /// so now we have a utility to get to it faster. + /// + /// Revit Document + /// + public static Element GetProjectInfo(Document doc) + { + var pInfo = new FilteredElementCollector(doc) + .OfClass(typeof(ProjectInfo)) + .FirstElement(); + return pInfo; + } + + /// + /// Updates existing Speckle Schema Entity on given Element. + /// + /// + /// + /// + /// + public static void UpdateSchemaEntity(Schema s, Element e, string fName, Dictionary fValue) + { + if (s == null) + { + throw new NullReferenceException("schema"); + } + if (e == null) + { + throw new NullReferenceException("element"); + } + if (string.IsNullOrEmpty(fName)) + { + throw new NullReferenceException("fieldName"); + } + + try + { + var entity = e.GetEntity(s); + var field = s.GetField(fName); + entity.Set>(field, fValue); + + e.SetEntity(entity); + } + catch(Exception ex) + { + Debug.Write(ex.Message); + } + } + } +} diff --git a/SpeckleView b/SpeckleView index 8f4850c..29f4379 160000 --- a/SpeckleView +++ b/SpeckleView @@ -1 +1 @@ -Subproject commit 8f4850c297f79ea4ad4165568d760b145c810c32 +Subproject commit 29f4379c354b8718edc4a8f91de96247d4259e87 diff --git a/UpgradeLog.htm b/UpgradeLog.htm new file mode 100644 index 0000000..3d964ba Binary files /dev/null and b/UpgradeLog.htm differ