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.
@@ -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
- ?? throw new ArgumentException("isolate_in_view requires an active graphical view in Revit.");
143
- ApplyTemporaryIsolate(doc, view, isolateTargets);
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 void ApplyTemporaryIsolate(Document doc, View view, ICollection<ElementId> elementIds)
193
- {
194
- using var transaction = new Transaction(doc, "manage_selection: temporary isolate");
195
- var failureGuard = FailureGuard.Attach(transaction);
196
- if (transaction.Start() != TransactionStatus.Started)
197
- throw new InvalidOperationException("Unable to start the temporary-isolate transaction.");
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
- if (transaction.Commit() != TransactionStatus.Committed)
205
- throw new InvalidOperationException("The temporary-isolate transaction failed to commit." + failureGuard.DescribeErrors());
206
- }
207
- catch (Autodesk.Revit.Exceptions.InvalidOperationException ex)
208
- {
209
- if (transaction.GetStatus() == TransactionStatus.Started)
210
- transaction.RollBack();
211
- throw new ArgumentException($"The active view '{view.Name}' does not support temporary isolate: {ex.Message}");
212
- }
213
- catch
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
- var failureGuard = FailureGuard.Attach(transaction);
74
- if (transaction.Start() != TransactionStatus.Started)
75
- throw new InvalidOperationException("Unable to start the set_parameters transaction.");
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
- if (transaction.Commit() != TransactionStatus.Committed)
105
- throw new InvalidOperationException(
106
- "Revit rejected the set_parameters commit and the transaction was rolled back; no changes were saved."
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
- if (transaction.GetStatus() == TransactionStatus.Started)
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
- /// <summary>
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
+ }
@@ -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; Revit 2025, 2026, or 2027 must be running with a project open
5
- (only `ping` and `search_api_docs` work without a document).
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>\ <- created automatically by the tools, one folder per Revit model
18
- ├─ model.txt <- the model's identity (GUID + file path), written by the add-in
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 `Models\<model title>\exports` automatically, keyed to the exported document. Pass
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 `Models\<model title>\captures` (`capture_view` writes to temp — copy
29
- the PNG over; the model title comes from `get_model_overview`), scripts and analysis in
30
- `Models\<model title>\scripts`. Create these subfolders on first use.
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
- - Before `execute_csharp`, verify unfamiliar API signatures with `search_api_docs`.
39
- - Write tools (`set_parameters`, `execute_csharp`) change the real model — state clearly what
40
- was changed. `set_parameters` commits partial successes: always check its `failed` list.
41
- A failed `execute_csharp` rolls back entirely and reports the error.
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.