pi-revit 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +167 -0
- package/CHANGELOG.md +44 -0
- package/README.md +644 -553
- 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 +342 -255
- package/extensions/pi-revit/instance-router.ts +86 -86
- 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 +144 -144
- package/extensions/pi-revit/tool-catalog.ts +113 -14
- package/extensions/pi-revit/tool-documentation.ts +72 -0
- package/extensions/pi-revit/tool-schema.ts +8 -0
- package/package.json +8 -2
- 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 +30 -218
- 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 +38 -27
- package/skills/pi-revit/references/operation-recovery.md +33 -0
- package/skills/pi-revit/references/room-documentation.md +37 -26
- 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 +93 -87
- package/src/Revit/OperationStore.cs +178 -178
- package/src/Revit/ToolRegistry.cs +88 -57
- package/src/Revit/Tools/CaptureView.cs +10 -2
- package/src/Revit/Tools/ChangeElementTypes.cs +74 -60
- package/src/Revit/Tools/ChangeSet.cs +39 -0
- package/src/Revit/Tools/CreateTags.cs +107 -95
- package/src/Revit/Tools/DeleteElements.cs +53 -44
- package/src/Revit/Tools/DocumentGuard.cs +74 -64
- package/src/Revit/Tools/ElementNames.cs +103 -0
- package/src/Revit/Tools/ElementQueryScope.cs +27 -27
- package/src/Revit/Tools/ElementTraits.cs +53 -0
- package/src/Revit/Tools/ExecuteCsharp.cs +54 -45
- package/src/Revit/Tools/ExportDocuments.cs +129 -121
- package/src/Revit/Tools/GetElementDetails.cs +37 -41
- package/src/Revit/Tools/GetElementRelationships.cs +82 -76
- package/src/Revit/Tools/GetElementTypes.cs +8 -0
- package/src/Revit/Tools/GetElements.cs +75 -86
- package/src/Revit/Tools/GetLinkedElements.cs +89 -82
- package/src/Revit/Tools/GetLinkedModels.cs +73 -66
- package/src/Revit/Tools/GetModelCoordinates.cs +56 -49
- package/src/Revit/Tools/GetModelHealth.cs +7 -0
- package/src/Revit/Tools/GetModelOverview.cs +185 -158
- package/src/Revit/Tools/GetScheduleFields.cs +44 -37
- package/src/Revit/Tools/GetSchedules.cs +96 -89
- package/src/Revit/Tools/InheritedState.Summary.cs +57 -0
- package/src/Revit/Tools/InheritedState.cs +144 -0
- package/src/Revit/Tools/ManageElementSets.cs +114 -106
- package/src/Revit/Tools/ManageSchedules.cs +174 -164
- package/src/Revit/Tools/ManageSelection.cs +45 -37
- package/src/Revit/Tools/ManageSheetPlacements.cs +113 -97
- package/src/Revit/Tools/ManageSheets.cs +72 -63
- package/src/Revit/Tools/ManageViews.cs +115 -100
- package/src/Revit/Tools/MeasureGeometry.cs +60 -54
- package/src/Revit/Tools/ModelChanges.cs +154 -0
- package/src/Revit/Tools/ModelEditBatch.cs +105 -102
- package/src/Revit/Tools/ModelEditInputs.cs +49 -49
- package/src/Revit/Tools/OpenView.cs +9 -2
- package/src/Revit/Tools/ParameterResolver.cs +94 -0
- package/src/Revit/Tools/QuerySpatialElements.cs +70 -63
- package/src/Revit/Tools/SearchApiDocs.cs +72 -4
- package/src/Revit/Tools/SetParameters.cs +60 -79
- package/src/Revit/Tools/SpatialBounds.cs +30 -30
- package/src/Revit/Tools/SummarizeElements.cs +94 -87
- package/src/Revit/Tools/ToolContract.cs +48 -0
- package/src/Revit/Tools/ToolSupport.cs +5 -1
- package/src/Revit/Tools/TransformElements.cs +72 -57
- package/workspace/AGENTS.md +26 -20
|
@@ -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 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.";
|
|
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
|
{
|
|
@@ -72,13 +80,13 @@ namespace RevitBridge.Tools
|
|
|
72
80
|
level = new { type = "string", description = "Only elements hosted on this level (level name or id)." },
|
|
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
|
-
count_only = new { type = "boolean", description = "Return only total_count, no element rows. Default false." },
|
|
76
|
-
parameter_names = new
|
|
77
|
-
{
|
|
78
|
-
type = "array", maxItems = 20, items = new { type = "string" },
|
|
79
|
-
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.",
|
|
80
|
-
},
|
|
81
|
-
include_type_parameters = new { type = "boolean", description = "Also project requested parameters from each element's type. Default false." },
|
|
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." },
|
|
82
90
|
fields = new
|
|
83
91
|
{
|
|
84
92
|
type = "array",
|
|
@@ -109,10 +117,10 @@ namespace RevitBridge.Tools
|
|
|
109
117
|
|| string.Equals(JsonArgs.GetString(args, "mode"), "count", StringComparison.OrdinalIgnoreCase);
|
|
110
118
|
int offset = Math.Max(0, JsonArgs.GetInt(args, "offset", 0));
|
|
111
119
|
int limit = Math.Clamp(JsonArgs.GetInt(args, "limit", DefaultLimit), 1, MaxLimit);
|
|
112
|
-
var fields = ResolveFields(args);
|
|
113
|
-
var parameterNames = JsonArgs.GetStringArray(args, "parameter_names");
|
|
114
|
-
if (parameterNames?.Count > 20) throw new ArgumentException("parameter_names allows at most 20 parameter identities.");
|
|
115
|
-
bool includeTypeParameters = JsonArgs.GetBool(args, "include_type_parameters", false);
|
|
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);
|
|
116
124
|
|
|
117
125
|
ElementId? categoryId = string.IsNullOrWhiteSpace(categoryInput) ? null : CategoryResolver.Resolve(doc, categoryInput);
|
|
118
126
|
Type? elementClass = string.IsNullOrWhiteSpace(classInput) ? null : ElementClassResolver.Resolve(classInput);
|
|
@@ -145,7 +153,7 @@ namespace RevitBridge.Tools
|
|
|
145
153
|
return collector;
|
|
146
154
|
}
|
|
147
155
|
|
|
148
|
-
var (quickFilter, postPredicate, filterWarnings) = BuildParameterFilter(doc, args, CreateBaseCollector);
|
|
156
|
+
var (quickFilter, postPredicate, filterWarnings, stickyWarnings) = BuildParameterFilter(doc, args, CreateBaseCollector);
|
|
149
157
|
|
|
150
158
|
FilteredElementCollector CreateCollector()
|
|
151
159
|
{
|
|
@@ -158,7 +166,7 @@ namespace RevitBridge.Tools
|
|
|
158
166
|
if (countOnly && postPredicate is null)
|
|
159
167
|
{
|
|
160
168
|
int quickCount = CreateCollector().GetElementCount();
|
|
161
|
-
return CountResult(scope, quickCount, quickCount > 0 ?
|
|
169
|
+
return CountResult(scope, quickCount, quickCount > 0 ? stickyWarnings : filterWarnings);
|
|
162
170
|
}
|
|
163
171
|
|
|
164
172
|
var rows = countOnly ? null : new List<Dictionary<string, object?>>(Math.Min(limit, 256));
|
|
@@ -167,21 +175,22 @@ namespace RevitBridge.Tools
|
|
|
167
175
|
{
|
|
168
176
|
if (postPredicate != null && !postPredicate(element))
|
|
169
177
|
continue;
|
|
170
|
-
if (rows != null && total >= offset && rows.Count < limit)
|
|
171
|
-
{
|
|
172
|
-
var row = ElementIdentity.Build(doc, element, fields);
|
|
173
|
-
if (parameterNames is { Count: > 0 })
|
|
174
|
-
row["parameters"] = GetElementDetails.ProjectParameters(doc, element, parameterNames, includeTypeParameters);
|
|
175
|
-
rows.Add(row);
|
|
176
|
-
}
|
|
178
|
+
if (rows != null && total >= offset && rows.Count < limit)
|
|
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
|
+
}
|
|
177
185
|
total++;
|
|
178
186
|
}
|
|
179
187
|
|
|
180
|
-
// A found match proves
|
|
188
|
+
// A found match proves most rules worked: a not-found-on-probes warning is
|
|
181
189
|
// load-bearing only next to a zero, where it distinguishes 'unknown parameter
|
|
182
|
-
// 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).
|
|
183
192
|
if (total > 0)
|
|
184
|
-
filterWarnings =
|
|
193
|
+
filterWarnings = stickyWarnings;
|
|
185
194
|
|
|
186
195
|
if (rows is null)
|
|
187
196
|
return CountResult(scope, total, filterWarnings);
|
|
@@ -286,17 +295,18 @@ namespace RevitBridge.Tools
|
|
|
286
295
|
public required RuleOp Op { get; init; }
|
|
287
296
|
public JsonElement Value { get; init; }
|
|
288
297
|
public string? Unit { get; init; }
|
|
298
|
+
public required ParameterReference Reference { get; init; }
|
|
289
299
|
public BuiltInParameter? BuiltIn { get; init; }
|
|
290
300
|
public Guid? SharedGuid { get; init; }
|
|
291
301
|
public Regex? CompiledRegex { get; init; }
|
|
292
302
|
public FilterRule? QuickRule { get; set; }
|
|
293
303
|
}
|
|
294
304
|
|
|
295
|
-
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(
|
|
296
306
|
Document doc, JsonElement args, Func<FilteredElementCollector> createBaseCollector)
|
|
297
307
|
{
|
|
298
308
|
if (args.ValueKind != JsonValueKind.Object || !args.TryGetProperty("filter", out var filterElement) || filterElement.ValueKind != JsonValueKind.Object)
|
|
299
|
-
return (null, null, Array.Empty<string>());
|
|
309
|
+
return (null, null, Array.Empty<string>(), Array.Empty<string>());
|
|
300
310
|
|
|
301
311
|
bool matchAny = string.Equals(JsonArgs.GetString(filterElement, "match"), "any", StringComparison.OrdinalIgnoreCase);
|
|
302
312
|
if (!filterElement.TryGetProperty("rules", out var rulesElement) || rulesElement.ValueKind != JsonValueKind.Array)
|
|
@@ -304,11 +314,11 @@ namespace RevitBridge.Tools
|
|
|
304
314
|
|
|
305
315
|
var rules = rulesElement.EnumerateArray().Select(ParseRule).ToList();
|
|
306
316
|
if (rules.Count == 0)
|
|
307
|
-
return (null, null, Array.Empty<string>());
|
|
317
|
+
return (null, null, Array.Empty<string>(), Array.Empty<string>());
|
|
308
318
|
|
|
309
|
-
// Probe a few in-scope elements for missing-name diagnostics and to find
|
|
310
|
-
// storage/spec exemplars for explicit parameter identities. A sample must
|
|
311
|
-
// never turn a display name into an assumed globally uniform identity.
|
|
319
|
+
// Probe a few in-scope elements for missing-name diagnostics and to find
|
|
320
|
+
// storage/spec exemplars for explicit parameter identities. A sample must
|
|
321
|
+
// never turn a display name into an assumed globally uniform identity.
|
|
312
322
|
var probes = new List<Element>(ProbeSize);
|
|
313
323
|
foreach (Element element in createBaseCollector())
|
|
314
324
|
{
|
|
@@ -325,13 +335,25 @@ namespace RevitBridge.Tools
|
|
|
325
335
|
// A warning (not an error: the parameter may exist past the probe window,
|
|
326
336
|
// and is_empty legitimately matches missing parameters) makes the zero honest.
|
|
327
337
|
var warnings = new List<string>();
|
|
338
|
+
var stickyWarnings = new List<string>();
|
|
328
339
|
if (probes.Count > 0)
|
|
329
340
|
{
|
|
330
341
|
foreach (var rule in rules)
|
|
331
342
|
{
|
|
332
343
|
if (rule.BuiltIn != null || rule.SharedGuid != null)
|
|
333
344
|
continue;
|
|
334
|
-
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))
|
|
335
357
|
warnings.Add(
|
|
336
358
|
$"Filter parameter '{rule.ParamInput}' was not found on any of the {probes.Count} probed element(s) in scope, "
|
|
337
359
|
+ "so a 0-match result may mean 'unknown parameter name', not 'no matching elements'. Display names are "
|
|
@@ -340,19 +362,19 @@ namespace RevitBridge.Tools
|
|
|
340
362
|
}
|
|
341
363
|
}
|
|
342
364
|
|
|
343
|
-
foreach (var rule in rules)
|
|
344
|
-
{
|
|
345
|
-
if (rule.Op is RuleOp.Regex or RuleOp.IsEmpty or RuleOp.IsNotEmpty)
|
|
346
|
-
continue; // always post-scan (missing-parameter semantics)
|
|
347
|
-
// Category/class scope and unanimous samples do not establish a
|
|
348
|
-
// uniform parameter identity across later families. Resolve display
|
|
349
|
-
// names per element; only explicit identities may use a quick rule.
|
|
350
|
-
if (rule.BuiltIn is null && rule.SharedGuid is null)
|
|
351
|
-
continue;
|
|
352
|
-
var found = probes.Select(probe => FindParameter(probe, rule)).Where(parameter => parameter != null).ToList();
|
|
353
|
-
if (found.Count == 0)
|
|
354
|
-
continue;
|
|
355
|
-
rule.QuickRule = TryBuildQuickRule(doc, rule, found[0]!);
|
|
365
|
+
foreach (var rule in rules)
|
|
366
|
+
{
|
|
367
|
+
if (rule.Op is RuleOp.Regex or RuleOp.IsEmpty or RuleOp.IsNotEmpty)
|
|
368
|
+
continue; // always post-scan (missing-parameter semantics)
|
|
369
|
+
// Category/class scope and unanimous samples do not establish a
|
|
370
|
+
// uniform parameter identity across later families. Resolve display
|
|
371
|
+
// names per element; only explicit identities may use a quick rule.
|
|
372
|
+
if (rule.BuiltIn is null && rule.SharedGuid is null)
|
|
373
|
+
continue;
|
|
374
|
+
var found = probes.Select(probe => FindParameter(probe, rule)).Where(parameter => parameter != null).ToList();
|
|
375
|
+
if (found.Count == 0)
|
|
376
|
+
continue;
|
|
377
|
+
rule.QuickRule = TryBuildQuickRule(doc, rule, found[0]!);
|
|
356
378
|
}
|
|
357
379
|
|
|
358
380
|
var quickRules = rules.Where(rule => rule.QuickRule != null).ToList();
|
|
@@ -363,10 +385,10 @@ namespace RevitBridge.Tools
|
|
|
363
385
|
// OR with any post-scan rule means everything must be post-scanned: a
|
|
364
386
|
// collector-level OR filter would wrongly exclude post-rule-only matches.
|
|
365
387
|
if (postRules.Count > 0)
|
|
366
|
-
return (null, element => rules.Any(rule => EvaluatePost(doc, element, rule)), warnings);
|
|
388
|
+
return (null, element => rules.Any(rule => EvaluatePost(doc, element, rule)), warnings, stickyWarnings);
|
|
367
389
|
|
|
368
390
|
var filters = quickRules.Select(rule => (ElementFilter)new ElementParameterFilter(rule.QuickRule!)).ToList();
|
|
369
|
-
return (filters.Count == 1 ? filters[0] : new LogicalOrFilter(filters), null, warnings);
|
|
391
|
+
return (filters.Count == 1 ? filters[0] : new LogicalOrFilter(filters), null, warnings, stickyWarnings);
|
|
370
392
|
}
|
|
371
393
|
|
|
372
394
|
ElementFilter? quick = quickRules.Count > 0
|
|
@@ -375,7 +397,7 @@ namespace RevitBridge.Tools
|
|
|
375
397
|
Func<Element, bool>? post = postRules.Count > 0
|
|
376
398
|
? element => postRules.All(rule => EvaluatePost(doc, element, rule))
|
|
377
399
|
: null;
|
|
378
|
-
return (quick, post, warnings);
|
|
400
|
+
return (quick, post, warnings, stickyWarnings);
|
|
379
401
|
}
|
|
380
402
|
|
|
381
403
|
private static Rule ParseRule(JsonElement element)
|
|
@@ -407,26 +429,7 @@ namespace RevitBridge.Tools
|
|
|
407
429
|
if (!hasValue && op is not (RuleOp.IsEmpty or RuleOp.IsNotEmpty))
|
|
408
430
|
throw new ArgumentException($"Filter rule on '{param}' with op {opText} needs a value.");
|
|
409
431
|
|
|
410
|
-
|
|
411
|
-
Guid? sharedGuid = null;
|
|
412
|
-
if (param.StartsWith("guid:", StringComparison.OrdinalIgnoreCase))
|
|
413
|
-
{
|
|
414
|
-
sharedGuid = Guid.TryParse(param["guid:".Length..], out var guid)
|
|
415
|
-
? guid
|
|
416
|
-
: throw new ArgumentException($"Invalid shared parameter guid: {param}");
|
|
417
|
-
}
|
|
418
|
-
else
|
|
419
|
-
{
|
|
420
|
-
string enumName = param.StartsWith("BuiltInParameter.", StringComparison.OrdinalIgnoreCase)
|
|
421
|
-
? param["BuiltInParameter.".Length..]
|
|
422
|
-
: param;
|
|
423
|
-
// BuiltInParameter names are SHOUTY_SNAKE_CASE; require an underscore or
|
|
424
|
-
// all-caps so plain display names like "Comments" never collide.
|
|
425
|
-
bool looksLikeEnumName = enumName.Length > 0 && char.IsLetter(enumName[0]) && !enumName.Contains(' ')
|
|
426
|
-
&& (enumName.Contains('_') || enumName.All(c => !char.IsLetter(c) || char.IsUpper(c)));
|
|
427
|
-
if (looksLikeEnumName && Enum.TryParse<BuiltInParameter>(enumName, true, out var parsed) && parsed != BuiltInParameter.INVALID)
|
|
428
|
-
builtIn = parsed;
|
|
429
|
-
}
|
|
432
|
+
var reference = ParameterReference.Parse(param);
|
|
430
433
|
|
|
431
434
|
Regex? regex = null;
|
|
432
435
|
if (op == RuleOp.Regex)
|
|
@@ -448,29 +451,15 @@ namespace RevitBridge.Tools
|
|
|
448
451
|
Op = op,
|
|
449
452
|
Value = hasValue ? value : default,
|
|
450
453
|
Unit = JsonArgs.GetString(element, "unit"),
|
|
451
|
-
|
|
452
|
-
|
|
454
|
+
Reference = reference,
|
|
455
|
+
BuiltIn = reference.BuiltIn,
|
|
456
|
+
SharedGuid = reference.SharedGuid,
|
|
453
457
|
CompiledRegex = regex,
|
|
454
458
|
};
|
|
455
459
|
}
|
|
456
460
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
if (rule.BuiltIn is { } builtIn)
|
|
460
|
-
return element.get_Parameter(builtIn);
|
|
461
|
-
if (rule.SharedGuid is { } guid)
|
|
462
|
-
return element.get_Parameter(guid);
|
|
463
|
-
|
|
464
|
-
var direct = element.LookupParameter(rule.ParamInput);
|
|
465
|
-
if (direct != null)
|
|
466
|
-
return direct;
|
|
467
|
-
foreach (Parameter parameter in element.Parameters)
|
|
468
|
-
{
|
|
469
|
-
if (string.Equals(parameter.Definition?.Name, rule.ParamInput, StringComparison.OrdinalIgnoreCase))
|
|
470
|
-
return parameter;
|
|
471
|
-
}
|
|
472
|
-
return null;
|
|
473
|
-
}
|
|
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);
|
|
474
463
|
|
|
475
464
|
/// <summary>Builds a collector-level FilterRule, or null when the op/storage pair must post-scan.</summary>
|
|
476
465
|
private static FilterRule? TryBuildQuickRule(Document doc, Rule rule, Parameter exemplar)
|
|
@@ -1,82 +1,89 @@
|
|
|
1
|
-
using System.Text.Json;
|
|
2
|
-
using System.Text.Json.Nodes;
|
|
3
|
-
using Autodesk.Revit.DB;
|
|
4
|
-
|
|
5
|
-
namespace RevitBridge.Tools;
|
|
6
|
-
|
|
7
|
-
internal sealed class GetLinkedElements : ITool
|
|
8
|
-
{
|
|
9
|
-
public string Name => "get_linked_elements";
|
|
10
|
-
public string
|
|
11
|
-
public string
|
|
12
|
-
public
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
var
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using System.Text.Json.Nodes;
|
|
3
|
+
using Autodesk.Revit.DB;
|
|
4
|
+
|
|
5
|
+
namespace RevitBridge.Tools;
|
|
6
|
+
|
|
7
|
+
internal sealed class GetLinkedElements : ITool
|
|
8
|
+
{
|
|
9
|
+
public string Name => "get_linked_elements";
|
|
10
|
+
public IReadOnlyList<string> DocumentKinds => DocumentKind.ProjectOnly;
|
|
11
|
+
public IReadOnlyList<string> Keywords => new[] { "linked model", "link", "linked elements", "elements in link", "rvt link", "consultant model" };
|
|
12
|
+
public IReadOnlyList<ToolLimit> Limits => new[]
|
|
13
|
+
{
|
|
14
|
+
new ToolLimit("Nested links", "api", "RevitLinkInstance.GetLinkDocument on the nested link instances"),
|
|
15
|
+
new ToolLimit("Editing linked elements", "user", "Edit the linked model itself"),
|
|
16
|
+
};
|
|
17
|
+
public string Label => "Get Linked Elements";
|
|
18
|
+
public string Tier => "advanced";
|
|
19
|
+
public string Description => "Query one loaded Revit link with get_elements filters and pagination. Use link_instance_id and expected_linked_document_id from get_linked_models. Returned element IDs belong to the linked document and cannot be passed to host write tools. Optional bounds are axis-aligned in host coordinates, in internal feet. Only direct links are supported; active-view scoping is unavailable.";
|
|
20
|
+
public object ParametersSchema
|
|
21
|
+
{
|
|
22
|
+
get
|
|
23
|
+
{
|
|
24
|
+
var schema = JsonSerializer.SerializeToNode(new GetElements().ParametersSchema)!.AsObject();
|
|
25
|
+
var properties = schema["properties"]!.AsObject();
|
|
26
|
+
properties.Remove("in_active_view");
|
|
27
|
+
properties["link_instance_id"] = new JsonObject { ["type"] = "integer" };
|
|
28
|
+
properties["expected_linked_document_id"] = new JsonObject { ["type"] = "string" };
|
|
29
|
+
properties["include_bounds"] = new JsonObject { ["type"] = "boolean" };
|
|
30
|
+
schema["required"] = new JsonArray("link_instance_id", "expected_linked_document_id");
|
|
31
|
+
return schema;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public object Execute(JsonElement args, ToolContext context)
|
|
36
|
+
{
|
|
37
|
+
var host = context.Document ?? throw new NoActiveDocumentException();
|
|
38
|
+
var id = JsonArgs.GetLong(args, "link_instance_id") ?? throw new ArgumentException("link_instance_id is required.");
|
|
39
|
+
var link = host.GetElement(new ElementId(id)) as RevitLinkInstance
|
|
40
|
+
?? throw new ArgumentException("link_instance_id is not a Revit link instance in the active host document.");
|
|
41
|
+
var linked = link.GetLinkDocument() ?? throw new ArgumentException("The link is unloaded. Load it in Revit before querying it.");
|
|
42
|
+
string identity = DocumentGuard.GetIdentity(linked);
|
|
43
|
+
if (!string.Equals(JsonArgs.GetString(args, "expected_linked_document_id"), identity, StringComparison.Ordinal))
|
|
44
|
+
throw new ArgumentException("The linked document identity is missing or stale. Read get_linked_models again.");
|
|
45
|
+
if (JsonArgs.GetBool(args, "in_active_view", false))
|
|
46
|
+
throw new ArgumentException("in_active_view is not supported for linked queries.");
|
|
47
|
+
|
|
48
|
+
var result = (ToolOutput)new GetElements().Execute(args, new ToolContext(linked, null))!;
|
|
49
|
+
var payload = JsonSerializer.SerializeToNode(result.Payload)!.AsObject();
|
|
50
|
+
payload["host_document_id"] = DocumentGuard.GetIdentity(host);
|
|
51
|
+
payload["linked_document_id"] = identity;
|
|
52
|
+
payload["link_instance_id"] = id;
|
|
53
|
+
payload["coordinate_unit"] = "ft";
|
|
54
|
+
if (payload["elements"] is JsonArray elements)
|
|
55
|
+
{
|
|
56
|
+
var transform = link.GetTotalTransform();
|
|
57
|
+
foreach (var row in elements)
|
|
58
|
+
{
|
|
59
|
+
var element = linked.GetElement(new ElementId(row!["id"]!.GetValue<long>()));
|
|
60
|
+
row["unique_id"] = element.UniqueId;
|
|
61
|
+
row["reference"] = new JsonObject
|
|
62
|
+
{
|
|
63
|
+
["host_document_id"] = DocumentGuard.GetIdentity(host),
|
|
64
|
+
["link_instance_id"] = id,
|
|
65
|
+
["linked_document_id"] = identity,
|
|
66
|
+
["element_id"] = element.Id.Value,
|
|
67
|
+
};
|
|
68
|
+
if (JsonArgs.GetBool(args, "include_bounds", false))
|
|
69
|
+
row["host_bounds"] = JsonSerializer.SerializeToNode(HostBounds(element.get_BoundingBox(null), transform));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return new ToolOutput(payload, result.CompactText);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
internal static object? HostBounds(BoundingBoxXYZ? box, Transform placement)
|
|
76
|
+
{
|
|
77
|
+
if (box == null) return null;
|
|
78
|
+
var corners = new List<XYZ>(8);
|
|
79
|
+
foreach (double x in new[] { box.Min.X, box.Max.X })
|
|
80
|
+
foreach (double y in new[] { box.Min.Y, box.Max.Y })
|
|
81
|
+
foreach (double z in new[] { box.Min.Z, box.Max.Z })
|
|
82
|
+
corners.Add(placement.OfPoint(box.Transform.OfPoint(new XYZ(x, y, z))));
|
|
83
|
+
return new
|
|
84
|
+
{
|
|
85
|
+
min = new[] { corners.Min(p => p.X), corners.Min(p => p.Y), corners.Min(p => p.Z) },
|
|
86
|
+
max = new[] { corners.Max(p => p.X), corners.Max(p => p.Y), corners.Max(p => p.Z) },
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -1,66 +1,73 @@
|
|
|
1
|
-
using System.Text.Json;
|
|
2
|
-
using Autodesk.Revit.DB;
|
|
3
|
-
|
|
4
|
-
namespace RevitBridge.Tools;
|
|
5
|
-
|
|
6
|
-
internal sealed class GetLinkedModels : ITool
|
|
7
|
-
{
|
|
8
|
-
public string Name => "get_linked_models";
|
|
9
|
-
public string
|
|
10
|
-
public string
|
|
11
|
-
public
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal sealed class GetLinkedModels : ITool
|
|
7
|
+
{
|
|
8
|
+
public string Name => "get_linked_models";
|
|
9
|
+
public IReadOnlyList<string> DocumentKinds => DocumentKind.ProjectOnly;
|
|
10
|
+
public IReadOnlyList<string> Keywords => new[] { "links", "linked files", "rvt links", "link status", "loaded", "unloaded" };
|
|
11
|
+
public IReadOnlyList<ToolLimit> Limits => new[]
|
|
12
|
+
{
|
|
13
|
+
new ToolLimit("Loading or reloading an unloaded link", "api", "RevitLinkType.Load"),
|
|
14
|
+
new ToolLimit("Nested links", "api", "RevitLinkInstance.GetLinkDocument"),
|
|
15
|
+
};
|
|
16
|
+
public string Label => "Get Linked Models";
|
|
17
|
+
public string Tier => "advanced";
|
|
18
|
+
public string Description => "List placed Revit links, including unloaded instances, exact linked document identities and their transforms into host coordinates. Nested links are not traversed. Coordinates and transform origins use internal feet.";
|
|
19
|
+
public object ParametersSchema => new
|
|
20
|
+
{
|
|
21
|
+
type = "object",
|
|
22
|
+
properties = new
|
|
23
|
+
{
|
|
24
|
+
offset = new { type = "integer", minimum = 0 },
|
|
25
|
+
limit = new { type = "integer", minimum = 1, maximum = 1000 },
|
|
26
|
+
},
|
|
27
|
+
required = Array.Empty<string>(),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
public object Execute(JsonElement args, ToolContext context)
|
|
31
|
+
{
|
|
32
|
+
var doc = context.Document ?? throw new NoActiveDocumentException();
|
|
33
|
+
int offset = Math.Max(0, JsonArgs.GetInt(args, "offset", 0));
|
|
34
|
+
int limit = Math.Clamp(JsonArgs.GetInt(args, "limit", 100), 1, 1000);
|
|
35
|
+
var links = new FilteredElementCollector(doc).OfClass(typeof(RevitLinkInstance))
|
|
36
|
+
.Cast<RevitLinkInstance>().OrderBy(x => x.Id.Value).ToList();
|
|
37
|
+
var rows = links.Skip(offset).Take(limit).Select(link =>
|
|
38
|
+
{
|
|
39
|
+
var linked = link.GetLinkDocument();
|
|
40
|
+
var transform = link.GetTotalTransform();
|
|
41
|
+
return new
|
|
42
|
+
{
|
|
43
|
+
link_instance_id = link.Id.Value,
|
|
44
|
+
unique_id = link.UniqueId,
|
|
45
|
+
name = link.Name,
|
|
46
|
+
type_id = link.GetTypeId().Value,
|
|
47
|
+
loaded = linked != null,
|
|
48
|
+
linked_document_id = linked == null ? null : DocumentGuard.GetIdentity(linked),
|
|
49
|
+
document_title = linked?.Title,
|
|
50
|
+
transform = new
|
|
51
|
+
{
|
|
52
|
+
origin = Coordinates(transform.Origin),
|
|
53
|
+
basis_x = Coordinates(transform.BasisX),
|
|
54
|
+
basis_y = Coordinates(transform.BasisY),
|
|
55
|
+
basis_z = Coordinates(transform.BasisZ),
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}).ToArray();
|
|
59
|
+
return new
|
|
60
|
+
{
|
|
61
|
+
document_id = DocumentGuard.GetIdentity(doc),
|
|
62
|
+
coordinate_unit = "ft",
|
|
63
|
+
total_count = links.Count,
|
|
64
|
+
returned_count = rows.Length,
|
|
65
|
+
offset,
|
|
66
|
+
has_more = offset + rows.Length < links.Count,
|
|
67
|
+
next_offset = offset + rows.Length < links.Count ? (int?)(offset + rows.Length) : null,
|
|
68
|
+
links = rows,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
internal static double[] Coordinates(XYZ point) => new[] { point.X, point.Y, point.Z };
|
|
73
|
+
}
|