pi-revit 0.4.0 → 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 +465 -430
- package/README.md +604 -548
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
using System.IO;
|
|
2
|
-
using System.Security.Cryptography;
|
|
3
|
-
using System.Text;
|
|
1
|
+
using System.IO;
|
|
2
|
+
using System.Security.Cryptography;
|
|
3
|
+
using System.Text;
|
|
4
4
|
using System.Text.Json;
|
|
5
5
|
using Autodesk.Revit.DB;
|
|
6
6
|
|
|
@@ -20,10 +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
|
-
public bool Write => true;
|
|
26
|
-
public IReadOnlyList<string> Effects => new[] { "model", "files" };
|
|
33
|
+
public bool Write => true;
|
|
34
|
+
public IReadOnlyList<string> Effects => new[] { "model", "files" };
|
|
27
35
|
public string Tier => "advanced";
|
|
28
36
|
|
|
29
37
|
public object ParametersSchema => new
|
|
@@ -89,37 +97,37 @@ namespace RevitBridge.Tools
|
|
|
89
97
|
// recent (a shared output_dir, another tool writing alongside) does not.
|
|
90
98
|
var before = SnapshotWriteTimes(outputDir);
|
|
91
99
|
|
|
92
|
-
IReadOnlyList<string> commitWarnings = Array.Empty<string>();
|
|
93
|
-
try
|
|
94
|
-
{
|
|
95
|
-
switch (format)
|
|
96
|
-
{
|
|
97
|
-
case "pdf": ExportPdf(doc, views, outputDir, baseName, combine); break;
|
|
98
|
-
case "dwg": ExportDwg(doc, views, outputDir, baseName); break;
|
|
99
|
-
case "png": ExportPng(doc, views, outputDir, baseName); break;
|
|
100
|
-
default: commitWarnings = ExportIfc(doc, views, outputDir, baseName); break;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
catch (Exception ex)
|
|
104
|
-
{
|
|
105
|
-
// A failed export may already have written some files. A Revit
|
|
106
|
-
// transaction rollback cannot undo those filesystem changes.
|
|
107
|
-
string observed;
|
|
108
|
-
try
|
|
109
|
-
{
|
|
110
|
-
var changed = FindChangedFiles(outputDir, before).ToList();
|
|
111
|
-
observed = changed.Count == 0
|
|
112
|
-
? "No changed files were observed."
|
|
113
|
-
: $"Observed {changed.Count} new or changed file(s): {string.Join(", ", changed.Take(20))}{(changed.Count > 20 ? " (additional files omitted)" : string.Empty)}.";
|
|
114
|
-
}
|
|
115
|
-
catch (Exception scanError)
|
|
116
|
-
{
|
|
117
|
-
observed = $"Could not inspect remaining files: {scanError.Message}.";
|
|
118
|
-
}
|
|
119
|
-
throw new InvalidOperationException($"{ex.Message} The export failed; files in '{outputDir}' may be incomplete and were not removed. {observed}", ex);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
var files = FindChangedFiles(outputDir, before)
|
|
100
|
+
IReadOnlyList<string> commitWarnings = Array.Empty<string>();
|
|
101
|
+
try
|
|
102
|
+
{
|
|
103
|
+
switch (format)
|
|
104
|
+
{
|
|
105
|
+
case "pdf": ExportPdf(doc, views, outputDir, baseName, combine); break;
|
|
106
|
+
case "dwg": ExportDwg(doc, views, outputDir, baseName); break;
|
|
107
|
+
case "png": ExportPng(doc, views, outputDir, baseName); break;
|
|
108
|
+
default: commitWarnings = ExportIfc(doc, views, outputDir, baseName); break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (Exception ex)
|
|
112
|
+
{
|
|
113
|
+
// A failed export may already have written some files. A Revit
|
|
114
|
+
// transaction rollback cannot undo those filesystem changes.
|
|
115
|
+
string observed;
|
|
116
|
+
try
|
|
117
|
+
{
|
|
118
|
+
var changed = FindChangedFiles(outputDir, before).ToList();
|
|
119
|
+
observed = changed.Count == 0
|
|
120
|
+
? "No changed files were observed."
|
|
121
|
+
: $"Observed {changed.Count} new or changed file(s): {string.Join(", ", changed.Take(20))}{(changed.Count > 20 ? " (additional files omitted)" : string.Empty)}.";
|
|
122
|
+
}
|
|
123
|
+
catch (Exception scanError)
|
|
124
|
+
{
|
|
125
|
+
observed = $"Could not inspect remaining files: {scanError.Message}.";
|
|
126
|
+
}
|
|
127
|
+
throw new InvalidOperationException($"{ex.Message} The export failed; files in '{outputDir}' may be incomplete and were not removed. {observed}", ex);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
var files = FindChangedFiles(outputDir, before)
|
|
123
131
|
.Select(path => new Dictionary<string, object?>
|
|
124
132
|
{
|
|
125
133
|
["path"] = path,
|
|
@@ -130,23 +138,23 @@ namespace RevitBridge.Tools
|
|
|
130
138
|
throw new InvalidOperationException($"The {format} export finished but produced no files in {outputDir}.");
|
|
131
139
|
|
|
132
140
|
string sample = string.Join(", ", files.Take(3).Select(file => Path.GetFileName((string)file["path"]!)));
|
|
133
|
-
string compact = $"Exported {files.Count} {format.ToUpperInvariant()} file(s) to {outputDir}: {sample}{(files.Count > 3 ? $" (+{files.Count - 3} more)" : string.Empty)}.";
|
|
134
|
-
if (commitWarnings.Count > 0)
|
|
135
|
-
compact += $" {commitWarnings.Count} Revit warning(s) auto-dismissed (see commitWarnings).";
|
|
141
|
+
string compact = $"Exported {files.Count} {format.ToUpperInvariant()} file(s) to {outputDir}: {sample}{(files.Count > 3 ? $" (+{files.Count - 3} more)" : string.Empty)}.";
|
|
142
|
+
if (commitWarnings.Count > 0)
|
|
143
|
+
compact += $" {commitWarnings.Count} Revit warning(s) auto-dismissed (see commitWarnings).";
|
|
136
144
|
return new ToolOutput(new
|
|
137
145
|
{
|
|
138
146
|
format,
|
|
139
147
|
outputDir,
|
|
140
148
|
fileCount = files.Count,
|
|
141
|
-
files,
|
|
142
|
-
commitWarnings,
|
|
143
|
-
}, compact);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
private static IEnumerable<string> FindChangedFiles(string directory, IReadOnlyDictionary<string, DateTime> before)
|
|
147
|
-
=> Directory.GetFiles(directory)
|
|
148
|
-
.Where(path => !before.TryGetValue(path, out DateTime writtenBefore) || SafeWriteTime(path) != writtenBefore)
|
|
149
|
-
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase);
|
|
149
|
+
files,
|
|
150
|
+
commitWarnings,
|
|
151
|
+
}, compact);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private static IEnumerable<string> FindChangedFiles(string directory, IReadOnlyDictionary<string, DateTime> before)
|
|
155
|
+
=> Directory.GetFiles(directory)
|
|
156
|
+
.Where(path => !before.TryGetValue(path, out DateTime writtenBefore) || SafeWriteTime(path) != writtenBefore)
|
|
157
|
+
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase);
|
|
150
158
|
|
|
151
159
|
/// <summary>path -> last write time for the files already in the output directory.
|
|
152
160
|
/// A file whose time cannot be read is left out, so the export reports it if it
|
|
@@ -228,29 +236,29 @@ namespace RevitBridge.Tools
|
|
|
228
236
|
/// <summary>The IFC exporter writes IFC GUID parameters onto exported elements,
|
|
229
237
|
/// so the Revit API requires a transaction around it — owned here, matching
|
|
230
238
|
/// Revit's own behavior of committing those GUIDs on export.</summary>
|
|
231
|
-
private static IReadOnlyList<string> ExportIfc(Document doc, List<View> views, string outputDir, string baseName)
|
|
239
|
+
private static IReadOnlyList<string> ExportIfc(Document doc, List<View> views, string outputDir, string baseName)
|
|
232
240
|
{
|
|
233
241
|
var options = new IFCExportOptions();
|
|
234
242
|
if (views.Count == 1)
|
|
235
243
|
options.FilterViewId = views[0].Id;
|
|
236
244
|
|
|
237
245
|
using var transaction = new Transaction(doc, "export_documents: ifc");
|
|
238
|
-
if (transaction.Start() != TransactionStatus.Started)
|
|
239
|
-
throw new InvalidOperationException("Unable to start the IFC export transaction.");
|
|
240
|
-
var failureGuard = FailureGuard.Attach(transaction);
|
|
246
|
+
if (transaction.Start() != TransactionStatus.Started)
|
|
247
|
+
throw new InvalidOperationException("Unable to start the IFC export transaction.");
|
|
248
|
+
var failureGuard = FailureGuard.Attach(transaction);
|
|
241
249
|
try
|
|
242
250
|
{
|
|
243
251
|
if (!Run(() => doc.Export(outputDir, baseName, options), "IFC"))
|
|
244
252
|
throw new InvalidOperationException("Revit reported a failed IFC export.");
|
|
245
|
-
var status = transaction.Commit();
|
|
246
|
-
var finalStatus = transaction.GetStatus();
|
|
247
|
-
if (status != TransactionStatus.Committed || finalStatus != TransactionStatus.Committed)
|
|
248
|
-
throw new InvalidOperationException($"The IFC export commit returned {status}; current transaction status is {finalStatus}." + failureGuard.DescribeErrors());
|
|
249
|
-
return failureGuard.Warnings;
|
|
250
|
-
}
|
|
251
|
-
catch (Exception ex)
|
|
252
|
-
{
|
|
253
|
-
throw new InvalidOperationException($"{ex.Message} {FailureGuard.RollBackAndDescribe(transaction)}", ex);
|
|
253
|
+
var status = transaction.Commit();
|
|
254
|
+
var finalStatus = transaction.GetStatus();
|
|
255
|
+
if (status != TransactionStatus.Committed || finalStatus != TransactionStatus.Committed)
|
|
256
|
+
throw new InvalidOperationException($"The IFC export commit returned {status}; current transaction status is {finalStatus}." + failureGuard.DescribeErrors());
|
|
257
|
+
return failureGuard.Warnings;
|
|
258
|
+
}
|
|
259
|
+
catch (Exception ex)
|
|
260
|
+
{
|
|
261
|
+
throw new InvalidOperationException($"{ex.Message} {FailureGuard.RollBackAndDescribe(transaction)}", ex);
|
|
254
262
|
}
|
|
255
263
|
}
|
|
256
264
|
|
|
@@ -311,72 +319,72 @@ namespace RevitBridge.Tools
|
|
|
311
319
|
}
|
|
312
320
|
|
|
313
321
|
/// <summary>Per-model folder under the pi-revit workspace:
|
|
314
|
-
/// Documents\pi-revit\Models\<model title>--<identity hash>.
|
|
315
|
-
/// Existing title-only folders are left untouched; identity markers are
|
|
316
|
-
/// diagnostic records, not the mechanism that separates exports.</summary>
|
|
317
|
-
private static string ModelFolder(Document doc)
|
|
322
|
+
/// Documents\pi-revit\Models\<model title>--<identity hash>.
|
|
323
|
+
/// Existing title-only folders are left untouched; identity markers are
|
|
324
|
+
/// diagnostic records, not the mechanism that separates exports.</summary>
|
|
325
|
+
private static string ModelFolder(Document doc)
|
|
318
326
|
{
|
|
319
327
|
string workspace = Path.Combine(
|
|
320
328
|
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "pi-revit");
|
|
321
|
-
string identity = GetModelIdentity(doc);
|
|
322
|
-
string folder = Path.Combine(workspace, "Models", GetModelFolderName(doc.Title, identity));
|
|
323
|
-
Directory.CreateDirectory(folder);
|
|
324
|
-
TryRecordModelIdentity(folder, doc, identity);
|
|
325
|
-
return folder;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
internal static string GetModelFolderName(string title, string identity)
|
|
329
|
-
{
|
|
330
|
-
string cleanTitle = SanitizeFileName(string.IsNullOrWhiteSpace(title) ? "untitled" : title);
|
|
331
|
-
// Bound the default path component, leaving room for the identity and exports.
|
|
332
|
-
if (cleanTitle.Length > 80)
|
|
333
|
-
cleanTitle = cleanTitle[..80];
|
|
334
|
-
string hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(identity))).ToLowerInvariant()[..24];
|
|
335
|
-
return $"{cleanTitle}--{hash}";
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Revit can return different managed wrappers for one open native document.
|
|
339
|
-
// Document.Equals/GetHashCode identify that open document, unlike reference
|
|
340
|
-
// equality in ConditionalWeakTable. Closed document entries are pruned below.
|
|
341
|
-
// Tool execution and this dictionary are confined to the Revit API thread.
|
|
342
|
-
private static readonly Dictionary<Document, string> OpenDocumentIdentities = new();
|
|
343
|
-
|
|
344
|
-
internal static string GetModelIdentity(Document doc)
|
|
345
|
-
{
|
|
346
|
-
try
|
|
347
|
-
{
|
|
348
|
-
if (doc.IsModelInCloud)
|
|
349
|
-
{
|
|
350
|
-
var path = doc.GetCloudModelPath();
|
|
351
|
-
return $"cloud:{path.Region.ToUpperInvariant()}:{path.GetProjectGUID():D}:{path.GetModelGUID():D}";
|
|
352
|
-
}
|
|
353
|
-
string pathName = doc.PathName;
|
|
354
|
-
if (!string.IsNullOrWhiteSpace(pathName))
|
|
355
|
-
{
|
|
356
|
-
// Revit file paths are Windows paths; canonicalize separators,
|
|
357
|
-
// relative segments and case. Revit Server identities use RSN URLs.
|
|
358
|
-
if (pathName.StartsWith("RSN://", StringComparison.OrdinalIgnoreCase))
|
|
359
|
-
return "server:" + pathName.Replace('\\', '/').TrimEnd('/').ToUpperInvariant();
|
|
360
|
-
return "file:" + Path.GetFullPath(pathName).Replace('/', '\\').ToUpperInvariant();
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
catch
|
|
364
|
-
{
|
|
365
|
-
// An unavailable identity must not collapse unrelated documents into
|
|
366
|
-
// a shared title-only directory. Use the same safe fallback as unsaved docs.
|
|
367
|
-
}
|
|
368
|
-
foreach (var closed in OpenDocumentIdentities.Keys.Where(key => !key.IsValidObject).ToList())
|
|
369
|
-
OpenDocumentIdentities.Remove(closed);
|
|
370
|
-
if (!OpenDocumentIdentities.TryGetValue(doc, out string? identity))
|
|
371
|
-
OpenDocumentIdentities[doc] = identity = $"session:{Guid.NewGuid():N}";
|
|
372
|
-
return identity;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
private static void TryRecordModelIdentity(string folder, Document doc, string identity)
|
|
329
|
+
string identity = GetModelIdentity(doc);
|
|
330
|
+
string folder = Path.Combine(workspace, "Models", GetModelFolderName(doc.Title, identity));
|
|
331
|
+
Directory.CreateDirectory(folder);
|
|
332
|
+
TryRecordModelIdentity(folder, doc, identity);
|
|
333
|
+
return folder;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
internal static string GetModelFolderName(string title, string identity)
|
|
337
|
+
{
|
|
338
|
+
string cleanTitle = SanitizeFileName(string.IsNullOrWhiteSpace(title) ? "untitled" : title);
|
|
339
|
+
// Bound the default path component, leaving room for the identity and exports.
|
|
340
|
+
if (cleanTitle.Length > 80)
|
|
341
|
+
cleanTitle = cleanTitle[..80];
|
|
342
|
+
string hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(identity))).ToLowerInvariant()[..24];
|
|
343
|
+
return $"{cleanTitle}--{hash}";
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Revit can return different managed wrappers for one open native document.
|
|
347
|
+
// Document.Equals/GetHashCode identify that open document, unlike reference
|
|
348
|
+
// equality in ConditionalWeakTable. Closed document entries are pruned below.
|
|
349
|
+
// Tool execution and this dictionary are confined to the Revit API thread.
|
|
350
|
+
private static readonly Dictionary<Document, string> OpenDocumentIdentities = new();
|
|
351
|
+
|
|
352
|
+
internal static string GetModelIdentity(Document doc)
|
|
353
|
+
{
|
|
354
|
+
try
|
|
355
|
+
{
|
|
356
|
+
if (doc.IsModelInCloud)
|
|
357
|
+
{
|
|
358
|
+
var path = doc.GetCloudModelPath();
|
|
359
|
+
return $"cloud:{path.Region.ToUpperInvariant()}:{path.GetProjectGUID():D}:{path.GetModelGUID():D}";
|
|
360
|
+
}
|
|
361
|
+
string pathName = doc.PathName;
|
|
362
|
+
if (!string.IsNullOrWhiteSpace(pathName))
|
|
363
|
+
{
|
|
364
|
+
// Revit file paths are Windows paths; canonicalize separators,
|
|
365
|
+
// relative segments and case. Revit Server identities use RSN URLs.
|
|
366
|
+
if (pathName.StartsWith("RSN://", StringComparison.OrdinalIgnoreCase))
|
|
367
|
+
return "server:" + pathName.Replace('\\', '/').TrimEnd('/').ToUpperInvariant();
|
|
368
|
+
return "file:" + Path.GetFullPath(pathName).Replace('/', '\\').ToUpperInvariant();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
catch
|
|
372
|
+
{
|
|
373
|
+
// An unavailable identity must not collapse unrelated documents into
|
|
374
|
+
// a shared title-only directory. Use the same safe fallback as unsaved docs.
|
|
375
|
+
}
|
|
376
|
+
foreach (var closed in OpenDocumentIdentities.Keys.Where(key => !key.IsValidObject).ToList())
|
|
377
|
+
OpenDocumentIdentities.Remove(closed);
|
|
378
|
+
if (!OpenDocumentIdentities.TryGetValue(doc, out string? identity))
|
|
379
|
+
OpenDocumentIdentities[doc] = identity = $"session:{Guid.NewGuid():N}";
|
|
380
|
+
return identity;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private static void TryRecordModelIdentity(string folder, Document doc, string identity)
|
|
376
384
|
{
|
|
377
385
|
try
|
|
378
386
|
{
|
|
379
|
-
string line = $"{identity}\t{doc.PathName}";
|
|
387
|
+
string line = $"{identity}\t{doc.PathName}";
|
|
380
388
|
string marker = Path.Combine(folder, "model.txt");
|
|
381
389
|
if (!File.Exists(marker) || !File.ReadAllLines(marker).Contains(line))
|
|
382
390
|
File.AppendAllLines(marker, new[] { line });
|
|
@@ -9,6 +9,14 @@ 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
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
|
|
|
@@ -36,7 +44,7 @@ namespace RevitBridge.Tools
|
|
|
36
44
|
properties = new
|
|
37
45
|
{
|
|
38
46
|
parameters = new { type = "boolean", description = "Instance parameter values. Default true." },
|
|
39
|
-
type_parameters = new { type = "boolean", description = "Include the element type's parameters, marked isType=true, independently of the instance parameters flag. Default false." },
|
|
47
|
+
type_parameters = new { type = "boolean", description = "Include the element type's parameters, marked isType=true, independently of the instance parameters flag. Default false." },
|
|
40
48
|
location = new { type = "boolean", description = "Location point or curve (coordinates in internal feet). Default false." },
|
|
41
49
|
bounding_box = new { type = "boolean", description = "Model bounding box min/max (internal feet). Default false." },
|
|
42
50
|
materials = new { type = "boolean", description = "Material ids/names with area/volume (internal units). Default false." },
|
|
@@ -70,9 +78,9 @@ namespace RevitBridge.Tools
|
|
|
70
78
|
JsonElement include = args.TryGetProperty("include", out var includeElement) && includeElement.ValueKind == JsonValueKind.Object
|
|
71
79
|
? includeElement
|
|
72
80
|
: default;
|
|
73
|
-
bool withParameters = JsonArgs.GetBool(include, "parameters", true);
|
|
74
|
-
bool withTypeParameters = JsonArgs.GetBool(include, "type_parameters", false);
|
|
75
|
-
bool withAnyParameters = withParameters || withTypeParameters;
|
|
81
|
+
bool withParameters = JsonArgs.GetBool(include, "parameters", true);
|
|
82
|
+
bool withTypeParameters = JsonArgs.GetBool(include, "type_parameters", false);
|
|
83
|
+
bool withAnyParameters = withParameters || withTypeParameters;
|
|
76
84
|
bool withLocation = JsonArgs.GetBool(include, "location", false);
|
|
77
85
|
bool withBoundingBox = JsonArgs.GetBool(include, "bounding_box", false);
|
|
78
86
|
bool withMaterials = JsonArgs.GetBool(include, "materials", false);
|
|
@@ -103,11 +111,11 @@ namespace RevitBridge.Tools
|
|
|
103
111
|
|
|
104
112
|
int parameterCount = 0;
|
|
105
113
|
int parameterTotal = 0;
|
|
106
|
-
if (withAnyParameters)
|
|
107
|
-
{
|
|
108
|
-
var parameters = new List<Dictionary<string, object?>>();
|
|
109
|
-
if (withParameters)
|
|
110
|
-
parameterTotal += AppendParameters(doc, element, nameFilter, isType: false, parameters);
|
|
114
|
+
if (withAnyParameters)
|
|
115
|
+
{
|
|
116
|
+
var parameters = new List<Dictionary<string, object?>>();
|
|
117
|
+
if (withParameters)
|
|
118
|
+
parameterTotal += AppendParameters(doc, element, nameFilter, isType: false, parameters);
|
|
111
119
|
if (withTypeParameters && elementType != null)
|
|
112
120
|
parameterTotal += AppendParameters(doc, elementType, nameFilter, isType: true, parameters);
|
|
113
121
|
dto["parameters"] = parameters;
|
|
@@ -124,7 +132,7 @@ namespace RevitBridge.Tools
|
|
|
124
132
|
elements.Add(dto);
|
|
125
133
|
// With a name filter active, "0 params" is ambiguous (none matched vs none
|
|
126
134
|
// exist): report matched-of-total so a localization miss is visible.
|
|
127
|
-
string paramSummary = !withAnyParameters ? string.Empty
|
|
135
|
+
string paramSummary = !withAnyParameters ? string.Empty
|
|
128
136
|
: nameFilter != null ? $", {parameterCount} of {parameterTotal} params matched parameter_names"
|
|
129
137
|
: $", {parameterCount} params";
|
|
130
138
|
compactParts.Add($"'{element.Name}' (id {id}, {element.Category?.Name ?? "no category"}{paramSummary})");
|
|
@@ -167,37 +175,25 @@ namespace RevitBridge.Tools
|
|
|
167
175
|
return total;
|
|
168
176
|
}
|
|
169
177
|
|
|
170
|
-
internal static IReadOnlyList<object> ProjectParameters(Document doc, Element element, IReadOnlyList<string> names, bool includeType)
|
|
171
|
-
{
|
|
172
|
-
var result = new List<object>();
|
|
173
|
-
void Project(Element source, bool isType)
|
|
174
|
-
{
|
|
175
|
-
foreach (string input in names.Distinct(StringComparer.OrdinalIgnoreCase))
|
|
176
|
-
{
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
else
|
|
190
|
-
matches = source.Parameters.Cast<Parameter>().Where(p => string.Equals(p.Definition?.Name, input, StringComparison.OrdinalIgnoreCase));
|
|
191
|
-
var values = matches.Select(p => DescribeParameter(doc, p, p.Definition?.Name ?? "", isType)).ToArray();
|
|
192
|
-
result.Add(new { requested = input, isType, found = values.Length > 0, ambiguous = values.Length > 1, matches = values });
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
Project(element, false);
|
|
196
|
-
if (includeType && doc.GetElement(element.GetTypeId()) is { } type) Project(type, true);
|
|
197
|
-
return result;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
internal 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)
|
|
201
197
|
{
|
|
202
198
|
bool hasValue = parameter.HasValue;
|
|
203
199
|
var row = new Dictionary<string, object?>
|
|
@@ -1,76 +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 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
|
-
var
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|