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.
- package/AGENTS.md +167 -0
- package/CHANGELOG.md +114 -42
- package/README.md +598 -138
- package/bin/pi-revit.js +9 -9
- package/docs/architecture.md +271 -0
- package/docs/evaluation.md +434 -0
- package/docs/invariants.json +147 -0
- package/extensions/pi-revit/completion-monitor.ts +55 -0
- package/extensions/pi-revit/contracts.ts +146 -0
- package/extensions/pi-revit/discovery.ts +93 -0
- package/extensions/pi-revit/index.ts +231 -85
- package/extensions/pi-revit/instance-router.ts +86 -0
- package/extensions/pi-revit/platform-prompt.ts +40 -0
- package/extensions/pi-revit/scope-monitor.ts +114 -0
- package/extensions/pi-revit/script-library.ts +146 -0
- package/extensions/pi-revit/tool-catalog.ts +166 -0
- package/extensions/pi-revit/tool-documentation.ts +72 -0
- package/extensions/pi-revit/tool-schema.ts +8 -0
- package/package.json +65 -59
- package/scripts/build.ps1 +9 -9
- package/scripts/check-sdk.ps1 +66 -66
- package/scripts/check-tool-documentation.mjs +287 -0
- package/scripts/deploy.ps1 +16 -16
- package/scripts/generate-contracts.mjs +80 -0
- package/scripts/lib/platform.mjs +226 -0
- package/scripts/test-extension.mjs +15 -0
- package/skills/pi-revit/SKILL.md +39 -63
- package/skills/pi-revit/contracts.generated.json +3524 -0
- package/skills/pi-revit/references/execution-rules.md +41 -0
- package/skills/pi-revit/references/model-audit-export.md +40 -0
- package/skills/pi-revit/references/operation-recovery.md +33 -0
- package/skills/pi-revit/references/room-documentation.md +39 -0
- package/skills/pi-revit/references/tool-index.md +89 -0
- package/skills/pi-revit/references/tools/capture_view.md +62 -0
- package/skills/pi-revit/references/tools/change_element_types.md +65 -0
- package/skills/pi-revit/references/tools/create_tags.md +85 -0
- package/skills/pi-revit/references/tools/delete_elements.md +66 -0
- package/skills/pi-revit/references/tools/execute_csharp.md +81 -0
- package/skills/pi-revit/references/tools/export_documents.md +75 -0
- package/skills/pi-revit/references/tools/find_revit_tools.md +96 -0
- package/skills/pi-revit/references/tools/get_element_details.md +66 -0
- package/skills/pi-revit/references/tools/get_element_relationships.md +61 -0
- package/skills/pi-revit/references/tools/get_element_types.md +67 -0
- package/skills/pi-revit/references/tools/get_elements.md +87 -0
- package/skills/pi-revit/references/tools/get_linked_elements.md +79 -0
- package/skills/pi-revit/references/tools/get_linked_models.md +57 -0
- package/skills/pi-revit/references/tools/get_model_coordinates.md +64 -0
- package/skills/pi-revit/references/tools/get_model_health.md +53 -0
- package/skills/pi-revit/references/tools/get_model_overview.md +57 -0
- package/skills/pi-revit/references/tools/get_revit_operation.md +54 -0
- package/skills/pi-revit/references/tools/get_schedule_fields.md +62 -0
- package/skills/pi-revit/references/tools/get_schedules.md +71 -0
- package/skills/pi-revit/references/tools/manage_element_sets.md +92 -0
- package/skills/pi-revit/references/tools/manage_revit_instances.md +63 -0
- package/skills/pi-revit/references/tools/manage_revit_scripts.md +109 -0
- package/skills/pi-revit/references/tools/manage_schedules.md +90 -0
- package/skills/pi-revit/references/tools/manage_selection.md +66 -0
- package/skills/pi-revit/references/tools/manage_sheet_placements.md +82 -0
- package/skills/pi-revit/references/tools/manage_sheets.md +71 -0
- package/skills/pi-revit/references/tools/manage_views.md +95 -0
- package/skills/pi-revit/references/tools/measure_geometry.md +71 -0
- package/skills/pi-revit/references/tools/open_view.md +59 -0
- package/skills/pi-revit/references/tools/ping.md +41 -0
- package/skills/pi-revit/references/tools/query_spatial_elements.md +74 -0
- package/skills/pi-revit/references/tools/read_revit_result.md +53 -0
- package/skills/pi-revit/references/tools/search_api_docs.md +65 -0
- package/skills/pi-revit/references/tools/set_parameters.md +75 -0
- package/skills/pi-revit/references/tools/summarize_elements.md +64 -0
- package/skills/pi-revit/references/tools/transform_elements.md +79 -0
- package/skills/pi-revit/references/visual-verification.md +36 -0
- package/skills/pi-revit/tool-manifest.json +338 -0
- package/src/Revit/BridgeServer.cs +75 -19
- package/src/Revit/OperationStore.cs +178 -0
- package/src/Revit/ToolRegistry.cs +61 -8
- package/src/Revit/Tools/CaptureView.cs +9 -0
- package/src/Revit/Tools/ChangeElementTypes.cs +74 -0
- package/src/Revit/Tools/ChangeSet.cs +39 -0
- package/src/Revit/Tools/CreateTags.cs +107 -0
- package/src/Revit/Tools/DeleteElements.cs +53 -0
- package/src/Revit/Tools/DocumentGuard.cs +12 -2
- package/src/Revit/Tools/ElementNames.cs +103 -0
- package/src/Revit/Tools/ElementQueryScope.cs +27 -0
- package/src/Revit/Tools/ElementTraits.cs +53 -0
- package/src/Revit/Tools/ExecuteCsharp.cs +26 -7
- package/src/Revit/Tools/ExportDocuments.cs +9 -0
- package/src/Revit/Tools/FailureGuard.cs +26 -26
- package/src/Revit/Tools/GetElementDetails.cs +28 -2
- package/src/Revit/Tools/GetElementRelationships.cs +82 -0
- package/src/Revit/Tools/GetElementTypes.cs +8 -0
- package/src/Revit/Tools/GetElements.cs +58 -55
- package/src/Revit/Tools/GetLinkedElements.cs +89 -0
- package/src/Revit/Tools/GetLinkedModels.cs +73 -0
- package/src/Revit/Tools/GetModelCoordinates.cs +56 -0
- package/src/Revit/Tools/GetModelHealth.cs +7 -0
- package/src/Revit/Tools/GetModelOverview.cs +187 -160
- package/src/Revit/Tools/GetScheduleFields.cs +44 -0
- package/src/Revit/Tools/GetSchedules.cs +96 -0
- package/src/Revit/Tools/InheritedState.Summary.cs +57 -0
- package/src/Revit/Tools/InheritedState.cs +144 -0
- package/src/Revit/Tools/ManageElementSets.cs +114 -0
- package/src/Revit/Tools/ManageSchedules.cs +174 -0
- package/src/Revit/Tools/ManageSelection.cs +9 -0
- package/src/Revit/Tools/ManageSheetPlacements.cs +113 -0
- package/src/Revit/Tools/ManageSheets.cs +72 -0
- package/src/Revit/Tools/ManageViews.cs +115 -0
- package/src/Revit/Tools/MeasureGeometry.cs +60 -0
- package/src/Revit/Tools/ModelChanges.cs +154 -0
- package/src/Revit/Tools/ModelEditBatch.cs +105 -0
- package/src/Revit/Tools/ModelEditInputs.cs +49 -0
- package/src/Revit/Tools/OpenView.cs +8 -0
- package/src/Revit/Tools/ParameterResolver.cs +94 -0
- package/src/Revit/Tools/QuerySpatialElements.cs +70 -0
- package/src/Revit/Tools/SearchApiDocs.cs +72 -4
- package/src/Revit/Tools/SetParameters.cs +50 -119
- package/src/Revit/Tools/SpatialBounds.cs +30 -0
- package/src/Revit/Tools/SummarizeElements.cs +94 -0
- package/src/Revit/Tools/ToolContract.cs +48 -0
- package/src/Revit/Tools/ToolSupport.cs +4 -0
- package/src/Revit/Tools/TransformElements.cs +73 -0
- 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
|
|
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)
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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 ?
|
|
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
|
-
|
|
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
|
|
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'.
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
438
|
-
|
|
454
|
+
Reference = reference,
|
|
455
|
+
BuiltIn = reference.BuiltIn,
|
|
456
|
+
SharedGuid = reference.SharedGuid,
|
|
439
457
|
CompiledRegex = regex,
|
|
440
458
|
};
|
|
441
459
|
}
|
|
442
460
|
|
|
443
|
-
|
|
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)
|