diff --git a/src/RhinoInside.Revit.AddIn/Commands/Grasshopper/GrasshopperPlayerCommand.cs b/src/RhinoInside.Revit.AddIn/Commands/Grasshopper/GrasshopperPlayerCommand.cs index 67177e789..d0d971bfa 100644 --- a/src/RhinoInside.Revit.AddIn/Commands/Grasshopper/GrasshopperPlayerCommand.cs +++ b/src/RhinoInside.Revit.AddIn/Commands/Grasshopper/GrasshopperPlayerCommand.cs @@ -5,17 +5,19 @@ using System.IO; using System.Linq; using System.Threading; -using System.Windows.Forms.Interop; using System.Windows.Input; using Autodesk.Revit.Attributes; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using Autodesk.Revit.UI.Selection; using GH_IO.Serialization; +using Grasshopper; using Grasshopper.Kernel; using Grasshopper.Kernel.Parameters; using Grasshopper.Kernel.Types; +using Grasshopper.Plugin; using Microsoft.Win32.SafeHandles; +using Rhino; namespace RhinoInside.Revit.AddIn.Commands { @@ -95,6 +97,7 @@ public static Result ReadFromFile(string filePath, out GH_Document definition) } } + #region Prompt internal static IList GetInputParams(GH_Document definition) { var inputs = new List(); @@ -342,6 +345,7 @@ internal static IEnumerable PromptBrep(UIDocument doc, string prompt) return null; } + #endregion public override Result Execute(ExternalCommandData data, ref string message, ElementSet elements) { @@ -354,6 +358,82 @@ public override Result Execute(ExternalCommandData data, ref string message, Ele return result; } + private struct RunInCommandContextGuard : IDisposable + { + public readonly GH_Document Document; + private readonly bool DocumentWasModified; + private readonly bool DocumentWasEnabled; + private readonly bool SolverWasEnabled; + + private readonly RhinoDoc RhinoDocument; + private readonly uint UndoRecord; + + public RunInCommandContextGuard(GH_Document document) + { + Document = document; + RhinoDocument = document.RhinoDocument(); + DocumentWasModified = document.IsModified; + DocumentWasEnabled = document.Enabled; + SolverWasEnabled = GH_Document.EnableSolutions; + + GH_Document.EnableSolutions = true; + document.Enabled = true; + + var urName = File.Exists(document.FilePath) ? Path.GetFileNameWithoutExtension(document.FilePath).Replace("-", " ") : "unnamed"; + UndoRecord = RhinoDocument?.BeginUndoRecord(urName) ?? 0; + } + + void IDisposable.Dispose() + { + RhinoDocument?.EndUndoRecord(UndoRecord); + + Document.IsModified = DocumentWasModified; + Document.Enabled = DocumentWasEnabled; + GH_Document.EnableSolutions = SolverWasEnabled; + } + } + + internal static Result Execute + ( + UIApplication app, + View view, + IDictionary journalData, + GH_Document definition, + ref string message + ) + { + if (definition is null) return Result.Failed; + if (definition.RhinoDocument() is RhinoDoc rhinoDocument && rhinoDocument != RhinoDoc.ActiveDoc) return Result.Failed; + + try + { + using (new RunInCommandContextGuard(definition)) + { + using (var transGroup = new TransactionGroup(app.ActiveUIDocument.Document)) + { + transGroup.Start(Path.GetFileNameWithoutExtension(definition.Properties.ProjectFileName)); + + definition.NewSolution(expireAllObjects: true); + + if (definition.SolutionState == GH_ProcessStep.Aborted) + { + message = $"Solution aborted by user after ~{definition.SolutionSpan.TotalSeconds} seconds"; + return Result.Cancelled; + } + + transGroup.Assimilate(); + } + } + } + catch (Exception e) + { + message = e.Message; + return Result.Failed; + } + + return Result.Succeeded; + } + public static Result Execute ( UIApplication app, @@ -367,74 +447,164 @@ ref string message { // Load Grasshopper window in case the user has not did it before. // This enables definitions that relay on Grasshopper window like those that show UI. - GH.Guest.LoadEditor(); + var script = new GH_RhinoScriptInterface(); + script.DisableBanner(); + script.LoadEditor(); - var result = ReadFromFile(filePath, out var definition); - if (result == Result.Succeeded) - { - using (definition) - { - bool enableSolutions = GH_Document.EnableSolutions; - var currentCulture = Thread.CurrentThread.CurrentCulture; - try - { - using (var transGroup = new TransactionGroup(app.ActiveUIDocument.Document)) - { - transGroup.Start(Path.GetFileNameWithoutExtension(definition.Properties.ProjectFileName)); + var remotePanelVisible = Instances.IsRemotePanelVisible; + var editorWasEnabled = Instances.ActiveCanvas.ModifiersEnabled; + var editorWasVisible = Instances.DocumentEditor.Visible; - GH_Document.EnableSolutions = true; - definition.Enabled = true; - definition.ExpireSolution(); + if (editorWasVisible) + { + Instances.DocumentEditor.FadeOut(); + } + if (editorWasEnabled) Instances.DocumentEditor.DisableUI(); - var inputs = GetInputParams(definition); - result = PromptForInputs(app.ActiveUIDocument, inputs, out var values); - if (result != Result.Succeeded) - return (result, default); + var index = Instances.DocumentServer.IndexOf(filePath); + var wasOpen = index >= 0; + var document = default(GH_Document); + { + if (wasOpen) + { + document = Instances.DocumentServer[index]; + document.ExpireSolution(); + } + else + { + var io = new GH_DocumentIO(); + if (!io.Open(filePath)) return (Result.Failed, default); + document = io.Document; - // Update input volatile data values - foreach (var value in values) - value.Key.AddVolatileDataList(new Grasshopper.Kernel.Data.GH_Path(0), value.Value); + Instances.DocumentServer.AddDocument(document); + } + } - Grasshopper.Instances.EnforceInvariantCulture(); - using (var modal = new ModalScope()) - { - definition.NewSolution(false, GH_SolutionMode.Silent); + // Synchronize units. + GH.Guest.AuditUnits(view.Document); - do - { - if (modal.Run(false, false) == Result.Failed) - return (Result.Failed, default); + // Activate the document without computing it + if (Instances.ActiveCanvas.Document != document) + { + var enableSolutions = GH_Document.EnableSolutions; + try + { + GH_Document.EnableSolutions = false; + Instances.ActiveCanvas.Document = document; + document.Enabled = false; + if (!wasOpen) document.IsModified = false; + } + finally + { + GH_Document.EnableSolutions = enableSolutions; + document.Enabled = true; + } + } - } while (definition.ScheduleDelay >= GH_Document.ScheduleRecursive); - } - Thread.CurrentThread.CurrentCulture = currentCulture; + if (document.RemotePanelLayout.Count > 0) Instances.ShowRemotePanel(); - if (definition.SolutionState == GH_ProcessStep.Aborted) - { - return (Result.Cancelled, $"Solution aborted by user after ~{ definition.SolutionSpan.TotalSeconds} seconds"); - } + var m = default(string); + var result = Execute(app, view, journalData, document, ref m); + if (!document.KeepOpen() || result != Result.Succeeded) + { + if (!remotePanelVisible) Instances.HideRemotePanel(); + if (!wasOpen) Instances.DocumentServer.RemoveDocument(document); + } - transGroup.Assimilate(); - } - } - catch (Exception e) - { - return (Result.Failed, e.Message); - } - finally - { - Thread.CurrentThread.CurrentCulture = currentCulture; - GH_Document.EnableSolutions = enableSolutions; - } - } + if (editorWasEnabled) Instances.DocumentEditor.EnableUI(); + if (editorWasVisible) + { + Instances.DocumentEditor.FadeIn(); } - return (result, default); - }, default); + return (result, m); + }); message = msg; return res; } + + //public static Result Execute + //( + // UIApplication app, + // View view, + // IDictionary journalData, + // string filePath, + // ref string message + //) + //{ + // var (res, msg) = External.ActivationGate.Open(() => + // { + // // Load Grasshopper window in case the user has not did it before. + // // This enables definitions that relay on Grasshopper window like those that show UI. + // GH.Guest.LoadEditor(); + + // var result = ReadFromFile(filePath, out var definition); + // if (result == Result.Succeeded) + // { + // using (definition) + // { + // bool enableSolutions = GH_Document.EnableSolutions; + // var currentCulture = Thread.CurrentThread.CurrentCulture; + // try + // { + // using (var transGroup = new TransactionGroup(app.ActiveUIDocument.Document)) + // { + // transGroup.Start(Path.GetFileNameWithoutExtension(definition.Properties.ProjectFileName)); + + // GH_Document.EnableSolutions = true; + // definition.Enabled = true; + // definition.ExpireSolution(); + + // var inputs = GetInputParams(definition); + // result = PromptForInputs(app.ActiveUIDocument, inputs, out var values); + // if (result != Result.Succeeded) + // return (result, default); + + // // Update input volatile data values + // foreach (var value in values) + // value.Key.AddVolatileDataList(new Grasshopper.Kernel.Data.GH_Path(0), value.Value); + + // Grasshopper.Instances.EnforceInvariantCulture(); + // using (var modal = new ModalScope()) + // { + // definition.NewSolution(false, GH_SolutionMode.Silent); + + // do + // { + // if (modal.Run(false, false) == Result.Failed) + // return (Result.Failed, default); + + // } while (definition.ScheduleDelay >= GH_Document.ScheduleRecursive); + // } + // Thread.CurrentThread.CurrentCulture = currentCulture; + + // if (definition.SolutionState == GH_ProcessStep.Aborted) + // { + // return (Result.Cancelled, $"Solution aborted by user after ~{ definition.SolutionSpan.TotalSeconds} seconds"); + // } + + // transGroup.Assimilate(); + // } + // } + // catch (Exception e) + // { + // return (Result.Failed, e.Message); + // } + // finally + // { + // Thread.CurrentThread.CurrentCulture = currentCulture; + // GH_Document.EnableSolutions = enableSolutions; + // } + // } + // } + + // return (result, default); + // }, default); + + // message = msg; + // return res; + //} } /// diff --git a/src/RhinoInside.Revit.External/Extensions/Grasshopper.cs b/src/RhinoInside.Revit.External/Extensions/Grasshopper.cs new file mode 100644 index 000000000..ef6b4ceda --- /dev/null +++ b/src/RhinoInside.Revit.External/Extensions/Grasshopper.cs @@ -0,0 +1,25 @@ +using Grasshopper.Kernel; + +namespace Grasshopper +{ + internal static class GH_DocumentExtension + { + public static bool KeepOpen(this GH_Document document) + { +#if RHINO_8 + return document.Properties.KeepOpen; +#else + return false; +#endif + } + + public static Rhino.RhinoDoc RhinoDocument(this GH_Document document) + { +#if RHINO_8 + return document.RhinoDocument; +#else + return Rhino.RhinoDoc.ActiveDoc; +#endif + } + } +} diff --git a/src/RhinoInside.Revit.External/Extensions/System.cs b/src/RhinoInside.Revit.External/Extensions/System.cs index 39d2ca142..18602af4a 100644 --- a/src/RhinoInside.Revit.External/Extensions/System.cs +++ b/src/RhinoInside.Revit.External/Extensions/System.cs @@ -5,7 +5,7 @@ namespace System { - static class EnumExtensions + static class EnumExtension { public static T WithFlag(this T @enum, T flag, bool value) where T : struct, Enum { @@ -190,7 +190,7 @@ internal static string Unescape(this string value, params char[] allowed) #endregion } - static class EventHandlerExtenion + static class EventHandlerExtension { #region Events /// diff --git a/src/RhinoInside.Revit/GH/Guest.cs b/src/RhinoInside.Revit/GH/Guest.cs index 4136d4762..19748033e 100755 --- a/src/RhinoInside.Revit/GH/Guest.cs +++ b/src/RhinoInside.Revit/GH/Guest.cs @@ -227,7 +227,7 @@ void ActivationGate_Enter(object sender, EventArgs e) if (Instances.ActiveCanvas?.Document is GH_Document definition) { definition.ForcePreview(false); - definition.Enabled = Instances.ActiveCanvas?.Visible is true; + definition.Enabled = Instances.ActiveCanvas?.Visible is true || definition.KeepOpen(); } if (EnableSolutions.HasValue) @@ -242,7 +242,7 @@ void ActivationGate_Exit(object sender, EventArgs e) if (Instances.ActiveCanvas?.Document is GH_Document definition) { definition.Enabled = false; - definition.ForcePreview(Instances.ActiveCanvas?.Visible is true); + definition.ForcePreview(Instances.ActiveCanvas?.Visible is true || definition.KeepOpen()); } } @@ -253,7 +253,7 @@ private void DocumentServer_DocumentAdded(GH_DocumentServer sender, GH_Document // If we don't disable the solutions Grasshopper will // evaluate doc before notifiy us the document is being active. - if (GH_Document.EnableSolutions) + if (GH_Document.EnableSolutions && !External.ActivationGate.IsOpen) { GH_Document.EnableSolutions = false; EnableSolutions = true; @@ -504,11 +504,13 @@ public static UnitScale ModelUnitScale private set => modelUnitScale = value; } - void DocumentEditor_Activated(object sender, EventArgs e) + void DocumentEditor_Activated(object sender, EventArgs e) => AuditUnits(Revit.ActiveUIDocument?.Document); + + internal static void AuditUnits(ARDB.Document document) { var revitUS = UnitScale.Unset; - if (Revit.ActiveUIDocument?.Document is ARDB.Document revitDoc) + if (document is ARDB.Document revitDoc) { var units = revitDoc.GetUnits(); revitUS = units.ToUnitScale(out var _); @@ -538,12 +540,6 @@ void ActiveCanvas_DocumentChanged(GH_Canvas sender, GH_CanvasDocumentChangedEven e.NewDocument.SolutionStart += ActiveDefinition_SolutionStart; e.NewDocument.SolutionEnd += ActiveDefinition_SolutionEnd; } - - if (EnableSolutions.HasValue) - { - GH_Document.EnableSolutions = EnableSolutions.Value; - EnableSolutions = null; - } } #endregion