pi-revit 0.3.1 → 0.5.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.
Files changed (120) hide show
  1. package/AGENTS.md +167 -0
  2. package/CHANGELOG.md +114 -42
  3. package/README.md +598 -138
  4. package/bin/pi-revit.js +9 -9
  5. package/docs/architecture.md +271 -0
  6. package/docs/evaluation.md +434 -0
  7. package/docs/invariants.json +147 -0
  8. package/extensions/pi-revit/completion-monitor.ts +55 -0
  9. package/extensions/pi-revit/contracts.ts +146 -0
  10. package/extensions/pi-revit/discovery.ts +93 -0
  11. package/extensions/pi-revit/index.ts +231 -85
  12. package/extensions/pi-revit/instance-router.ts +86 -0
  13. package/extensions/pi-revit/platform-prompt.ts +40 -0
  14. package/extensions/pi-revit/scope-monitor.ts +114 -0
  15. package/extensions/pi-revit/script-library.ts +146 -0
  16. package/extensions/pi-revit/tool-catalog.ts +166 -0
  17. package/extensions/pi-revit/tool-documentation.ts +72 -0
  18. package/extensions/pi-revit/tool-schema.ts +8 -0
  19. package/package.json +65 -59
  20. package/scripts/build.ps1 +9 -9
  21. package/scripts/check-sdk.ps1 +66 -66
  22. package/scripts/check-tool-documentation.mjs +287 -0
  23. package/scripts/deploy.ps1 +16 -16
  24. package/scripts/generate-contracts.mjs +80 -0
  25. package/scripts/lib/platform.mjs +226 -0
  26. package/scripts/test-extension.mjs +15 -0
  27. package/skills/pi-revit/SKILL.md +39 -63
  28. package/skills/pi-revit/contracts.generated.json +3524 -0
  29. package/skills/pi-revit/references/execution-rules.md +41 -0
  30. package/skills/pi-revit/references/model-audit-export.md +40 -0
  31. package/skills/pi-revit/references/operation-recovery.md +33 -0
  32. package/skills/pi-revit/references/room-documentation.md +39 -0
  33. package/skills/pi-revit/references/tool-index.md +89 -0
  34. package/skills/pi-revit/references/tools/capture_view.md +62 -0
  35. package/skills/pi-revit/references/tools/change_element_types.md +65 -0
  36. package/skills/pi-revit/references/tools/create_tags.md +85 -0
  37. package/skills/pi-revit/references/tools/delete_elements.md +66 -0
  38. package/skills/pi-revit/references/tools/execute_csharp.md +81 -0
  39. package/skills/pi-revit/references/tools/export_documents.md +75 -0
  40. package/skills/pi-revit/references/tools/find_revit_tools.md +96 -0
  41. package/skills/pi-revit/references/tools/get_element_details.md +66 -0
  42. package/skills/pi-revit/references/tools/get_element_relationships.md +61 -0
  43. package/skills/pi-revit/references/tools/get_element_types.md +67 -0
  44. package/skills/pi-revit/references/tools/get_elements.md +87 -0
  45. package/skills/pi-revit/references/tools/get_linked_elements.md +79 -0
  46. package/skills/pi-revit/references/tools/get_linked_models.md +57 -0
  47. package/skills/pi-revit/references/tools/get_model_coordinates.md +64 -0
  48. package/skills/pi-revit/references/tools/get_model_health.md +53 -0
  49. package/skills/pi-revit/references/tools/get_model_overview.md +57 -0
  50. package/skills/pi-revit/references/tools/get_revit_operation.md +54 -0
  51. package/skills/pi-revit/references/tools/get_schedule_fields.md +62 -0
  52. package/skills/pi-revit/references/tools/get_schedules.md +71 -0
  53. package/skills/pi-revit/references/tools/manage_element_sets.md +92 -0
  54. package/skills/pi-revit/references/tools/manage_revit_instances.md +63 -0
  55. package/skills/pi-revit/references/tools/manage_revit_scripts.md +109 -0
  56. package/skills/pi-revit/references/tools/manage_schedules.md +90 -0
  57. package/skills/pi-revit/references/tools/manage_selection.md +66 -0
  58. package/skills/pi-revit/references/tools/manage_sheet_placements.md +82 -0
  59. package/skills/pi-revit/references/tools/manage_sheets.md +71 -0
  60. package/skills/pi-revit/references/tools/manage_views.md +95 -0
  61. package/skills/pi-revit/references/tools/measure_geometry.md +71 -0
  62. package/skills/pi-revit/references/tools/open_view.md +59 -0
  63. package/skills/pi-revit/references/tools/ping.md +41 -0
  64. package/skills/pi-revit/references/tools/query_spatial_elements.md +74 -0
  65. package/skills/pi-revit/references/tools/read_revit_result.md +53 -0
  66. package/skills/pi-revit/references/tools/search_api_docs.md +65 -0
  67. package/skills/pi-revit/references/tools/set_parameters.md +75 -0
  68. package/skills/pi-revit/references/tools/summarize_elements.md +64 -0
  69. package/skills/pi-revit/references/tools/transform_elements.md +79 -0
  70. package/skills/pi-revit/references/visual-verification.md +36 -0
  71. package/skills/pi-revit/tool-manifest.json +338 -0
  72. package/src/Revit/BridgeServer.cs +75 -19
  73. package/src/Revit/OperationStore.cs +178 -0
  74. package/src/Revit/ToolRegistry.cs +61 -8
  75. package/src/Revit/Tools/CaptureView.cs +9 -0
  76. package/src/Revit/Tools/ChangeElementTypes.cs +74 -0
  77. package/src/Revit/Tools/ChangeSet.cs +39 -0
  78. package/src/Revit/Tools/CreateTags.cs +107 -0
  79. package/src/Revit/Tools/DeleteElements.cs +53 -0
  80. package/src/Revit/Tools/DocumentGuard.cs +12 -2
  81. package/src/Revit/Tools/ElementNames.cs +103 -0
  82. package/src/Revit/Tools/ElementQueryScope.cs +27 -0
  83. package/src/Revit/Tools/ElementTraits.cs +53 -0
  84. package/src/Revit/Tools/ExecuteCsharp.cs +26 -7
  85. package/src/Revit/Tools/ExportDocuments.cs +9 -0
  86. package/src/Revit/Tools/FailureGuard.cs +26 -26
  87. package/src/Revit/Tools/GetElementDetails.cs +28 -2
  88. package/src/Revit/Tools/GetElementRelationships.cs +82 -0
  89. package/src/Revit/Tools/GetElementTypes.cs +8 -0
  90. package/src/Revit/Tools/GetElements.cs +58 -55
  91. package/src/Revit/Tools/GetLinkedElements.cs +89 -0
  92. package/src/Revit/Tools/GetLinkedModels.cs +73 -0
  93. package/src/Revit/Tools/GetModelCoordinates.cs +56 -0
  94. package/src/Revit/Tools/GetModelHealth.cs +7 -0
  95. package/src/Revit/Tools/GetModelOverview.cs +187 -160
  96. package/src/Revit/Tools/GetScheduleFields.cs +44 -0
  97. package/src/Revit/Tools/GetSchedules.cs +96 -0
  98. package/src/Revit/Tools/InheritedState.Summary.cs +57 -0
  99. package/src/Revit/Tools/InheritedState.cs +144 -0
  100. package/src/Revit/Tools/ManageElementSets.cs +114 -0
  101. package/src/Revit/Tools/ManageSchedules.cs +174 -0
  102. package/src/Revit/Tools/ManageSelection.cs +9 -0
  103. package/src/Revit/Tools/ManageSheetPlacements.cs +113 -0
  104. package/src/Revit/Tools/ManageSheets.cs +72 -0
  105. package/src/Revit/Tools/ManageViews.cs +115 -0
  106. package/src/Revit/Tools/MeasureGeometry.cs +60 -0
  107. package/src/Revit/Tools/ModelChanges.cs +154 -0
  108. package/src/Revit/Tools/ModelEditBatch.cs +105 -0
  109. package/src/Revit/Tools/ModelEditInputs.cs +49 -0
  110. package/src/Revit/Tools/OpenView.cs +8 -0
  111. package/src/Revit/Tools/ParameterResolver.cs +94 -0
  112. package/src/Revit/Tools/QuerySpatialElements.cs +70 -0
  113. package/src/Revit/Tools/SearchApiDocs.cs +72 -4
  114. package/src/Revit/Tools/SetParameters.cs +50 -119
  115. package/src/Revit/Tools/SpatialBounds.cs +30 -0
  116. package/src/Revit/Tools/SummarizeElements.cs +94 -0
  117. package/src/Revit/Tools/ToolContract.cs +48 -0
  118. package/src/Revit/Tools/ToolSupport.cs +4 -0
  119. package/src/Revit/Tools/TransformElements.cs +73 -0
  120. package/workspace/AGENTS.md +54 -48
@@ -20,11 +20,12 @@ namespace RevitBridge.Tools
20
20
  /// </summary>
21
21
  public sealed class ScriptGlobals
22
22
  {
23
- internal ScriptGlobals(Document document, UIDocument uiDocument, UIApplication uiApplication, Action<object?> dump)
23
+ internal ScriptGlobals(Document document, UIDocument uiDocument, UIApplication uiApplication, JsonElement inputValues, Action<object?> dump)
24
24
  {
25
25
  doc = document;
26
26
  uidoc = uiDocument;
27
27
  uiapp = uiApplication;
28
+ inputs = inputValues;
28
29
  Dump = dump;
29
30
  }
30
31
 
@@ -37,6 +38,9 @@ namespace RevitBridge.Tools
37
38
  /// <summary>The Revit UI application.</summary>
38
39
  public UIApplication uiapp { get; }
39
40
 
41
+ /// <summary>Structured inputs supplied separately from the script source.</summary>
42
+ public JsonElement inputs { get; }
43
+
40
44
  /// <summary>Records a value into the result's dumps[] (safely projected at call time).</summary>
41
45
  public Action<object?> Dump { get; }
42
46
  }
@@ -58,9 +62,19 @@ namespace RevitBridge.Tools
58
62
  private const int MaxReportedErrors = 20;
59
63
 
60
64
  public string Name => "execute_csharp";
65
+ public IReadOnlyList<string> DocumentKinds => DocumentKind.Both;
66
+ public IReadOnlyList<string> Keywords => new[] { "script", "code", "csharp", "c#", "custom", "automation", "macro" };
67
+ public IReadOnlyList<ToolLimit> Limits => new[]
68
+ {
69
+ new ToolLimit("Automatic preview or rollback of custom code", "tool", "dedicated tools with preview (set_parameters, transform_elements, delete_elements, manage_views, ...)"),
70
+ new ToolLimit("Undoing file, UI or external effects on rollback", "user", "Inspect and clean up those effects explicitly"),
71
+ new ToolLimit("Saving the model", "user", "Save only when the user asks"),
72
+ };
73
+ public string? Verification => "reread";
61
74
  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; on failure rollback is attempted and its confirmed status is reported (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.";
75
+ public string Description => "Compile and run a C# script on the Revit API thread against the open model, for operations that no dedicated tool covers. Specialist tools start inactive, so resolve the capability with find_revit_tools first and follow a tool's declared limits and alternatives. Globals: doc (Document), uidoc (UIDocument), uiapp (UIApplication), inputs (System.Text.Json.JsonElement, defaults to an empty object), 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; on failure rollback is attempted and its confirmed status is reported (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
76
  public bool Write => true;
77
+ public IReadOnlyList<string> Effects => new[] { "model", "ui", "files", "external" };
64
78
 
65
79
  public object ParametersSchema => new
66
80
  {
@@ -70,8 +84,9 @@ namespace RevitBridge.Tools
70
84
  code = new
71
85
  {
72
86
  type = "string",
73
- description = "C# script body (top-level statements; using directives allowed at the top). Globals doc/uidoc/uiapp and Dump(value) are in scope. The final expression or a return statement is the result.",
87
+ description = "C# script body (top-level statements; using directives allowed at the top). Globals doc/uidoc/uiapp/inputs and Dump(value) are in scope. The final expression or a return statement is the result.",
74
88
  },
89
+ inputs = new { type = "object", description = "Optional structured JSON inputs, available as the inputs JsonElement global. Default empty object. Values are never interpolated into source code." },
75
90
  expected_document = new
76
91
  {
77
92
  type = "string",
@@ -81,10 +96,10 @@ namespace RevitBridge.Tools
81
96
  required = new[] { "code" },
82
97
  };
83
98
 
84
- public string? PromptSnippet => "Run a C# script against the Revit API inside one transaction (globals doc/uidoc/uiapp, Dump helper) — the escape hatch for operations without a dedicated tool.";
99
+ public string? PromptSnippet => "Run a C# script against the Revit API inside one transaction (globals doc/uidoc/uiapp, Dump helper) for operations no dedicated tool covers (check find_revit_tools first).";
85
100
  public IReadOnlyList<string>? PromptGuidelines => new[]
86
101
  {
87
- "Use execute_csharp for Revit operations no dedicated tool covers (creation, deletion, geometry edits, views/sheets, tagging, ...); check exact signatures with search_api_docs before writing the script.",
102
+ "Use execute_csharp only when no dedicated tool covers the operation (check find_revit_tools and the tool's declared limits first); verify exact signatures with search_api_docs before writing the script.",
88
103
  "execute_csharp scripts must be fully synchronous (await/async is rejected; never block on Task.Result/.Wait()) and should return primitives or anonymous objects/lists, using Dump(...) for intermediates — raw Revit objects are projected to compact summaries; keep loops bounded and filter at the collector level.",
89
104
  };
90
105
 
@@ -98,6 +113,9 @@ namespace RevitBridge.Tools
98
113
  string code = JsonArgs.GetString(args, "code") ?? string.Empty;
99
114
  if (string.IsNullOrWhiteSpace(code))
100
115
  throw new ArgumentException("code must be a non-empty C# script.");
116
+ var inputValues = args.TryGetProperty("inputs", out var suppliedInputs) ? suppliedInputs.Clone() : JsonSerializer.SerializeToElement(new { });
117
+ if (inputValues.ValueKind != JsonValueKind.Object) throw new ArgumentException("inputs must be a JSON object.");
118
+ if (inputValues.GetRawText().Length > 100000) throw new ArgumentException("inputs must not exceed 100,000 JSON characters.");
101
119
 
102
120
  var stopwatch = Stopwatch.StartNew();
103
121
 
@@ -109,7 +127,7 @@ namespace RevitBridge.Tools
109
127
  RejectAsyncCode(script.GetCompilation());
110
128
 
111
129
  var dumps = new List<object?>();
112
- var globals = new ScriptGlobals(doc, uidoc, uiapp, value =>
130
+ var globals = new ScriptGlobals(doc, uidoc, uiapp, inputValues, value =>
113
131
  {
114
132
  if (dumps.Count < MaxDumps)
115
133
  dumps.Add(Project(value, 0));
@@ -195,6 +213,7 @@ namespace RevitBridge.Tools
195
213
  typeof(Enumerable).Assembly, // System.Linq
196
214
  typeof(Regex).Assembly, // System.Text.RegularExpressions
197
215
  typeof(Console).Assembly, // System.Console
216
+ typeof(JsonElement).Assembly, // System.Text.Json
198
217
  typeof(Document).Assembly, // RevitAPI
199
218
  typeof(UIApplication).Assembly) // RevitAPIUI
200
219
  .WithImports("System", "System.Linq", "System.Collections.Generic", "Autodesk.Revit.DB", "Autodesk.Revit.UI")
@@ -209,7 +228,7 @@ namespace RevitBridge.Tools
209
228
  });
210
229
  string more = errors.Count > MaxReportedErrors ? $"\n … +{errors.Count - MaxReportedErrors} more error(s)" : string.Empty;
211
230
  return $"C# compilation failed with {errors.Count} error(s):\n{string.Join("\n", lines)}{more}\n"
212
- + "Globals: doc, uidoc, uiapp, Dump(value). Default imports: System, System.Linq, System.Collections.Generic, "
231
+ + "Globals: doc, uidoc, uiapp, inputs (JsonElement), Dump(value). Default imports: System, System.Linq, System.Collections.Generic, "
213
232
  + "Autodesk.Revit.DB, Autodesk.Revit.UI — add using directives for other namespaces.";
214
233
  }
215
234
 
@@ -20,9 +20,18 @@ namespace RevitBridge.Tools
20
20
  private const int PngPixelWidth = 2048;
21
21
 
22
22
  public string Name => "export_documents";
23
+ public IReadOnlyList<string> DocumentKinds => DocumentKind.Both;
24
+ public IReadOnlyList<string> Keywords => new[] { "pdf", "dwg", "ifc", "png", "print", "publish", "plot", "export sheets" };
25
+ public IReadOnlyList<ToolLimit> Limits => new[]
26
+ {
27
+ new ToolLimit("Formats other than PDF, DWG, PNG and IFC", "api", "Document.Export overloads (e.g. DXFExportOptions, NavisworksExportOptions); verify the installed exporter"),
28
+ new ToolLimit("Printing to a physical printer", "api", "PrintManager"),
29
+ };
30
+ public string? Verification => "inspect_output";
23
31
  public string Label => "Export Documents";
24
32
  public string Description => "Export documents from the open Revit model. format 'pdf'/'dwg'/'png' export the given sheet/view ids to files: pdf combines everything into one file by default (combine=false writes one PDF per sheet/view, named by Revit's naming rule); png renders 2048 px wide; ifc exports the whole model, or just what one given view shows. Files sort themselves per model: with output_dir omitted they land in Documents\\pi-revit\\Models\\<model title>--<identity hash>\\exports, derived from the document being exported (pass output_dir only for a different explicit target); file_name_prefix sets the base file name (default: the document title; Revit appends view/sheet suffixes for multi-file exports). Returns the produced file paths with sizes. Find sheet/view ids with get_elements (category 'Sheets' or 'Views') first.";
25
33
  public bool Write => true;
34
+ public IReadOnlyList<string> Effects => new[] { "model", "files" };
26
35
  public string Tier => "advanced";
27
36
 
28
37
  public object ParametersSchema => new
@@ -16,36 +16,36 @@ namespace RevitBridge.Tools
16
16
  public List<string> Warnings { get; } = new();
17
17
  public List<string> Errors { get; } = new();
18
18
 
19
- public static FailureGuard Attach(Transaction transaction)
20
- {
21
- if (transaction.GetStatus() != TransactionStatus.Started)
22
- throw new InvalidOperationException("FailureGuard must be attached after the transaction starts; Start resets failure handling options.");
23
- var guard = new FailureGuard();
19
+ public static FailureGuard Attach(Transaction transaction)
20
+ {
21
+ if (transaction.GetStatus() != TransactionStatus.Started)
22
+ throw new InvalidOperationException("FailureGuard must be attached after the transaction starts; Start resets failure handling options.");
23
+ var guard = new FailureGuard();
24
24
  var options = transaction.GetFailureHandlingOptions();
25
25
  options.SetFailuresPreprocessor(guard);
26
26
  options.SetClearAfterRollback(true);
27
27
  transaction.SetFailureHandlingOptions(options);
28
- return guard;
29
- }
30
-
31
- /// <summary>Best-effort cleanup that preserves the original failure and never
32
- /// claims a rollback unless Revit confirms its final transaction status.</summary>
33
- public static string RollBackAndDescribe(Transaction transaction)
34
- {
35
- try
36
- {
37
- if (transaction.GetStatus() == TransactionStatus.Started)
38
- transaction.RollBack();
39
- var status = transaction.GetStatus();
40
- return status == TransactionStatus.RolledBack
41
- ? "The transaction was rolled back; no changes from this transaction were saved."
42
- : $"Transaction status is {status}; rollback is not confirmed. Inspect Revit before retrying.";
43
- }
44
- catch (Exception ex)
45
- {
46
- return $"Rollback could not be confirmed ({ex.GetType().Name}: {ex.Message}). Inspect Revit before retrying.";
47
- }
48
- }
28
+ return guard;
29
+ }
30
+
31
+ /// <summary>Best-effort cleanup that preserves the original failure and never
32
+ /// claims a rollback unless Revit confirms its final transaction status.</summary>
33
+ public static string RollBackAndDescribe(Transaction transaction)
34
+ {
35
+ try
36
+ {
37
+ if (transaction.GetStatus() == TransactionStatus.Started)
38
+ transaction.RollBack();
39
+ var status = transaction.GetStatus();
40
+ return status == TransactionStatus.RolledBack
41
+ ? "The transaction was rolled back; no changes from this transaction were saved."
42
+ : $"Transaction status is {status}; rollback is not confirmed. Inspect Revit before retrying.";
43
+ }
44
+ catch (Exception ex)
45
+ {
46
+ return $"Rollback could not be confirmed ({ex.GetType().Name}: {ex.Message}). Inspect Revit before retrying.";
47
+ }
48
+ }
49
49
 
50
50
  public FailureProcessingResult PreprocessFailures(FailuresAccessor accessor)
51
51
  {
@@ -9,8 +9,16 @@ namespace RevitBridge.Tools
9
9
  private const int MaxIds = 50;
10
10
 
11
11
  public string Name => "get_element_details";
12
+ public IReadOnlyList<string> DocumentKinds => DocumentKind.Both;
13
+ public IReadOnlyList<string> Keywords => new[] { "parameters", "properties", "inspect", "values", "material", "bounding box", "location" };
14
+ public IReadOnlyList<ToolLimit> Limits => new[]
15
+ {
16
+ new ToolLimit("Listing or filtering elements", "tool", "get_elements"),
17
+ new ToolLimit("Elements inside linked models", "tool", "get_linked_elements"),
18
+ new ToolLimit("Exact solid geometry or faces", "api", "Element.Geometry(Options)"),
19
+ };
12
20
  public string Label => "Get Element Details";
13
- public string Description => "Inspect one or more elements by id: parameter VALUES (name, internal value, formatted displayValue, storage type, display unit where applicable, read-only/shared flags), plus optional type parameters, location (point/curve), bounding box, and materials. Internal numeric values are Revit internal units (feet-based); displayValue is formatted in the document's display units and 'unit' names that display unit. The single home for reading parameter values — get_elements only returns identity fields.";
21
+ public string Description => "Inspect one or more elements by id: parameter VALUES (name, internal value, formatted displayValue, storage type, display unit where applicable, read-only/shared flags), plus optional type parameters, location (point/curve), bounding box, and materials. Internal numeric values are Revit internal units (feet-based); displayValue is formatted in the document's display units and 'unit' names that display unit. Use get_elements parameter_names to project a small set of values alongside a query.";
14
22
 
15
23
  public object ParametersSchema => new
16
24
  {
@@ -167,7 +175,25 @@ namespace RevitBridge.Tools
167
175
  return total;
168
176
  }
169
177
 
170
- private static Dictionary<string, object?> DescribeParameter(Document doc, Parameter parameter, string name, bool isType)
178
+ internal static IReadOnlyList<object> ProjectParameters(Document doc, Element element, IReadOnlyList<string> names, bool includeType)
179
+ {
180
+ var result = new List<object>();
181
+ void Project(Element source, bool isType)
182
+ {
183
+ foreach (string input in names.Distinct(StringComparer.OrdinalIgnoreCase))
184
+ {
185
+ // Shared policy: projections report every match and flag ambiguity instead of choosing.
186
+ var matches = ParameterResolver.FindAll(source, ParameterReference.Parse(input));
187
+ var values = matches.Select(p => DescribeParameter(doc, p, p.Definition?.Name ?? "", isType)).ToArray();
188
+ result.Add(new { requested = input, isType, found = values.Length > 0, ambiguous = values.Length > 1, matches = values });
189
+ }
190
+ }
191
+ Project(element, false);
192
+ if (includeType && doc.GetElement(element.GetTypeId()) is { } type) Project(type, true);
193
+ return result;
194
+ }
195
+
196
+ internal static Dictionary<string, object?> DescribeParameter(Document doc, Parameter parameter, string name, bool isType)
171
197
  {
172
198
  bool hasValue = parameter.HasValue;
173
199
  var row = new Dictionary<string, object?>
@@ -0,0 +1,82 @@
1
+ using System.Text.Json;
2
+ using Autodesk.Revit.DB;
3
+
4
+ namespace RevitBridge.Tools;
5
+
6
+ internal sealed class GetElementRelationships : ITool
7
+ {
8
+ public string Name => "get_element_relationships";
9
+ public IReadOnlyList<string> DocumentKinds => DocumentKind.Both;
10
+ public IReadOnlyList<string> Keywords => new[] { "host", "hosted", "dependents", "nested", "group members", "joined", "relationship", "family hierarchy" };
11
+ public IReadOnlyList<ToolLimit> Limits => new[]
12
+ {
13
+ new ToolLimit("A complete prediction of deletion effects", "tool", "delete_elements with preview"),
14
+ };
15
+ public string Label => "Get Element Relationships";
16
+ public string Tier => "advanced";
17
+ public string Description => "Inspect host-document element relationships: type, level, owning view, host, family parent/subcomponents, group or assembly membership, joined geometry and logical dependents. Dependents are not a complete deletion-impact prediction. Pagination applies independently to each relationship; links are not traversed.";
18
+ private static readonly string[] Kinds = { "type", "level", "owner_view", "host", "parent", "subcomponents", "group", "assembly", "members", "joined", "dependents" };
19
+ public object ParametersSchema => new
20
+ {
21
+ type = "object",
22
+ properties = new
23
+ {
24
+ element_id = new { type = "integer" },
25
+ relationships = new { type = "array", items = new { type = "string", @enum = Kinds } },
26
+ offset = new { type = "integer", minimum = 0 },
27
+ limit = new { type = "integer", minimum = 1, maximum = 200 },
28
+ },
29
+ required = new[] { "element_id" },
30
+ };
31
+
32
+ public object Execute(JsonElement args, ToolContext context)
33
+ {
34
+ var doc = context.Document ?? throw new NoActiveDocumentException();
35
+ var id = JsonArgs.GetLong(args, "element_id") ?? throw new ArgumentException("element_id is required.");
36
+ var element = doc.GetElement(new ElementId(id)) ?? throw new ArgumentException("element_id does not exist in the active document.");
37
+ var kinds = (IReadOnlyList<string>?)JsonArgs.GetStringArray(args, "relationships") ?? Kinds;
38
+ if (kinds.Any(kind => !Kinds.Contains(kind))) throw new ArgumentException("Unknown relationship kind.");
39
+ int offset = Math.Max(0, JsonArgs.GetInt(args, "offset", 0));
40
+ int limit = Math.Clamp(JsonArgs.GetInt(args, "limit", 100), 1, 200);
41
+ var result = new Dictionary<string, object?>();
42
+ foreach (var kind in kinds.Distinct())
43
+ {
44
+ IEnumerable<ElementId> ids = kind switch
45
+ {
46
+ "type" => One(element.GetTypeId()),
47
+ "level" => One(element.LevelId),
48
+ "owner_view" => One(element.OwnerViewId),
49
+ "host" => One((element as FamilyInstance)?.Host?.Id),
50
+ "parent" => One((element as FamilyInstance)?.SuperComponent?.Id),
51
+ "subcomponents" => element is FamilyInstance family ? family.GetSubComponentIds() : Array.Empty<ElementId>(),
52
+ "group" => One(element.GroupId),
53
+ "assembly" => One(element.AssemblyInstanceId),
54
+ "members" => element is Group group ? group.GetMemberIds()
55
+ : element is AssemblyInstance assembly ? assembly.GetMemberIds() : Array.Empty<ElementId>(),
56
+ "joined" => Joined(doc, element),
57
+ "dependents" => element.GetDependentElements(null),
58
+ _ => Array.Empty<ElementId>(),
59
+ };
60
+ var all = ids.Where(x => x != ElementId.InvalidElementId).DistinctBy(x => x.Value).OrderBy(x => x.Value).ToList();
61
+ var rows = all.Skip(offset).Take(limit).Select(x =>
62
+ {
63
+ var related = doc.GetElement(x);
64
+ return new { id = x.Value, name = related?.Name, category = related?.Category?.Name, unique_id = related?.UniqueId };
65
+ }).ToArray();
66
+ result[kind] = new { total_count = all.Count, returned_count = rows.Length, offset,
67
+ has_more = offset + rows.Length < all.Count,
68
+ next_offset = offset + rows.Length < all.Count ? (int?)(offset + rows.Length) : null, elements = rows };
69
+ }
70
+ return new { document_id = DocumentGuard.GetIdentity(doc), element_id = id, relationships = result };
71
+ }
72
+
73
+ private static IEnumerable<ElementId> One(ElementId? id)
74
+ => id == null || id == ElementId.InvalidElementId ? Array.Empty<ElementId>() : new[] { id };
75
+
76
+ private static IEnumerable<ElementId> Joined(Document doc, Element element)
77
+ {
78
+ // JoinGeometryUtils rejects family documents; surface that restriction explicitly.
79
+ if (doc.IsFamilyDocument) throw new ArgumentException("joined relationships require a project document. Request other relationship kinds for families.");
80
+ return JoinGeometryUtils.GetJoinedElements(doc, element);
81
+ }
82
+ }
@@ -9,6 +9,14 @@ namespace RevitBridge.Tools
9
9
  private const int MaxLimit = 1000;
10
10
 
11
11
  public string Name => "get_element_types";
12
+ public IReadOnlyList<string> DocumentKinds => DocumentKind.Both;
13
+ public IReadOnlyList<string> Keywords => new[] { "types", "family types", "family symbols", "unused types", "loaded families", "purge candidates" };
14
+ public IReadOnlyList<ToolLimit> Limits => new[]
15
+ {
16
+ new ToolLimit("Creating or duplicating types", "api", "ElementType.Duplicate"),
17
+ new ToolLimit("Loading families", "api", "Document.LoadFamily"),
18
+ new ToolLimit("The types of the family being edited in a family document", "api", DocumentKind.FamilyApi),
19
+ };
12
20
  public string Label => "Get Element Types";
13
21
  public string Description => "List element types / family symbols (wall types, door types, ...) for a category or class: id, name, familyName, category, isFamilySymbol, optional placed-instance count per type (answers 'used vs merely loaded'). Use the ids with get_elements type_id or for type assignment.";
14
22
 
@@ -20,8 +20,16 @@ namespace RevitBridge.Tools
20
20
  private const double Epsilon = 1e-6;
21
21
 
22
22
  public string Name => "get_elements";
23
+ public IReadOnlyList<string> DocumentKinds => DocumentKind.Both;
24
+ public IReadOnlyList<string> Keywords => new[] { "list", "find", "query", "count", "filter", "search elements", "how many" };
25
+ public IReadOnlyList<ToolLimit> Limits => new[]
26
+ {
27
+ new ToolLimit("Elements inside linked models", "tool", "get_linked_elements"),
28
+ new ToolLimit("Grouped counts or statistics over a whole scope", "tool", "summarize_elements"),
29
+ new ToolLimit("Spatial containment or intersection", "tool", "query_spatial_elements"),
30
+ };
23
31
  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.";
32
+ 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 and optional parameter_names projections; include_type_parameters also reads the requested values from each type. 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
33
 
26
34
  public object ParametersSchema => new
27
35
  {
@@ -73,11 +81,17 @@ namespace RevitBridge.Tools
73
81
  type_id = new { type = "integer", description = "Only instances of this element type id (see get_element_types)." },
74
82
  in_active_view = new { type = "boolean", description = "Only elements visible in the active view. Default false." },
75
83
  count_only = new { type = "boolean", description = "Return only total_count, no element rows. Default false." },
84
+ parameter_names = new
85
+ {
86
+ type = "array", maxItems = 20, items = new { type = "string" },
87
+ description = "Optional parameter projections for each returned element: display names, BuiltInParameter names or guid:<GUID>. Every matching parameter is reported, including missing/ambiguous matches. Numeric values use internal units with separate display values. Ignored by count_only.",
88
+ },
89
+ include_type_parameters = new { type = "boolean", description = "Also project requested parameters from each element's type. Default false." },
76
90
  fields = new
77
91
  {
78
92
  type = "array",
79
93
  items = new { type = "string", @enum = new[] { "id", "name", "category", "typeName", "levelId" } },
80
- description = "Identity fields per row; default all five. id is always included. Parameter values live in get_element_details.",
94
+ description = "Identity fields per row; default all five. id is always included. Use parameter_names for optional values alongside these fields.",
81
95
  },
82
96
  offset = new { type = "integer", description = "Pagination offset. Default 0." },
83
97
  limit = new { type = "integer", description = "Max rows to return (1-1000). Default 200." },
@@ -89,7 +103,7 @@ namespace RevitBridge.Tools
89
103
  public IReadOnlyList<string>? PromptGuidelines => new[]
90
104
  {
91
105
  "Use get_elements to list or count elements of any category or class (walls, doors, rooms, sheets, views, ...); use the returned ids with the other Revit tools.",
92
- "get_elements returns identity fields only; read parameter values with get_element_details.",
106
+ "get_elements can project up to 20 parameter_names per row; use get_element_details for full element inspection.",
93
107
  };
94
108
 
95
109
  public object? Execute(JsonElement args, ToolContext context)
@@ -104,6 +118,9 @@ namespace RevitBridge.Tools
104
118
  int offset = Math.Max(0, JsonArgs.GetInt(args, "offset", 0));
105
119
  int limit = Math.Clamp(JsonArgs.GetInt(args, "limit", DefaultLimit), 1, MaxLimit);
106
120
  var fields = ResolveFields(args);
121
+ var parameterNames = JsonArgs.GetStringArray(args, "parameter_names");
122
+ if (parameterNames?.Count > 20) throw new ArgumentException("parameter_names allows at most 20 parameter identities.");
123
+ bool includeTypeParameters = JsonArgs.GetBool(args, "include_type_parameters", false);
107
124
 
108
125
  ElementId? categoryId = string.IsNullOrWhiteSpace(categoryInput) ? null : CategoryResolver.Resolve(doc, categoryInput);
109
126
  Type? elementClass = string.IsNullOrWhiteSpace(classInput) ? null : ElementClassResolver.Resolve(classInput);
@@ -136,7 +153,7 @@ namespace RevitBridge.Tools
136
153
  return collector;
137
154
  }
138
155
 
139
- var (quickFilter, postPredicate, filterWarnings) = BuildParameterFilter(doc, args, CreateBaseCollector);
156
+ var (quickFilter, postPredicate, filterWarnings, stickyWarnings) = BuildParameterFilter(doc, args, CreateBaseCollector);
140
157
 
141
158
  FilteredElementCollector CreateCollector()
142
159
  {
@@ -149,7 +166,7 @@ namespace RevitBridge.Tools
149
166
  if (countOnly && postPredicate is null)
150
167
  {
151
168
  int quickCount = CreateCollector().GetElementCount();
152
- return CountResult(scope, quickCount, quickCount > 0 ? Array.Empty<string>() : filterWarnings);
169
+ return CountResult(scope, quickCount, quickCount > 0 ? stickyWarnings : filterWarnings);
153
170
  }
154
171
 
155
172
  var rows = countOnly ? null : new List<Dictionary<string, object?>>(Math.Min(limit, 256));
@@ -159,15 +176,21 @@ namespace RevitBridge.Tools
159
176
  if (postPredicate != null && !postPredicate(element))
160
177
  continue;
161
178
  if (rows != null && total >= offset && rows.Count < limit)
162
- rows.Add(ElementIdentity.Build(doc, element, fields));
179
+ {
180
+ var row = ElementIdentity.Build(doc, element, fields);
181
+ if (parameterNames is { Count: > 0 })
182
+ row["parameters"] = GetElementDetails.ProjectParameters(doc, element, parameterNames, includeTypeParameters);
183
+ rows.Add(row);
184
+ }
163
185
  total++;
164
186
  }
165
187
 
166
- // A found match proves the query worked: the not-found-on-probes warning is
188
+ // A found match proves most rules worked: a not-found-on-probes warning is
167
189
  // load-bearing only next to a zero, where it distinguishes 'unknown parameter
168
- // name' from 'no matching elements'. Beside real matches it is just noise.
190
+ // name' from 'no matching elements'. is_empty warnings stay, because a missing
191
+ // parameter can itself produce the matches (inv:missing-not-silent).
169
192
  if (total > 0)
170
- filterWarnings = Array.Empty<string>();
193
+ filterWarnings = stickyWarnings;
171
194
 
172
195
  if (rows is null)
173
196
  return CountResult(scope, total, filterWarnings);
@@ -272,17 +295,18 @@ namespace RevitBridge.Tools
272
295
  public required RuleOp Op { get; init; }
273
296
  public JsonElement Value { get; init; }
274
297
  public string? Unit { get; init; }
298
+ public required ParameterReference Reference { get; init; }
275
299
  public BuiltInParameter? BuiltIn { get; init; }
276
300
  public Guid? SharedGuid { get; init; }
277
301
  public Regex? CompiledRegex { get; init; }
278
302
  public FilterRule? QuickRule { get; set; }
279
303
  }
280
304
 
281
- private static (ElementFilter? Quick, Func<Element, bool>? Post, IReadOnlyList<string> Warnings) BuildParameterFilter(
305
+ private static (ElementFilter? Quick, Func<Element, bool>? Post, IReadOnlyList<string> Warnings, IReadOnlyList<string> StickyWarnings) BuildParameterFilter(
282
306
  Document doc, JsonElement args, Func<FilteredElementCollector> createBaseCollector)
283
307
  {
284
308
  if (args.ValueKind != JsonValueKind.Object || !args.TryGetProperty("filter", out var filterElement) || filterElement.ValueKind != JsonValueKind.Object)
285
- return (null, null, Array.Empty<string>());
309
+ return (null, null, Array.Empty<string>(), Array.Empty<string>());
286
310
 
287
311
  bool matchAny = string.Equals(JsonArgs.GetString(filterElement, "match"), "any", StringComparison.OrdinalIgnoreCase);
288
312
  if (!filterElement.TryGetProperty("rules", out var rulesElement) || rulesElement.ValueKind != JsonValueKind.Array)
@@ -290,7 +314,7 @@ namespace RevitBridge.Tools
290
314
 
291
315
  var rules = rulesElement.EnumerateArray().Select(ParseRule).ToList();
292
316
  if (rules.Count == 0)
293
- return (null, null, Array.Empty<string>());
317
+ return (null, null, Array.Empty<string>(), Array.Empty<string>());
294
318
 
295
319
  // Probe a few in-scope elements for missing-name diagnostics and to find
296
320
  // storage/spec exemplars for explicit parameter identities. A sample must
@@ -311,13 +335,25 @@ namespace RevitBridge.Tools
311
335
  // A warning (not an error: the parameter may exist past the probe window,
312
336
  // and is_empty legitimately matches missing parameters) makes the zero honest.
313
337
  var warnings = new List<string>();
338
+ var stickyWarnings = new List<string>();
314
339
  if (probes.Count > 0)
315
340
  {
316
341
  foreach (var rule in rules)
317
342
  {
318
343
  if (rule.BuiltIn != null || rule.SharedGuid != null)
319
344
  continue;
320
- if (probes.All(probe => FindParameter(probe, rule) is null))
345
+ if (probes.All(probe => FindParameter(probe, rule) is null) && rule.Op == RuleOp.IsEmpty)
346
+ {
347
+ // inv:missing-not-silent: is_empty matches elements that lack the parameter,
348
+ // so a misspelled or localized name matches the whole scope. Keep this warning.
349
+ string sticky = $"Filter parameter '{rule.ParamInput}' was not found on any of the {probes.Count} probed element(s) in scope. "
350
+ + "is_empty also matches elements that do not have the parameter at all, so these results may be elements without it "
351
+ + "rather than elements with an empty value. Check the name, or use the language-independent BuiltInParameter name or guid:<GUID> "
352
+ + "(get_element_details reports builtInParameter per parameter).";
353
+ warnings.Add(sticky);
354
+ stickyWarnings.Add(sticky);
355
+ }
356
+ else if (probes.All(probe => FindParameter(probe, rule) is null))
321
357
  warnings.Add(
322
358
  $"Filter parameter '{rule.ParamInput}' was not found on any of the {probes.Count} probed element(s) in scope, "
323
359
  + "so a 0-match result may mean 'unknown parameter name', not 'no matching elements'. Display names are "
@@ -349,10 +385,10 @@ namespace RevitBridge.Tools
349
385
  // OR with any post-scan rule means everything must be post-scanned: a
350
386
  // collector-level OR filter would wrongly exclude post-rule-only matches.
351
387
  if (postRules.Count > 0)
352
- return (null, element => rules.Any(rule => EvaluatePost(doc, element, rule)), warnings);
388
+ return (null, element => rules.Any(rule => EvaluatePost(doc, element, rule)), warnings, stickyWarnings);
353
389
 
354
390
  var filters = quickRules.Select(rule => (ElementFilter)new ElementParameterFilter(rule.QuickRule!)).ToList();
355
- return (filters.Count == 1 ? filters[0] : new LogicalOrFilter(filters), null, warnings);
391
+ return (filters.Count == 1 ? filters[0] : new LogicalOrFilter(filters), null, warnings, stickyWarnings);
356
392
  }
357
393
 
358
394
  ElementFilter? quick = quickRules.Count > 0
@@ -361,7 +397,7 @@ namespace RevitBridge.Tools
361
397
  Func<Element, bool>? post = postRules.Count > 0
362
398
  ? element => postRules.All(rule => EvaluatePost(doc, element, rule))
363
399
  : null;
364
- return (quick, post, warnings);
400
+ return (quick, post, warnings, stickyWarnings);
365
401
  }
366
402
 
367
403
  private static Rule ParseRule(JsonElement element)
@@ -393,26 +429,7 @@ namespace RevitBridge.Tools
393
429
  if (!hasValue && op is not (RuleOp.IsEmpty or RuleOp.IsNotEmpty))
394
430
  throw new ArgumentException($"Filter rule on '{param}' with op {opText} needs a value.");
395
431
 
396
- BuiltInParameter? builtIn = null;
397
- Guid? sharedGuid = null;
398
- if (param.StartsWith("guid:", StringComparison.OrdinalIgnoreCase))
399
- {
400
- sharedGuid = Guid.TryParse(param["guid:".Length..], out var guid)
401
- ? guid
402
- : throw new ArgumentException($"Invalid shared parameter guid: {param}");
403
- }
404
- else
405
- {
406
- string enumName = param.StartsWith("BuiltInParameter.", StringComparison.OrdinalIgnoreCase)
407
- ? param["BuiltInParameter.".Length..]
408
- : param;
409
- // BuiltInParameter names are SHOUTY_SNAKE_CASE; require an underscore or
410
- // all-caps so plain display names like "Comments" never collide.
411
- bool looksLikeEnumName = enumName.Length > 0 && char.IsLetter(enumName[0]) && !enumName.Contains(' ')
412
- && (enumName.Contains('_') || enumName.All(c => !char.IsLetter(c) || char.IsUpper(c)));
413
- if (looksLikeEnumName && Enum.TryParse<BuiltInParameter>(enumName, true, out var parsed) && parsed != BuiltInParameter.INVALID)
414
- builtIn = parsed;
415
- }
432
+ var reference = ParameterReference.Parse(param);
416
433
 
417
434
  Regex? regex = null;
418
435
  if (op == RuleOp.Regex)
@@ -434,29 +451,15 @@ namespace RevitBridge.Tools
434
451
  Op = op,
435
452
  Value = hasValue ? value : default,
436
453
  Unit = JsonArgs.GetString(element, "unit"),
437
- BuiltIn = builtIn,
438
- SharedGuid = sharedGuid,
454
+ Reference = reference,
455
+ BuiltIn = reference.BuiltIn,
456
+ SharedGuid = reference.SharedGuid,
439
457
  CompiledRegex = regex,
440
458
  };
441
459
  }
442
460
 
443
- private static Parameter? FindParameter(Element element, Rule rule)
444
- {
445
- if (rule.BuiltIn is { } builtIn)
446
- return element.get_Parameter(builtIn);
447
- if (rule.SharedGuid is { } guid)
448
- return element.get_Parameter(guid);
449
-
450
- var direct = element.LookupParameter(rule.ParamInput);
451
- if (direct != null)
452
- return direct;
453
- foreach (Parameter parameter in element.Parameters)
454
- {
455
- if (string.Equals(parameter.Definition?.Name, rule.ParamInput, StringComparison.OrdinalIgnoreCase))
456
- return parameter;
457
- }
458
- return null;
459
- }
461
+ /// <summary>Shared resolution policy: missing is null; an ambiguous display name throws with candidates.</summary>
462
+ private static Parameter? FindParameter(Element element, Rule rule) => ParameterResolver.FindSingle(element, rule.Reference);
460
463
 
461
464
  /// <summary>Builds a collector-level FilterRule, or null when the op/storage pair must post-scan.</summary>
462
465
  private static FilterRule? TryBuildQuickRule(Document doc, Rule rule, Parameter exemplar)