/
DynamoRevit.cs
1225 lines (1045 loc) · 51.1 KB
/
DynamoRevit.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using System.Xml.Serialization;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Dynamo.Applications;
using Dynamo.Applications.Models;
using Dynamo.Applications.ViewModel;
using Dynamo.Configuration;
using Dynamo.Controls;
using Dynamo.Core;
using Dynamo.Graph.Workspaces;
using Dynamo.Logging;
using Dynamo.Models;
using Dynamo.Scheduler;
using Dynamo.ViewModels;
using Dynamo.Wpf.Interfaces;
using DynamoInstallDetective;
using Greg.AuthProviders;
using Newtonsoft.Json;
using RevitServices.Persistence;
using RevitServices.Threading;
using RevitServices.Transactions;
using MessageBox = System.Windows.Forms.MessageBox;
using Resources = Dynamo.Applications.Properties.Resources;
namespace RevitServices.Threading
{
// SCHEDULER: This class will be removed once DynamoScheduler work is
// tested working. When that happens, all the callers will be redirected
// to use RevitDynamoModel.DynamoScheduler directly for task scheduling.
//
public static class IdlePromise
{
[ThreadStatic]
private static bool idle;
public static bool InIdleThread
{
get { return idle; }
set { idle = value; }
}
/// <summary>
/// Call this method to schedule a DelegateBasedAsyncTask for execution.
/// </summary>
/// <param name="p">The delegate to execute on the idle thread.</param>
/// <param name="completionHandler">Event handler that will be invoked
/// when the scheduled DelegateBasedAsyncTask is completed. This parameter
/// is optional.</param>
///
internal static void ExecuteOnIdleAsync(Action p,
AsyncTaskCompletedHandler completionHandler = null)
{
var scheduler = DynamoRevit.RevitDynamoModel.Scheduler;
var task = new DelegateBasedAsyncTask(scheduler, p);
if (completionHandler != null)
task.Completed += completionHandler;
scheduler.ScheduleForExecution(task);
}
}
}
public class DynamoExternalEventHandler : IExternalEventHandler
{
private Action callback;
public void Execute(UIApplication app)
{
callback();
}
public string GetName()
{
return nameof(DynamoExternalEventHandler);
}
internal DynamoExternalEventHandler(Action callback)
{
this.callback = callback;
}
}
namespace Dynamo.Applications
{
/// <summary>
/// Defines DynamoRevitCommandData class modeled after ExternalCommandData
/// </summary>
public class DynamoRevitCommandData
{
public DynamoRevitCommandData(ExternalCommandData externalCommandData)
{
Application = externalCommandData.Application;
JournalData = externalCommandData.JournalData;
}
public DynamoRevitCommandData()
{
}
// Summary:
// Retrieves an object that represents the current Application for external
// command.
public UIApplication Application { get; set; }
//
// Summary:
// A data map that can be used to read and write data to the Autodesk Revit
// journal file.
//
// Remarks:
// The data map is a string to string map that can be used to store data in
// the Revit journal file at the end of execution of the external command. If
// the command is then executed from the journal file during playback this data
// is then passed to the external command in this Data property so the external
// command can execute with this passed data in a UI-less mode, hence providing
// non interactive journal playback for automated testing purposes. For more
// information on Revit's journaling features contact the Autodesk Developer
// Network.
public IDictionary<string, string> JournalData { get; set; }
}
/// <summary>
/// Defines startup parameters for DynamoRevitModel
/// </summary>
public class JournalKeys
{
/// <summary>
/// The journal file can use this key to specify whether
/// the Dynamo UI should be visible at run time.
/// </summary>
public const string ShowUiKey = "dynShowUI";
/// <summary>
/// If the journal file specifies automation mode,
/// Dynamo will run on the main thread without the idle loop.
/// </summary>
public const string AutomationModeKey = "dynAutomation";
/// <summary>
/// The journal file can specify a Dynamo workspace to be opened
/// (and executed if we are in automation mode) at run time.
/// </summary>
public const string DynPathKey = "dynPath";
/// <summary>
/// The journal file can specify if the Dynamo workspace opened
/// from DynPathKey will be executed or not.
/// If we are in automation mode the workspace will be executed regardless of this key.
/// </summary>
public const string DynPathExecuteKey = "dynPathExecute";
/// <summary>
/// The journal file can specify if the Dynamo workspace opened
/// from DynPathKey will be forced in manual mode.
/// </summary>
public const string ForceManualRunKey = "dynForceManualRun";
/// <summary>
/// The journal file can specify if the existing UIless RevitDynamoModel
/// needs to be shutdown before performing any action.
/// </summary>
public const string ModelShutDownKey = "dynModelShutDown";
/// <summary>
/// The journal file can specify if a check should be performed to see if the
/// current workspaceModel already points to the Dynamo file we want to
/// run (or perform other tasks). If that's the case, we want to use the
/// current workspaceModel.
/// </summary>
public const string DynPathCheckExisting = "dynPathCheckExisting";
/// <summary>
/// The journal file can specify the values of Dynamo nodes.
/// </summary>
public const string ModelNodesInfo = "dynModelNodesInfo";
}
/// <summary>
/// Defines parameters for Dynamo nodes needed in order to update nodes
/// values through UpdateModelValueCommand.
/// </summary>
public class JournalNodeKeys
{
public const string Id = "Id";
public const string Name = "Name";
public const string Value = "Value";
}
[Transaction(TransactionMode.Manual),
Regeneration(RegenerationOption.Manual)]
public class DynamoRevit : IExternalCommand
{
public static ExternalEvent SplashScreenExternalEvent { get; set; }
public static ExternalEvent DynamoAppExternalEvent { get; set; }
private static readonly string DYNAMO_REVIT_HOST_NAME = "Dynamo Revit";
/// <summary>
/// Based on the RevitDynamoModelState a dependent component can take certain
/// decisions regarding its UI and functionality.
/// In order to be able to run a specified graph , revitDynamoModel needs to be
/// at least in StartedUIless state.
/// </summary>
[Obsolete("This enum will be removed, please use the State property on DynamoModel along with DynamoModelState")]
public enum RevitDynamoModelState { NotStarted, StartedUIless, StartedUI };
private static List<Action> idleActions;
private static DynamoRevitCommandData extCommandData;
private static bool handledCrash;
private static List<Exception> preLoadExceptions;
private static Action shutdownHandler;
private Stopwatch startupTimer;
private Dynamo.UI.Views.SplashScreen splashScreen;
/// <summary>
/// The modelState tels us if the RevitDynamoModel was started and if has the
/// the Dynamo UI attached to it or not
/// </summary>
[Obsolete("This property will be removed, please use the State property on DynamoModel.")]
public static RevitDynamoModelState ModelState
{
get;
private set;
}
/// <summary>
/// Get or Set the current RevitDynamoModel available in Revit context
/// </summary>
public static RevitDynamoModel RevitDynamoModel { get; set; }
/// <summary>
/// Get or Set the current DynamoViewModel available in Revit context
/// </summary>
public static DynamoViewModel RevitDynamoViewModel { get; private set; }
static DynamoRevit()
{
idleActions = new List<Action>();
extCommandData = null;
RevitDynamoViewModel = null;
RevitDynamoModel = null;
handledCrash = false;
ModelState = RevitDynamoModelState.NotStarted;
preLoadExceptions = new List<Exception>();
}
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
return ExecuteCommand(new DynamoRevitCommandData(commandData));
}
private void AssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
//push any exceptions generated before DynamoLoad to this list
preLoadExceptions.AddRange(StartupUtils.CheckAssemblyForVersionMismatches(args.LoadedAssembly));
}
public Result ExecuteCommand(DynamoRevitCommandData commandData)
{
startupTimer = Stopwatch.StartNew();
if (ModelState == RevitDynamoModelState.StartedUIless)
{
if (CheckJournalForKey(commandData, JournalKeys.ShowUiKey, true) ||
CheckJournalForKey(commandData, JournalKeys.ModelShutDownKey))
{
//When we move from UIless to UI we prefer to start with a fresh revitDynamoModel
//in order to benefit from a startup sequence similar to Dynamo Revit UI launch.
//Also there might be other situations which demand a new revitDynamoModel.
//In order to be able to address them we process ModelShutDownKey.
//An example of this situation is when you have a revitDynamoModel already started and you switch
//the document in Revit. Since revitDynamoModel is well connected to the previous document we need to
//shut it down and start a new one in order to able to run a graph in the new document.
RevitDynamoModel.ShutDown(false);
ModelState = RevitDynamoModelState.NotStarted;
}
else
{
TryOpenAndExecuteWorkspaceInCommandData(commandData);
return Result.Succeeded;
}
}
if (ModelState == RevitDynamoModelState.StartedUI)
{
TryOpenAndExecuteWorkspaceInCommandData(commandData);
return Result.Succeeded;
}
HandleDebug(commandData);
InitializeCore(commandData);
//subscribe to the assembly load
AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoad;
try
{
// Launch main Dynamo directly when ShowUiKey is true.
bool bSkipSplashScreen = false; // TODO: remove this when issue with System.Windows.Application.Current not being null
if (CheckJournalForKey(commandData, JournalKeys.ShowUiKey, false) || bSkipSplashScreen)
{
extCommandData = commandData;
LoadDynamoWithoutSplashScreen();
return Result.Succeeded;
}
// Show splash screen when dynamo is started, otherwise run UIless mode when needed.
if (CheckJournalForKey(commandData, JournalKeys.ShowUiKey, true))
{
var ssEventHandler = new DynamoExternalEventHandler(new Action(() =>
{
LoadDynamoView();
}));
/* Register DynamoExternalEventHandler so Revit will call our callback
we requested with API access. */
SplashScreenExternalEvent = ExternalEvent.Create(ssEventHandler);
extCommandData = commandData;
UpdateSystemPathForProcess();
splashScreen = new Dynamo.UI.Views.SplashScreen();
/* When the splashscreen is ready, raise a request to revit to call
our callback within an api context. */
splashScreen.DynamicSplashScreenReady += () =>
{
SplashScreenExternalEvent.Raise();
};
splashScreen.Closed += OnSplashScreenClosed;
// Set the owner for splashscreen window.
IntPtr mwHandle = commandData.Application.MainWindowHandle;
new WindowInteropHelper(splashScreen).Owner = mwHandle;
// show the splashscreen.
splashScreen.Show();
// Disable the Dynamo button in Revit to avoid launching multiple instances.
DynamoRevitApp.DynamoButtonEnabled = false;
return Result.Succeeded;
}
else // Run UIless mode by just initializing RevitDynamoModel
{
extCommandData = commandData;
UpdateSystemPathForProcess();
// create core data models
RevitDynamoModel = InitializeCoreModel(extCommandData);
RevitDynamoModel.Logger.Log("SYSTEM", string.Format("Environment Path:{0}", Environment.GetEnvironmentVariable("PATH")));
// handle initialization steps after RevitDynamoModel is created in UIless mode
RevitDynamoModel.HandlePostInitialization();
ModelState = RevitDynamoModelState.StartedUIless;
//unsubscribe to the assembly load
AppDomain.CurrentDomain.AssemblyLoad -= AssemblyLoad;
return Result.Succeeded;
}
}
catch (Exception ex)
{
// notify instrumentation
Analytics.TrackException(ex, true);
MessageBox.Show(ex.ToString());
DynamoRevitApp.DynamoButtonEnabled = true;
//If for some reason Dynamo has crashed while startup make sure the Dynamo Model is properly shutdown.
if (RevitDynamoModel != null)
{
RevitDynamoModel.ShutDown(false);
RevitDynamoModel = null;
}
return Result.Failed;
}
}
private void LoadDynamoView()
{
// create core data models
RevitDynamoModel = InitializeCoreModel(extCommandData);
RevitDynamoModel.OnDetectLanguage();
RevitDynamoModel.Logger.Log("SYSTEM", string.Format("Environment Path:{0}", Environment.GetEnvironmentVariable("PATH")));
// handle initialization steps after RevitDynamoModel is created.
RevitDynamoModel.HandlePostInitialization();
ModelState = RevitDynamoModelState.StartedUIless;
// show the window
if (CheckJournalForKey(extCommandData, JournalKeys.ShowUiKey, true))
{
RevitDynamoViewModel = InitializeCoreViewModel(RevitDynamoModel);
SetRevitProperties();
// Let the host (e.g. Revit) control the rendering mode
var save = RenderOptions.ProcessRenderMode;
splashScreen.DynamoView = InitializeCoreView(extCommandData);
RenderOptions.ProcessRenderMode = save;
RevitDynamoModel.Logger.Log(Dynamo.Applications.Properties.Resources.WPFRenderMode + RenderOptions.ProcessRenderMode.ToString());
ModelState = RevitDynamoModelState.StartedUI;
// Disable the Dynamo button to prevent a re-run
DynamoRevitApp.DynamoButtonEnabled = false;
}
/* foreach preloaded exception send a notification to the Dynamo Logger
these are messages we want the user to notice.*/
preLoadExceptions.ForEach(x => RevitDynamoModel.Logger.LogNotification
(RevitDynamoModel.GetType().ToString(),
x.GetType().ToString(),
DynamoApplications.Properties.Resources.MismatchedAssemblyVersionShortMessage,
x.Message));
TryOpenAndExecuteWorkspaceInCommandData(extCommandData);
//unsubscribe to the assembly load
AppDomain.CurrentDomain.AssemblyLoad -= AssemblyLoad;
Analytics.TrackStartupTime("DynamoRevit", startupTimer.Elapsed, ModelState.ToString());
splashScreen.OnRequestStaticSplashScreen();
splashScreen.DynamicSplashScreenReady -= LoadDynamoView;
}
// Start main Dynamo directly without splash screen.
private void LoadDynamoWithoutSplashScreen()
{
// Create core data models
RevitDynamoModel = InitializeCoreModel(extCommandData);
RevitDynamoModel.Logger.Log("SYSTEM", string.Format("Environment Path:{0}", Environment.GetEnvironmentVariable("PATH")));
// Handle initialization steps after RevitDynamoModel is created.
RevitDynamoModel.HandlePostInitialization();
ModelState = RevitDynamoModelState.StartedUIless;
// Show the window
if (CheckJournalForKey(extCommandData, JournalKeys.ShowUiKey, true))
{
RevitDynamoViewModel = InitializeCoreViewModel(RevitDynamoModel);
// Let the host (e.g. Revit) control the rendering mode
var save = RenderOptions.ProcessRenderMode;
InitializeCoreView(extCommandData).Show();
RenderOptions.ProcessRenderMode = save;
RevitDynamoModel.Logger.Log(Dynamo.Applications.Properties.Resources.WPFRenderMode + RenderOptions.ProcessRenderMode.ToString());
ModelState = RevitDynamoModelState.StartedUI;
// Disable the Dynamo button to prevent a re-run
DynamoRevitApp.DynamoButtonEnabled = false;
}
/* foreach preloaded exception send a notification to the Dynamo Logger
//these are messages we want the user to notice. */
preLoadExceptions.ForEach(x => RevitDynamoModel.Logger.LogNotification
(RevitDynamoModel.GetType().ToString(),
x.GetType().ToString(),
DynamoApplications.Properties.Resources.MismatchedAssemblyVersionShortMessage,
x.Message));
TryOpenAndExecuteWorkspaceInCommandData(extCommandData);
// Unsubscribe to the assembly load
AppDomain.CurrentDomain.AssemblyLoad -= AssemblyLoad;
Analytics.TrackStartupTime("DynamoRevit", startupTimer.Elapsed, ModelState.ToString());
}
/// <summary>
/// Add the main exec path to the system PATH
/// This is required to pickup certain dlls.
/// </summary>
private static void UpdateSystemPathForProcess()
{
var path =
Environment.GetEnvironmentVariable(
"Path",
EnvironmentVariableTarget.Process) + ";" + DynamoRevitApp.DynamoCorePath;
Environment.SetEnvironmentVariable("Path", path, EnvironmentVariableTarget.Process);
}
#region Initialization
/// <summary>
/// DynamoShapeManager.dll is a companion assembly of Dynamo core components,
/// we do not want a static reference to it (since the Revit add-on can be
/// installed anywhere that's outside of Dynamo), we do not want a duplicated
/// reference to it. Here we use reflection to obtain GetGeometryFactoryPath
/// method, and call it to get the geometry factory assembly path.
/// </summary>
/// <param name="corePath">The path where DynamoShapeManager.dll can be
/// located.</param>
/// <returns>Returns the full path to geometry factory assembly.</returns>
///
[Obsolete("Please use the overload which specifies the version of the geometry library to load")]
public static string GetGeometryFactoryPath(string corePath)
{
var dynamoAsmPath = Path.Combine(corePath, "DynamoShapeManager.dll");
var assembly = Assembly.LoadFrom(dynamoAsmPath);
if (assembly == null)
throw new FileNotFoundException("File not found", dynamoAsmPath);
var utilities = assembly.GetType("DynamoShapeManager.Utilities");
var getGeometryFactoryPath = utilities.GetMethod("GetGeometryFactoryPath2");
//if old method is called default to 224.4.0
return (getGeometryFactoryPath.Invoke(null,
new object[] { corePath, new Version(224, 4, 0) }) as string);
}
public static string GetGeometryFactoryPath(string corePath, Version version)
{
var dynamoAsmPath = Path.Combine(corePath, "DynamoShapeManager.dll");
var assembly = Assembly.LoadFrom(dynamoAsmPath);
if (assembly == null)
throw new FileNotFoundException("File not found", dynamoAsmPath);
var utilities = assembly.GetType("DynamoShapeManager.Utilities");
var getGeometryFactoryPath = utilities.GetMethod("GetGeometryFactoryPath2");
return (getGeometryFactoryPath.Invoke(null,
new object[] { corePath, version }) as string);
}
private static void PreloadDynamoCoreDlls()
{
// Assume Revit Install folder as look for root. Assembly name is compromised.
var assemblyList = new[]
{
//"SDA\\bin\\ICSharpCode.AvalonEdit.dll"
"Addins\\DynamoForRevit\\ICSharpCode.AvalonEdit.dll"
};
foreach (var assembly in assemblyList)
{
var assemblyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, assembly);
// TODO: remove this when the above will work
if (!File.Exists(assemblyPath))
assemblyPath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), assembly);
if (File.Exists(assemblyPath))
Assembly.LoadFrom(assemblyPath);
}
}
private static RevitDynamoModel InitializeCoreModel(DynamoRevitCommandData commandData)
{
// Temporary fix to pre-load DLLs that were also referenced in Revit folder.
// To do: Need to align with Revit when provided a chance.
PreloadDynamoCoreDlls();
var corePath = DynamoRevitApp.DynamoCorePath;
var dynamoRevitExePath = Assembly.GetExecutingAssembly().Location;
var dynamoRevitRoot = Path.GetDirectoryName(dynamoRevitExePath);// ...\Revit_xxxx\ folder
// get Dynamo Revit Version
var dynRevitVersion = Assembly.GetExecutingAssembly().GetName().Version;
HostAnalyticsInfo hostAnalyticsInfo = new HostAnalyticsInfo {
HostName = DYNAMO_REVIT_HOST_NAME,
HostVersion = dynRevitVersion
};
var userDataFolder = Path.Combine(Environment.GetFolderPath(
Environment.SpecialFolder.ApplicationData),
"Dynamo", "Dynamo Revit");
var commonDataFolder = Path.Combine(Environment.GetFolderPath(
Environment.SpecialFolder.CommonApplicationData),
"Autodesk", "RVT " + commandData.Application.Application.VersionNumber, "Dynamo");
bool isAutomationMode = CheckJournalForKey(extCommandData, JournalKeys.AutomationModeKey);
// when Dynamo runs on top of Revit we must load the same version of ASM as revit
// so tell Dynamo core we've loaded that version.
var loadedLibGVersion = PreloadAsmFromRevit();
return RevitDynamoModel.Start(
new RevitDynamoModel.RevitStartConfiguration()
{
DynamoCorePath = corePath,
DynamoHostPath = dynamoRevitRoot,
GeometryFactoryPath = GetGeometryFactoryPath(corePath, loadedLibGVersion),
PathResolver = new RevitPathResolver(userDataFolder, commonDataFolder),
Context = GetRevitContext(commandData),
SchedulerThread = new RevitSchedulerThread(commandData.Application),
StartInTestMode = isAutomationMode,
AuthProvider = new RevitOAuth2Provider(new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher)),
ExternalCommandData = commandData,
ProcessMode = isAutomationMode ? TaskProcessMode.Synchronous : TaskProcessMode.Asynchronous,
HostAnalyticsInfo = hostAnalyticsInfo
});
}
internal static Version PreloadAsmFromRevit()
{
var asmLocation = AppDomain.CurrentDomain.BaseDirectory;
// TODO: remove this when above will work
if (string.IsNullOrEmpty(asmLocation))
asmLocation = Path.GetDirectoryName(Environment.ProcessPath);
Version libGVersion = findRevitASMVersion(asmLocation);
var dynCorePath = DynamoRevitApp.DynamoCorePath;
// Get the corresponding libG preloader location for the target ASM loading version.
// If there is exact match preloader version to the target ASM version, use it,
// otherwise use the closest below.
var preloaderLocation = DynamoShapeManager.Utilities.GetLibGPreloaderLocation(libGVersion, dynCorePath);
// [Tech Debt] (Will refactor the code later)
// The LibG version maybe different in Dynamo and Revit, using the one which is in Dynamo.
Version preLoadLibGVersion = PreloadLibGVersion(preloaderLocation);
DynamoShapeManager.Utilities.PreloadAsmFromPath(preloaderLocation, asmLocation);
return preLoadLibGVersion;
}
// [Tech Debt] (Will refactor the code later)
/// <summary>
/// Return the preload version of LibG.
/// </summary>
/// <param name="preloaderLocation"></param>
/// <returns></returns>
internal static Version PreloadLibGVersion(string preloaderLocation)
{
preloaderLocation = new DirectoryInfo(preloaderLocation).Name;
var regExp = new Regex(@"^libg_(\d\d\d)_(\d)_(\d)$", RegexOptions.IgnoreCase);
var match = regExp.Match(preloaderLocation);
if (match.Groups.Count == 4)
{
return new Version(
Convert.ToInt32(match.Groups[1].Value),
Convert.ToInt32(match.Groups[2].Value),
Convert.ToInt32(match.Groups[3].Value));
}
return new Version();
}
/// <summary>
/// Returns the version of ASM which is installed with Revit at the requested path.
/// This version number can be used to load the appropriate libG version.
/// </summary>
/// <param name="asmLocation">path where asm dlls are located, this is usually the product(Revit) install path</param>
/// <returns></returns>
internal static Version findRevitASMVersion(string asmLocation)
{
var lookup = new InstalledProductLookUp("Revit", "ASMAHL*.dll");
var product = lookup.GetProductFromInstallPath(asmLocation);
var libGversion = new Version(product.VersionInfo.Item1, product.VersionInfo.Item2, product.VersionInfo.Item3);
return libGversion;
}
private static DynamoViewModel InitializeCoreViewModel(RevitDynamoModel revitDynamoModel)
{
var viewModel = DynamoRevitViewModel.Start(
new DynamoViewModel.StartConfiguration()
{
DynamoModel = revitDynamoModel,
WatchHandler =
new RevitWatchHandler(revitDynamoModel.PreferenceSettings)
});
return viewModel;
}
private static DynamoView InitializeCoreView(DynamoRevitCommandData commandData)
{
IntPtr mwHandle = commandData.Application.MainWindowHandle;
var dynamoView = new DynamoView(RevitDynamoViewModel);
new WindowInteropHelper(dynamoView).Owner = mwHandle;
handledCrash = false;
//Register DynamoExternalEventHandler so Revit will call our callback
//we requested with API access.
var appEventHandler = new DynamoExternalEventHandler(new Action(() =>
{
UpdateLibraryLayoutSpec();
}));
DynamoAppExternalEvent = ExternalEvent.Create(appEventHandler);
dynamoView.Dispatcher.UnhandledException += Dispatcher_UnhandledException;
dynamoView.Closed += OnDynamoViewClosed;
dynamoView.Loaded += (o, e) => DynamoAppExternalEvent.Raise();
return dynamoView;
}
private static void AddSyncWithRevitControls(DynamoView dynamoView)
{
// Get the RunSettingsControl field from the DynamoWindow
var runsettingField = dynamoView.GetType().GetField("RunSettingsControl", BindingFlags.Instance | BindingFlags.NonPublic);
// Get the value from the field, this should be of type UserControl
var runsettingsValue = runsettingField.GetValue(dynamoView) as UserControl;
// Get the grid from the RunSettingsControl
var runsettingsGrid = runsettingsValue.Content as System.Windows.Controls.Grid;
// Get StackPanel from the RunSettingsControls main grid, this is where all of its UIElements are defined
var runsettingStackPanel = runsettingsGrid.Children.OfType<StackPanel>().FirstOrDefault();
var srcDic = Dynamo.UI.SharedDictionaryManager.DynamoModernDictionary;
var toggleItem = new System.Windows.Controls.Primitives.ToggleButton
{
Width = 40,
Height = 20,
IsChecked = true,
VerticalContentAlignment = System.Windows.VerticalAlignment.Center,
ToolTip = Resources.SyncWithRevitToolTip
};
toggleItem.SetValue(System.Windows.Controls.Primitives.ToggleButton.StyleProperty, srcDic["EllipseToggleButton1"]);
toggleItem.Click += OnReadOnlyModeToggleChecked;
var toggleLabel = new Label
{
Content = Resources.SyncWithRevit,
VerticalAlignment = System.Windows.VerticalAlignment.Center,
Margin = new System.Windows.Thickness(0, 0, 0, 0),
ToolTip = Resources.SyncWithRevitToolTip
};
toggleLabel.SetValue(Label.ForegroundProperty, srcDic["PreferencesWindowFontColor"]);
// Add a new control to the RunSettingsControls StackPanel
runsettingStackPanel.Children.Add(toggleItem);
runsettingStackPanel.Children.Add(toggleLabel);
}
private static void OnReadOnlyModeToggleChecked(object sender, System.Windows.RoutedEventArgs e)
{
var toggle = sender as System.Windows.Controls.Primitives.ToggleButton;
if (!toggle.IsChecked.HasValue ||
toggle.IsChecked.Value != TransactionManager.Instance.DisableTransactions)
return;
TransactionManager.Instance.DisableTransactions = !toggle.IsChecked.Value;
}
/// <summary>
/// Updates the Libarary Layout spec to include layout for Revit nodes.
/// The Revit layout spec is embeded as resource "LayoutSpecs.json".
/// </summary>
private static void UpdateLibraryLayoutSpec()
{
//Get the library view customization service to update spec
var customization = RevitDynamoModel.ExtensionManager.Service<ILibraryViewCustomization>();
if (customization == null) return;
if (shutdownHandler == null && extCommandData != null)
{
//Make sure to notify customization for application closing, so that
//the CEF can be shutdown for clean Revit exit
shutdownHandler = () => customization.OnAppShutdown();
extCommandData.Application.ApplicationClosing += (o, _) => shutdownHandler();
}
//Register the icon resource
customization.RegisterResourceStream("/icons/Category.Revit.svg",
GetResourceStream("Dynamo.Applications.Resources.Category.Revit.svg"));
//Read the revitspec from the resource stream
LayoutSpecification revitspec;
using (Stream stream = GetResourceStream("Dynamo.Applications.Resources.LayoutSpecs.json"))
{
revitspec = LayoutSpecification.FromJSONStream(stream);
}
//The revitspec should have only one section, add all its child elements to the customization
var elements = revitspec.sections.First().childElements;
// Extend it with the layoutSpecs from internal nodes
var internalNodesLayouts = DynamoRevitInternalNodes.GetLayoutSpecsFiles();
foreach (var layoutSpecsFile in internalNodesLayouts)
{
try
{
LayoutSpecification spec = LayoutSpecification.FromJSONString(File.ReadAllText(layoutSpecsFile));
var revitSection = spec.sections.First();
var revitCategory = revitSection.childElements.First();
var revitCategoryToExtend = elements.First(elem => elem.text == "Revit");
revitCategoryToExtend.childElements.AddRange(revitCategory.childElements);
}
catch (Exception)
{
Console.WriteLine(string.Format("Exception while trying to load {0}", layoutSpecsFile));
}
}
customization.AddElements(elements); //add all the elements to default section
}
/// <summary>
/// Reads the embeded resource stream by given name
/// </summary>
/// <param name="resource">Fully qualified name of the embeded resource.</param>
/// <returns>The resource Stream if successful else null</returns>
private static Stream GetResourceStream(string resource)
{
var assembly = Assembly.GetExecutingAssembly();
var stream = assembly.GetManifestResourceStream(resource);
return stream;
}
private static bool initializedCore;
private static void InitializeCore(DynamoRevitCommandData commandData)
{
if (initializedCore) return;
// Change the locale that LibG depends on.
StringBuilder sb = new StringBuilder("LANGUAGE=");
var revitLocale = System.Globalization.CultureInfo.CurrentUICulture.ToString();
// Record host locale for locale switching in DynamoCore
System.Globalization.CultureInfo.DefaultThreadCurrentCulture = System.Globalization.CultureInfo.CurrentUICulture;
sb.Append(revitLocale.Replace("-", "_"));
_putenv(sb.ToString());
InitializeDocumentManager(commandData);
initializedCore = true;
}
private static void InitializeDocumentManager(DynamoRevitCommandData commandData)
{
if (DocumentManager.Instance.CurrentUIApplication == null)
DocumentManager.Instance.CurrentUIApplication = commandData.Application;
}
/// <summary>
/// Creates Revit-specific preferences
/// </summary>
private void SetRevitProperties()
{
SetScale();
}
/// <summary>
/// Sets current Revit document units
/// </summary>
private void SetScale()
{
var doc = extCommandData.Application.ActiveUIDocument.Document;
var docUnitType = doc.GetUnits().GetFormatOptions(SpecTypeId.Length).GetUnitTypeId();
if (docUnitType.TypeId == UnitTypeId.Millimeters.TypeId)
{
RevitDynamoViewModel.Model.PreferenceSettings.CurrentHostUnits = Configurations.Units.Millimeters;
}
else if (docUnitType.TypeId == UnitTypeId.Centimeters.TypeId || docUnitType.TypeId == UnitTypeId.Decimeters.TypeId)
{
RevitDynamoViewModel.Model.PreferenceSettings.CurrentHostUnits = Configurations.Units.Centimeters;
}
else if (docUnitType.TypeId == UnitTypeId.Meters.TypeId || docUnitType.TypeId == UnitTypeId.MetersCentimeters.TypeId)
{
RevitDynamoViewModel.Model.PreferenceSettings.CurrentHostUnits = Configurations.Units.Meters;
}
else if (docUnitType.TypeId == UnitTypeId.Feet.TypeId || docUnitType.TypeId == UnitTypeId.FeetFractionalInches.TypeId || docUnitType.TypeId == UnitTypeId.UsSurveyFeet.TypeId)
{
RevitDynamoViewModel.Model.PreferenceSettings.CurrentHostUnits = Configurations.Units.Feet;
}
else if (docUnitType.TypeId == UnitTypeId.Inches.TypeId || docUnitType.TypeId == UnitTypeId.FractionalInches.TypeId)
{
RevitDynamoViewModel.Model.PreferenceSettings.CurrentHostUnits = Configurations.Units.Inches;
}
else
{
// Default unit
RevitDynamoViewModel.Model.PreferenceSettings.CurrentHostUnits = Configurations.Units.Millimeters;
}
}
#endregion
#region Helpers
private static void HandleDebug(DynamoRevitCommandData commandData)
{
if (commandData.JournalData != null && commandData.JournalData.ContainsKey("debug"))
{
if (Boolean.Parse(commandData.JournalData["debug"]))
Debugger.Launch();
}
}
private static bool CheckJournalForKey(DynamoRevitCommandData commandData, string key, bool defaultReturn = false)
{
var result = defaultReturn;
if (commandData.JournalData == null)
{
return result;
}
if (commandData.JournalData.ContainsKey(key))
{
bool.TryParse(commandData.JournalData[key], out result);
}
return result;
}
private static void TryOpenAndExecuteWorkspaceInCommandData(DynamoRevitCommandData commandData)
{
if (commandData.JournalData == null)
{
return;
}
if (commandData.JournalData.ContainsKey(JournalKeys.DynPathKey))
{
bool isAutomationMode = CheckJournalForKey(commandData, JournalKeys.AutomationModeKey);
bool forceManualRun = CheckJournalForKey(commandData, JournalKeys.ForceManualRunKey);
bool useExistingWorkspace = false;
if (CheckJournalForKey(commandData, JournalKeys.DynPathCheckExisting))
{
WorkspaceModel currentWorkspace = RevitDynamoModel.CurrentWorkspace;
if (currentWorkspace.FileName.Equals(commandData.JournalData[JournalKeys.DynPathKey],
StringComparison.OrdinalIgnoreCase))
{
useExistingWorkspace = true;
}
}
if (!useExistingWorkspace) //if use existing is false, open the specified workspace
{
if (ModelState == RevitDynamoModelState.StartedUIless)
{
RevitDynamoModel.OpenFileFromPath(commandData.JournalData[JournalKeys.DynPathKey], forceManualRun);
}
else
{
RevitDynamoViewModel.OpenIfSavedCommand.Execute(new Dynamo.Models.DynamoModel.OpenFileCommand(commandData.JournalData[JournalKeys.DynPathKey], forceManualRun));
RevitDynamoViewModel.ShowStartPage = false;
}
}
//If we have information about the nodes and their values we want to push those values after the file is opened.
if (commandData.JournalData.ContainsKey(JournalKeys.ModelNodesInfo))
{
try
{
var allNodesInfo = JsonConvert.DeserializeObject<List<Dictionary<string, string>>>(commandData.JournalData[JournalKeys.ModelNodesInfo]);
if (allNodesInfo != null)
{
foreach (var nodeInfo in allNodesInfo)
{
if (nodeInfo.ContainsKey(JournalNodeKeys.Id) &&
nodeInfo.ContainsKey(JournalNodeKeys.Name) &&
nodeInfo.ContainsKey(JournalNodeKeys.Value))
{
var modelCommand = new DynamoModel.UpdateModelValueCommand(nodeInfo[JournalNodeKeys.Id],
nodeInfo[JournalNodeKeys.Name],
nodeInfo[JournalNodeKeys.Value]);
modelCommand.Execute(RevitDynamoModel);
}
}
}
}
catch
{
Console.WriteLine("Exception while trying to update nodes with new values");
}
}
//If we are in automation mode the model will run anyway (on the main thread
//without the idle loop) regardless of the DynPathExecuteKey.
if (!isAutomationMode && commandData.JournalData.ContainsKey(JournalKeys.DynPathExecuteKey))
{
bool executePath = false;
bool.TryParse(commandData.JournalData[JournalKeys.DynPathExecuteKey], out executePath);
if (executePath)
{
HomeWorkspaceModel modelToRun = RevitDynamoModel.CurrentWorkspace as HomeWorkspaceModel;
if (modelToRun != null)
{
modelToRun.Run();
return;
}
}
}