@rui.branco/revit-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +496 -0
- package/index.js +124 -0
- package/lib/bridge.js +161 -0
- package/lib/tools/detail.js +86 -0
- package/lib/tools/diagnostics.js +57 -0
- package/lib/tools/document.js +149 -0
- package/lib/tools/documentation.js +550 -0
- package/lib/tools/geometry.js +331 -0
- package/lib/tools/graphics.js +197 -0
- package/lib/tools/install.js +208 -0
- package/lib/tools/material-appearance.js +168 -0
- package/lib/tools/materials.js +247 -0
- package/lib/tools/model.js +285 -0
- package/lib/tools/parameters.js +140 -0
- package/lib/tools/quality.js +200 -0
- package/lib/tools/read.js +142 -0
- package/lib/tools/reload.js +27 -0
- package/lib/tools/sheet-collections.js +62 -0
- package/lib/tools/sheets.js +76 -0
- package/lib/tools/titleblocks.js +152 -0
- package/lib/tools/views.js +746 -0
- package/lib/tools/write.js +122 -0
- package/manifest.json +416 -0
- package/package.json +52 -0
- package/revit-bridge/README.md +1256 -0
- package/revit-bridge/dist/RevitMcpBridge.Handlers.deps.json +23 -0
- package/revit-bridge/dist/RevitMcpBridge.Handlers.dll +0 -0
- package/revit-bridge/dist/RevitMcpBridge.deps.json +23 -0
- package/revit-bridge/dist/RevitMcpBridge.dll +0 -0
- package/revit-bridge/install.ps1 +676 -0
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
#Requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
Installs the Revit MCP Bridge add-in for every installed Revit 2025+.
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
One command, no Visual Studio, no admin rights, and no .NET SDK:
|
|
8
|
+
|
|
9
|
+
.\install.ps1
|
|
10
|
+
|
|
11
|
+
Detects Revit under Program Files, copies the add-in to
|
|
12
|
+
%APPDATA%\Autodesk\Revit\Addins\<version>\RevitMcpBridge\ and writes the .addin manifest
|
|
13
|
+
next to it. Re-running is safe: the install folder is replaced and the manifest overwritten.
|
|
14
|
+
|
|
15
|
+
By default the add-in comes from the prebuilt dist\ folder next to this script - that is
|
|
16
|
+
what the npm package ships, and it is why an end user needs neither the .NET SDK nor a
|
|
17
|
+
build step. The DLL there is compiled against the Revit 2025 reference assemblies and
|
|
18
|
+
loads in Revit 2025, 2026 and 2027 alike, so one binary covers every supported version.
|
|
19
|
+
Pass -Build to compile a fresh one from source instead; that is the contributor path and
|
|
20
|
+
the only one that needs the SDK.
|
|
21
|
+
|
|
22
|
+
Written for Windows PowerShell 5.1 as well as PowerShell 7 - deliberately no ternary
|
|
23
|
+
operator, no ??, no && / ||, and no -AsHashtable.
|
|
24
|
+
|
|
25
|
+
Every human-readable line is prefixed with "[revit-bridge] " and a fixed-width level so a
|
|
26
|
+
caller can grep it. Pass -Json to get a single JSON result object as the only stdout output
|
|
27
|
+
instead, which is the mode an MCP "install bridge" tool should use.
|
|
28
|
+
|
|
29
|
+
.PARAMETER RevitVersion
|
|
30
|
+
Restrict the operation to one version, e.g. 2026. Without it, every detected version is used.
|
|
31
|
+
|
|
32
|
+
.PARAMETER Build
|
|
33
|
+
Force a fresh dotnet build -c Release and install that output, ignoring dist\. Requires the
|
|
34
|
+
.NET SDK. For contributors working on the C# side.
|
|
35
|
+
|
|
36
|
+
.PARAMETER SkipBuild
|
|
37
|
+
Never build. Install the prebuilt dist\ folder, or whatever is already in bin\Release if
|
|
38
|
+
dist\ is absent. This is the default behaviour whenever dist\ is present, so the switch is
|
|
39
|
+
only meaningful in a source checkout with no dist\.
|
|
40
|
+
|
|
41
|
+
.PARAMETER Uninstall
|
|
42
|
+
Remove the manifest and the install folder instead of installing.
|
|
43
|
+
|
|
44
|
+
.PARAMETER Json
|
|
45
|
+
Emit one JSON result object as the only stdout output. Nothing else is printed.
|
|
46
|
+
|
|
47
|
+
.EXAMPLE
|
|
48
|
+
.\install.ps1
|
|
49
|
+
.EXAMPLE
|
|
50
|
+
.\install.ps1 -RevitVersion 2026
|
|
51
|
+
.EXAMPLE
|
|
52
|
+
.\install.ps1 -Build
|
|
53
|
+
.EXAMPLE
|
|
54
|
+
.\install.ps1 -Uninstall -Json
|
|
55
|
+
#>
|
|
56
|
+
[CmdletBinding()]
|
|
57
|
+
param(
|
|
58
|
+
[string] $RevitVersion,
|
|
59
|
+
[switch] $Build,
|
|
60
|
+
[switch] $SkipBuild,
|
|
61
|
+
[switch] $Uninstall,
|
|
62
|
+
[switch] $Json
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
$ErrorActionPreference = 'Stop'
|
|
66
|
+
|
|
67
|
+
# ---------------------------------------------------------------------------------------------
|
|
68
|
+
# Constants. The AddInId is fixed and must never change: Revit keys the add-in on this GUID, and
|
|
69
|
+
# a new one would leave a stale duplicate registered.
|
|
70
|
+
# ---------------------------------------------------------------------------------------------
|
|
71
|
+
$AddInName = 'RevitMcpBridge'
|
|
72
|
+
$AssemblyFileName = 'RevitMcpBridge.dll'
|
|
73
|
+
$FullClassName = 'RevitMcpBridge.BridgeApplication'
|
|
74
|
+
$AddInId = '8f2c7a41-3e6b-4d19-9c05-1b7a52e4d83f'
|
|
75
|
+
$VendorId = 'com.github.revit-mcp'
|
|
76
|
+
$VendorDescription = 'Revit MCP Bridge - in-process HTTP bridge for the Node MCP server'
|
|
77
|
+
$ManifestFileName = 'RevitMcpBridge.addin'
|
|
78
|
+
$MinimumRevitYear = 2025
|
|
79
|
+
$ProjectFileName = 'RevitMcpBridge.csproj'
|
|
80
|
+
$DistDirName = 'dist'
|
|
81
|
+
|
|
82
|
+
# Revit ships these itself. If one ever appears in the build output it must not be copied next to
|
|
83
|
+
# the add-in, or Revit loads two copies of the API and every type check fails.
|
|
84
|
+
$RevitOwnedFiles = @(
|
|
85
|
+
'RevitAPI.dll',
|
|
86
|
+
'RevitAPIUI.dll',
|
|
87
|
+
'AdWindows.dll',
|
|
88
|
+
'UIFramework.dll',
|
|
89
|
+
'RevitAPI.xml',
|
|
90
|
+
'RevitAPIUI.xml',
|
|
91
|
+
'RevitAPI.runtimeconfig.json',
|
|
92
|
+
'RevitAPIUI.runtimeconfig.json'
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
$ScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
96
|
+
|
|
97
|
+
$script:Log = New-Object System.Collections.ArrayList
|
|
98
|
+
$script:Warnings = New-Object System.Collections.ArrayList
|
|
99
|
+
$script:Errors = New-Object System.Collections.ArrayList
|
|
100
|
+
$script:VersionResults = New-Object System.Collections.ArrayList
|
|
101
|
+
$script:ExitCode = 0
|
|
102
|
+
$script:BuildStatus = 'skipped'
|
|
103
|
+
$script:OutputDir = $null
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------------------------
|
|
106
|
+
# Output helpers
|
|
107
|
+
#
|
|
108
|
+
# Writes straight to the process stdout handle rather than the PowerShell pipeline, so these can
|
|
109
|
+
# be called from inside a function without their text becoming that function's return value.
|
|
110
|
+
# ---------------------------------------------------------------------------------------------
|
|
111
|
+
function Write-Line {
|
|
112
|
+
param(
|
|
113
|
+
[string] $Level,
|
|
114
|
+
[string] $Message
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
$line = '[revit-bridge] ' + $Level.PadRight(9) + ' ' + $Message
|
|
118
|
+
$null = $script:Log.Add($line)
|
|
119
|
+
|
|
120
|
+
if (-not $Json) {
|
|
121
|
+
[Console]::Out.WriteLine($line)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function Write-Warn {
|
|
126
|
+
param([string] $Message)
|
|
127
|
+
|
|
128
|
+
$null = $script:Warnings.Add($Message)
|
|
129
|
+
Write-Line -Level 'WARN' -Message $Message
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function Write-Err {
|
|
133
|
+
param([string] $Message)
|
|
134
|
+
|
|
135
|
+
$null = $script:Errors.Add($Message)
|
|
136
|
+
Write-Line -Level 'ERROR' -Message $Message
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# ---------------------------------------------------------------------------------------------
|
|
140
|
+
# Detection
|
|
141
|
+
# ---------------------------------------------------------------------------------------------
|
|
142
|
+
function Get-AutodeskRoots {
|
|
143
|
+
$roots = New-Object System.Collections.ArrayList
|
|
144
|
+
|
|
145
|
+
$candidates = @($env:ProgramW6432, $env:ProgramFiles, 'C:\Program Files')
|
|
146
|
+
|
|
147
|
+
foreach ($candidate in $candidates) {
|
|
148
|
+
if ([string]::IsNullOrWhiteSpace($candidate)) {
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
$root = Join-Path $candidate 'Autodesk'
|
|
153
|
+
|
|
154
|
+
if (-not (Test-Path -LiteralPath $root)) {
|
|
155
|
+
continue
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if ($roots -notcontains $root) {
|
|
159
|
+
$null = $roots.Add($root)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return $roots.ToArray()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function Get-InstalledRevitVersions {
|
|
167
|
+
$found = New-Object System.Collections.ArrayList
|
|
168
|
+
$seen = New-Object System.Collections.ArrayList
|
|
169
|
+
|
|
170
|
+
foreach ($root in (Get-AutodeskRoots)) {
|
|
171
|
+
$directories = @(Get-ChildItem -LiteralPath $root -Directory -ErrorAction SilentlyContinue)
|
|
172
|
+
|
|
173
|
+
foreach ($directory in $directories) {
|
|
174
|
+
if ($directory.Name -notmatch '^Revit\s+(\d{4})$') {
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
$year = $Matches[1]
|
|
179
|
+
|
|
180
|
+
if ($seen -contains $year) {
|
|
181
|
+
continue
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
$null = $seen.Add($year)
|
|
185
|
+
|
|
186
|
+
$null = $found.Add([pscustomobject]@{
|
|
187
|
+
Version = $year
|
|
188
|
+
RevitPath = $directory.FullName
|
|
189
|
+
HasExe = (Test-Path -LiteralPath (Join-Path $directory.FullName 'Revit.exe'))
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
$sorted = @($found.ToArray() | Sort-Object -Property @{ Expression = { [int] $_.Version } })
|
|
195
|
+
return $sorted
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function Get-AddinsRoot {
|
|
199
|
+
$path = Join-Path $env:APPDATA 'Autodesk'
|
|
200
|
+
$path = Join-Path $path 'Revit'
|
|
201
|
+
$path = Join-Path $path 'Addins'
|
|
202
|
+
return $path
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# Versions that already have an add-ins folder. Used by -Uninstall so the bridge can still be
|
|
206
|
+
# cleaned up after the matching Revit itself has been removed.
|
|
207
|
+
function Get-ConfiguredVersions {
|
|
208
|
+
$root = Get-AddinsRoot
|
|
209
|
+
$years = New-Object System.Collections.ArrayList
|
|
210
|
+
|
|
211
|
+
if (-not (Test-Path -LiteralPath $root)) {
|
|
212
|
+
return $years.ToArray()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
$directories = @(Get-ChildItem -LiteralPath $root -Directory -ErrorAction SilentlyContinue)
|
|
216
|
+
|
|
217
|
+
foreach ($directory in $directories) {
|
|
218
|
+
if ($directory.Name -match '^\d{4}$') {
|
|
219
|
+
$null = $years.Add($directory.Name)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return $years.ToArray()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# ---------------------------------------------------------------------------------------------
|
|
227
|
+
# Build
|
|
228
|
+
# ---------------------------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
# The add-in the npm package ships: already compiled, so no SDK is needed to install it.
|
|
231
|
+
function Resolve-PrebuiltOutput {
|
|
232
|
+
$distDir = Join-Path $ScriptRoot $DistDirName
|
|
233
|
+
|
|
234
|
+
if (-not (Test-Path -LiteralPath (Join-Path $distDir $AssemblyFileName))) {
|
|
235
|
+
return $null
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return $distDir
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function Test-DotnetAvailable {
|
|
242
|
+
$command = Get-Command 'dotnet' -ErrorAction SilentlyContinue
|
|
243
|
+
|
|
244
|
+
if ($null -eq $command) {
|
|
245
|
+
return $false
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return $true
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function Invoke-Build {
|
|
252
|
+
$projectPath = Join-Path $ScriptRoot $ProjectFileName
|
|
253
|
+
|
|
254
|
+
if (-not (Test-Path -LiteralPath $projectPath)) {
|
|
255
|
+
Write-Err ('Project not found: ' + $projectPath)
|
|
256
|
+
return $false
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
# Reached only when there is no prebuilt add-in to fall back on, so name both ways out.
|
|
260
|
+
if (-not (Test-DotnetAvailable)) {
|
|
261
|
+
$script:BuildStatus = 'failed'
|
|
262
|
+
Write-Err ('The .NET SDK is not installed (no "dotnet" on PATH) and there is no prebuilt add-in at ' + (Join-Path $ScriptRoot $DistDirName) + '. Either install the .NET SDK from https://dotnet.microsoft.com/download and re-run, or reinstall the npm package @rui.branco/revit-mcp, which ships the compiled add-in and needs no SDK at all.')
|
|
263
|
+
return $false
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
Write-Line -Level 'BUILD' -Message 'status=started configuration=Release'
|
|
267
|
+
|
|
268
|
+
# dotnet writes diagnostics to stderr; with ErrorActionPreference=Stop a merged 2>&1 stream
|
|
269
|
+
# would throw NativeCommandError, so relax it just for the call.
|
|
270
|
+
$previous = $ErrorActionPreference
|
|
271
|
+
$ErrorActionPreference = 'Continue'
|
|
272
|
+
$output = & dotnet build $projectPath -c Release --nologo 2>&1
|
|
273
|
+
$code = $LASTEXITCODE
|
|
274
|
+
$ErrorActionPreference = $previous
|
|
275
|
+
|
|
276
|
+
foreach ($entry in $output) {
|
|
277
|
+
$text = [string] $entry
|
|
278
|
+
|
|
279
|
+
if ([string]::IsNullOrWhiteSpace($text)) {
|
|
280
|
+
continue
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
Write-Line -Level 'BUILD' -Message ('| ' + $text.Trim())
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if ($code -ne 0) {
|
|
287
|
+
$script:BuildStatus = 'failed'
|
|
288
|
+
Write-Err ('dotnet build failed with exit code ' + $code + '.')
|
|
289
|
+
return $false
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
$script:BuildStatus = 'ok'
|
|
293
|
+
Write-Line -Level 'BUILD' -Message 'status=ok'
|
|
294
|
+
return $true
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function Resolve-BuildOutput {
|
|
298
|
+
$binRoot = Join-Path $ScriptRoot 'bin'
|
|
299
|
+
|
|
300
|
+
if (-not (Test-Path -LiteralPath $binRoot)) {
|
|
301
|
+
return $null
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
$matchesFound = @(
|
|
305
|
+
Get-ChildItem -LiteralPath $binRoot -Recurse -File -Filter $AssemblyFileName -ErrorAction SilentlyContinue |
|
|
306
|
+
Where-Object { $_.DirectoryName -like '*\Release*' } |
|
|
307
|
+
Sort-Object -Property LastWriteTimeUtc -Descending
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
if ($matchesFound.Count -eq 0) {
|
|
311
|
+
return $null
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return $matchesFound[0].DirectoryName
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
# ---------------------------------------------------------------------------------------------
|
|
318
|
+
# Install / uninstall for one version
|
|
319
|
+
# ---------------------------------------------------------------------------------------------
|
|
320
|
+
function New-AddinManifest {
|
|
321
|
+
param(
|
|
322
|
+
[string] $AssemblyPath
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
# Paths can legitimately contain & - escape before embedding in XML.
|
|
326
|
+
$safePath = [System.Security.SecurityElement]::Escape($AssemblyPath)
|
|
327
|
+
|
|
328
|
+
$lines = @(
|
|
329
|
+
'<?xml version="1.0" encoding="utf-8"?>',
|
|
330
|
+
'<RevitAddIns>',
|
|
331
|
+
' <AddIn Type="Application">',
|
|
332
|
+
(' <Name>' + $AddInName + '</Name>'),
|
|
333
|
+
(' <Assembly>' + $safePath + '</Assembly>'),
|
|
334
|
+
(' <AddInId>' + $AddInId + '</AddInId>'),
|
|
335
|
+
(' <FullClassName>' + $FullClassName + '</FullClassName>'),
|
|
336
|
+
(' <VendorId>' + $VendorId + '</VendorId>'),
|
|
337
|
+
(' <VendorDescription>' + $VendorDescription + '</VendorDescription>'),
|
|
338
|
+
' </AddIn>',
|
|
339
|
+
'</RevitAddIns>'
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
return ($lines -join [Environment]::NewLine)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function Install-ForVersion {
|
|
346
|
+
param(
|
|
347
|
+
[psobject] $Target,
|
|
348
|
+
[string] $OutputDir
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
$version = $Target.Version
|
|
352
|
+
$addinsDir = Join-Path (Get-AddinsRoot) $version
|
|
353
|
+
$installDir = Join-Path $addinsDir $AddInName
|
|
354
|
+
$manifest = Join-Path $addinsDir $ManifestFileName
|
|
355
|
+
$assembly = Join-Path $installDir $AssemblyFileName
|
|
356
|
+
|
|
357
|
+
# Idempotent: drop the previous copy wholesale rather than merging into it, so a renamed or
|
|
358
|
+
# removed file from an older build cannot linger.
|
|
359
|
+
if (Test-Path -LiteralPath $installDir) {
|
|
360
|
+
Remove-Item -LiteralPath $installDir -Recurse -Force
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
$null = New-Item -ItemType Directory -Path $installDir -Force
|
|
364
|
+
|
|
365
|
+
$copied = 0
|
|
366
|
+
|
|
367
|
+
foreach ($file in @(Get-ChildItem -LiteralPath $OutputDir -File)) {
|
|
368
|
+
if ($RevitOwnedFiles -contains $file.Name) {
|
|
369
|
+
Write-Warn ('Not copying ' + $file.Name + ' - Revit supplies it at runtime. Check that the Revit API PackageReference is still ExcludeAssets=runtime.')
|
|
370
|
+
continue
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
Copy-Item -LiteralPath $file.FullName -Destination $installDir -Force
|
|
374
|
+
$copied = $copied + 1
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
foreach ($directory in @(Get-ChildItem -LiteralPath $OutputDir -Directory)) {
|
|
378
|
+
Copy-Item -LiteralPath $directory.FullName -Destination $installDir -Recurse -Force
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
Set-Content -LiteralPath $manifest -Value (New-AddinManifest -AssemblyPath $assembly) -Encoding utf8
|
|
382
|
+
|
|
383
|
+
Write-Line -Level 'INSTALLED' -Message ('version=' + $version + ' files=' + $copied + ' dir="' + $installDir + '" manifest="' + $manifest + '"')
|
|
384
|
+
|
|
385
|
+
$null = $script:VersionResults.Add([ordered]@{
|
|
386
|
+
version = $version
|
|
387
|
+
revitPath = $Target.RevitPath
|
|
388
|
+
addinsDir = $addinsDir
|
|
389
|
+
installDir = $installDir
|
|
390
|
+
manifest = $manifest
|
|
391
|
+
assembly = $assembly
|
|
392
|
+
files = $copied
|
|
393
|
+
status = 'installed'
|
|
394
|
+
})
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function Uninstall-ForVersion {
|
|
398
|
+
param(
|
|
399
|
+
[psobject] $Target
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
$version = $Target.Version
|
|
403
|
+
$addinsDir = Join-Path (Get-AddinsRoot) $version
|
|
404
|
+
$installDir = Join-Path $addinsDir $AddInName
|
|
405
|
+
$manifest = Join-Path $addinsDir $ManifestFileName
|
|
406
|
+
|
|
407
|
+
$removedDir = $false
|
|
408
|
+
$removedManifest = $false
|
|
409
|
+
|
|
410
|
+
if (Test-Path -LiteralPath $installDir) {
|
|
411
|
+
Remove-Item -LiteralPath $installDir -Recurse -Force
|
|
412
|
+
$removedDir = $true
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (Test-Path -LiteralPath $manifest) {
|
|
416
|
+
Remove-Item -LiteralPath $manifest -Force
|
|
417
|
+
$removedManifest = $true
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
$status = 'not-installed'
|
|
421
|
+
|
|
422
|
+
if ($removedDir -or $removedManifest) {
|
|
423
|
+
$status = 'removed'
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
Write-Line -Level 'REMOVED' -Message ('version=' + $version + ' status=' + $status + ' dir="' + $installDir + '" manifest="' + $manifest + '"')
|
|
427
|
+
|
|
428
|
+
$null = $script:VersionResults.Add([ordered]@{
|
|
429
|
+
version = $version
|
|
430
|
+
revitPath = $Target.RevitPath
|
|
431
|
+
addinsDir = $addinsDir
|
|
432
|
+
installDir = $installDir
|
|
433
|
+
manifest = $manifest
|
|
434
|
+
removedFolder = $removedDir
|
|
435
|
+
removedManifest = $removedManifest
|
|
436
|
+
status = $status
|
|
437
|
+
})
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
# ---------------------------------------------------------------------------------------------
|
|
441
|
+
# Main
|
|
442
|
+
# ---------------------------------------------------------------------------------------------
|
|
443
|
+
function Invoke-Main {
|
|
444
|
+
$action = 'install'
|
|
445
|
+
|
|
446
|
+
if ($Uninstall) {
|
|
447
|
+
$action = 'uninstall'
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
Write-Line -Level 'INFO' -Message ('action=' + $action + ' addin=' + $AddInName + ' minimumRevit=' + $MinimumRevitYear)
|
|
451
|
+
|
|
452
|
+
$detected = @(Get-InstalledRevitVersions)
|
|
453
|
+
|
|
454
|
+
foreach ($entry in $detected) {
|
|
455
|
+
Write-Line -Level 'DETECTED' -Message ('version=' + $entry.Version + ' hasExe=' + $entry.HasExe + ' path="' + $entry.RevitPath + '"')
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
$candidates = New-Object System.Collections.ArrayList
|
|
459
|
+
|
|
460
|
+
foreach ($entry in $detected) {
|
|
461
|
+
$null = $candidates.Add($entry)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
# -Uninstall also cleans versions whose Revit is gone but whose add-ins folder remains.
|
|
465
|
+
if ($Uninstall) {
|
|
466
|
+
foreach ($year in (Get-ConfiguredVersions)) {
|
|
467
|
+
$known = @($candidates.ToArray() | Where-Object { $_.Version -eq $year })
|
|
468
|
+
|
|
469
|
+
if ($known.Count -eq 0) {
|
|
470
|
+
Write-Line -Level 'DETECTED' -Message ('version=' + $year + ' hasExe=False path="" source=addins-folder')
|
|
471
|
+
|
|
472
|
+
$null = $candidates.Add([pscustomobject]@{
|
|
473
|
+
Version = $year
|
|
474
|
+
RevitPath = $null
|
|
475
|
+
HasExe = $false
|
|
476
|
+
})
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
# --- honour -RevitVersion ------------------------------------------------------------------
|
|
482
|
+
if (-not [string]::IsNullOrWhiteSpace($RevitVersion)) {
|
|
483
|
+
$wanted = $RevitVersion.Trim()
|
|
484
|
+
|
|
485
|
+
if ($wanted -notmatch '^\d{4}$') {
|
|
486
|
+
Write-Err ('-RevitVersion must be a four-digit year such as 2026, but was "' + $RevitVersion + '".')
|
|
487
|
+
$script:ExitCode = 3
|
|
488
|
+
return
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
$selected = @($candidates.ToArray() | Where-Object { $_.Version -eq $wanted })
|
|
492
|
+
|
|
493
|
+
if ($selected.Count -eq 0) {
|
|
494
|
+
Write-Warn ('Revit ' + $wanted + ' was not found under Program Files. Continuing anyway because -RevitVersion was given explicitly; the add-ins folder does not require Revit to be installed in the default location.')
|
|
495
|
+
|
|
496
|
+
$selected = @([pscustomobject]@{
|
|
497
|
+
Version = $wanted
|
|
498
|
+
RevitPath = $null
|
|
499
|
+
HasExe = $false
|
|
500
|
+
})
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
if ($candidates.Count -eq 0) {
|
|
505
|
+
if ($Uninstall) {
|
|
506
|
+
Write-Err 'Nothing to uninstall: no Revit installation was found under Program Files\Autodesk (looked for folders named "Revit <year>") and no add-ins folder exists under %APPDATA%\Autodesk\Revit\Addins. Pass -RevitVersion <year> to target a version explicitly.'
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
Write-Err 'No Revit installation was found under Program Files\Autodesk (looked for folders named "Revit <year>"). Install Revit, or pass -RevitVersion <year> to target a version explicitly.'
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
$script:ExitCode = 2
|
|
513
|
+
return
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
$selected = @($candidates.ToArray())
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
# --- .NET Framework gate --------------------------------------------------------------------
|
|
520
|
+
# Revit 2024 and earlier run add-ins on .NET Framework 4.8; this project targets net8.0-windows
|
|
521
|
+
# and simply cannot be loaded by them. Installing anyway would produce a confusing load error
|
|
522
|
+
# inside Revit instead of a clear message here.
|
|
523
|
+
#
|
|
524
|
+
# The gate applies to installs only. -Uninstall is allowed to clean any version, because a
|
|
525
|
+
# leftover folder under a 2024 add-ins directory should still be removable.
|
|
526
|
+
$supported = New-Object System.Collections.ArrayList
|
|
527
|
+
|
|
528
|
+
foreach ($entry in $selected) {
|
|
529
|
+
if ((-not $Uninstall) -and ([int] $entry.Version -lt $MinimumRevitYear)) {
|
|
530
|
+
Write-Warn ('Revit ' + $entry.Version + ' runs add-ins on .NET Framework 4.8 and cannot load this build (it targets net8.0-windows). Revit ' + $MinimumRevitYear + ' or newer is required.')
|
|
531
|
+
|
|
532
|
+
$null = $script:VersionResults.Add([ordered]@{
|
|
533
|
+
version = $entry.Version
|
|
534
|
+
status = 'skipped-unsupported'
|
|
535
|
+
reason = 'Revit ' + $entry.Version + ' is .NET Framework based; this build requires Revit ' + $MinimumRevitYear + '+.'
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
continue
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
$null = $supported.Add($entry)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if ($supported.Count -eq 0) {
|
|
545
|
+
Write-Err ('No supported Revit version to work with. This build requires Revit ' + $MinimumRevitYear + ' or newer.')
|
|
546
|
+
$script:ExitCode = 3
|
|
547
|
+
return
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
# --- uninstall --------------------------------------------------------------------------------
|
|
551
|
+
if ($Uninstall) {
|
|
552
|
+
foreach ($entry in $supported) {
|
|
553
|
+
Uninstall-ForVersion -Target $entry
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
Write-Line -Level 'RESULT' -Message ('status=ok action=uninstall versions=' + (($supported.ToArray() | ForEach-Object { $_.Version }) -join ','))
|
|
557
|
+
Write-Line -Level 'DONE' -Message 'Restart Revit to load the bridge'
|
|
558
|
+
$script:ExitCode = 0
|
|
559
|
+
return
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
# --- pick the add-in to install ----------------------------------------------------------------
|
|
563
|
+
# Default: the prebuilt DLL in dist\, which is what the npm package ships - no .NET SDK, no
|
|
564
|
+
# build step. -Build forces a fresh compile instead, for someone working on the C# side. Only a
|
|
565
|
+
# source checkout with no dist\ ever falls through to building on its own.
|
|
566
|
+
$outputDir = $null
|
|
567
|
+
|
|
568
|
+
if ($Build) {
|
|
569
|
+
if (-not (Invoke-Build)) {
|
|
570
|
+
$script:ExitCode = 4
|
|
571
|
+
return
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
$outputDir = Resolve-BuildOutput
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
$outputDir = Resolve-PrebuiltOutput
|
|
578
|
+
|
|
579
|
+
if (-not [string]::IsNullOrWhiteSpace($outputDir)) {
|
|
580
|
+
$script:BuildStatus = 'prebuilt'
|
|
581
|
+
Write-Line -Level 'BUILD' -Message 'status=skipped reason=prebuilt-dist'
|
|
582
|
+
}
|
|
583
|
+
elseif ($SkipBuild) {
|
|
584
|
+
Write-Line -Level 'BUILD' -Message 'status=skipped reason=-SkipBuild'
|
|
585
|
+
$outputDir = Resolve-BuildOutput
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
if (-not (Invoke-Build)) {
|
|
589
|
+
$script:ExitCode = 4
|
|
590
|
+
return
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
$outputDir = Resolve-BuildOutput
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if ([string]::IsNullOrWhiteSpace($outputDir)) {
|
|
598
|
+
Write-Err ('Nothing to install: no prebuilt ' + $AssemblyFileName + ' in ' + (Join-Path $ScriptRoot $DistDirName) + ' and no Release build output under ' + (Join-Path $ScriptRoot 'bin') + '. Re-run with -Build to compile one (needs the .NET SDK), or reinstall the npm package @rui.branco/revit-mcp, which ships the add-in already compiled.')
|
|
599
|
+
$script:ExitCode = 5
|
|
600
|
+
return
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
$script:OutputDir = $outputDir
|
|
604
|
+
Write-Line -Level 'OUTPUT' -Message ('dir="' + $outputDir + '"')
|
|
605
|
+
|
|
606
|
+
# --- install ----------------------------------------------------------------------------------
|
|
607
|
+
foreach ($entry in $supported) {
|
|
608
|
+
Install-ForVersion -Target $entry -OutputDir $outputDir
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
Write-Line -Level 'RESULT' -Message ('status=ok action=install versions=' + (($supported.ToArray() | ForEach-Object { $_.Version }) -join ','))
|
|
612
|
+
Write-Line -Level 'DONE' -Message 'Restart Revit to load the bridge'
|
|
613
|
+
$script:ExitCode = 0
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
try {
|
|
617
|
+
Invoke-Main
|
|
618
|
+
}
|
|
619
|
+
catch {
|
|
620
|
+
Write-Err ('Unhandled failure: ' + $_.Exception.Message)
|
|
621
|
+
|
|
622
|
+
if ($null -ne $_.ScriptStackTrace) {
|
|
623
|
+
$null = $script:Log.Add('[revit-bridge] TRACE ' + $_.ScriptStackTrace)
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if ($script:ExitCode -eq 0) {
|
|
627
|
+
$script:ExitCode = 1
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if ($Json) {
|
|
632
|
+
$action = 'install'
|
|
633
|
+
|
|
634
|
+
if ($Uninstall) {
|
|
635
|
+
$action = 'uninstall'
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
$ok = $false
|
|
639
|
+
|
|
640
|
+
if ($script:ExitCode -eq 0) {
|
|
641
|
+
$ok = $true
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
$message = 'Restart Revit to load the bridge'
|
|
645
|
+
|
|
646
|
+
if (-not $ok) {
|
|
647
|
+
$message = 'The Revit MCP Bridge installer failed; see errors.'
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
$result = [ordered]@{
|
|
651
|
+
ok = $ok
|
|
652
|
+
action = $action
|
|
653
|
+
exitCode = $script:ExitCode
|
|
654
|
+
message = $message
|
|
655
|
+
addInName = $AddInName
|
|
656
|
+
addInId = $AddInId
|
|
657
|
+
fullClassName = $FullClassName
|
|
658
|
+
vendorId = $VendorId
|
|
659
|
+
buildStatus = $script:BuildStatus
|
|
660
|
+
outputDir = $script:OutputDir
|
|
661
|
+
versions = @($script:VersionResults.ToArray())
|
|
662
|
+
warnings = @($script:Warnings.ToArray())
|
|
663
|
+
errors = @($script:Errors.ToArray())
|
|
664
|
+
log = @($script:Log.ToArray())
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
# The @(...) wrappers above matter: they keep one-element and empty collections rendering as
|
|
668
|
+
# real JSON arrays under Windows PowerShell 5.1, so the caller never has to special-case them.
|
|
669
|
+
# Not named $json: PowerShell variable names are case-insensitive, so that would clobber the
|
|
670
|
+
# [switch] $Json parameter with a string and blow up parameter binding.
|
|
671
|
+
$jsonText = ConvertTo-Json -InputObject $result -Depth 12
|
|
672
|
+
|
|
673
|
+
[Console]::Out.WriteLine($jsonText)
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
exit $script:ExitCode
|