1
1
using System ;
2
2
using System . Collections . Generic ;
3
+ using System . Diagnostics . CodeAnalysis ;
3
4
using System . IO ;
4
5
using System . Linq ;
5
- using System . Reflection ;
6
- using System . Reflection . Emit ;
6
+ using System . Text ;
7
7
using System . Windows . Forms ;
8
8
using System . Xml . Linq ;
9
9
using Autodesk . Revit . Attributes ;
10
10
using Autodesk . Revit . UI ;
11
+ using Microsoft . CodeAnalysis ;
11
12
using RpsRuntime ;
13
+ using TaskDialog = Autodesk . Revit . UI . TaskDialog ;
12
14
13
15
namespace RevitPythonShell . RevitCommands
14
16
{
@@ -23,11 +25,45 @@ namespace RevitPythonShell.RevitCommands
23
25
[ Regeneration ( RegenerationOption . Manual ) ]
24
26
public class DeployRpsAddinCommand : IExternalCommand
25
27
{
28
+ private const string FileHeaderTemplate = """
29
+ using Autodesk.Revit.Attributes;
30
+ using RevitPythonShell.RevitCommands;
31
+
32
+ #nullable disable
33
+ """ ;
34
+
35
+ private const string ExternalCommandTemplate = """
36
+ using Autodesk.Revit.Attributes;
37
+ using RevitPythonShell.RevitCommands;
38
+
39
+ #nullable disable
40
+
41
+ [Regeneration]
42
+ [Transaction]
43
+ public class CLASSNAME : RpsExternalCommandBase
44
+ {
45
+ }
46
+ """ ;
47
+
48
+ private const string ExternalApplicationTemplate = """
49
+ using Autodesk.Revit.Attributes;
50
+ using RevitPythonShell.RevitCommands;
51
+
52
+ #nullable disable
53
+
54
+ [Regeneration]
55
+ [Transaction]
56
+ public class CLASSNAME : RpsExternalApplicationBase
57
+ {
58
+ }
59
+ """ ;
60
+
26
61
private string _outputFolder ;
27
62
private string _rootFolder ;
28
63
private string _addinName ;
29
64
private XDocument _doc ;
30
65
66
+ [ UnconditionalSuppressMessage ( "SingleFile" , "IL3000:Avoid accessing Assembly file path when publishing as a single file" , Justification = "<Pending>" ) ]
31
67
Result IExternalCommand . Execute ( ExternalCommandData commandData , ref string message , Autodesk . Revit . DB . ElementSet elements )
32
68
{
33
69
try
@@ -46,8 +82,8 @@ Result IExternalCommand.Execute(ExternalCommandData commandData, ref string mess
46
82
// copy static stuff (rpsaddin runtime, ironpython dlls etc., addin installation utilities)
47
83
CopyFile ( typeof ( RpsExternalApplicationBase ) . Assembly . Location ) ; // RpsRuntime.dll
48
84
49
- var ironPythonPath = Path . GetDirectoryName ( this . GetType ( ) . Assembly . Location ) ;
50
- CopyFile ( Path . Combine ( ironPythonPath , "IronPython.dll" ) ) ; // IronPython.dll
85
+ var ironPythonPath = Path . GetDirectoryName ( GetType ( ) . Assembly . Location ) ;
86
+ CopyFile ( Path . Combine ( ironPythonPath ! , "IronPython.dll" ) ) ; // IronPython.dll
51
87
CopyFile ( Path . Combine ( ironPythonPath , "IronPython.Modules.dll" ) ) ; // IronPython.Modules.dll
52
88
CopyFile ( Path . Combine ( ironPythonPath , "Microsoft.Scripting.dll" ) ) ; // Microsoft.Scripting.dll
53
89
CopyFile ( Path . Combine ( ironPythonPath , "Microsoft.Scripting.Metadata.dll" ) ) ; // Microsoft.Scripting.Metadata.dll
@@ -68,7 +104,7 @@ Result IExternalCommand.Execute(ExternalCommandData commandData, ref string mess
68
104
catch ( Exception exception )
69
105
{
70
106
71
- TaskDialog . Show ( "Deploy RpsAddin" , "Error deploying addin: " + exception . ToString ( ) ) ;
107
+ TaskDialog . Show ( "Deploy RpsAddin" , $ "Error deploying addin: { exception } " ) ;
72
108
return Result . Failed ;
73
109
}
74
110
}
@@ -84,8 +120,6 @@ Result IExternalCommand.Execute(ExternalCommandData commandData, ref string mess
84
120
/// </summary>
85
121
private void CopyIcons ( )
86
122
{
87
- HashSet < string > copiedIcons = new HashSet < string > ( ) ;
88
-
89
123
foreach ( var pb in _doc . Descendants ( "PushButton" ) )
90
124
{
91
125
CopyReferencedFileToOutputFolder ( pb . Attribute ( "largeImage" ) ) ;
@@ -108,7 +142,7 @@ private void CopyExplicitFiles()
108
142
{
109
143
foreach ( var xmlFile in _doc . Descendants ( "Files" ) . SelectMany ( f => f . Descendants ( "File" ) ) )
110
144
{
111
- var source = xmlFile . Attribute ( "src" ) . Value ;
145
+ var source = xmlFile . Attribute ( "src" ) ! . Value ;
112
146
var sourcePath = GetRootedPath ( _rootFolder , source ) ;
113
147
114
148
if ( ! File . Exists ( sourcePath ) )
@@ -122,7 +156,7 @@ private void CopyExplicitFiles()
122
156
File . Copy ( sourcePath , Path . Combine ( _outputFolder , fileName ) ) ;
123
157
124
158
// remove path information for deployment
125
- xmlFile . Attribute ( "src" ) . Value = fileName ;
159
+ xmlFile . Attribute ( "src" ) ! . Value = fileName ;
126
160
}
127
161
}
128
162
@@ -166,7 +200,7 @@ private string GetAddinXmlPath()
166
200
dialog . CheckPathExists = true ;
167
201
dialog . Multiselect = false ;
168
202
dialog . DefaultExt = "xml" ;
169
- dialog . Filter = "RpsAddin xml files (*.xml)|*.xml" ;
203
+ dialog . Filter = @ "RpsAddin xml files (*.xml)|*.xml";
170
204
171
205
dialog . ShowDialog ( ) ;
172
206
return dialog . FileName ;
@@ -180,63 +214,76 @@ private string GetAddinXmlPath()
180
214
/// </summary>
181
215
private void CreateAssembly ( )
182
216
{
183
- var assemblyName = new AssemblyName { Name = _addinName + ".dll" , Version = new Version ( 1 , 0 , 0 , 0 ) } ; // FIXME: read version from doc
184
- var assemblyBuilder = AppDomain . CurrentDomain . DefineDynamicAssembly ( assemblyName , AssemblyBuilderAccess . RunAndSave , _outputFolder ) ;
185
- var moduleBuilder = assemblyBuilder . DefineDynamicModule ( "RpsAddinModule" , _addinName + ".dll" ) ;
186
-
217
+ string dllPath = Path . Combine ( _outputFolder , $ "{ _addinName } .dll") ;
218
+
219
+ StringBuilder sourceCode = new StringBuilder ( ) ;
220
+ sourceCode . Append ( FileHeaderTemplate ) ;
221
+ sourceCode . Append ( ExternalApplicationTemplate . Replace ( "CLASSNAME" , _addinName ) ) ;
222
+
223
+ List < ResourceDescription > resources = new List < ResourceDescription > ( ) ;
224
+
187
225
foreach ( var xmlPushButton in _doc . Descendants ( "PushButton" ) )
188
226
{
189
227
string scriptFileName ;
190
228
if ( xmlPushButton . Attribute ( "src" ) != null )
191
229
{
192
- scriptFileName = xmlPushButton . Attribute ( "src" ) . Value ;
230
+ scriptFileName = xmlPushButton . Attribute ( "src" ) ! . Value ;
193
231
}
194
232
else if ( xmlPushButton . Attribute ( "script" ) != null ) // Backwards compatibility
195
233
{
196
- scriptFileName = xmlPushButton . Attribute ( "script" ) . Value ;
234
+ scriptFileName = xmlPushButton . Attribute ( "script" ) ! . Value ;
197
235
}
198
236
else
199
237
{
200
238
throw new ApplicationException ( "<PushButton/> tag missing a src attribute in addin manifest" ) ;
201
239
}
202
240
203
- var scriptFile = GetRootedPath ( _rootFolder , scriptFileName ) ; // e.g. "C:\projects\helloworld\helloworld.py" or "..\helloworld.py"
204
- var newScriptFile = Path . GetFileName ( scriptFile ) ; // e.g. "helloworld.py" - strip path for embedded resource
205
- var className = "ec_" + Path . GetFileNameWithoutExtension ( newScriptFile ) ; // e.g. "ec_helloworld", "ec" stands for ExternalCommand
206
-
207
- var scriptStream = File . OpenRead ( scriptFile ) ;
208
- moduleBuilder . DefineManifestResource ( newScriptFile , scriptStream , ResourceAttributes . Public ) ;
241
+ var scriptFilePath = GetRootedPath ( _rootFolder , scriptFileName ) ; // e.g. "C:\projects\helloworld\helloworld.py" or "..\helloworld.py"
242
+ var embeddedScriptFileName = Path . GetFileName ( scriptFilePath ) ; // e.g. "helloworld.py" - strip path for embedded resource
243
+ var className = "ec_" + Path . GetFileNameWithoutExtension ( embeddedScriptFileName ) ; // e.g. "ec_helloworld", "ec" stands for ExternalCommand
209
244
245
+ var resourceDescription = new ResourceDescription (
246
+ embeddedScriptFileName ,
247
+ ( ) => new FileStream ( scriptFilePath , FileMode . Open , FileAccess . Read ) ,
248
+ isPublic : true ) ;
249
+ resources . Add ( resourceDescription ) ;
250
+
210
251
// script has new path inside assembly, rename it for the RpsAddin xml file we intend to save as a resource
211
- xmlPushButton . Attribute ( "src" ) . Value = newScriptFile ;
212
-
213
- var typeBuilder = moduleBuilder . DefineType (
214
- className ,
215
- TypeAttributes . Class | TypeAttributes . Public ,
216
- typeof ( RpsExternalCommandBase ) ) ;
252
+ xmlPushButton . Attribute ( "src" ) ! . Value = embeddedScriptFileName ;
217
253
218
- AddRegenerationAttributeToType ( typeBuilder ) ;
219
- AddTransactionAttributeToType ( typeBuilder ) ;
220
-
221
- typeBuilder . CreateType ( ) ;
254
+ sourceCode . Append ( ExternalCommandTemplate . Replace ( "CLASSNAME" , className ) ) ;
222
255
}
223
256
224
257
// add StartupScript to addin assembly
225
- if ( _doc . Descendants ( "StartupScript" ) . Count ( ) > 0 )
258
+ if ( _doc . Descendants ( "StartupScript" ) . Any ( ) )
226
259
{
227
260
var tag = _doc . Descendants ( "StartupScript" ) . First ( ) ;
228
- var scriptFile = GetRootedPath ( _rootFolder , tag . Attribute ( "src" ) . Value ) ;
229
- var newScriptFile = Path . GetFileName ( scriptFile ) ;
230
- var scriptStream = File . OpenRead ( scriptFile ) ;
231
- moduleBuilder . DefineManifestResource ( newScriptFile , scriptStream , ResourceAttributes . Public ) ;
232
-
261
+ var scriptFilePath = GetRootedPath ( _rootFolder , tag . Attribute ( "src" ) ! . Value ) ;
262
+ var embeddedScriptFileName = Path . GetFileName ( scriptFilePath ) ;
263
+
264
+ var resourceDescription = new ResourceDescription (
265
+ embeddedScriptFileName ,
266
+ ( ) => new FileStream ( scriptFilePath , FileMode . Open , FileAccess . Read ) ,
267
+ isPublic : true ) ;
268
+ resources . Add ( resourceDescription ) ;
269
+
233
270
// script has new path inside assembly, rename it for the RpsAddin xml file we intend to save as a resource
234
- tag . Attribute ( "src" ) . Value = newScriptFile ;
271
+ tag . Attribute ( "src" ) ! . Value = embeddedScriptFileName ;
235
272
}
236
273
237
- AddRpsAddinXmlToAssembly ( _addinName , _doc , moduleBuilder ) ;
238
- AddExternalApplicationToAssembly ( _addinName , moduleBuilder ) ;
239
- assemblyBuilder . Save ( _addinName + ".dll" ) ;
274
+ resources . Add ( new ResourceDescription ( $ "{ _addinName } .xml", ( ) =>
275
+ {
276
+ var stream = new MemoryStream ( ) ;
277
+ using ( var writer = new StreamWriter ( stream ) )
278
+ {
279
+ writer . Write ( _doc . ToString ( ) ) ;
280
+ writer . Flush ( ) ;
281
+ }
282
+ stream . Position = 0 ;
283
+ return stream ;
284
+ } , isPublic : true ) ) ;
285
+
286
+ DynamicAssemblyCompiler . CompileAndSave ( sourceCode . ToString ( ) , dllPath , resources . ToArray ( ) ) ;
240
287
}
241
288
242
289
/// <summary>
@@ -258,52 +305,6 @@ private static string GetRootedPath(string sourceFolder, string possiblyRelative
258
305
return possiblyRelativePath ;
259
306
}
260
307
261
- /// <summary>
262
- /// Adds a subclass of RpsExternalApplicationBase to make the assembly
263
- /// work as an external application.
264
- /// </summary>
265
- private void AddExternalApplicationToAssembly ( string addinName , ModuleBuilder moduleBuilder )
266
- {
267
- var typeBuilder = moduleBuilder . DefineType (
268
- addinName ,
269
- TypeAttributes . Class | TypeAttributes . Public ,
270
- typeof ( RpsExternalApplicationBase ) ) ;
271
- AddRegenerationAttributeToType ( typeBuilder ) ;
272
- AddTransactionAttributeToType ( typeBuilder ) ;
273
- typeBuilder . CreateType ( ) ;
274
- }
275
-
276
- /// <summary>
277
- /// Adds the [Transaction(TransactionMode.Manual)] attribute to the type.
278
- /// </summary>
279
- private void AddTransactionAttributeToType ( TypeBuilder typeBuilder )
280
- {
281
- var transactionConstructorInfo = typeof ( TransactionAttribute ) . GetConstructor ( new Type [ ] { typeof ( TransactionMode ) } ) ;
282
- var transactionAttributeBuilder = new CustomAttributeBuilder ( transactionConstructorInfo , new object [ ] { TransactionMode . Manual } ) ;
283
- typeBuilder . SetCustomAttribute ( transactionAttributeBuilder ) ;
284
- }
285
-
286
- /// <summary>
287
- /// Adds the [Transaction(TransactionMode.Manual)] attribute to the type.
288
- /// </summary>
289
- /// <param name="typeBuilder"></param>
290
- private void AddRegenerationAttributeToType ( TypeBuilder typeBuilder )
291
- {
292
- var regenerationConstrutorInfo = typeof ( RegenerationAttribute ) . GetConstructor ( new Type [ ] { typeof ( RegenerationOption ) } ) ;
293
- var regenerationAttributeBuilder = new CustomAttributeBuilder ( regenerationConstrutorInfo , new object [ ] { RegenerationOption . Manual } ) ;
294
- typeBuilder . SetCustomAttribute ( regenerationAttributeBuilder ) ;
295
- }
296
-
297
- private void AddRpsAddinXmlToAssembly ( string addinName , XDocument doc , ModuleBuilder moduleBuilder )
298
- {
299
- var stream = new MemoryStream ( ) ;
300
- var writer = new StreamWriter ( stream ) ;
301
- writer . Write ( doc . ToString ( ) ) ;
302
- writer . Flush ( ) ;
303
- stream . Position = 0 ;
304
- moduleBuilder . DefineManifestResource ( addinName + ".xml" , stream , ResourceAttributes . Public ) ;
305
- }
306
-
307
308
/// <summary>
308
309
/// Creates a subfolder in rootFolder with the basename of the
309
310
/// RpsAddin xml file and returns the name of that folder.
@@ -314,7 +315,7 @@ private void AddRpsAddinXmlToAssembly(string addinName, XDocument doc, ModuleBui
314
315
/// </summary>
315
316
private string CreateOutputFolder ( )
316
317
{
317
- var folderName = string . Format ( "{0}_{1}" , "Output" , _addinName ) ;
318
+ var folderName = $ "Output_ { _addinName } " ;
318
319
var folderPath = Path . Combine ( _rootFolder , folderName ) ;
319
320
320
321
if ( Directory . Exists ( folderPath ) )
@@ -323,7 +324,7 @@ private string CreateOutputFolder()
323
324
Directory . Delete ( folderPath , true ) ;
324
325
}
325
326
326
- Directory . CreateDirectory ( folderPath , Directory . GetAccessControl ( _rootFolder ) ) ;
327
+ Directory . CreateDirectory ( folderPath ) ;
327
328
return folderPath ;
328
329
}
329
330
}
0 commit comments