pi-revit 0.1.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.
@@ -0,0 +1,53 @@
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, Projects\sample template,
6
+ double-click launcher). Existing AGENTS.md files are 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 <project> -> Pi in Projects\<project>
10
+ pi-revit <project> -c -> continue that project's last session
11
+
12
+ Idempotent — safe to re-run. Usage:
13
+ scripts\setup-workspace.ps1
14
+ scripts\setup-workspace.ps1 -WorkspaceDir "D:\work\pi-revit"
15
+ #>
16
+ param(
17
+ [string]$WorkspaceDir = (Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'pi-revit')
18
+ )
19
+
20
+ $ErrorActionPreference = 'Stop'
21
+ $templates = Join-Path (Split-Path $PSScriptRoot -Parent) 'workspace'
22
+
23
+ # 1. Workspace folders
24
+ New-Item -ItemType Directory -Force -Path (Join-Path $WorkspaceDir 'Projects\sample') | Out-Null
25
+
26
+ # Conventions / notes: copy only when missing so user edits survive re-runs.
27
+ $workspaceAgents = Join-Path $WorkspaceDir 'AGENTS.md'
28
+ if (-not (Test-Path $workspaceAgents)) {
29
+ Copy-Item (Join-Path $templates 'AGENTS.md') $workspaceAgents
30
+ }
31
+ $projectAgents = Join-Path $WorkspaceDir 'Projects\sample\AGENTS.md'
32
+ if (-not (Test-Path $projectAgents)) {
33
+ Copy-Item (Join-Path $templates 'project-AGENTS.md') $projectAgents
34
+ }
35
+
36
+ # Launcher is code, not user data: always refresh.
37
+ Copy-Item (Join-Path $templates 'pi-revit-here.cmd') (Join-Path $WorkspaceDir 'pi-revit.cmd') -Force
38
+
39
+ Write-Host "Workspace ready: $WorkspaceDir" -ForegroundColor Green
40
+
41
+ # 2. Global pi-revit command next to pi (that directory is on PATH by definition).
42
+ # The template's workspace path is rewritten to honor -WorkspaceDir.
43
+ $pi = Get-Command pi -ErrorAction SilentlyContinue
44
+ if ($pi) {
45
+ $binDir = Split-Path $pi.Source -Parent
46
+ $globalCmd = Get-Content (Join-Path $templates 'pi-revit-global.cmd')
47
+ $globalCmd = $globalCmd -replace '^set "WORKSPACE=.*$', ('set "WORKSPACE=' + $WorkspaceDir + '"')
48
+ Set-Content -Path (Join-Path $binDir 'pi-revit.cmd') -Value $globalCmd -Encoding Ascii
49
+ Write-Host "Global command installed: $(Join-Path $binDir 'pi-revit.cmd')" -ForegroundColor Green
50
+ Write-Host 'Run pi-revit from any terminal (pi-revit <project> for a project folder).'
51
+ } else {
52
+ Write-Warning 'pi command not found on PATH. Install Pi first (npm install -g --ignore-scripts @earendil-works/pi-coding-agent), then re-run this script to get the global pi-revit command.'
53
+ }
@@ -0,0 +1,103 @@
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 pi.
58
+ $pi = Get-Command pi -ErrorAction SilentlyContinue
59
+ if ($pi) {
60
+ $binDir = Split-Path $pi.Source -Parent
61
+ Remove-IfExists (Join-Path $binDir 'pi-revit.cmd') 'global pi-revit command'
62
+ } else {
63
+ Write-Host 'pi not found on PATH; skipping the global pi-revit command.'
64
+ }
65
+
66
+ # 3. Bridge runtime folder (connection token; recreated on each Revit start).
67
+ Remove-IfExists (Join-Path $env:APPDATA 'RevitBridge') 'bridge runtime folder'
68
+
69
+ # 4. Pi package registration. Mirror the documented install command exactly:
70
+ # `pi install ./` stores the package keyed by a path resolved relative to Pi's
71
+ # settings file, so removal must use the same `./` form from the repo root.
72
+ # An absolute path may not match the original registration. The repo root is
73
+ # the parent of this script's folder.
74
+ if ($pi) {
75
+ $repoRoot = Split-Path $PSScriptRoot -Parent
76
+ Push-Location $repoRoot
77
+ try {
78
+ $global:LASTEXITCODE = 0
79
+ & pi remove ./
80
+ if ($LASTEXITCODE -eq 0) {
81
+ Write-Host 'Removed the pi-revit Pi package.' -ForegroundColor Green
82
+ } else {
83
+ Write-Warning "pi remove exited with code $LASTEXITCODE; run 'pi remove ./' from this repo manually."
84
+ }
85
+ } catch {
86
+ Write-Warning "Could not auto-remove the Pi package; run 'pi remove ./' from this repo manually. ($_)"
87
+ } finally {
88
+ Pop-Location
89
+ }
90
+ }
91
+
92
+ # 5. Workspace (user data) — opt-in only.
93
+ if ($RemoveWorkspace) {
94
+ Remove-IfExists $WorkspaceDir 'workspace'
95
+ } else {
96
+ Write-Host ''
97
+ Write-Host "Workspace preserved (your notes + session history): $WorkspaceDir" -ForegroundColor Yellow
98
+ Write-Host 'Pass -RemoveWorkspace to delete it too.'
99
+ }
100
+
101
+ Write-Host ''
102
+ Write-Host 'pi-revit uninstalled. Pi itself is untouched; remove it yourself if you want:' -ForegroundColor Green
103
+ Write-Host ' npm uninstall -g @earendil-works/pi-coding-agent'
@@ -0,0 +1,50 @@
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, 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
+ | Write parameter values; rename anything (levels, views, sheets, types) | `set_parameters` |
21
+ | Look up Revit API classes/members/signatures | `search_api_docs` |
22
+ | Everything else (create, delete, move, views, sheets, tagging, ...) | `execute_csharp` |
23
+ | PNG snapshot of a view (visual QA) | `capture_view` (advanced) |
24
+ | PDF/DWG/PNG/IFC file export | `export_documents` (advanced) |
25
+ | Warnings / model quality audit | `get_model_health` (advanced) |
26
+
27
+ Workflow guidance:
28
+
29
+ - Call `get_model_overview` first when starting work on an unfamiliar model — one call returns project metadata, units, levels, grids, and category counts.
30
+ - `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.
31
+ - The selection pipeline is `get_elements` -> ids -> `manage_selection` (action `set`); there is no inline filter on selection.
32
+ - `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.
33
+ - 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).
34
+
35
+ ## execute_csharp playbook
36
+
37
+ - Globals: `doc` (Document), `uidoc` (UIDocument), `uiapp` (UIApplication), and `Dump(value)` to record intermediates into the result's `dumps[]`.
38
+ - 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).
39
+ - Scripts must be fully synchronous: `await`/`async` is rejected at compile time; never block on `Task.Result`/`.Wait()`.
40
+ - 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}`).
41
+ - Lengths are internal units (decimal feet) — convert with `UnitUtils.ConvertToInternalUnits`/`ConvertFromInternalUnits`.
42
+ - 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`.
43
+ - `capture_view` returns a `filePath` to a temp PNG, never image data — open it with the read tool to actually see it.
44
+
45
+ ## Failure modes
46
+
47
+ - **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`.
48
+ - **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.
49
+ - **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.
50
+ - **Cancelled**: same caveat — the bridge cannot abort queued or running work, so verify model state before retrying a write tool.
@@ -0,0 +1,94 @@
1
+ using Autodesk.Revit.UI;
2
+ using RevitApplication = Autodesk.Revit.ApplicationServices.Application;
3
+
4
+ namespace RevitBridge
5
+ {
6
+ /// <summary>
7
+ /// Headless add-in entry point. Starts the local bridge on Revit startup and
8
+ /// stops it on shutdown. No ribbon, no panels, no UI.
9
+ /// </summary>
10
+ public sealed class Application : IExternalApplication
11
+ {
12
+ private BridgeServer? _server;
13
+ private ExternalEvent? _externalEvent;
14
+
15
+ // ExternalEvents are only pumped while Revit is idle with a document
16
+ // context; with zero documents open a queued tool call would hang
17
+ // instead of failing. This cached flag lets the bridge answer 409
18
+ // immediately. Maintained from document events.
19
+ private static RevitApplication? _application;
20
+ private static volatile bool _hasOpenDocument;
21
+
22
+ internal static bool HasOpenDocument => _hasOpenDocument;
23
+
24
+ public Result OnStartup(UIControlledApplication application)
25
+ {
26
+ try
27
+ {
28
+ var controlled = application.ControlledApplication;
29
+ controlled.DocumentOpened += (sender, _) => UpdateDocumentState(sender);
30
+ controlled.DocumentCreated += (sender, _) => UpdateDocumentState(sender);
31
+ controlled.DocumentClosed += (sender, _) => UpdateDocumentState(sender);
32
+
33
+ var queue = new CommandQueue();
34
+ // Must be created here: OnStartup is a valid Revit API context.
35
+ _externalEvent = ExternalEvent.Create(queue);
36
+ queue.ExternalEvent = _externalEvent;
37
+
38
+ _server = new BridgeServer(
39
+ queue,
40
+ ToolRegistry.CreateDefault(),
41
+ application.ControlledApplication.VersionNumber,
42
+ () => HasOpenDocument);
43
+ _server.Start();
44
+ TryDeleteStartupError();
45
+ return Result.Succeeded;
46
+ }
47
+ catch (Exception ex)
48
+ {
49
+ // A broken bridge must never block Revit startup; Pi-side tooling
50
+ // reports the missing bridge info file instead. Leave a breadcrumb
51
+ // so "add-in loaded but bridge startup threw" is diagnosable.
52
+ _server?.Stop();
53
+ _server = null;
54
+ TryWriteStartupError(ex);
55
+ return Result.Succeeded;
56
+ }
57
+ }
58
+
59
+ public Result OnShutdown(UIControlledApplication application)
60
+ {
61
+ _server?.Stop();
62
+ _externalEvent?.Dispose();
63
+ return Result.Succeeded;
64
+ }
65
+
66
+ private static void UpdateDocumentState(object? sender)
67
+ {
68
+ if (sender is RevitApplication app)
69
+ _application = app;
70
+ _hasOpenDocument = (_application?.Documents?.Size ?? 0) > 0;
71
+ }
72
+
73
+ private static string StartupErrorPath() =>
74
+ Path.Combine(BridgeServer.BridgeInfoDirectory(), "startup-error.txt");
75
+
76
+ private static void TryWriteStartupError(Exception ex)
77
+ {
78
+ try
79
+ {
80
+ Directory.CreateDirectory(BridgeServer.BridgeInfoDirectory());
81
+ File.WriteAllText(StartupErrorPath(), $"{DateTime.Now:o} Revit bridge failed to start:{Environment.NewLine}{ex}{Environment.NewLine}");
82
+ }
83
+ catch
84
+ {
85
+ // Diagnostics must never break Revit startup.
86
+ }
87
+ }
88
+
89
+ private static void TryDeleteStartupError()
90
+ {
91
+ try { File.Delete(StartupErrorPath()); } catch { }
92
+ }
93
+ }
94
+ }