pi-revit 0.2.12 → 0.2.13
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/CHANGELOG.md +272 -230
- package/README.md +3 -1
- package/bin/pi-revit.js +1 -1
- package/package.json +56 -56
- package/scripts/benchmark-search-docs.py +313 -313
- package/scripts/deploy.ps1 +12 -0
- package/scripts/setup-workspace.ps1 +80 -80
- package/scripts/uninstall.ps1 +146 -146
- package/skills/pi-revit/SKILL.md +53 -53
- package/src/Revit/BridgeServer.cs +67 -0
- package/src/Revit/Tools/CaptureView.cs +60 -1
- package/src/Revit/Tools/ExecuteCsharp.cs +17 -2
- package/src/Revit/Tools/ExportDocuments.cs +36 -5
- package/src/Revit/Tools/FailureGuard.cs +62 -62
- package/src/Revit/Tools/GetElements.cs +49 -15
- package/src/Revit/Tools/OpenView.cs +103 -103
- package/src/Revit/Tools/ToolSupport.cs +301 -301
- package/workspace/pi-revit-global.cmd +12 -12
package/scripts/deploy.ps1
CHANGED
|
@@ -86,6 +86,18 @@ foreach ($t in $targets) {
|
|
|
86
86
|
|
|
87
87
|
$addinsDir = Join-Path $env:APPDATA "Autodesk\Revit\Addins\$($t.Version)"
|
|
88
88
|
$targetDir = Join-Path $addinsDir 'RevitBridge'
|
|
89
|
+
|
|
90
|
+
# Replace the add-in folder instead of copying over it. It holds nothing but our own
|
|
91
|
+
# build output, and an additive copy leaves files from the previous release behind --
|
|
92
|
+
# a renamed or downgraded dependency stays next to the new one for Revit to load.
|
|
93
|
+
if (Test-Path $targetDir) {
|
|
94
|
+
try {
|
|
95
|
+
Remove-Item -LiteralPath $targetDir -Recurse -Force -ErrorAction Stop
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
throw "Could not clear '$targetDir': $($_.Exception.Message). Close Revit $($t.Version) and run deploy.ps1 again."
|
|
99
|
+
}
|
|
100
|
+
}
|
|
89
101
|
New-Item -ItemType Directory -Force -Path $targetDir | Out-Null
|
|
90
102
|
|
|
91
103
|
# execute_csharp ships Roslyn: copy the ENTIRE build output (RevitBridge.dll,
|
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
#Requires -Version 5.1
|
|
2
|
-
<#
|
|
3
|
-
Sets up the pi-revit user environment on this PC:
|
|
4
|
-
|
|
5
|
-
1. Workspace at Documents\pi-revit (AGENTS.md conventions, Models\ output tree,
|
|
6
|
-
double-click launcher). An existing AGENTS.md is never overwritten.
|
|
7
|
-
2. Global `pi-revit` command installed next to the `pi` command, so any terminal can run:
|
|
8
|
-
pi-revit -> Pi in the workspace
|
|
9
|
-
pi-revit -c -> continue the last session
|
|
10
|
-
Model output sorts itself: the Revit tools file each export under
|
|
11
|
-
Models\<model title>\ automatically, keyed by the document it came from.
|
|
12
|
-
|
|
13
|
-
Idempotent — safe to re-run. Usage:
|
|
14
|
-
scripts\setup-workspace.ps1
|
|
15
|
-
scripts\setup-workspace.ps1 -WorkspaceDir "D:\work\pi-revit"
|
|
16
|
-
#>
|
|
17
|
-
param(
|
|
18
|
-
[string]$WorkspaceDir = (Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'pi-revit')
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
$ErrorActionPreference = 'Stop'
|
|
22
|
-
$templates = Join-Path (Split-Path $PSScriptRoot -Parent) 'workspace'
|
|
23
|
-
|
|
24
|
-
# 1. Workspace folders. Models\ is where the Revit tools file per-model output.
|
|
25
|
-
New-Item -ItemType Directory -Force -Path (Join-Path $WorkspaceDir 'Models') | Out-Null
|
|
26
|
-
|
|
27
|
-
# Conventions / notes: copy only when missing so user edits survive re-runs.
|
|
28
|
-
$workspaceAgents = Join-Path $WorkspaceDir 'AGENTS.md'
|
|
29
|
-
if (-not (Test-Path $workspaceAgents)) {
|
|
30
|
-
Copy-Item (Join-Path $templates 'AGENTS.md') $workspaceAgents
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
# Launcher is code, not user data: always refresh.
|
|
34
|
-
Copy-Item (Join-Path $templates 'pi-revit-here.cmd') (Join-Path $WorkspaceDir 'pi-revit.cmd') -Force
|
|
35
|
-
|
|
36
|
-
Write-Host "Workspace ready: $WorkspaceDir" -ForegroundColor Green
|
|
37
|
-
|
|
38
|
-
# 2. Global pi-revit command next to pi (that directory is on PATH by definition).
|
|
39
|
-
# The template's workspace path is rewritten to honor -WorkspaceDir.
|
|
40
|
-
# Under `npx pi-revit`, npx prepends its ephemeral cache's node_modules\.bin to
|
|
41
|
-
# PATH, and that folder holds a pi shim pulled in via peerDependencies. A launcher
|
|
42
|
-
# written next to that shim lands in a folder that is neither on the user's own
|
|
43
|
-
# PATH nor long-lived, so skip pi entries inside this package's node_modules tree
|
|
44
|
-
# or any npx cache and use the user's permanent pi instead.
|
|
45
|
-
function Get-PermanentPiCommand {
|
|
46
|
-
# Long-form both sides of the prefix comparison: PATH entries may carry 8.3
|
|
47
|
-
# short names (SOMEUS~1.NAM) while $PSScriptRoot is normalized, and a form
|
|
48
|
-
# mismatch would silently defeat the own-tree exclusion.
|
|
49
|
-
function Resolve-LongPath([string]$p) {
|
|
50
|
-
try { (Get-Item -LiteralPath $p -ErrorAction Stop).FullName } catch { $p }
|
|
51
|
-
}
|
|
52
|
-
$ownTree = $null
|
|
53
|
-
$repoRoot = Resolve-LongPath (Split-Path $PSScriptRoot -Parent)
|
|
54
|
-
$idx = $repoRoot.LastIndexOf('\node_modules\', [StringComparison]::OrdinalIgnoreCase)
|
|
55
|
-
if ($idx -ge 0) { $ownTree = $repoRoot.Substring(0, $idx + '\node_modules\'.Length) }
|
|
56
|
-
Get-Command pi -All -ErrorAction SilentlyContinue | Where-Object {
|
|
57
|
-
if (-not $_.Source) { return $false }
|
|
58
|
-
$src = Resolve-LongPath $_.Source
|
|
59
|
-
($src -notmatch '\\_npx\\') -and
|
|
60
|
-
(-not $ownTree -or -not $src.StartsWith($ownTree, [StringComparison]::OrdinalIgnoreCase))
|
|
61
|
-
} | Select-Object -First 1
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
$pi = Get-PermanentPiCommand
|
|
65
|
-
if ($pi) {
|
|
66
|
-
$binDir = Split-Path $pi.Source -Parent
|
|
67
|
-
# Literal line swap (no regex): the workspace path may contain characters
|
|
68
|
-
# like $ that a -replace replacement string would misinterpret.
|
|
69
|
-
$globalCmd = Get-Content (Join-Path $templates 'pi-revit-global.cmd') | ForEach-Object {
|
|
70
|
-
if ($_ -like 'set "WORKSPACE=*') { 'set "WORKSPACE=' + $WorkspaceDir + '"' } else { $_ }
|
|
71
|
-
}
|
|
72
|
-
# cmd.exe parses .cmd files in the console's OEM code page: write the launcher
|
|
73
|
-
# in that encoding so workspace paths with non-ASCII characters (user names,
|
|
74
|
-
# localized folder names) stay intact.
|
|
75
|
-
Set-Content -Path (Join-Path $binDir 'pi-revit.cmd') -Value $globalCmd -Encoding Oem
|
|
76
|
-
Write-Host "Global command installed: $(Join-Path $binDir 'pi-revit.cmd')" -ForegroundColor Green
|
|
77
|
-
Write-Host 'Run pi-revit from any terminal (pi-revit -c continues the last session).'
|
|
78
|
-
} else {
|
|
79
|
-
Write-Warning 'No permanent pi command found on PATH. Install Pi first (npm install -g --ignore-scripts @earendil-works/pi-coding-agent), then run npx.cmd -y pi-revit again (or re-run this script) to get the global pi-revit command.'
|
|
80
|
-
}
|
|
1
|
+
#Requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
Sets up the pi-revit user environment on this PC:
|
|
4
|
+
|
|
5
|
+
1. Workspace at Documents\pi-revit (AGENTS.md conventions, Models\ output tree,
|
|
6
|
+
double-click launcher). An existing AGENTS.md is never overwritten.
|
|
7
|
+
2. Global `pi-revit` command installed next to the `pi` command, so any terminal can run:
|
|
8
|
+
pi-revit -> Pi in the workspace
|
|
9
|
+
pi-revit -c -> continue the last session
|
|
10
|
+
Model output sorts itself: the Revit tools file each export under
|
|
11
|
+
Models\<model title>\ automatically, keyed by the document it came from.
|
|
12
|
+
|
|
13
|
+
Idempotent — safe to re-run. Usage:
|
|
14
|
+
scripts\setup-workspace.ps1
|
|
15
|
+
scripts\setup-workspace.ps1 -WorkspaceDir "D:\work\pi-revit"
|
|
16
|
+
#>
|
|
17
|
+
param(
|
|
18
|
+
[string]$WorkspaceDir = (Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'pi-revit')
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
$ErrorActionPreference = 'Stop'
|
|
22
|
+
$templates = Join-Path (Split-Path $PSScriptRoot -Parent) 'workspace'
|
|
23
|
+
|
|
24
|
+
# 1. Workspace folders. Models\ is where the Revit tools file per-model output.
|
|
25
|
+
New-Item -ItemType Directory -Force -Path (Join-Path $WorkspaceDir 'Models') | Out-Null
|
|
26
|
+
|
|
27
|
+
# Conventions / notes: copy only when missing so user edits survive re-runs.
|
|
28
|
+
$workspaceAgents = Join-Path $WorkspaceDir 'AGENTS.md'
|
|
29
|
+
if (-not (Test-Path $workspaceAgents)) {
|
|
30
|
+
Copy-Item (Join-Path $templates 'AGENTS.md') $workspaceAgents
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Launcher is code, not user data: always refresh.
|
|
34
|
+
Copy-Item (Join-Path $templates 'pi-revit-here.cmd') (Join-Path $WorkspaceDir 'pi-revit.cmd') -Force
|
|
35
|
+
|
|
36
|
+
Write-Host "Workspace ready: $WorkspaceDir" -ForegroundColor Green
|
|
37
|
+
|
|
38
|
+
# 2. Global pi-revit command next to pi (that directory is on PATH by definition).
|
|
39
|
+
# The template's workspace path is rewritten to honor -WorkspaceDir.
|
|
40
|
+
# Under `npx pi-revit`, npx prepends its ephemeral cache's node_modules\.bin to
|
|
41
|
+
# PATH, and that folder holds a pi shim pulled in via peerDependencies. A launcher
|
|
42
|
+
# written next to that shim lands in a folder that is neither on the user's own
|
|
43
|
+
# PATH nor long-lived, so skip pi entries inside this package's node_modules tree
|
|
44
|
+
# or any npx cache and use the user's permanent pi instead.
|
|
45
|
+
function Get-PermanentPiCommand {
|
|
46
|
+
# Long-form both sides of the prefix comparison: PATH entries may carry 8.3
|
|
47
|
+
# short names (SOMEUS~1.NAM) while $PSScriptRoot is normalized, and a form
|
|
48
|
+
# mismatch would silently defeat the own-tree exclusion.
|
|
49
|
+
function Resolve-LongPath([string]$p) {
|
|
50
|
+
try { (Get-Item -LiteralPath $p -ErrorAction Stop).FullName } catch { $p }
|
|
51
|
+
}
|
|
52
|
+
$ownTree = $null
|
|
53
|
+
$repoRoot = Resolve-LongPath (Split-Path $PSScriptRoot -Parent)
|
|
54
|
+
$idx = $repoRoot.LastIndexOf('\node_modules\', [StringComparison]::OrdinalIgnoreCase)
|
|
55
|
+
if ($idx -ge 0) { $ownTree = $repoRoot.Substring(0, $idx + '\node_modules\'.Length) }
|
|
56
|
+
Get-Command pi -All -ErrorAction SilentlyContinue | Where-Object {
|
|
57
|
+
if (-not $_.Source) { return $false }
|
|
58
|
+
$src = Resolve-LongPath $_.Source
|
|
59
|
+
($src -notmatch '\\_npx\\') -and
|
|
60
|
+
(-not $ownTree -or -not $src.StartsWith($ownTree, [StringComparison]::OrdinalIgnoreCase))
|
|
61
|
+
} | Select-Object -First 1
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
$pi = Get-PermanentPiCommand
|
|
65
|
+
if ($pi) {
|
|
66
|
+
$binDir = Split-Path $pi.Source -Parent
|
|
67
|
+
# Literal line swap (no regex): the workspace path may contain characters
|
|
68
|
+
# like $ that a -replace replacement string would misinterpret.
|
|
69
|
+
$globalCmd = Get-Content (Join-Path $templates 'pi-revit-global.cmd') | ForEach-Object {
|
|
70
|
+
if ($_ -like 'set "WORKSPACE=*') { 'set "WORKSPACE=' + $WorkspaceDir + '"' } else { $_ }
|
|
71
|
+
}
|
|
72
|
+
# cmd.exe parses .cmd files in the console's OEM code page: write the launcher
|
|
73
|
+
# in that encoding so workspace paths with non-ASCII characters (user names,
|
|
74
|
+
# localized folder names) stay intact.
|
|
75
|
+
Set-Content -Path (Join-Path $binDir 'pi-revit.cmd') -Value $globalCmd -Encoding Oem
|
|
76
|
+
Write-Host "Global command installed: $(Join-Path $binDir 'pi-revit.cmd')" -ForegroundColor Green
|
|
77
|
+
Write-Host 'Run pi-revit from any terminal (pi-revit -c continues the last session).'
|
|
78
|
+
} else {
|
|
79
|
+
Write-Warning 'No permanent pi command found on PATH. Install Pi first (npm install -g --ignore-scripts @earendil-works/pi-coding-agent), then run npx.cmd -y pi-revit again (or re-run this script) to get the global pi-revit command.'
|
|
80
|
+
}
|
package/scripts/uninstall.ps1
CHANGED
|
@@ -1,146 +1,146 @@
|
|
|
1
|
-
#Requires -Version 5.1
|
|
2
|
-
<#
|
|
3
|
-
Removes everything the pi-revit install scripts placed on this PC:
|
|
4
|
-
|
|
5
|
-
- Revit bridge add-in: %APPDATA%\Autodesk\Revit\Addins\<version>\RevitBridge.addin
|
|
6
|
-
%APPDATA%\Autodesk\Revit\Addins\<version>\RevitBridge\
|
|
7
|
-
- Global command: the pi-revit command next to the pi command
|
|
8
|
-
- Bridge runtime: %APPDATA%\RevitBridge\ (per-start connection token / bridge.json)
|
|
9
|
-
- Pi package: the pi-revit registration (pi remove), best-effort when pi is on PATH
|
|
10
|
-
|
|
11
|
-
The workspace at Documents\pi-revit holds YOUR data (AGENTS.md notes, per-project
|
|
12
|
-
session history) and is preserved by default. Pass -RemoveWorkspace to delete it too.
|
|
13
|
-
|
|
14
|
-
Pi itself (the global coding agent) is yours to manage and is never touched.
|
|
15
|
-
|
|
16
|
-
Close Revit before running. Idempotent — safe to re-run.
|
|
17
|
-
|
|
18
|
-
Usage:
|
|
19
|
-
scripts\uninstall.ps1
|
|
20
|
-
scripts\uninstall.ps1 -RevitVersion 2027
|
|
21
|
-
scripts\uninstall.ps1 -RemoveWorkspace
|
|
22
|
-
#>
|
|
23
|
-
param(
|
|
24
|
-
[string]$RevitVersion = '',
|
|
25
|
-
[string]$WorkspaceDir = (Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'pi-revit'),
|
|
26
|
-
[switch]$RemoveWorkspace
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
$ErrorActionPreference = 'Stop'
|
|
30
|
-
|
|
31
|
-
function Remove-IfExists {
|
|
32
|
-
param([string]$Path, [string]$Label)
|
|
33
|
-
if (Test-Path -LiteralPath $Path) {
|
|
34
|
-
Remove-Item -LiteralPath $Path -Recurse -Force
|
|
35
|
-
Write-Host "Removed $Label : $Path" -ForegroundColor Green
|
|
36
|
-
} else {
|
|
37
|
-
Write-Host "Not present, skipped $Label : $Path"
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
# 1. Revit add-in. With no -RevitVersion, sweep every Addins\<version> folder so the
|
|
42
|
-
# user need not remember which Revit they deployed to.
|
|
43
|
-
$addinsRoot = Join-Path $env:APPDATA 'Autodesk\Revit\Addins'
|
|
44
|
-
$versions = if ($RevitVersion) {
|
|
45
|
-
@($RevitVersion)
|
|
46
|
-
} elseif (Test-Path -LiteralPath $addinsRoot) {
|
|
47
|
-
Get-ChildItem -LiteralPath $addinsRoot -Directory | ForEach-Object { $_.Name }
|
|
48
|
-
} else {
|
|
49
|
-
@()
|
|
50
|
-
}
|
|
51
|
-
foreach ($v in $versions) {
|
|
52
|
-
$dir = Join-Path $addinsRoot $v
|
|
53
|
-
Remove-IfExists (Join-Path $dir 'RevitBridge.addin') "Revit $v add-in manifest"
|
|
54
|
-
Remove-IfExists (Join-Path $dir 'RevitBridge') "Revit $v add-in folder"
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
# 2. Global pi-revit command next to the user's permanent pi. When this script runs
|
|
58
|
-
# from an npx cache or a node_modules copy, PATH may lead Get-Command to a pi shim
|
|
59
|
-
# inside that ephemeral tree first (see setup-workspace.ps1) — the launcher was
|
|
60
|
-
# never installed there, so resolve the permanent pi the same way setup does.
|
|
61
|
-
function Get-PermanentPiCommand {
|
|
62
|
-
# Long-form both sides of the prefix comparison: PATH entries may carry 8.3
|
|
63
|
-
# short names (SOMEUS~1.NAM) while $PSScriptRoot is normalized, and a form
|
|
64
|
-
# mismatch would silently defeat the own-tree exclusion.
|
|
65
|
-
function Resolve-LongPath([string]$p) {
|
|
66
|
-
try { (Get-Item -LiteralPath $p -ErrorAction Stop).FullName } catch { $p }
|
|
67
|
-
}
|
|
68
|
-
$ownTree = $null
|
|
69
|
-
$repoRoot = Resolve-LongPath (Split-Path $PSScriptRoot -Parent)
|
|
70
|
-
$idx = $repoRoot.LastIndexOf('\node_modules\', [StringComparison]::OrdinalIgnoreCase)
|
|
71
|
-
if ($idx -ge 0) { $ownTree = $repoRoot.Substring(0, $idx + '\node_modules\'.Length) }
|
|
72
|
-
Get-Command pi -All -ErrorAction SilentlyContinue | Where-Object {
|
|
73
|
-
if (-not $_.Source) { return $false }
|
|
74
|
-
$src = Resolve-LongPath $_.Source
|
|
75
|
-
($src -notmatch '\\_npx\\') -and
|
|
76
|
-
(-not $ownTree -or -not $src.StartsWith($ownTree, [StringComparison]::OrdinalIgnoreCase))
|
|
77
|
-
} | Select-Object -First 1
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
$pi = Get-Command pi -ErrorAction SilentlyContinue
|
|
81
|
-
$piPermanent = Get-PermanentPiCommand
|
|
82
|
-
if ($piPermanent) {
|
|
83
|
-
$binDir = Split-Path $piPermanent.Source -Parent
|
|
84
|
-
Remove-IfExists (Join-Path $binDir 'pi-revit.cmd') 'global pi-revit command'
|
|
85
|
-
} else {
|
|
86
|
-
Write-Host 'No permanent pi found on PATH; skipping the global pi-revit command.'
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
# 3. Bridge runtime folder (connection token; recreated on each Revit start).
|
|
90
|
-
Remove-IfExists (Join-Path $env:APPDATA 'RevitBridge') 'bridge runtime folder'
|
|
91
|
-
|
|
92
|
-
# 4. Pi package registration. Pi keys a package by its install source: the npm
|
|
93
|
-
# installer (npx.cmd -y pi-revit) registers `npm:pi-revit`, while an install
|
|
94
|
-
# from a clone of this repo (`pi install ./`) registers the repo path resolved
|
|
95
|
-
# from the repo root. Try both forms; each succeeds only for the source that
|
|
96
|
-
# is actually registered, so a machine with both gets both removed.
|
|
97
|
-
if ($pi) {
|
|
98
|
-
$repoRoot = Split-Path $PSScriptRoot -Parent
|
|
99
|
-
# When this script runs from the npm-installed copy, `pi remove npm:pi-revit`
|
|
100
|
-
# deletes the very folder the script lives in — and Windows fails that delete
|
|
101
|
-
# (EBUSY) while any working directory sits inside it. Step our own process out
|
|
102
|
-
# if needed; the calling shell's location we can only warn about.
|
|
103
|
-
if ($repoRoot -match '\\node_modules\\' -and
|
|
104
|
-
(Get-Location).Path.StartsWith($repoRoot, [StringComparison]::OrdinalIgnoreCase)) {
|
|
105
|
-
Set-Location $env:SystemRoot
|
|
106
|
-
Write-Warning "This PowerShell was started inside $repoRoot. If the package removal below fails, run 'cd \' in your shell and re-run the uninstall."
|
|
107
|
-
}
|
|
108
|
-
try {
|
|
109
|
-
$removed = @()
|
|
110
|
-
# './' resolves against the repo root, so it runs from there — and first,
|
|
111
|
-
# while this folder is guaranteed to still exist. 'npm:pi-revit' then runs
|
|
112
|
-
# from a neutral directory so no working directory blocks the folder delete.
|
|
113
|
-
foreach ($source in @('./', 'npm:pi-revit')) {
|
|
114
|
-
Push-Location $(if ($source -eq './') { $repoRoot } else { $env:SystemRoot })
|
|
115
|
-
try {
|
|
116
|
-
$global:LASTEXITCODE = 0
|
|
117
|
-
& pi remove $source
|
|
118
|
-
if ($LASTEXITCODE -eq 0) {
|
|
119
|
-
$removed += $source
|
|
120
|
-
}
|
|
121
|
-
} finally {
|
|
122
|
-
Pop-Location
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
if ($removed.Count -gt 0) {
|
|
126
|
-
Write-Host "Removed the pi-revit Pi package ($($removed -join ', '))." -ForegroundColor Green
|
|
127
|
-
} else {
|
|
128
|
-
Write-Warning "No pi-revit Pi package registration was found. If pi still lists it, run 'pi remove npm:pi-revit' (npm install) or 'pi remove ./' from this repo (install from source)."
|
|
129
|
-
}
|
|
130
|
-
} catch {
|
|
131
|
-
Write-Warning "Could not auto-remove the Pi package; run 'pi remove npm:pi-revit' (npm install) or 'pi remove ./' from this repo (install from source). ($_)"
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
# 5. Workspace (user data) — opt-in only.
|
|
136
|
-
if ($RemoveWorkspace) {
|
|
137
|
-
Remove-IfExists $WorkspaceDir 'workspace'
|
|
138
|
-
} else {
|
|
139
|
-
Write-Host ''
|
|
140
|
-
Write-Host "Workspace preserved (your notes + session history): $WorkspaceDir" -ForegroundColor Yellow
|
|
141
|
-
Write-Host 'Pass -RemoveWorkspace to delete it too.'
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
Write-Host ''
|
|
145
|
-
Write-Host 'pi-revit uninstalled. Pi itself is untouched; remove it yourself if you want:' -ForegroundColor Green
|
|
146
|
-
Write-Host ' npm uninstall -g @earendil-works/pi-coding-agent'
|
|
1
|
+
#Requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
Removes everything the pi-revit install scripts placed on this PC:
|
|
4
|
+
|
|
5
|
+
- Revit bridge add-in: %APPDATA%\Autodesk\Revit\Addins\<version>\RevitBridge.addin
|
|
6
|
+
%APPDATA%\Autodesk\Revit\Addins\<version>\RevitBridge\
|
|
7
|
+
- Global command: the pi-revit command next to the pi command
|
|
8
|
+
- Bridge runtime: %APPDATA%\RevitBridge\ (per-start connection token / bridge.json)
|
|
9
|
+
- Pi package: the pi-revit registration (pi remove), best-effort when pi is on PATH
|
|
10
|
+
|
|
11
|
+
The workspace at Documents\pi-revit holds YOUR data (AGENTS.md notes, per-project
|
|
12
|
+
session history) and is preserved by default. Pass -RemoveWorkspace to delete it too.
|
|
13
|
+
|
|
14
|
+
Pi itself (the global coding agent) is yours to manage and is never touched.
|
|
15
|
+
|
|
16
|
+
Close Revit before running. Idempotent — safe to re-run.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
scripts\uninstall.ps1
|
|
20
|
+
scripts\uninstall.ps1 -RevitVersion 2027
|
|
21
|
+
scripts\uninstall.ps1 -RemoveWorkspace
|
|
22
|
+
#>
|
|
23
|
+
param(
|
|
24
|
+
[string]$RevitVersion = '',
|
|
25
|
+
[string]$WorkspaceDir = (Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'pi-revit'),
|
|
26
|
+
[switch]$RemoveWorkspace
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
$ErrorActionPreference = 'Stop'
|
|
30
|
+
|
|
31
|
+
function Remove-IfExists {
|
|
32
|
+
param([string]$Path, [string]$Label)
|
|
33
|
+
if (Test-Path -LiteralPath $Path) {
|
|
34
|
+
Remove-Item -LiteralPath $Path -Recurse -Force
|
|
35
|
+
Write-Host "Removed $Label : $Path" -ForegroundColor Green
|
|
36
|
+
} else {
|
|
37
|
+
Write-Host "Not present, skipped $Label : $Path"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# 1. Revit add-in. With no -RevitVersion, sweep every Addins\<version> folder so the
|
|
42
|
+
# user need not remember which Revit they deployed to.
|
|
43
|
+
$addinsRoot = Join-Path $env:APPDATA 'Autodesk\Revit\Addins'
|
|
44
|
+
$versions = if ($RevitVersion) {
|
|
45
|
+
@($RevitVersion)
|
|
46
|
+
} elseif (Test-Path -LiteralPath $addinsRoot) {
|
|
47
|
+
Get-ChildItem -LiteralPath $addinsRoot -Directory | ForEach-Object { $_.Name }
|
|
48
|
+
} else {
|
|
49
|
+
@()
|
|
50
|
+
}
|
|
51
|
+
foreach ($v in $versions) {
|
|
52
|
+
$dir = Join-Path $addinsRoot $v
|
|
53
|
+
Remove-IfExists (Join-Path $dir 'RevitBridge.addin') "Revit $v add-in manifest"
|
|
54
|
+
Remove-IfExists (Join-Path $dir 'RevitBridge') "Revit $v add-in folder"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# 2. Global pi-revit command next to the user's permanent pi. When this script runs
|
|
58
|
+
# from an npx cache or a node_modules copy, PATH may lead Get-Command to a pi shim
|
|
59
|
+
# inside that ephemeral tree first (see setup-workspace.ps1) — the launcher was
|
|
60
|
+
# never installed there, so resolve the permanent pi the same way setup does.
|
|
61
|
+
function Get-PermanentPiCommand {
|
|
62
|
+
# Long-form both sides of the prefix comparison: PATH entries may carry 8.3
|
|
63
|
+
# short names (SOMEUS~1.NAM) while $PSScriptRoot is normalized, and a form
|
|
64
|
+
# mismatch would silently defeat the own-tree exclusion.
|
|
65
|
+
function Resolve-LongPath([string]$p) {
|
|
66
|
+
try { (Get-Item -LiteralPath $p -ErrorAction Stop).FullName } catch { $p }
|
|
67
|
+
}
|
|
68
|
+
$ownTree = $null
|
|
69
|
+
$repoRoot = Resolve-LongPath (Split-Path $PSScriptRoot -Parent)
|
|
70
|
+
$idx = $repoRoot.LastIndexOf('\node_modules\', [StringComparison]::OrdinalIgnoreCase)
|
|
71
|
+
if ($idx -ge 0) { $ownTree = $repoRoot.Substring(0, $idx + '\node_modules\'.Length) }
|
|
72
|
+
Get-Command pi -All -ErrorAction SilentlyContinue | Where-Object {
|
|
73
|
+
if (-not $_.Source) { return $false }
|
|
74
|
+
$src = Resolve-LongPath $_.Source
|
|
75
|
+
($src -notmatch '\\_npx\\') -and
|
|
76
|
+
(-not $ownTree -or -not $src.StartsWith($ownTree, [StringComparison]::OrdinalIgnoreCase))
|
|
77
|
+
} | Select-Object -First 1
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
$pi = Get-Command pi -ErrorAction SilentlyContinue
|
|
81
|
+
$piPermanent = Get-PermanentPiCommand
|
|
82
|
+
if ($piPermanent) {
|
|
83
|
+
$binDir = Split-Path $piPermanent.Source -Parent
|
|
84
|
+
Remove-IfExists (Join-Path $binDir 'pi-revit.cmd') 'global pi-revit command'
|
|
85
|
+
} else {
|
|
86
|
+
Write-Host 'No permanent pi found on PATH; skipping the global pi-revit command.'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# 3. Bridge runtime folder (connection token; recreated on each Revit start).
|
|
90
|
+
Remove-IfExists (Join-Path $env:APPDATA 'RevitBridge') 'bridge runtime folder'
|
|
91
|
+
|
|
92
|
+
# 4. Pi package registration. Pi keys a package by its install source: the npm
|
|
93
|
+
# installer (npx.cmd -y pi-revit) registers `npm:pi-revit`, while an install
|
|
94
|
+
# from a clone of this repo (`pi install ./`) registers the repo path resolved
|
|
95
|
+
# from the repo root. Try both forms; each succeeds only for the source that
|
|
96
|
+
# is actually registered, so a machine with both gets both removed.
|
|
97
|
+
if ($pi) {
|
|
98
|
+
$repoRoot = Split-Path $PSScriptRoot -Parent
|
|
99
|
+
# When this script runs from the npm-installed copy, `pi remove npm:pi-revit`
|
|
100
|
+
# deletes the very folder the script lives in — and Windows fails that delete
|
|
101
|
+
# (EBUSY) while any working directory sits inside it. Step our own process out
|
|
102
|
+
# if needed; the calling shell's location we can only warn about.
|
|
103
|
+
if ($repoRoot -match '\\node_modules\\' -and
|
|
104
|
+
(Get-Location).Path.StartsWith($repoRoot, [StringComparison]::OrdinalIgnoreCase)) {
|
|
105
|
+
Set-Location $env:SystemRoot
|
|
106
|
+
Write-Warning "This PowerShell was started inside $repoRoot. If the package removal below fails, run 'cd \' in your shell and re-run the uninstall."
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
$removed = @()
|
|
110
|
+
# './' resolves against the repo root, so it runs from there — and first,
|
|
111
|
+
# while this folder is guaranteed to still exist. 'npm:pi-revit' then runs
|
|
112
|
+
# from a neutral directory so no working directory blocks the folder delete.
|
|
113
|
+
foreach ($source in @('./', 'npm:pi-revit')) {
|
|
114
|
+
Push-Location $(if ($source -eq './') { $repoRoot } else { $env:SystemRoot })
|
|
115
|
+
try {
|
|
116
|
+
$global:LASTEXITCODE = 0
|
|
117
|
+
& pi remove $source
|
|
118
|
+
if ($LASTEXITCODE -eq 0) {
|
|
119
|
+
$removed += $source
|
|
120
|
+
}
|
|
121
|
+
} finally {
|
|
122
|
+
Pop-Location
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if ($removed.Count -gt 0) {
|
|
126
|
+
Write-Host "Removed the pi-revit Pi package ($($removed -join ', '))." -ForegroundColor Green
|
|
127
|
+
} else {
|
|
128
|
+
Write-Warning "No pi-revit Pi package registration was found. If pi still lists it, run 'pi remove npm:pi-revit' (npm install) or 'pi remove ./' from this repo (install from source)."
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
Write-Warning "Could not auto-remove the Pi package; run 'pi remove npm:pi-revit' (npm install) or 'pi remove ./' from this repo (install from source). ($_)"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# 5. Workspace (user data) — opt-in only.
|
|
136
|
+
if ($RemoveWorkspace) {
|
|
137
|
+
Remove-IfExists $WorkspaceDir 'workspace'
|
|
138
|
+
} else {
|
|
139
|
+
Write-Host ''
|
|
140
|
+
Write-Host "Workspace preserved (your notes + session history): $WorkspaceDir" -ForegroundColor Yellow
|
|
141
|
+
Write-Host 'Pass -RemoveWorkspace to delete it too.'
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
Write-Host ''
|
|
145
|
+
Write-Host 'pi-revit uninstalled. Pi itself is untouched; remove it yourself if you want:' -ForegroundColor Green
|
|
146
|
+
Write-Host ' npm uninstall -g @earendil-works/pi-coding-agent'
|
package/skills/pi-revit/SKILL.md
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pi-revit
|
|
3
|
-
description: Work with the open Autodesk Revit model through the Revit bridge tools (ping, get_model_overview, get_elements, get_element_details, get_element_types, manage_selection, open_view, set_parameters, search_api_docs, execute_csharp, capture_view, export_documents, get_model_health). Use when the user asks about the Revit project, its elements, parameters, selection, or wants to change, script, capture, or export the model.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Revit
|
|
7
|
-
|
|
8
|
-
Work with the live Revit model. The tools call a headless bridge add-in inside Revit (2025, 2026, or 2027); Revit must be running with a project open (only `ping` and `search_api_docs` work without a document).
|
|
9
|
-
|
|
10
|
-
## Tool selection
|
|
11
|
-
|
|
12
|
-
| Task | Tool |
|
|
13
|
-
|------|------|
|
|
14
|
-
| Bridge alive? Which Revit version? | `ping` |
|
|
15
|
-
| Orientation: project info, units, levels, grids, category counts | `get_model_overview` |
|
|
16
|
-
| List or count elements of ANY category (walls, doors, rooms, sheets, views, ...) | `get_elements` |
|
|
17
|
-
| Read parameter VALUES, location, bounding box, materials of specific elements | `get_element_details` |
|
|
18
|
-
| List element types / family symbols; "used vs merely loaded" | `get_element_types` |
|
|
19
|
-
| Read or change the user's selection; zoom; temporary isolate | `manage_selection` |
|
|
20
|
-
| Put a view or sheet on the user's screen (activate it) | `open_view` |
|
|
21
|
-
| Write parameter values; rename anything (levels, views, sheets, types) | `set_parameters` |
|
|
22
|
-
| Look up Revit API classes/members/signatures | `search_api_docs` |
|
|
23
|
-
| Everything else (create, delete, move, views, sheets, tagging, ...) | `execute_csharp` |
|
|
24
|
-
| PNG snapshot of a view (visual QA) | `capture_view` (advanced) |
|
|
25
|
-
| PDF/DWG/PNG/IFC file export | `export_documents` (advanced) |
|
|
26
|
-
| Warnings / model quality audit | `get_model_health` (advanced) |
|
|
27
|
-
|
|
28
|
-
Workflow guidance:
|
|
29
|
-
|
|
30
|
-
- Call `get_model_overview` first when starting work on an unfamiliar model — one call returns project metadata, units, levels, grids, and category counts.
|
|
31
|
-
- `get_elements` is the listing/counting primitive (`count_only: true` for bare counts). It returns identity fields only (id, name, category, typeName, levelId); read parameter values with `get_element_details`. Prefer a `category` or `of_class` scope when filtering by a parameter's display name.
|
|
32
|
-
- The selection pipeline is `get_elements` -> ids -> `manage_selection` (action `set`); there is no inline filter on selection.
|
|
33
|
-
- `set_parameters` is the home for bulk parameter writes AND renames (the `Name` parameter covers levels, views, sheets, types). One transaction per batch; per-element failures are reported. Pass `expected_document` (the model title) when several models are open or the session is long — it makes the write fail cleanly instead of landing in a different active document.
|
|
34
|
-
- Parameter display names are LOCALIZED: in a non-English Revit UI, `Mark` is `Kennzeichen` (German), `マーク` (Japanese), etc. When a display-name lookup or `parameter_names` filter finds nothing, or the document may be non-English, use the language-independent `BuiltInParameter` enum name instead (e.g. `ALL_MODEL_MARK` for Mark, `ALL_MODEL_INSTANCE_COMMENTS` for Comments) — `set_parameters`, `get_element_details.parameter_names`, and `get_elements` filter rules all accept them, and `get_element_details` reports each parameter's `builtInParameter` name for discovery.
|
|
35
|
-
- Before writing `execute_csharp` code, verify unfamiliar classes/members with `search_api_docs` (works with no document open; first query builds the index and takes a few seconds). The top match carries its remarks, parameter docs, and returns inline, and every public API enum value is searchable — trust the result over guessing or web search; narrow the query to promote a different match into the top slot.
|
|
36
|
-
- `export_documents` files its output under `Documents\pi-revit\Models\<model title>\exports` automatically when `output_dir` is omitted — keyed to the exported document, so it lands right even across many models. Pass `output_dir` only when the user names a different target.
|
|
37
|
-
|
|
38
|
-
## execute_csharp playbook
|
|
39
|
-
|
|
40
|
-
- Globals: `doc` (Document), `uidoc` (UIDocument), `uiapp` (UIApplication), and `Dump(value)` to record intermediates into the result's `dumps[]`.
|
|
41
|
-
- The transaction is automatic: the whole script runs inside ONE backend-owned transaction — committed on success, rolled back on any exception. Do not open your own `Transaction` (sub-transactions are fine).
|
|
42
|
-
- Scripts must be fully synchronous: `await`/`async` is rejected at compile time; never block on `Task.Result`/`.Wait()`.
|
|
43
|
-
- Return primitives, strings, or anonymous objects/lists; raw Revit API objects are projected to compact shapes (Element -> `{id,name,category,typeName,levelId}`, ElementId -> number, XYZ -> `{x,y,z}`).
|
|
44
|
-
- Lengths are internal units (decimal feet) — convert with `UnitUtils.ConvertToInternalUnits`/`ConvertFromInternalUnits`.
|
|
45
|
-
- Common pitfalls: call `FamilySymbol.Activate()` before `NewFamilyInstance`; use collector-level filtering (`OfCategory`/`OfClass`/`WhereElementIsNotElementType`) and bounded loops — the budget is 120s and Revit cannot be interrupted mid-script; modal dialogs are auto-dismissed and reported in `suppressedDialogs`.
|
|
46
|
-
- `capture_view` returns a `filePath` to a temp PNG, never image data — open it with the read tool to actually see it.
|
|
47
|
-
|
|
48
|
-
## Failure modes
|
|
49
|
-
|
|
50
|
-
- **Bridge not reachable** ("Revit bridge is not available" / "Could not reach the Revit bridge"): Revit is not running or the add-in did not load. Ask the user to start Revit, then retry `ping`.
|
|
51
|
-
- **HTTP 409 / "No active Revit document is open."** (`hasActiveDocument: false`): Revit is running but no project is open. Ask the user to open a project, then retry. This fails immediately; do not wait or retry blindly.
|
|
52
|
-
- **Timeout** ("Revit did not answer within Ns", 30s default / 120s for execute_csharp, capture_view, export_documents): Revit is busy or showing a modal dialog. An already-started tool still runs to completion in Revit — verify model state (e.g. `get_elements`) before re-issuing a write.
|
|
53
|
-
- **Cancelled**: same caveat — the bridge cannot abort queued or running work, so verify model state before retrying a write tool.
|
|
1
|
+
---
|
|
2
|
+
name: pi-revit
|
|
3
|
+
description: Work with the open Autodesk Revit model through the Revit bridge tools (ping, get_model_overview, get_elements, get_element_details, get_element_types, manage_selection, open_view, set_parameters, search_api_docs, execute_csharp, capture_view, export_documents, get_model_health). Use when the user asks about the Revit project, its elements, parameters, selection, or wants to change, script, capture, or export the model.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Revit
|
|
7
|
+
|
|
8
|
+
Work with the live Revit model. The tools call a headless bridge add-in inside Revit (2025, 2026, or 2027); Revit must be running with a project open (only `ping` and `search_api_docs` work without a document).
|
|
9
|
+
|
|
10
|
+
## Tool selection
|
|
11
|
+
|
|
12
|
+
| Task | Tool |
|
|
13
|
+
|------|------|
|
|
14
|
+
| Bridge alive? Which Revit version? | `ping` |
|
|
15
|
+
| Orientation: project info, units, levels, grids, category counts | `get_model_overview` |
|
|
16
|
+
| List or count elements of ANY category (walls, doors, rooms, sheets, views, ...) | `get_elements` |
|
|
17
|
+
| Read parameter VALUES, location, bounding box, materials of specific elements | `get_element_details` |
|
|
18
|
+
| List element types / family symbols; "used vs merely loaded" | `get_element_types` |
|
|
19
|
+
| Read or change the user's selection; zoom; temporary isolate | `manage_selection` |
|
|
20
|
+
| Put a view or sheet on the user's screen (activate it) | `open_view` |
|
|
21
|
+
| Write parameter values; rename anything (levels, views, sheets, types) | `set_parameters` |
|
|
22
|
+
| Look up Revit API classes/members/signatures | `search_api_docs` |
|
|
23
|
+
| Everything else (create, delete, move, views, sheets, tagging, ...) | `execute_csharp` |
|
|
24
|
+
| PNG snapshot of a view (visual QA) | `capture_view` (advanced) |
|
|
25
|
+
| PDF/DWG/PNG/IFC file export | `export_documents` (advanced) |
|
|
26
|
+
| Warnings / model quality audit | `get_model_health` (advanced) |
|
|
27
|
+
|
|
28
|
+
Workflow guidance:
|
|
29
|
+
|
|
30
|
+
- Call `get_model_overview` first when starting work on an unfamiliar model — one call returns project metadata, units, levels, grids, and category counts.
|
|
31
|
+
- `get_elements` is the listing/counting primitive (`count_only: true` for bare counts). It returns identity fields only (id, name, category, typeName, levelId); read parameter values with `get_element_details`. Prefer a `category` or `of_class` scope when filtering by a parameter's display name.
|
|
32
|
+
- The selection pipeline is `get_elements` -> ids -> `manage_selection` (action `set`); there is no inline filter on selection.
|
|
33
|
+
- `set_parameters` is the home for bulk parameter writes AND renames (the `Name` parameter covers levels, views, sheets, types). One transaction per batch; per-element failures are reported. Pass `expected_document` (the model title) when several models are open or the session is long — it makes the write fail cleanly instead of landing in a different active document.
|
|
34
|
+
- Parameter display names are LOCALIZED: in a non-English Revit UI, `Mark` is `Kennzeichen` (German), `マーク` (Japanese), etc. When a display-name lookup or `parameter_names` filter finds nothing, or the document may be non-English, use the language-independent `BuiltInParameter` enum name instead (e.g. `ALL_MODEL_MARK` for Mark, `ALL_MODEL_INSTANCE_COMMENTS` for Comments) — `set_parameters`, `get_element_details.parameter_names`, and `get_elements` filter rules all accept them, and `get_element_details` reports each parameter's `builtInParameter` name for discovery.
|
|
35
|
+
- Before writing `execute_csharp` code, verify unfamiliar classes/members with `search_api_docs` (works with no document open; first query builds the index and takes a few seconds). The top match carries its remarks, parameter docs, and returns inline, and every public API enum value is searchable — trust the result over guessing or web search; narrow the query to promote a different match into the top slot.
|
|
36
|
+
- `export_documents` files its output under `Documents\pi-revit\Models\<model title>\exports` automatically when `output_dir` is omitted — keyed to the exported document, so it lands right even across many models. Pass `output_dir` only when the user names a different target.
|
|
37
|
+
|
|
38
|
+
## execute_csharp playbook
|
|
39
|
+
|
|
40
|
+
- Globals: `doc` (Document), `uidoc` (UIDocument), `uiapp` (UIApplication), and `Dump(value)` to record intermediates into the result's `dumps[]`.
|
|
41
|
+
- The transaction is automatic: the whole script runs inside ONE backend-owned transaction — committed on success, rolled back on any exception. Do not open your own `Transaction` (sub-transactions are fine).
|
|
42
|
+
- Scripts must be fully synchronous: `await`/`async` is rejected at compile time; never block on `Task.Result`/`.Wait()`.
|
|
43
|
+
- Return primitives, strings, or anonymous objects/lists; raw Revit API objects are projected to compact shapes (Element -> `{id,name,category,typeName,levelId}`, ElementId -> number, XYZ -> `{x,y,z}`).
|
|
44
|
+
- Lengths are internal units (decimal feet) — convert with `UnitUtils.ConvertToInternalUnits`/`ConvertFromInternalUnits`.
|
|
45
|
+
- Common pitfalls: call `FamilySymbol.Activate()` before `NewFamilyInstance`; use collector-level filtering (`OfCategory`/`OfClass`/`WhereElementIsNotElementType`) and bounded loops — the budget is 120s and Revit cannot be interrupted mid-script; modal dialogs are auto-dismissed and reported in `suppressedDialogs`.
|
|
46
|
+
- `capture_view` returns a `filePath` to a temp PNG, never image data — open it with the read tool to actually see it.
|
|
47
|
+
|
|
48
|
+
## Failure modes
|
|
49
|
+
|
|
50
|
+
- **Bridge not reachable** ("Revit bridge is not available" / "Could not reach the Revit bridge"): Revit is not running or the add-in did not load. Ask the user to start Revit, then retry `ping`.
|
|
51
|
+
- **HTTP 409 / "No active Revit document is open."** (`hasActiveDocument: false`): Revit is running but no project is open. Ask the user to open a project, then retry. This fails immediately; do not wait or retry blindly.
|
|
52
|
+
- **Timeout** ("Revit did not answer within Ns", 30s default / 120s for execute_csharp, capture_view, export_documents): Revit is busy or showing a modal dialog. An already-started tool still runs to completion in Revit — verify model state (e.g. `get_elements`) before re-issuing a write.
|
|
53
|
+
- **Cancelled**: same caveat — the bridge cannot abort queued or running work, so verify model state before retrying a write tool.
|