pi-revit 0.2.4 → 0.2.6

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 CHANGED
@@ -7,6 +7,40 @@ Format follows [Keep a Changelog](https://keepachangelog.com/); version headers
7
7
  Every published version gets an entry with **Added** / **Changed** / **Fixed** sections
8
8
  describing what the user will notice — not internal refactors.
9
9
 
10
+ ## [0.2.6] - 2026-07-20
11
+
12
+ ### Fixed
13
+ - Write transactions (`set_parameters`, `execute_csharp`, and the temporary-isolate
14
+ branch of `manage_selection`) now register a failures preprocessor. Previously Revit
15
+ handled commit failures interactively: warnings popped the transient toast and spammed
16
+ the journal, and an error-severity failure showed the modal resolution dialog, blocking
17
+ the bridge until a human clicked. Now warnings are auto-dismissed and reported back
18
+ (`commitWarnings` in the result, e.g. duplicate Mark values), and errors roll the
19
+ transaction back with the actual Revit failure text in the error message.
20
+
21
+ ### Changed
22
+ - `set_parameters` tool description tells the model to relay `commitWarnings` to the user.
23
+
24
+ Requires redeploying the Revit add-in (`scripts\deploy.ps1` + Revit restart).
25
+
26
+ ## [0.2.5] - 2026-07-20
27
+
28
+ ### Fixed
29
+ - `execute_csharp` dialog guard no longer answers every Revit popup with OK. On some
30
+ dialogs OK is the destructive choice (e.g. "Delete Element(s)"), so a script could
31
+ silently delete dimensions or constraints and still report success. Unrecognized
32
+ dialogs are now answered dismissively (Cancel, then Close, then No; OK only as the
33
+ last resort so Revit can never hang behind a popup), a small allowlist keeps OK for
34
+ dialogs that are safe to confirm, and `suppressedDialogs` now reports which answer
35
+ was given (e.g. `TaskDialog_… (answered Cancel)`).
36
+
37
+ ### Changed
38
+ - The `execute_csharp` tool description tells the model that confirmation prompts may be
39
+ cancelled and to check `suppressedDialogs` when a result looks incomplete.
40
+
41
+ Requires redeploying the Revit add-in (`scripts\deploy.ps1` + Revit restart) — `ping`
42
+ warns on a version mismatch until then.
43
+
10
44
  ## [0.2.4] - 2026-07-20
11
45
 
12
46
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-revit",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Native Pi connector for Autodesk Revit. Run npx.cmd -y pi-revit for the full Windows install.",
5
5
  "author": "Ahmad Altahlawi",
6
6
  "license": "MIT",
@@ -59,7 +59,7 @@ namespace RevitBridge.Tools
59
59
 
60
60
  public string Name => "execute_csharp";
61
61
  public string Label => "Execute C#";
62
- public string Description => "Compile and run a C# script on the Revit API thread against the open model — the escape hatch for anything without a dedicated tool: element creation, deletion, geometry edits (move/copy/rotate), views, sheets, schedules, tagging, families, links, worksets. Globals: doc (Document), uidoc (UIDocument), uiapp (UIApplication), and Dump(value) to record intermediate values into the result's dumps[]. Default imports: System, System.Linq, System.Collections.Generic, Autodesk.Revit.DB, Autodesk.Revit.UI — add using directives at the top for sub-namespaces (e.g. using Autodesk.Revit.DB.Architecture;). The entire run is wrapped in ONE transaction named 'execute_csharp': committed on success, rolled back on any exception, so a failed script never changes the model (do not open your own Transaction; sub-transactions are fine). The script's final expression or return statement becomes returnValue; return primitives, strings, or anonymous objects/lists — Revit API values are projected to safe shapes (Element -> {id,name,category,typeName,levelId}, ElementId -> number, XYZ -> {x,y,z}, Parameter -> {name,value,displayValue}; other API objects become strings) with depth and item caps, so never rely on raw API objects round-tripping. Lengths are in internal units (decimal feet) — convert with UnitUtils. Prefer collector-level filtering (FilteredElementCollector .OfCategory/.OfClass/.WhereElementIsNotElementType) and bounded loops: the call budget is 120s and Revit cannot be interrupted mid-script. Scripts must be fully synchronous — await/async is rejected at compile time, and blocking on tasks (Task.Result/.Wait()) can freeze Revit. Modal dialogs raised while running are auto-dismissed and reported in suppressedDialogs. Verify unfamiliar signatures with search_api_docs first.";
62
+ public string Description => "Compile and run a C# script on the Revit API thread against the open model — the escape hatch for anything without a dedicated tool: element creation, deletion, geometry edits (move/copy/rotate), views, sheets, schedules, tagging, families, links, worksets. Globals: doc (Document), uidoc (UIDocument), uiapp (UIApplication), and Dump(value) to record intermediate values into the result's dumps[]. Default imports: System, System.Linq, System.Collections.Generic, Autodesk.Revit.DB, Autodesk.Revit.UI — add using directives at the top for sub-namespaces (e.g. using Autodesk.Revit.DB.Architecture;). The entire run is wrapped in ONE transaction named 'execute_csharp': committed on success, rolled back on any exception, so a failed script never changes the model (do not open your own Transaction; sub-transactions are fine). The script's final expression or return statement becomes returnValue; return primitives, strings, or anonymous objects/lists — Revit API values are projected to safe shapes (Element -> {id,name,category,typeName,levelId}, ElementId -> number, XYZ -> {x,y,z}, Parameter -> {name,value,displayValue}; other API objects become strings) with depth and item caps, so never rely on raw API objects round-tripping. Lengths are in internal units (decimal feet) — convert with UnitUtils. Prefer collector-level filtering (FilteredElementCollector .OfCategory/.OfClass/.WhereElementIsNotElementType) and bounded loops: the call budget is 120s and Revit cannot be interrupted mid-script. Scripts must be fully synchronous — await/async is rejected at compile time, and blocking on tasks (Task.Result/.Wait()) can freeze Revit. Modal dialogs raised while running are auto-dismissed and reported in suppressedDialogs — unrecognized dialogs are answered dismissively (Cancel/Close/No) rather than confirmed, so an operation that raises a confirmation prompt may be cancelled; check suppressedDialogs when a result looks incomplete. Verify unfamiliar signatures with search_api_docs first.";
63
63
  public bool Write => true;
64
64
 
65
65
  public object ParametersSchema => new
@@ -113,6 +113,7 @@ namespace RevitBridge.Tools
113
113
 
114
114
  using var dialogGuard = new DialogGuard(uiapp);
115
115
  using var transaction = new Transaction(doc, "execute_csharp");
116
+ var failureGuard = FailureGuard.Attach(transaction);
116
117
  if (transaction.Start() != TransactionStatus.Started)
117
118
  throw new InvalidOperationException("Unable to start the execute_csharp transaction.");
118
119
 
@@ -142,6 +143,7 @@ namespace RevitBridge.Tools
142
143
  if (transaction.Commit() != TransactionStatus.Committed)
143
144
  throw new InvalidOperationException(
144
145
  "Revit rolled back the execute_csharp transaction during commit (failure processing rejected the changes); no model changes were saved."
146
+ + failureGuard.DescribeErrors()
145
147
  + DescribeDialogs(dialogGuard.Suppressed));
146
148
 
147
149
  stopwatch.Stop();
@@ -154,6 +156,8 @@ namespace RevitBridge.Tools
154
156
  };
155
157
  if (dialogGuard.Suppressed.Count > 0)
156
158
  payload["suppressedDialogs"] = dialogGuard.Suppressed;
159
+ if (failureGuard.Warnings.Count > 0)
160
+ payload["commitWarnings"] = failureGuard.Warnings;
157
161
  return payload;
158
162
  }
159
163
 
@@ -234,9 +238,31 @@ namespace RevitBridge.Tools
234
238
  }
235
239
 
236
240
  /// <summary>Auto-dismisses any modal Revit dialog raised while the script runs, so a
237
- /// popup cannot hang the Revit thread; dismissed dialog ids are reported.</summary>
241
+ /// popup cannot hang the Revit thread; dismissed dialog ids and the answer given are
242
+ /// reported. Unrecognized dialogs get the dismissive answer (Cancel, then Close, then
243
+ /// No) — on several Revit dialogs OK is the destructive choice (e.g. "Delete
244
+ /// Element(s)"), so confirming blind can silently damage the model. OK leads only for
245
+ /// dialogs known to be safe to confirm, and is otherwise the last resort: every
246
+ /// override attempt failing would leave the dialog up and hang the Revit thread,
247
+ /// which is the one outcome this guard exists to prevent.</summary>
238
248
  private sealed class DialogGuard : IDisposable
239
249
  {
250
+ private const int IDOK = 1;
251
+ private const int IDCANCEL = 2;
252
+ private const int IDNO = 7;
253
+ private const int IDCLOSE = 8;
254
+
255
+ /// <summary>Dialogs where confirming is the benign answer and cancelling would
256
+ /// abort the operation the script deliberately started.</summary>
257
+ private static readonly HashSet<string> ConfirmSafeDialogIds = new(StringComparer.OrdinalIgnoreCase)
258
+ {
259
+ // "Export with temporary hide/isolate": OK exports the view as displayed.
260
+ "TaskDialog_Really_Print_Or_Export_Temp_View_Modes",
261
+ };
262
+
263
+ private static readonly int[] ConfirmFirst = { IDOK, IDCANCEL, IDCLOSE };
264
+ private static readonly int[] DismissFirst = { IDCANCEL, IDCLOSE, IDNO, IDOK };
265
+
240
266
  private readonly UIApplication _uiapp;
241
267
  public List<string> Suppressed { get; } = new();
242
268
 
@@ -248,17 +274,35 @@ namespace RevitBridge.Tools
248
274
 
249
275
  private void OnDialogBoxShowing(object? sender, DialogBoxShowingEventArgs e)
250
276
  {
251
- Suppressed.Add(string.IsNullOrEmpty(e.DialogId) ? e.GetType().Name : e.DialogId);
252
- try
253
- {
254
- e.OverrideResult(1); // IDOK
255
- }
256
- catch
277
+ string id = string.IsNullOrEmpty(e.DialogId) ? e.GetType().Name : e.DialogId;
278
+ int[] answers = ConfirmSafeDialogIds.Contains(id) ? ConfirmFirst : DismissFirst;
279
+ foreach (int answer in answers)
257
280
  {
258
- // Some dialogs reject overrides; never let the guard itself throw.
281
+ try
282
+ {
283
+ if (e.OverrideResult(answer))
284
+ {
285
+ Suppressed.Add($"{id} (answered {AnswerName(answer)})");
286
+ return;
287
+ }
288
+ }
289
+ catch
290
+ {
291
+ // Some dialogs reject specific overrides; try the next answer.
292
+ }
259
293
  }
294
+ Suppressed.Add($"{id} (override rejected — dialog may need manual dismissal)");
260
295
  }
261
296
 
297
+ private static string AnswerName(int answer) => answer switch
298
+ {
299
+ IDOK => "OK",
300
+ IDCANCEL => "Cancel",
301
+ IDNO => "No",
302
+ IDCLOSE => "Close",
303
+ _ => answer.ToString(),
304
+ };
305
+
262
306
  public void Dispose()
263
307
  {
264
308
  try
@@ -0,0 +1,62 @@
1
+ using Autodesk.Revit.DB;
2
+
3
+ namespace RevitBridge.Tools
4
+ {
5
+ /// <summary>
6
+ /// Deterministic commit-time failure handling for tool-owned transactions. Without a
7
+ /// preprocessor, Revit resolves commit failures interactively: warnings pop the
8
+ /// transient toast (and spam the journal), and error-severity failures block the
9
+ /// Revit API thread behind the modal resolution dialog until a human clicks — past
10
+ /// the bridge's call budget. Attach() deletes warnings (recorded for the tool
11
+ /// result) and turns errors into a rollback, so a headless caller always gets an
12
+ /// answer instead of a hang.
13
+ /// </summary>
14
+ internal sealed class FailureGuard : IFailuresPreprocessor
15
+ {
16
+ public List<string> Warnings { get; } = new();
17
+ public List<string> Errors { get; } = new();
18
+
19
+ public static FailureGuard Attach(Transaction transaction)
20
+ {
21
+ var guard = new FailureGuard();
22
+ var options = transaction.GetFailureHandlingOptions();
23
+ options.SetFailuresPreprocessor(guard);
24
+ options.SetClearAfterRollback(true);
25
+ transaction.SetFailureHandlingOptions(options);
26
+ return guard;
27
+ }
28
+
29
+ public FailureProcessingResult PreprocessFailures(FailuresAccessor accessor)
30
+ {
31
+ bool hasError = false;
32
+ foreach (FailureMessageAccessor failure in accessor.GetFailureMessages())
33
+ {
34
+ string description;
35
+ try
36
+ {
37
+ description = failure.GetDescriptionText();
38
+ }
39
+ catch
40
+ {
41
+ description = "(failure without description)";
42
+ }
43
+
44
+ if (failure.GetSeverity() == FailureSeverity.Warning)
45
+ {
46
+ Warnings.Add(description);
47
+ accessor.DeleteWarning(failure);
48
+ }
49
+ else
50
+ {
51
+ hasError = true;
52
+ Errors.Add(description);
53
+ }
54
+ }
55
+ return hasError ? FailureProcessingResult.ProceedWithRollBack : FailureProcessingResult.Continue;
56
+ }
57
+
58
+ /// <summary>Formats the recorded errors for a commit-failure message.</summary>
59
+ public string DescribeErrors()
60
+ => Errors.Count > 0 ? $" Revit failure(s): {string.Join("; ", Errors)}." : string.Empty;
61
+ }
62
+ }
@@ -192,6 +192,7 @@ namespace RevitBridge.Tools
192
192
  private static void ApplyTemporaryIsolate(Document doc, View view, ICollection<ElementId> elementIds)
193
193
  {
194
194
  using var transaction = new Transaction(doc, "manage_selection: temporary isolate");
195
+ var failureGuard = FailureGuard.Attach(transaction);
195
196
  if (transaction.Start() != TransactionStatus.Started)
196
197
  throw new InvalidOperationException("Unable to start the temporary-isolate transaction.");
197
198
  try
@@ -201,7 +202,7 @@ namespace RevitBridge.Tools
201
202
  else
202
203
  view.IsolateElementsTemporary(elementIds);
203
204
  if (transaction.Commit() != TransactionStatus.Committed)
204
- throw new InvalidOperationException("The temporary-isolate transaction failed to commit.");
205
+ throw new InvalidOperationException("The temporary-isolate transaction failed to commit." + failureGuard.DescribeErrors());
205
206
  }
206
207
  catch (Autodesk.Revit.Exceptions.InvalidOperationException ex)
207
208
  {
@@ -19,7 +19,7 @@ namespace RevitBridge.Tools
19
19
 
20
20
  public string Name => "set_parameters";
21
21
  public string Label => "Set Parameters";
22
- public string Description => "Write parameter values on elements — also the home for rename: parameter 'Name' covers levels, views, sheets, types, etc. (falls back to the element's Name property when the Name parameter is read-only). updates apply in ONE transaction: partial success commits and lists the failures; if every update fails the transaction is rolled back and nothing changes. parameter accepts a display name (Comments, Mark, Name), a BuiltInParameter enum name (e.g. ALL_MODEL_MARK), or guid:<GUID> for a shared parameter; type parameters live on the element type, so pass the type's id. Values are validated against the parameter's storage type; numeric values are interpreted in the document's display units for that parameter unless 'unit' (e.g. millimeters, feet, squareMeters) is given.";
22
+ public string Description => "Write parameter values on elements — also the home for rename: parameter 'Name' covers levels, views, sheets, types, etc. (falls back to the element's Name property when the Name parameter is read-only). updates apply in ONE transaction: partial success commits and lists the failures; if every update fails the transaction is rolled back and nothing changes. parameter accepts a display name (Comments, Mark, Name), a BuiltInParameter enum name (e.g. ALL_MODEL_MARK), or guid:<GUID> for a shared parameter; type parameters live on the element type, so pass the type's id. Values are validated against the parameter's storage type; numeric values are interpreted in the document's display units for that parameter unless 'unit' (e.g. millimeters, feet, squareMeters) is given. Revit warnings raised at commit (e.g. duplicate Mark values) are auto-dismissed and listed in commitWarnings — mention them to the user; error-severity failures roll the whole transaction back.";
23
23
  public bool Write => true;
24
24
 
25
25
  public object ParametersSchema => new
@@ -64,6 +64,7 @@ namespace RevitBridge.Tools
64
64
  var failed = new List<Dictionary<string, object?>>();
65
65
 
66
66
  using var transaction = new Transaction(doc, "set_parameters");
67
+ var failureGuard = FailureGuard.Attach(transaction);
67
68
  if (transaction.Start() != TransactionStatus.Started)
68
69
  throw new InvalidOperationException("Unable to start the set_parameters transaction.");
69
70
 
@@ -95,7 +96,9 @@ namespace RevitBridge.Tools
95
96
  if (succeeded.Count > 0)
96
97
  {
97
98
  if (transaction.Commit() != TransactionStatus.Committed)
98
- throw new InvalidOperationException("The set_parameters transaction failed to commit; no changes were saved.");
99
+ throw new InvalidOperationException(
100
+ "Revit rejected the set_parameters commit and the transaction was rolled back; no changes were saved."
101
+ + failureGuard.DescribeErrors());
99
102
  }
100
103
  else
101
104
  {
@@ -118,6 +121,8 @@ namespace RevitBridge.Tools
118
121
  : succeeded.Count == 0
119
122
  ? $"No updates applied; all {failed.Count} failed — transaction rolled back.{failureSample}"
120
123
  : $"Updated {succeeded.Count} parameter value(s) on {elementCount} element(s); {failed.Count} failed.{failureSample}";
124
+ if (failureGuard.Warnings.Count > 0)
125
+ compact += $" {failureGuard.Warnings.Count} Revit warning(s) auto-dismissed at commit (see commitWarnings), e.g.: {failureGuard.Warnings[0]}";
121
126
 
122
127
  return new ToolOutput(new
123
128
  {
@@ -125,6 +130,7 @@ namespace RevitBridge.Tools
125
130
  committed = succeeded.Count > 0,
126
131
  succeeded,
127
132
  failed,
133
+ commitWarnings = failureGuard.Warnings,
128
134
  }, compact);
129
135
  }
130
136