pi-revit 0.3.0 → 0.3.1

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.
@@ -36,7 +36,7 @@ namespace RevitBridge.Tools
36
36
  properties = new
37
37
  {
38
38
  parameters = new { type = "boolean", description = "Instance parameter values. Default true." },
39
- type_parameters = new { type = "boolean", description = "Include the element type's parameters, marked isType=true, independently of the instance parameters flag. Default false." },
39
+ type_parameters = new { type = "boolean", description = "Include the element type's parameters, marked isType=true, independently of the instance parameters flag. Default false." },
40
40
  location = new { type = "boolean", description = "Location point or curve (coordinates in internal feet). Default false." },
41
41
  bounding_box = new { type = "boolean", description = "Model bounding box min/max (internal feet). Default false." },
42
42
  materials = new { type = "boolean", description = "Material ids/names with area/volume (internal units). Default false." },
@@ -70,9 +70,9 @@ namespace RevitBridge.Tools
70
70
  JsonElement include = args.TryGetProperty("include", out var includeElement) && includeElement.ValueKind == JsonValueKind.Object
71
71
  ? includeElement
72
72
  : default;
73
- bool withParameters = JsonArgs.GetBool(include, "parameters", true);
74
- bool withTypeParameters = JsonArgs.GetBool(include, "type_parameters", false);
75
- bool withAnyParameters = withParameters || withTypeParameters;
73
+ bool withParameters = JsonArgs.GetBool(include, "parameters", true);
74
+ bool withTypeParameters = JsonArgs.GetBool(include, "type_parameters", false);
75
+ bool withAnyParameters = withParameters || withTypeParameters;
76
76
  bool withLocation = JsonArgs.GetBool(include, "location", false);
77
77
  bool withBoundingBox = JsonArgs.GetBool(include, "bounding_box", false);
78
78
  bool withMaterials = JsonArgs.GetBool(include, "materials", false);
@@ -103,11 +103,11 @@ namespace RevitBridge.Tools
103
103
 
104
104
  int parameterCount = 0;
105
105
  int parameterTotal = 0;
106
- if (withAnyParameters)
107
- {
108
- var parameters = new List<Dictionary<string, object?>>();
109
- if (withParameters)
110
- parameterTotal += AppendParameters(doc, element, nameFilter, isType: false, parameters);
106
+ if (withAnyParameters)
107
+ {
108
+ var parameters = new List<Dictionary<string, object?>>();
109
+ if (withParameters)
110
+ parameterTotal += AppendParameters(doc, element, nameFilter, isType: false, parameters);
111
111
  if (withTypeParameters && elementType != null)
112
112
  parameterTotal += AppendParameters(doc, elementType, nameFilter, isType: true, parameters);
113
113
  dto["parameters"] = parameters;
@@ -124,7 +124,7 @@ namespace RevitBridge.Tools
124
124
  elements.Add(dto);
125
125
  // With a name filter active, "0 params" is ambiguous (none matched vs none
126
126
  // exist): report matched-of-total so a localization miss is visible.
127
- string paramSummary = !withAnyParameters ? string.Empty
127
+ string paramSummary = !withAnyParameters ? string.Empty
128
128
  : nameFilter != null ? $", {parameterCount} of {parameterTotal} params matched parameter_names"
129
129
  : $", {parameterCount} params";
130
130
  compactParts.Add($"'{element.Name}' (id {id}, {element.Category?.Name ?? "no category"}{paramSummary})");
@@ -21,7 +21,7 @@ namespace RevitBridge.Tools
21
21
 
22
22
  public string Name => "get_elements";
23
23
  public string Label => "Get Elements";
24
- public string Description => "Query Revit elements: scope by category (display name like 'Walls' or enum name like 'OST_Walls'), element class, level, type id, or active view; filter by parameter rules; paginate with offset/limit or just count with count_only. Returns identity fields only (id, name, category, typeName, levelId) — read parameter values with get_element_details. Rules with an explicit BuiltInParameter or shared GUID can evaluate inside Revit's collector; display-name rules, regex rules, and unsupported quick filters run as a per-element scan. A display name can identify different parameters even within one category or class. Scope queries to reduce the scan, or use an explicit parameter identity when appropriate. Numeric rule values are interpreted in the document's display units for that parameter unless 'unit' is given.";
24
+ public string Description => "Query Revit elements: scope by category (display name like 'Walls' or enum name like 'OST_Walls'), element class, level, type id, or active view; filter by parameter rules; paginate with offset/limit or just count with count_only. Returns identity fields only (id, name, category, typeName, levelId) — read parameter values with get_element_details. Rules with an explicit BuiltInParameter or shared GUID can evaluate inside Revit's collector; display-name rules, regex rules, and unsupported quick filters run as a per-element scan. A display name can identify different parameters even within one category or class. Scope queries to reduce the scan, or use an explicit parameter identity when appropriate. Numeric rule values are interpreted in the document's display units for that parameter unless 'unit' is given.";
25
25
 
26
26
  public object ParametersSchema => new
27
27
  {
@@ -292,9 +292,9 @@ namespace RevitBridge.Tools
292
292
  if (rules.Count == 0)
293
293
  return (null, null, Array.Empty<string>());
294
294
 
295
- // Probe a few in-scope elements for missing-name diagnostics and to find
296
- // storage/spec exemplars for explicit parameter identities. A sample must
297
- // never turn a display name into an assumed globally uniform identity.
295
+ // Probe a few in-scope elements for missing-name diagnostics and to find
296
+ // storage/spec exemplars for explicit parameter identities. A sample must
297
+ // never turn a display name into an assumed globally uniform identity.
298
298
  var probes = new List<Element>(ProbeSize);
299
299
  foreach (Element element in createBaseCollector())
300
300
  {
@@ -326,19 +326,19 @@ namespace RevitBridge.Tools
326
326
  }
327
327
  }
328
328
 
329
- foreach (var rule in rules)
330
- {
331
- if (rule.Op is RuleOp.Regex or RuleOp.IsEmpty or RuleOp.IsNotEmpty)
332
- continue; // always post-scan (missing-parameter semantics)
333
- // Category/class scope and unanimous samples do not establish a
334
- // uniform parameter identity across later families. Resolve display
335
- // names per element; only explicit identities may use a quick rule.
336
- if (rule.BuiltIn is null && rule.SharedGuid is null)
337
- continue;
338
- var found = probes.Select(probe => FindParameter(probe, rule)).Where(parameter => parameter != null).ToList();
339
- if (found.Count == 0)
340
- continue;
341
- rule.QuickRule = TryBuildQuickRule(doc, rule, found[0]!);
329
+ foreach (var rule in rules)
330
+ {
331
+ if (rule.Op is RuleOp.Regex or RuleOp.IsEmpty or RuleOp.IsNotEmpty)
332
+ continue; // always post-scan (missing-parameter semantics)
333
+ // Category/class scope and unanimous samples do not establish a
334
+ // uniform parameter identity across later families. Resolve display
335
+ // names per element; only explicit identities may use a quick rule.
336
+ if (rule.BuiltIn is null && rule.SharedGuid is null)
337
+ continue;
338
+ var found = probes.Select(probe => FindParameter(probe, rule)).Where(parameter => parameter != null).ToList();
339
+ if (found.Count == 0)
340
+ continue;
341
+ rule.QuickRule = TryBuildQuickRule(doc, rule, found[0]!);
342
342
  }
343
343
 
344
344
  var quickRules = rules.Where(rule => rule.QuickRule != null).ToList();
@@ -45,8 +45,8 @@ namespace RevitBridge.Tools
45
45
  string? lengthUnit = DisplayUnitName(units, SpecTypeId.Length);
46
46
  var project = new Dictionary<string, object?>
47
47
  {
48
- ["title"] = doc.Title,
49
- ["documentId"] = DocumentGuard.GetIdentity(doc),
48
+ ["title"] = doc.Title,
49
+ ["documentId"] = DocumentGuard.GetIdentity(doc),
50
50
  ["name"] = info?.Name,
51
51
  ["number"] = info?.Number,
52
52
  ["clientName"] = info?.ClientName,
@@ -136,25 +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
- 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
- }
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
+ }
158
158
  payload["viewId"] = view.Id.Value;
159
159
  payload["viewName"] = view.Name;
160
160
  if (isolateTargets.Count == 0)
@@ -203,28 +203,28 @@ namespace RevitBridge.Tools
203
203
  /// only changes it inside an open transaction — so exactly this branch wraps
204
204
  /// a small one while the tool stays Write = false.
205
205
  /// </summary>
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);
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);
212
212
  try
213
213
  {
214
214
  if (elementIds.Count == 0)
215
215
  view.DisableTemporaryViewMode(TemporaryViewMode.TemporaryHideIsolate);
216
216
  else
217
217
  view.IsolateElementsTemporary(elementIds);
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
- }
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
+ }
228
228
  }
229
229
  }
230
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
- if (transaction.Start() != TransactionStatus.Started)
74
- throw new InvalidOperationException("Unable to start the set_parameters transaction.");
75
- var failureGuard = FailureGuard.Attach(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,23 +101,23 @@ namespace RevitBridge.Tools
101
101
 
102
102
  if (succeeded.Count > 0)
103
103
  {
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}."
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}."
109
109
  + failureGuard.DescribeErrors());
110
110
  }
111
111
  else
112
112
  {
113
- var status = transaction.RollBack();
114
- if (status != TransactionStatus.RolledBack || transaction.GetStatus() != TransactionStatus.RolledBack)
115
- throw new InvalidOperationException($"The set_parameters rollback returned {status}.");
113
+ var status = transaction.RollBack();
114
+ if (status != TransactionStatus.RolledBack || transaction.GetStatus() != TransactionStatus.RolledBack)
115
+ throw new InvalidOperationException($"The set_parameters rollback returned {status}.");
116
116
  }
117
117
  }
118
- catch (Exception ex)
119
- {
120
- throw new InvalidOperationException($"{ex.Message} {FailureGuard.RollBackAndDescribe(transaction)}", ex);
118
+ catch (Exception ex)
119
+ {
120
+ throw new InvalidOperationException($"{ex.Message} {FailureGuard.RollBackAndDescribe(transaction)}", ex);
121
121
  }
122
122
 
123
123
  int elementCount = succeeded.Select(row => row["id"]).Distinct().Count();
@@ -259,4 +259,4 @@ namespace RevitBridge.Tools
259
259
  }
260
260
  }
261
261
 
262
- }
262
+ }
@@ -1,10 +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, 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.
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.
8
8
 
9
9
  ## File rules — where every file goes
10
10
 
@@ -16,57 +16,57 @@ Documents\pi-revit\
16
16
  ├─ AGENTS.md <- this file
17
17
  ├─ pi-revit.cmd <- double-click launcher
18
18
  └─ Models\
19
- └─ <model title>--<identity hash>\ <- selected automatically for the exported document
20
- ├─ model.txt <- document identity/path marker 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
21
21
  ├─ exports\ <- export_documents output (its default)
22
22
  ├─ captures\ <- view snapshots worth keeping
23
23
  └─ scripts\ <- generated scripts and analysis for that model
24
24
  ```
25
25
 
26
26
  1. **Let exports sort themselves**: call `export_documents` without `output_dir` — files land
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
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
31
31
  `output_dir` only when the user names a different target.
32
32
  2. **Anything else you produce about a model goes into that model's folder**: view captures the
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.
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.
39
39
  3. **Never create files loose in the workspace root.** The root holds `AGENTS.md`,
40
40
  `pi-revit.cmd`, `Models\`, and Pi's own session data — nothing else, ever.
41
41
 
42
42
  ## Tool habits
43
43
 
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.
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.