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,64 +1,74 @@
|
|
|
1
|
-
using System.Text.Json;
|
|
2
|
-
using Autodesk.Revit.DB;
|
|
3
|
-
|
|
4
|
-
namespace RevitBridge.Tools;
|
|
5
|
-
|
|
6
|
-
/// <summary>Opaque identity of one open native document in this loaded bridge session.</summary>
|
|
7
|
-
internal static class DocumentGuard
|
|
8
|
-
{
|
|
9
|
-
private sealed record Identity(Document Document, string Value);
|
|
10
|
-
private static readonly string Generation = Guid.NewGuid().ToString("N");
|
|
11
|
-
private static readonly List<Identity> Identities = new();
|
|
12
|
-
|
|
13
|
-
public static string GetIdentity(Document document)
|
|
14
|
-
{
|
|
15
|
-
if (!document.IsValidObject)
|
|
16
|
-
throw new ArgumentException("The target document is closed or invalid. Read get_model_overview again.");
|
|
17
|
-
// Revit can supply multiple managed wrappers for the same native document.
|
|
18
|
-
// Its documented Equals semantics identify that native document; reference identity cannot.
|
|
19
|
-
// All accesses happen on the Revit API thread. Pruning avoids retaining closed documents.
|
|
20
|
-
Identities.RemoveAll(entry => !entry.Document.IsValidObject);
|
|
21
|
-
var existing = Identities.FirstOrDefault(entry => entry.Document.Equals(document));
|
|
22
|
-
if (existing != null) return existing.Value;
|
|
23
|
-
var created = new Identity(document, Generation + ":" + Guid.NewGuid().ToString("N"));
|
|
24
|
-
Identities.Add(created);
|
|
25
|
-
return created.Value;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
public static bool AlwaysRequiresIdentity(string toolName)
|
|
29
|
-
=> toolName is "set_parameters" or "execute_csharp" or "export_documents" or "open_view";
|
|
30
|
-
|
|
31
|
-
/// <summary>
|
|
32
|
-
public static
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
/// <summary>Opaque identity of one open native document in this loaded bridge session.</summary>
|
|
7
|
+
internal static class DocumentGuard
|
|
8
|
+
{
|
|
9
|
+
private sealed record Identity(Document Document, string Value);
|
|
10
|
+
private static readonly string Generation = Guid.NewGuid().ToString("N");
|
|
11
|
+
private static readonly List<Identity> Identities = new();
|
|
12
|
+
|
|
13
|
+
public static string GetIdentity(Document document)
|
|
14
|
+
{
|
|
15
|
+
if (!document.IsValidObject)
|
|
16
|
+
throw new ArgumentException("The target document is closed or invalid. Read get_model_overview again.");
|
|
17
|
+
// Revit can supply multiple managed wrappers for the same native document.
|
|
18
|
+
// Its documented Equals semantics identify that native document; reference identity cannot.
|
|
19
|
+
// All accesses happen on the Revit API thread. Pruning avoids retaining closed documents.
|
|
20
|
+
Identities.RemoveAll(entry => !entry.Document.IsValidObject);
|
|
21
|
+
var existing = Identities.FirstOrDefault(entry => entry.Document.Equals(document));
|
|
22
|
+
if (existing != null) return existing.Value;
|
|
23
|
+
var created = new Identity(document, Generation + ":" + Guid.NewGuid().ToString("N"));
|
|
24
|
+
Identities.Add(created);
|
|
25
|
+
return created.Value;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public static bool AlwaysRequiresIdentity(string toolName)
|
|
29
|
+
=> toolName is "set_parameters" or "execute_csharp" or "export_documents" or "open_view";
|
|
30
|
+
|
|
31
|
+
/// <summary>Project or family: the kind of the active document, as tools declare it.</summary>
|
|
32
|
+
public static string KindOf(Document document) => document.IsFamilyDocument ? DocumentKind.Family : DocumentKind.Project;
|
|
33
|
+
|
|
34
|
+
/// <summary>Refuse a tool in a document kind it does not declare, before it runs (inv:document-kind-declared).</summary>
|
|
35
|
+
public static void CheckKind(ITool tool, Document document)
|
|
36
|
+
{
|
|
37
|
+
if (DocumentKind.Refusal(tool.Name, tool.DocumentKinds, KindOf(document), document.Title) is { } refusal)
|
|
38
|
+
throw new ArgumentException(refusal);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// <summary>Must run on the Revit API thread immediately before the tool action.</summary>
|
|
42
|
+
public static void CheckForTool(JsonElement args, Document document, string toolName, bool writes = false)
|
|
43
|
+
{
|
|
44
|
+
bool required = writes || AlwaysRequiresIdentity(toolName)
|
|
45
|
+
|| (toolName == "manage_selection"
|
|
46
|
+
&& (!string.Equals((JsonArgs.GetString(args, "action") ?? "get").Trim(), "get", StringComparison.OrdinalIgnoreCase)
|
|
47
|
+
|| JsonArgs.GetBool(args, "isolate_in_view", false)));
|
|
48
|
+
if (required && !args.TryGetProperty("expected_document_id", out _))
|
|
49
|
+
throw new ArgumentException("expected_document_id is required for this operation. Read project.documentId from get_model_overview for the intended open document and pass it unchanged. A title alone cannot identify a document. No action was performed.");
|
|
50
|
+
CheckExpectedDocument(args, document);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public static void CheckExpectedDocument(JsonElement args, Document document)
|
|
54
|
+
{
|
|
55
|
+
if (args.TryGetProperty("expected_document_id", out var id))
|
|
56
|
+
{
|
|
57
|
+
if (id.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace(id.GetString()))
|
|
58
|
+
throw new ArgumentException("expected_document_id must be a non-empty identity from get_model_overview. No action was performed.");
|
|
59
|
+
if (!string.Equals(id.GetString(), GetIdentity(document), StringComparison.Ordinal))
|
|
60
|
+
throw new ArgumentException($"The active document '{document.Title}' is not the exact open document this call expected, or that identity is stale after reopening/restarting. No action was performed. Activate the intended document and read get_model_overview again.");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Retained as an additional human-readable check, never as a substitute for identity.
|
|
64
|
+
string? expected = JsonArgs.GetString(args, "expected_document");
|
|
65
|
+
if (string.IsNullOrWhiteSpace(expected)) return;
|
|
66
|
+
static string Normalize(string title)
|
|
67
|
+
{
|
|
68
|
+
string value = title.Trim();
|
|
69
|
+
return value.EndsWith(".rvt", StringComparison.OrdinalIgnoreCase) ? value[..^4] : value;
|
|
70
|
+
}
|
|
71
|
+
if (!string.Equals(Normalize(expected), Normalize(document.Title), StringComparison.OrdinalIgnoreCase))
|
|
72
|
+
throw new ArgumentException($"Active document is '{document.Title}' but this call expected title '{expected}'. No action was performed. Read get_model_overview for the intended document.");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
using Autodesk.Revit.DB;
|
|
2
|
+
|
|
3
|
+
namespace RevitBridge.Tools
|
|
4
|
+
{
|
|
5
|
+
/// <summary>
|
|
6
|
+
/// The one place a tool assigns a name or sheet number (inv:existing-objects-not-reused).
|
|
7
|
+
/// A name another object of the same kind already uses is rejected before Revit is asked,
|
|
8
|
+
/// with the existing object's identity in the failure, so the caller learns that the object
|
|
9
|
+
/// predates this call instead of silently reusing or editing it. There is deliberately no
|
|
10
|
+
/// "reuse" option: reusing someone else's object needs the user's decision, after which the
|
|
11
|
+
/// caller works with that object's ID directly. The gate in check-tool-documentation.mjs
|
|
12
|
+
/// rejects any other Name or SheetNumber assignment in a tool.
|
|
13
|
+
/// </summary>
|
|
14
|
+
internal static class ElementNames
|
|
15
|
+
{
|
|
16
|
+
public const string CollisionKey = "name_collision";
|
|
17
|
+
|
|
18
|
+
/// <summary>Rename element, or fail with the colliding object's identity.</summary>
|
|
19
|
+
public static void Assign(Element element, string name)
|
|
20
|
+
{
|
|
21
|
+
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("name must be nonempty.");
|
|
22
|
+
if (element.Name == name) return;
|
|
23
|
+
if (RequiresUniqueName(element) && FindSameKind(element, name) is { } existing) throw Collision(element, existing, "name", name);
|
|
24
|
+
element.Name = name;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// <summary>
|
|
28
|
+
/// Kinds whose names Revit keeps unique. Sheets (identified by number), rooms and other
|
|
29
|
+
/// objects may legitimately share a name, so they are not checked here.
|
|
30
|
+
/// </summary>
|
|
31
|
+
public static bool RequiresUniqueName(Element element) =>
|
|
32
|
+
element is (View and not ViewSheet) or Level or Grid or ElementType or Material or ParameterFilterElement;
|
|
33
|
+
|
|
34
|
+
/// <summary>Set a sheet number, or fail with the sheet that already has it.</summary>
|
|
35
|
+
public static void AssignSheetNumber(ViewSheet sheet, string number)
|
|
36
|
+
{
|
|
37
|
+
if (string.IsNullOrWhiteSpace(number)) throw new ArgumentException("number must be nonempty.");
|
|
38
|
+
if (sheet.SheetNumber == number) return;
|
|
39
|
+
var existing = new FilteredElementCollector(sheet.Document).OfClass(typeof(ViewSheet)).Cast<ViewSheet>()
|
|
40
|
+
.FirstOrDefault(other => other.Id != sheet.Id && other.SheetNumber == number);
|
|
41
|
+
if (existing != null) throw Collision(sheet, existing, "sheet number", number);
|
|
42
|
+
sheet.SheetNumber = number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// <summary>Create a family type in a family document, or fail when the name is taken.</summary>
|
|
46
|
+
public static FamilyType NewFamilyType(FamilyManager manager, string name)
|
|
47
|
+
{
|
|
48
|
+
EnsureFamilyTypeFree(manager, name);
|
|
49
|
+
return manager.NewType(name);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// <summary>Rename the current family type, or fail when another type has the name.</summary>
|
|
53
|
+
public static void RenameCurrentFamilyType(FamilyManager manager, string name)
|
|
54
|
+
{
|
|
55
|
+
if (manager.CurrentType?.Name == name) return;
|
|
56
|
+
EnsureFamilyTypeFree(manager, name);
|
|
57
|
+
manager.RenameCurrentType(name);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// <summary>Family types are not elements and have no ID; the collision names the type instead.</summary>
|
|
61
|
+
private static void EnsureFamilyTypeFree(FamilyManager manager, string name)
|
|
62
|
+
{
|
|
63
|
+
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("name must be nonempty.");
|
|
64
|
+
if (!manager.Types.Cast<FamilyType>().Any(type => type.Name == name)) return;
|
|
65
|
+
var error = new ArgumentException(Message("family type", "name", name, null));
|
|
66
|
+
error.Data[CollisionKey] = new Dictionary<string, object?> { ["existing_id"] = null, ["kind"] = "family type", ["name"] = name };
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// <summary>
|
|
71
|
+
/// An object of the same kind with this exact name: same class, and for views the same
|
|
72
|
+
/// view type (a floor plan and a ceiling plan may share a name). Null when the name is free.
|
|
73
|
+
/// </summary>
|
|
74
|
+
public static Element? FindSameKind(Element element, string name)
|
|
75
|
+
{
|
|
76
|
+
FilteredElementCollector collector;
|
|
77
|
+
// Some API classes (for example Room) are not native filter classes; Revit's own
|
|
78
|
+
// uniqueness check still applies to them when the name is assigned.
|
|
79
|
+
try { collector = new FilteredElementCollector(element.Document).OfClass(element.GetType()); }
|
|
80
|
+
catch (Autodesk.Revit.Exceptions.ArgumentException) { return null; }
|
|
81
|
+
return collector.FirstOrDefault(other => other.Id != element.Id && other.Name == name
|
|
82
|
+
&& (element is not View view || (other is View otherView && otherView.ViewType == view.ViewType && otherView.IsTemplate == view.IsTemplate))
|
|
83
|
+
&& (element is not ElementType || other.Category?.Id == element.Category?.Id));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// <summary>Pure message text, shared by every tool and tested offline.</summary>
|
|
87
|
+
public static string Message(string kind, string what, string value, long? existingId) =>
|
|
88
|
+
$"A {kind} with the {what} '{value}' already exists{(existingId is long id ? $" (id {id})" : "")}. Nothing was given that {what}. "
|
|
89
|
+
+ "That object existed before this call: do not edit, reuse, replace or delete it unless the user asks. "
|
|
90
|
+
+ "Ask the user, or choose a distinct value and report the collision.";
|
|
91
|
+
|
|
92
|
+
private static ArgumentException Collision(Element target, Element existing, string what, string value)
|
|
93
|
+
{
|
|
94
|
+
string kind = target is View view ? $"{view.ViewType} view" : target.GetType().Name;
|
|
95
|
+
var error = new ArgumentException(Message(kind, what, value, existing.Id.Value));
|
|
96
|
+
error.Data[CollisionKey] = new Dictionary<string, object?>
|
|
97
|
+
{
|
|
98
|
+
["existing_id"] = existing.Id.Value, ["existing_unique_id"] = existing.UniqueId, ["kind"] = kind, [what.Replace(' ', '_')] = value,
|
|
99
|
+
};
|
|
100
|
+
return error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
using System.Text.Json;
|
|
2
|
-
using System.Text.Json.Nodes;
|
|
3
|
-
|
|
4
|
-
namespace RevitBridge.Tools;
|
|
5
|
-
|
|
6
|
-
internal static class ElementQueryScope
|
|
7
|
-
{
|
|
8
|
-
public static JsonObject Schema()
|
|
9
|
-
{
|
|
10
|
-
var schema = JsonSerializer.SerializeToNode(new GetElements().ParametersSchema)!.AsObject();
|
|
11
|
-
foreach (string key in new[] { "count_only", "fields", "offset", "limit", "parameter_names", "include_type_parameters" })
|
|
12
|
-
schema["properties"]!.AsObject().Remove(key);
|
|
13
|
-
schema["description"] = "Filter the whole matching scope. Query paging and projections are not supported here.";
|
|
14
|
-
schema["additionalProperties"] = false;
|
|
15
|
-
return schema;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
public static JsonObject Parse(JsonElement args)
|
|
19
|
-
{
|
|
20
|
-
var query = args.TryGetProperty("query", out var input) ? JsonNode.Parse(input.GetRawText()) as JsonObject : new JsonObject();
|
|
21
|
-
if (query == null) throw new ArgumentException("query must be an object.");
|
|
22
|
-
var allowed = Schema()["properties"]!.AsObject();
|
|
23
|
-
foreach (var property in query)
|
|
24
|
-
if (!allowed.ContainsKey(property.Key)) throw new ArgumentException($"query does not support '{property.Key}'. It always uses the whole matching scope.");
|
|
25
|
-
return query;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using System.Text.Json.Nodes;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal static class ElementQueryScope
|
|
7
|
+
{
|
|
8
|
+
public static JsonObject Schema()
|
|
9
|
+
{
|
|
10
|
+
var schema = JsonSerializer.SerializeToNode(new GetElements().ParametersSchema)!.AsObject();
|
|
11
|
+
foreach (string key in new[] { "count_only", "fields", "offset", "limit", "parameter_names", "include_type_parameters" })
|
|
12
|
+
schema["properties"]!.AsObject().Remove(key);
|
|
13
|
+
schema["description"] = "Filter the whole matching scope. Query paging and projections are not supported here.";
|
|
14
|
+
schema["additionalProperties"] = false;
|
|
15
|
+
return schema;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public static JsonObject Parse(JsonElement args)
|
|
19
|
+
{
|
|
20
|
+
var query = args.TryGetProperty("query", out var input) ? JsonNode.Parse(input.GetRawText()) as JsonObject : new JsonObject();
|
|
21
|
+
if (query == null) throw new ArgumentException("query must be an object.");
|
|
22
|
+
var allowed = Schema()["properties"]!.AsObject();
|
|
23
|
+
foreach (var property in query)
|
|
24
|
+
if (!allowed.ContainsKey(property.Key)) throw new ArgumentException($"query does not support '{property.Key}'. It always uses the whole matching scope.");
|
|
25
|
+
return query;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
using Autodesk.Revit.DB;
|
|
2
|
+
|
|
3
|
+
namespace RevitBridge.Tools
|
|
4
|
+
{
|
|
5
|
+
/// <summary>
|
|
6
|
+
/// Shared classification of special or system-owned objects (inv:special-objects-flagged).
|
|
7
|
+
/// Tools never mix these silently into ordinary results: they either flag them here or
|
|
8
|
+
/// exclude them and count the exclusion. Only non-default traits are emitted, so
|
|
9
|
+
/// ordinary elements add nothing to a result. New tools reuse this instead of inventing
|
|
10
|
+
/// their own checks, and new trait kinds are added here once.
|
|
11
|
+
/// </summary>
|
|
12
|
+
internal static class ElementTraits
|
|
13
|
+
{
|
|
14
|
+
/// <summary>Non-default traits of an element, or null when it has none.</summary>
|
|
15
|
+
public static Dictionary<string, object>? For(Element element)
|
|
16
|
+
{
|
|
17
|
+
var traits = new Dictionary<string, object>();
|
|
18
|
+
if (element is ScheduleSheetInstance { IsTitleblockRevisionSchedule: true })
|
|
19
|
+
traits["titleblock_revision_schedule"] = true;
|
|
20
|
+
if (element is ViewSchedule { IsTitleblockRevisionSchedule: true })
|
|
21
|
+
traits["titleblock_revision_schedule"] = true;
|
|
22
|
+
if (element is ViewSheet { IsPlaceholder: true })
|
|
23
|
+
traits["placeholder_sheet"] = true;
|
|
24
|
+
if (element is View view)
|
|
25
|
+
{
|
|
26
|
+
if (view.IsTemplate)
|
|
27
|
+
traits["view_template"] = true;
|
|
28
|
+
var primary = view.GetPrimaryViewId();
|
|
29
|
+
if (primary != null && primary != ElementId.InvalidElementId)
|
|
30
|
+
traits["dependent_view_of"] = primary.Value;
|
|
31
|
+
}
|
|
32
|
+
if (element.GroupId is { } group && group != ElementId.InvalidElementId)
|
|
33
|
+
traits["group_id"] = group.Value;
|
|
34
|
+
if (element.DesignOption is { } option)
|
|
35
|
+
traits["design_option_id"] = option.Id.Value;
|
|
36
|
+
if (element.Pinned)
|
|
37
|
+
traits["pinned"] = true;
|
|
38
|
+
return traits.Count > 0 ? traits : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// <summary>
|
|
42
|
+
/// Kind of a sheet placement. A titleblock revision schedule is part of the titleblock,
|
|
43
|
+
/// not content someone placed, so it has its own kind and cannot be moved.
|
|
44
|
+
/// </summary>
|
|
45
|
+
public static string PlacementKind(Element placement) => placement switch
|
|
46
|
+
{
|
|
47
|
+
Viewport => "viewport",
|
|
48
|
+
ScheduleSheetInstance { IsTitleblockRevisionSchedule: true } => "titleblock_revision_schedule",
|
|
49
|
+
ScheduleSheetInstance => "schedule",
|
|
50
|
+
_ => "other",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -20,12 +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, JsonElement inputValues, 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
|
-
uiapp = uiApplication;
|
|
28
|
-
inputs = inputValues;
|
|
27
|
+
uiapp = uiApplication;
|
|
28
|
+
inputs = inputValues;
|
|
29
29
|
Dump = dump;
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -36,10 +36,10 @@ namespace RevitBridge.Tools
|
|
|
36
36
|
public UIDocument uidoc { get; }
|
|
37
37
|
|
|
38
38
|
/// <summary>The Revit UI application.</summary>
|
|
39
|
-
public UIApplication uiapp { get; }
|
|
40
|
-
|
|
41
|
-
/// <summary>Structured inputs supplied separately from the script source.</summary>
|
|
42
|
-
public JsonElement inputs { get; }
|
|
39
|
+
public UIApplication uiapp { get; }
|
|
40
|
+
|
|
41
|
+
/// <summary>Structured inputs supplied separately from the script source.</summary>
|
|
42
|
+
public JsonElement inputs { get; }
|
|
43
43
|
|
|
44
44
|
/// <summary>Records a value into the result's dumps[] (safely projected at call time).</summary>
|
|
45
45
|
public Action<object?> Dump { get; }
|
|
@@ -62,22 +62,31 @@ namespace RevitBridge.Tools
|
|
|
62
62
|
private const int MaxReportedErrors = 20;
|
|
63
63
|
|
|
64
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";
|
|
65
74
|
public string Label => "Execute C#";
|
|
66
|
-
public string Description => "Compile and run a C# script on the Revit API thread against the open model
|
|
67
|
-
public bool Write => true;
|
|
68
|
-
public IReadOnlyList<string> Effects => new[] { "model", "ui", "files", "external" };
|
|
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.";
|
|
76
|
+
public bool Write => true;
|
|
77
|
+
public IReadOnlyList<string> Effects => new[] { "model", "ui", "files", "external" };
|
|
69
78
|
|
|
70
79
|
public object ParametersSchema => new
|
|
71
80
|
{
|
|
72
81
|
type = "object",
|
|
73
82
|
properties = new
|
|
74
83
|
{
|
|
75
|
-
code = new
|
|
84
|
+
code = new
|
|
76
85
|
{
|
|
77
86
|
type = "string",
|
|
78
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.",
|
|
79
|
-
},
|
|
80
|
-
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." },
|
|
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." },
|
|
81
90
|
expected_document = new
|
|
82
91
|
{
|
|
83
92
|
type = "string",
|
|
@@ -87,10 +96,10 @@ namespace RevitBridge.Tools
|
|
|
87
96
|
required = new[] { "code" },
|
|
88
97
|
};
|
|
89
98
|
|
|
90
|
-
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).";
|
|
91
100
|
public IReadOnlyList<string>? PromptGuidelines => new[]
|
|
92
101
|
{
|
|
93
|
-
"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.",
|
|
94
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.",
|
|
95
104
|
};
|
|
96
105
|
|
|
@@ -102,11 +111,11 @@ namespace RevitBridge.Tools
|
|
|
102
111
|
DocumentGuard.CheckExpectedDocument(args, doc);
|
|
103
112
|
|
|
104
113
|
string code = JsonArgs.GetString(args, "code") ?? string.Empty;
|
|
105
|
-
if (string.IsNullOrWhiteSpace(code))
|
|
106
|
-
throw new ArgumentException("code must be a non-empty C# script.");
|
|
107
|
-
var inputValues = args.TryGetProperty("inputs", out var suppliedInputs) ? suppliedInputs.Clone() : JsonSerializer.SerializeToElement(new { });
|
|
108
|
-
if (inputValues.ValueKind != JsonValueKind.Object) throw new ArgumentException("inputs must be a JSON object.");
|
|
109
|
-
if (inputValues.GetRawText().Length > 100000) throw new ArgumentException("inputs must not exceed 100,000 JSON characters.");
|
|
114
|
+
if (string.IsNullOrWhiteSpace(code))
|
|
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.");
|
|
110
119
|
|
|
111
120
|
var stopwatch = Stopwatch.StartNew();
|
|
112
121
|
|
|
@@ -118,7 +127,7 @@ namespace RevitBridge.Tools
|
|
|
118
127
|
RejectAsyncCode(script.GetCompilation());
|
|
119
128
|
|
|
120
129
|
var dumps = new List<object?>();
|
|
121
|
-
var globals = new ScriptGlobals(doc, uidoc, uiapp, inputValues, value =>
|
|
130
|
+
var globals = new ScriptGlobals(doc, uidoc, uiapp, inputValues, value =>
|
|
122
131
|
{
|
|
123
132
|
if (dumps.Count < MaxDumps)
|
|
124
133
|
dumps.Add(Project(value, 0));
|
|
@@ -128,9 +137,9 @@ namespace RevitBridge.Tools
|
|
|
128
137
|
|
|
129
138
|
using var dialogGuard = new DialogGuard(uiapp);
|
|
130
139
|
using var transaction = new Transaction(doc, "execute_csharp");
|
|
131
|
-
if (transaction.Start() != TransactionStatus.Started)
|
|
132
|
-
throw new InvalidOperationException("Unable to start the execute_csharp transaction.");
|
|
133
|
-
var failureGuard = FailureGuard.Attach(transaction);
|
|
140
|
+
if (transaction.Start() != TransactionStatus.Started)
|
|
141
|
+
throw new InvalidOperationException("Unable to start the execute_csharp transaction.");
|
|
142
|
+
var failureGuard = FailureGuard.Attach(transaction);
|
|
134
143
|
|
|
135
144
|
object? returnValue;
|
|
136
145
|
string? projectionError = null;
|
|
@@ -156,24 +165,24 @@ namespace RevitBridge.Tools
|
|
|
156
165
|
}
|
|
157
166
|
catch (Exception ex)
|
|
158
167
|
{
|
|
159
|
-
throw new InvalidOperationException(
|
|
160
|
-
FormatRuntimeError(ex, dialogGuard.Suppressed) + " " + FailureGuard.RollBackAndDescribe(transaction), ex);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
try
|
|
164
|
-
{
|
|
165
|
-
var status = transaction.Commit();
|
|
166
|
-
var finalStatus = transaction.GetStatus();
|
|
167
|
-
if (status != TransactionStatus.Committed || finalStatus != TransactionStatus.Committed)
|
|
168
|
-
throw new InvalidOperationException($"The execute_csharp commit returned {status}; current transaction status is {finalStatus}.");
|
|
169
|
-
}
|
|
170
|
-
catch (Exception ex)
|
|
171
|
-
{
|
|
172
|
-
throw new InvalidOperationException(
|
|
173
|
-
$"{ex.Message} {FailureGuard.RollBackAndDescribe(transaction)}"
|
|
174
|
-
+ failureGuard.DescribeErrors()
|
|
175
|
-
+ DescribeDialogs(dialogGuard.Suppressed), ex);
|
|
176
|
-
}
|
|
168
|
+
throw new InvalidOperationException(
|
|
169
|
+
FormatRuntimeError(ex, dialogGuard.Suppressed) + " " + FailureGuard.RollBackAndDescribe(transaction), ex);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try
|
|
173
|
+
{
|
|
174
|
+
var status = transaction.Commit();
|
|
175
|
+
var finalStatus = transaction.GetStatus();
|
|
176
|
+
if (status != TransactionStatus.Committed || finalStatus != TransactionStatus.Committed)
|
|
177
|
+
throw new InvalidOperationException($"The execute_csharp commit returned {status}; current transaction status is {finalStatus}.");
|
|
178
|
+
}
|
|
179
|
+
catch (Exception ex)
|
|
180
|
+
{
|
|
181
|
+
throw new InvalidOperationException(
|
|
182
|
+
$"{ex.Message} {FailureGuard.RollBackAndDescribe(transaction)}"
|
|
183
|
+
+ failureGuard.DescribeErrors()
|
|
184
|
+
+ DescribeDialogs(dialogGuard.Suppressed), ex);
|
|
185
|
+
}
|
|
177
186
|
|
|
178
187
|
stopwatch.Stop();
|
|
179
188
|
|
|
@@ -203,8 +212,8 @@ namespace RevitBridge.Tools
|
|
|
203
212
|
Assembly.Load("netstandard"),
|
|
204
213
|
typeof(Enumerable).Assembly, // System.Linq
|
|
205
214
|
typeof(Regex).Assembly, // System.Text.RegularExpressions
|
|
206
|
-
typeof(Console).Assembly, // System.Console
|
|
207
|
-
typeof(JsonElement).Assembly, // System.Text.Json
|
|
215
|
+
typeof(Console).Assembly, // System.Console
|
|
216
|
+
typeof(JsonElement).Assembly, // System.Text.Json
|
|
208
217
|
typeof(Document).Assembly, // RevitAPI
|
|
209
218
|
typeof(UIApplication).Assembly) // RevitAPIUI
|
|
210
219
|
.WithImports("System", "System.Linq", "System.Collections.Generic", "Autodesk.Revit.DB", "Autodesk.Revit.UI")
|
|
@@ -251,7 +260,7 @@ namespace RevitBridge.Tools
|
|
|
251
260
|
string line = TryGetScriptLine(ex) is { } scriptLine ? $" at script line {scriptLine}" : string.Empty;
|
|
252
261
|
string inner = ex.InnerException is { } innerEx ? $" Inner: {innerEx.GetType().Name}: {innerEx.Message}" : string.Empty;
|
|
253
262
|
return $"C# script threw {ex.GetType().Name}{line}: {ex.Message}.{inner}"
|
|
254
|
-
+ DescribeDialogs(suppressedDialogs);
|
|
263
|
+
+ DescribeDialogs(suppressedDialogs);
|
|
255
264
|
}
|
|
256
265
|
|
|
257
266
|
private static string DescribeDialogs(IReadOnlyList<string> suppressed)
|