pi-revit 0.2.18 → 0.3.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/CHANGELOG.md +36 -3
- package/README.md +118 -32
- package/bin/pi-revit.js +7 -5
- package/extensions/pi-revit/index.ts +95 -17
- package/package.json +3 -2
- package/skills/pi-revit/SKILL.md +21 -11
- package/src/Revit/BridgeServer.cs +9 -9
- package/src/Revit/ToolRegistry.cs +32 -7
- package/src/Revit/Tools/DocumentGuard.cs +64 -0
- package/src/Revit/Tools/ExecuteCsharp.cs +23 -23
- package/src/Revit/Tools/ExportDocuments.cs +121 -44
- package/src/Revit/Tools/FailureGuard.cs +26 -5
- package/src/Revit/Tools/GetElementDetails.cs +10 -8
- package/src/Revit/Tools/GetElements.cs +17 -27
- package/src/Revit/Tools/GetModelOverview.cs +2 -1
- package/src/Revit/Tools/ManageSelection.cs +35 -26
- package/src/Revit/Tools/SetParameters.cs +14 -12
- package/src/Revit/Tools/ToolSupport.cs +1 -40
- package/workspace/AGENTS.md +45 -14
|
@@ -136,11 +136,25 @@ namespace RevitBridge.Tools
|
|
|
136
136
|
compactParts.Add($"ids not found: {string.Join(", ", notFound)}");
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
if (isolateInView)
|
|
140
|
-
{
|
|
141
|
-
var view = uiDocument.ActiveGraphicalView
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
if (isolateInView)
|
|
140
|
+
{
|
|
141
|
+
var view = uiDocument.ActiveGraphicalView;
|
|
142
|
+
IReadOnlyList<string> warnings;
|
|
143
|
+
try
|
|
144
|
+
{
|
|
145
|
+
if (view is null)
|
|
146
|
+
throw new ArgumentException("isolate_in_view requires an active graphical view in Revit.");
|
|
147
|
+
warnings = ApplyTemporaryIsolate(doc, view, isolateTargets);
|
|
148
|
+
}
|
|
149
|
+
catch (Exception ex)
|
|
150
|
+
{
|
|
151
|
+
throw new InvalidOperationException($"Selection action '{action}' completed before temporary isolate failed: {ex.Message}. Selection/zoom changes were not rolled back.", ex);
|
|
152
|
+
}
|
|
153
|
+
if (warnings.Count > 0)
|
|
154
|
+
{
|
|
155
|
+
payload["commitWarnings"] = warnings;
|
|
156
|
+
compactParts.Add($"{warnings.Count} Revit warning(s) auto-dismissed (see commitWarnings)");
|
|
157
|
+
}
|
|
144
158
|
payload["viewId"] = view.Id.Value;
|
|
145
159
|
payload["viewName"] = view.Name;
|
|
146
160
|
if (isolateTargets.Count == 0)
|
|
@@ -189,33 +203,28 @@ namespace RevitBridge.Tools
|
|
|
189
203
|
/// only changes it inside an open transaction — so exactly this branch wraps
|
|
190
204
|
/// a small one while the tool stays Write = false.
|
|
191
205
|
/// </summary>
|
|
192
|
-
private static
|
|
193
|
-
{
|
|
194
|
-
using var transaction = new Transaction(doc, "manage_selection: temporary isolate");
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
206
|
+
private static IReadOnlyList<string> ApplyTemporaryIsolate(Document doc, View view, ICollection<ElementId> elementIds)
|
|
207
|
+
{
|
|
208
|
+
using var transaction = new Transaction(doc, "manage_selection: temporary isolate");
|
|
209
|
+
if (transaction.Start() != TransactionStatus.Started)
|
|
210
|
+
throw new InvalidOperationException("Unable to start the temporary-isolate transaction.");
|
|
211
|
+
var failureGuard = FailureGuard.Attach(transaction);
|
|
198
212
|
try
|
|
199
213
|
{
|
|
200
214
|
if (elementIds.Count == 0)
|
|
201
215
|
view.DisableTemporaryViewMode(TemporaryViewMode.TemporaryHideIsolate);
|
|
202
216
|
else
|
|
203
217
|
view.IsolateElementsTemporary(elementIds);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
{
|
|
215
|
-
if (transaction.GetStatus() == TransactionStatus.Started)
|
|
216
|
-
transaction.RollBack();
|
|
217
|
-
throw;
|
|
218
|
-
}
|
|
218
|
+
var status = transaction.Commit();
|
|
219
|
+
var finalStatus = transaction.GetStatus();
|
|
220
|
+
if (status != TransactionStatus.Committed || finalStatus != TransactionStatus.Committed)
|
|
221
|
+
throw new InvalidOperationException($"The temporary-isolate commit returned {status}; current transaction status is {finalStatus}." + failureGuard.DescribeErrors());
|
|
222
|
+
return failureGuard.Warnings;
|
|
223
|
+
}
|
|
224
|
+
catch (Exception ex)
|
|
225
|
+
{
|
|
226
|
+
throw new InvalidOperationException($"Temporary isolate in view '{view.Name}' failed: {ex.Message} {FailureGuard.RollBackAndDescribe(transaction)}", ex);
|
|
227
|
+
}
|
|
219
228
|
}
|
|
220
229
|
}
|
|
221
230
|
}
|
|
@@ -70,9 +70,9 @@ namespace RevitBridge.Tools
|
|
|
70
70
|
var failed = new List<Dictionary<string, object?>>();
|
|
71
71
|
|
|
72
72
|
using var transaction = new Transaction(doc, "set_parameters");
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
if (transaction.Start() != TransactionStatus.Started)
|
|
74
|
+
throw new InvalidOperationException("Unable to start the set_parameters transaction.");
|
|
75
|
+
var failureGuard = FailureGuard.Attach(transaction);
|
|
76
76
|
|
|
77
77
|
try
|
|
78
78
|
{
|
|
@@ -101,21 +101,23 @@ namespace RevitBridge.Tools
|
|
|
101
101
|
|
|
102
102
|
if (succeeded.Count > 0)
|
|
103
103
|
{
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
var status = transaction.Commit();
|
|
105
|
+
var finalStatus = transaction.GetStatus();
|
|
106
|
+
if (status != TransactionStatus.Committed || finalStatus != TransactionStatus.Committed)
|
|
107
|
+
throw new InvalidOperationException(
|
|
108
|
+
$"The set_parameters commit returned {status}; current transaction status is {finalStatus}."
|
|
107
109
|
+ failureGuard.DescribeErrors());
|
|
108
110
|
}
|
|
109
111
|
else
|
|
110
112
|
{
|
|
111
|
-
transaction.RollBack();
|
|
113
|
+
var status = transaction.RollBack();
|
|
114
|
+
if (status != TransactionStatus.RolledBack || transaction.GetStatus() != TransactionStatus.RolledBack)
|
|
115
|
+
throw new InvalidOperationException($"The set_parameters rollback returned {status}.");
|
|
112
116
|
}
|
|
113
117
|
}
|
|
114
|
-
catch
|
|
115
|
-
{
|
|
116
|
-
|
|
117
|
-
transaction.RollBack();
|
|
118
|
-
throw;
|
|
118
|
+
catch (Exception ex)
|
|
119
|
+
{
|
|
120
|
+
throw new InvalidOperationException($"{ex.Message} {FailureGuard.RollBackAndDescribe(transaction)}", ex);
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
int elementCount = succeeded.Select(row => row["id"]).Distinct().Count();
|
|
@@ -259,43 +259,4 @@ namespace RevitBridge.Tools
|
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
|
|
263
|
-
/// Optional wrong-document protection for write tools. A write queued while the
|
|
264
|
-
/// user switches models would otherwise land in whichever document is active when
|
|
265
|
-
/// the queued call runs -- possibly silently, since low element ids resolve in
|
|
266
|
-
/// most documents. Tools pass the caller's optional expected_document through
|
|
267
|
-
/// here before touching the model.
|
|
268
|
-
/// </summary>
|
|
269
|
-
internal static class DocumentGuard
|
|
270
|
-
{
|
|
271
|
-
/// <summary>Throws when expected_document is provided and does not match the
|
|
272
|
-
/// active document's title (case-insensitive; the .rvt extension and a
|
|
273
|
-
/// detached suffix are tolerated). No-op when the argument is absent.</summary>
|
|
274
|
-
public static void CheckExpectedDocument(JsonElement args, Document doc)
|
|
275
|
-
{
|
|
276
|
-
string? expected = JsonArgs.GetString(args, "expected_document");
|
|
277
|
-
if (string.IsNullOrWhiteSpace(expected))
|
|
278
|
-
return;
|
|
279
|
-
string actual = doc.Title;
|
|
280
|
-
if (Matches(expected, actual))
|
|
281
|
-
return;
|
|
282
|
-
throw new ArgumentException(
|
|
283
|
-
$"Active document is '{actual}' but this call expected '{expected}'. Nothing was changed. "
|
|
284
|
-
+ "The user switched models (or several are open); re-read the target model (get_model_overview) and retry against the right one.");
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
private static bool Matches(string expected, string actual)
|
|
288
|
-
{
|
|
289
|
-
static string Normalize(string value)
|
|
290
|
-
{
|
|
291
|
-
string v = value.Trim();
|
|
292
|
-
if (v.EndsWith(".rvt", StringComparison.OrdinalIgnoreCase))
|
|
293
|
-
v = v[..^4];
|
|
294
|
-
if (v.EndsWith("_detached", StringComparison.OrdinalIgnoreCase))
|
|
295
|
-
v = v[..^"_detached".Length];
|
|
296
|
-
return v;
|
|
297
|
-
}
|
|
298
|
-
return string.Equals(Normalize(expected), Normalize(actual), StringComparison.OrdinalIgnoreCase);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
262
|
+
}
|
package/workspace/AGENTS.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# pi-revit workspace
|
|
2
2
|
|
|
3
3
|
This folder tree is the working area for Pi + Revit sessions. The pi-revit tools talk to the
|
|
4
|
-
Revit bridge add-in
|
|
5
|
-
|
|
4
|
+
Revit bridge add-in, targeting Revit 2025, 2026, and 2027. The 0.3.0 changes were
|
|
5
|
+
tested live on Revit 2025. Bridge document tools require a project open; `ping` and
|
|
6
|
+
`search_api_docs` work without a document. `read_revit_result` reads saved local
|
|
7
|
+
results without contacting Revit.
|
|
6
8
|
|
|
7
9
|
## File rules — where every file goes
|
|
8
10
|
|
|
@@ -14,28 +16,57 @@ Documents\pi-revit\
|
|
|
14
16
|
├─ AGENTS.md <- this file
|
|
15
17
|
├─ pi-revit.cmd <- double-click launcher
|
|
16
18
|
└─ Models\
|
|
17
|
-
└─ <model title>\
|
|
18
|
-
├─ model.txt <-
|
|
19
|
+
└─ <model title>--<identity hash>\ <- selected automatically for the exported document
|
|
20
|
+
├─ model.txt <- document identity/path marker written by the add-in
|
|
19
21
|
├─ exports\ <- export_documents output (its default)
|
|
20
22
|
├─ captures\ <- view snapshots worth keeping
|
|
21
23
|
└─ scripts\ <- generated scripts and analysis for that model
|
|
22
24
|
```
|
|
23
25
|
|
|
24
26
|
1. **Let exports sort themselves**: call `export_documents` without `output_dir` — files land
|
|
25
|
-
in
|
|
27
|
+
in an identity-derived model folder automatically. Treat the returned `outputDir`
|
|
28
|
+
and file paths as authoritative; do not derive the hash from the model title or
|
|
29
|
+
opaque `project.documentId`. Save As can change the destination. Existing title-only
|
|
30
|
+
folders remain untouched. Pass
|
|
26
31
|
`output_dir` only when the user names a different target.
|
|
27
32
|
2. **Anything else you produce about a model goes into that model's folder**: view captures the
|
|
28
|
-
user wants to keep in
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
user wants to keep in that verified model folder's `captures` subfolder
|
|
34
|
+
(`capture_view` writes to temp), and scripts and analysis in its `scripts`
|
|
35
|
+
subfolder. For a default export, the model folder is the parent of returned
|
|
36
|
+
`outputDir`. If no folder has been established, identify it from existing model
|
|
37
|
+
markers or choose an explicit destination for the task; do not trigger an
|
|
38
|
+
unnecessary export or guess a title-only folder. Create subfolders on first use.
|
|
31
39
|
3. **Never create files loose in the workspace root.** The root holds `AGENTS.md`,
|
|
32
40
|
`pi-revit.cmd`, `Models\`, and Pi's own session data — nothing else, ever.
|
|
33
41
|
|
|
34
42
|
## Tool habits
|
|
35
43
|
|
|
36
|
-
- Start unfamiliar models with `get_model_overview`; use `get_elements` for any listing or
|
|
37
|
-
counting; read parameter values with `get_element_details`.
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
- Start unfamiliar models with `get_model_overview`; use `get_elements` for any listing or
|
|
45
|
+
counting; read parameter values with `get_element_details`.
|
|
46
|
+
- Copy the intended model's `project.documentId` unchanged into `expected_document_id`
|
|
47
|
+
for `set_parameters`, `execute_csharp`, `export_documents`, `open_view`, and
|
|
48
|
+
selection/zoom changes. Any `manage_selection` call with `isolate_in_view: true`
|
|
49
|
+
also requires it, including action `get`. Pure reads may omit it; supplied IDs
|
|
50
|
+
are checked. Legacy `expected_document` titles alone do not satisfy the guard.
|
|
51
|
+
After a rejection, verify the intended model before refreshing its ID. Closing
|
|
52
|
+
and reopening a model or restarting Revit invalidates earlier IDs.
|
|
53
|
+
- Before `execute_csharp`, verify unfamiliar API signatures with `search_api_docs`.
|
|
54
|
+
- Write tools (`set_parameters`, `execute_csharp`) change the real model — state clearly what
|
|
55
|
+
was changed. `set_parameters` commits partial successes: always check its `failed` list.
|
|
56
|
+
Inspect `commitWarnings` and the actual transaction outcome. A failed
|
|
57
|
+
`execute_csharp` attempts rollback; do not assume rollback succeeded. A
|
|
58
|
+
`returnValueError` can follow a committed edit. Earlier UI actions and created
|
|
59
|
+
files can persist after failure. Read reported effects before retrying.
|
|
60
|
+
- When a result returns `result_id`, use `read_revit_result` from offset zero and
|
|
61
|
+
follow `next_offset` until `has_more` is false. Join its `text` fragments in order;
|
|
62
|
+
offsets count UTF-16 code units. The returned absolute file path remains a
|
|
63
|
+
fallback for `read` after extension reload while the file exists. Tool query
|
|
64
|
+
pagination and limits still apply separately.
|
|
65
|
+
|
|
66
|
+
## Upgrade notes
|
|
67
|
+
|
|
68
|
+
After installing 0.3.0, deploy the matching add-in with Revit closed, restart Revit,
|
|
69
|
+
and start a fresh Pi session to load the new tool schemas. Obtain a new overview
|
|
70
|
+
before changing a document. Setup preserves existing `AGENTS.md`; older workspaces
|
|
71
|
+
need these exact-ID, saved-result, and identity-derived folder rules merged into
|
|
72
|
+
their existing instructions.
|