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,102 +1,105 @@
|
|
|
1
|
-
using System.Text.Json;
|
|
2
|
-
using Autodesk.Revit.DB;
|
|
3
|
-
|
|
4
|
-
namespace RevitBridge.Tools;
|
|
5
|
-
|
|
6
|
-
/// <summary>Runs model-only edits. Files, UI actions and arbitrary scripts must not use this preview contract.</summary>
|
|
7
|
-
internal static class ModelEditBatch
|
|
8
|
-
{
|
|
9
|
-
internal sealed record Step(Dictionary<string, object?> Target, Func<Dictionary<string, object?>> Apply);
|
|
10
|
-
|
|
11
|
-
internal sealed record Result(bool Committed, bool Preview, bool Atomic, bool CommitValidationPerformed,
|
|
12
|
-
IReadOnlyList<Dictionary<string, object?>> Succeeded, IReadOnlyList<Dictionary<string, object?>> Proposed,
|
|
13
|
-
IReadOnlyList<Dictionary<string, object?>> Failed, IReadOnlyList<string> Warnings)
|
|
14
|
-
{
|
|
15
|
-
public object Payload => new
|
|
16
|
-
{
|
|
17
|
-
updated = Committed ? Succeeded.Count : 0, committed = Committed, preview = Preview, atomic = Atomic,
|
|
18
|
-
succeeded = Succeeded, proposed = Proposed, failed = Failed, commitWarnings = Warnings,
|
|
19
|
-
commit_validation_performed = CommitValidationPerformed,
|
|
20
|
-
validation = Preview ? (CommitValidationPerformed ? "Revit commit validation completed; preview group rollback confirmed." : "Preview group rollback confirmed. Commit-time validation was not performed because the batch was rejected before commit.") : null,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
public static Result Run(Document doc, string name, JsonElement args, IReadOnlyList<Step> steps)
|
|
25
|
-
{
|
|
26
|
-
foreach (string flag in new[] { "preview", "atomic" })
|
|
27
|
-
if (args.TryGetProperty(flag, out var value) && value.ValueKind is not (JsonValueKind.True or JsonValueKind.False))
|
|
28
|
-
throw new ArgumentException($"{flag} must be a boolean; no edits were attempted.");
|
|
29
|
-
bool preview = JsonArgs.GetBool(args, "preview", false);
|
|
30
|
-
bool atomic = JsonArgs.GetBool(args, "atomic", false);
|
|
31
|
-
var accepted = new List<Dictionary<string, object?>>();
|
|
32
|
-
var failed = new List<Dictionary<string, object?>>();
|
|
33
|
-
using var group = preview ? new TransactionGroup(doc, name + " preview") : null;
|
|
34
|
-
using var transaction = new Transaction(doc, name);
|
|
35
|
-
try
|
|
36
|
-
{
|
|
37
|
-
if (group != null && group.Start() != TransactionStatus.Started) throw new InvalidOperationException("Could not start preview transaction group.");
|
|
38
|
-
if (transaction.Start() != TransactionStatus.Started) throw new InvalidOperationException("Could not start model transaction.");
|
|
39
|
-
var failures = FailureGuard.Attach(transaction);
|
|
40
|
-
for (int index = 0; index < steps.Count; index++)
|
|
41
|
-
{
|
|
42
|
-
var step = steps[index];
|
|
43
|
-
using var part = new SubTransaction(doc);
|
|
44
|
-
if (part.Start() != TransactionStatus.Started) throw new InvalidOperationException("Could not start edit subtransaction.");
|
|
45
|
-
Dictionary<string, object?> change;
|
|
46
|
-
try
|
|
47
|
-
{
|
|
48
|
-
change = step.Apply();
|
|
49
|
-
if (part.Commit() != TransactionStatus.Committed || part.GetStatus() != TransactionStatus.Committed)
|
|
50
|
-
throw new InvalidOperationException("Edit subtransaction did not commit.");
|
|
51
|
-
}
|
|
52
|
-
catch (Autodesk.Revit.Exceptions.RegenerationFailedException) { throw; }
|
|
53
|
-
catch (Exception error)
|
|
54
|
-
{
|
|
55
|
-
if (part.GetStatus() == TransactionStatus.Started) part.RollBack();
|
|
56
|
-
if (part.GetStatus() != TransactionStatus.RolledBack)
|
|
57
|
-
throw new InvalidOperationException("Edit rollback could not be confirmed; stopping the batch.", error);
|
|
58
|
-
var rejection = new Dictionary<string, object?>(step.Target) { ["index"] = index, ["reason"] = error.Message };
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
/// <summary>Runs model-only edits. Files, UI actions and arbitrary scripts must not use this preview contract.</summary>
|
|
7
|
+
internal static class ModelEditBatch
|
|
8
|
+
{
|
|
9
|
+
internal sealed record Step(Dictionary<string, object?> Target, Func<Dictionary<string, object?>> Apply);
|
|
10
|
+
|
|
11
|
+
internal sealed record Result(bool Committed, bool Preview, bool Atomic, bool CommitValidationPerformed,
|
|
12
|
+
IReadOnlyList<Dictionary<string, object?>> Succeeded, IReadOnlyList<Dictionary<string, object?>> Proposed,
|
|
13
|
+
IReadOnlyList<Dictionary<string, object?>> Failed, IReadOnlyList<string> Warnings)
|
|
14
|
+
{
|
|
15
|
+
public object Payload => new
|
|
16
|
+
{
|
|
17
|
+
updated = Committed ? Succeeded.Count : 0, committed = Committed, preview = Preview, atomic = Atomic,
|
|
18
|
+
succeeded = Succeeded, proposed = Proposed, failed = Failed, commitWarnings = Warnings,
|
|
19
|
+
commit_validation_performed = CommitValidationPerformed,
|
|
20
|
+
validation = Preview ? (CommitValidationPerformed ? "Revit commit validation completed; preview group rollback confirmed." : "Preview group rollback confirmed. Commit-time validation was not performed because the batch was rejected before commit.") : null,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public static Result Run(Document doc, string name, JsonElement args, IReadOnlyList<Step> steps)
|
|
25
|
+
{
|
|
26
|
+
foreach (string flag in new[] { "preview", "atomic" })
|
|
27
|
+
if (args.TryGetProperty(flag, out var value) && value.ValueKind is not (JsonValueKind.True or JsonValueKind.False))
|
|
28
|
+
throw new ArgumentException($"{flag} must be a boolean; no edits were attempted.");
|
|
29
|
+
bool preview = JsonArgs.GetBool(args, "preview", false);
|
|
30
|
+
bool atomic = JsonArgs.GetBool(args, "atomic", false);
|
|
31
|
+
var accepted = new List<Dictionary<string, object?>>();
|
|
32
|
+
var failed = new List<Dictionary<string, object?>>();
|
|
33
|
+
using var group = preview ? new TransactionGroup(doc, name + " preview") : null;
|
|
34
|
+
using var transaction = new Transaction(doc, name);
|
|
35
|
+
try
|
|
36
|
+
{
|
|
37
|
+
if (group != null && group.Start() != TransactionStatus.Started) throw new InvalidOperationException("Could not start preview transaction group.");
|
|
38
|
+
if (transaction.Start() != TransactionStatus.Started) throw new InvalidOperationException("Could not start model transaction.");
|
|
39
|
+
var failures = FailureGuard.Attach(transaction);
|
|
40
|
+
for (int index = 0; index < steps.Count; index++)
|
|
41
|
+
{
|
|
42
|
+
var step = steps[index];
|
|
43
|
+
using var part = new SubTransaction(doc);
|
|
44
|
+
if (part.Start() != TransactionStatus.Started) throw new InvalidOperationException("Could not start edit subtransaction.");
|
|
45
|
+
Dictionary<string, object?> change;
|
|
46
|
+
try
|
|
47
|
+
{
|
|
48
|
+
change = step.Apply();
|
|
49
|
+
if (part.Commit() != TransactionStatus.Committed || part.GetStatus() != TransactionStatus.Committed)
|
|
50
|
+
throw new InvalidOperationException("Edit subtransaction did not commit.");
|
|
51
|
+
}
|
|
52
|
+
catch (Autodesk.Revit.Exceptions.RegenerationFailedException) { throw; }
|
|
53
|
+
catch (Exception error)
|
|
54
|
+
{
|
|
55
|
+
if (part.GetStatus() == TransactionStatus.Started) part.RollBack();
|
|
56
|
+
if (part.GetStatus() != TransactionStatus.RolledBack)
|
|
57
|
+
throw new InvalidOperationException("Edit rollback could not be confirmed; stopping the batch.", error);
|
|
58
|
+
var rejection = new Dictionary<string, object?>(step.Target) { ["index"] = index, ["reason"] = error.Message };
|
|
59
|
+
// Structured facts a shared helper attached (for example ElementNames' name_collision).
|
|
60
|
+
foreach (System.Collections.DictionaryEntry entry in error.Data)
|
|
61
|
+
if (entry.Key is string key && !rejection.ContainsKey(key)) rejection[key] = entry.Value;
|
|
62
|
+
failed.Add(rejection);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
var row = new Dictionary<string, object?>(step.Target) { ["index"] = index };
|
|
66
|
+
foreach (var field in change) row[field.Key] = field.Value;
|
|
67
|
+
accepted.Add(row);
|
|
68
|
+
}
|
|
69
|
+
bool commit = accepted.Count > 0 && !(atomic && failed.Count > 0);
|
|
70
|
+
if (commit)
|
|
71
|
+
{
|
|
72
|
+
var status = transaction.Commit();
|
|
73
|
+
if (status != TransactionStatus.Committed || transaction.GetStatus() != TransactionStatus.Committed)
|
|
74
|
+
throw new InvalidOperationException($"Commit returned {status}; final status {transaction.GetStatus()}." + failures.DescribeErrors());
|
|
75
|
+
}
|
|
76
|
+
else
|
|
77
|
+
{
|
|
78
|
+
var status = transaction.RollBack();
|
|
79
|
+
if (status != TransactionStatus.RolledBack || transaction.GetStatus() != TransactionStatus.RolledBack)
|
|
80
|
+
throw new InvalidOperationException("Batch rollback could not be confirmed.");
|
|
81
|
+
}
|
|
82
|
+
if (group != null) RollBackGroup(group);
|
|
83
|
+
bool committed = commit && !preview;
|
|
84
|
+
return new Result(committed, preview, atomic, commit,
|
|
85
|
+
committed ? accepted : new List<Dictionary<string, object?>>(),
|
|
86
|
+
committed ? new List<Dictionary<string, object?>>() : accepted, failed, failures.Warnings);
|
|
87
|
+
}
|
|
88
|
+
catch (Exception error)
|
|
89
|
+
{
|
|
90
|
+
string cleanup = FailureGuard.RollBackAndDescribe(transaction);
|
|
91
|
+
if (group != null)
|
|
92
|
+
{
|
|
93
|
+
try { RollBackGroup(group); cleanup += " Preview group rollback confirmed."; }
|
|
94
|
+
catch (Exception rollback) { cleanup += " Preview group rollback not confirmed: " + rollback.Message; }
|
|
95
|
+
}
|
|
96
|
+
throw new InvalidOperationException(error.Message + " " + cleanup, error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private static void RollBackGroup(TransactionGroup group)
|
|
101
|
+
{
|
|
102
|
+
if (group.GetStatus() == TransactionStatus.Started) group.RollBack();
|
|
103
|
+
if (group.GetStatus() != TransactionStatus.RolledBack) throw new InvalidOperationException($"Preview group status is {group.GetStatus()}.");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
using System.Text.Json;
|
|
2
|
-
using Autodesk.Revit.DB;
|
|
3
|
-
|
|
4
|
-
namespace RevitBridge.Tools;
|
|
5
|
-
|
|
6
|
-
internal static class ModelEditInputs
|
|
7
|
-
{
|
|
8
|
-
public static object IdsSchema => new { type = "array", minItems = 1, maxItems = 200, uniqueItems = true, items = new { type = "integer", minimum = 1 } };
|
|
9
|
-
public static object PreviewSchema => new { type = "boolean", description = "Commit-validate then roll back all model edits. Default false." };
|
|
10
|
-
public static object LengthUnitSchema => new { type = "string", @enum = new[] { "millimeters", "centimeters", "meters", "feet", "inches" } };
|
|
11
|
-
public static object VectorSchema(string description) => new { type = "array", minItems = 3, maxItems = 3, items = new { type = "number" }, description };
|
|
12
|
-
public static List<ElementId> Ids(JsonElement args)
|
|
13
|
-
{
|
|
14
|
-
var ids = JsonArgs.GetLongArray(args, "element_ids");
|
|
15
|
-
if (ids.Count is < 1 or > 200 || ids.Any(id => id <= 0) || ids.Distinct().Count() != ids.Count)
|
|
16
|
-
throw new ArgumentException("element_ids must contain 1–200 distinct positive IDs.");
|
|
17
|
-
return ids.Select(id => new ElementId(id)).ToList();
|
|
18
|
-
}
|
|
19
|
-
public static double LengthScale(JsonElement args) => JsonArgs.GetString(args, "unit") switch
|
|
20
|
-
{
|
|
21
|
-
"millimeters" => 1 / 304.8, "centimeters" => 1 / 30.48, "meters" => 1 / 0.3048,
|
|
22
|
-
"feet" => 1, "inches" => 1 / 12.0,
|
|
23
|
-
_ => throw new ArgumentException("unit must be millimeters, centimeters, meters, feet or inches."),
|
|
24
|
-
};
|
|
25
|
-
public static double Number(JsonElement args, string name)
|
|
26
|
-
{
|
|
27
|
-
if (!args.TryGetProperty(name, out var value) || value.ValueKind != JsonValueKind.Number || !value.TryGetDouble(out double number) || !double.IsFinite(number))
|
|
28
|
-
throw new ArgumentException($"{name} must be a finite number.");
|
|
29
|
-
return number;
|
|
30
|
-
}
|
|
31
|
-
public static XYZ Vector(JsonElement args, string name)
|
|
32
|
-
{
|
|
33
|
-
if (!args.TryGetProperty(name, out var values) || values.ValueKind != JsonValueKind.Array || values.GetArrayLength() != 3)
|
|
34
|
-
throw new ArgumentException($"{name} must contain exactly three finite numbers.");
|
|
35
|
-
var numbers = values.EnumerateArray().Select(v => v.ValueKind == JsonValueKind.Number && v.TryGetDouble(out double n) && double.IsFinite(n) ? n : throw new ArgumentException($"{name} must contain finite numbers.")).ToArray();
|
|
36
|
-
return new XYZ(numbers[0], numbers[1], numbers[2]);
|
|
37
|
-
}
|
|
38
|
-
public static object Snapshot(Element element)
|
|
39
|
-
{
|
|
40
|
-
static double[] Point(XYZ p) => new[] { p.X, p.Y, p.Z };
|
|
41
|
-
object? location = element.Location switch
|
|
42
|
-
{
|
|
43
|
-
LocationPoint p => new { kind = "point", point = Point(p.Point) },
|
|
44
|
-
LocationCurve c when c.Curve.IsBound => new { kind = "curve_endpoints", start = Point(c.Curve.GetEndPoint(0)), end = Point(c.Curve.GetEndPoint(1)) } as object,
|
|
45
|
-
_ => null,
|
|
46
|
-
};
|
|
47
|
-
return new { id = element.Id.Value, unique_id = element.UniqueId, name = element.Name, type_id = element.GetTypeId().Value, location };
|
|
48
|
-
}
|
|
49
|
-
}
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal static class ModelEditInputs
|
|
7
|
+
{
|
|
8
|
+
public static object IdsSchema => new { type = "array", minItems = 1, maxItems = 200, uniqueItems = true, items = new { type = "integer", minimum = 1 } };
|
|
9
|
+
public static object PreviewSchema => new { type = "boolean", description = "Commit-validate then roll back all model edits. Default false." };
|
|
10
|
+
public static object LengthUnitSchema => new { type = "string", @enum = new[] { "millimeters", "centimeters", "meters", "feet", "inches" } };
|
|
11
|
+
public static object VectorSchema(string description) => new { type = "array", minItems = 3, maxItems = 3, items = new { type = "number" }, description };
|
|
12
|
+
public static List<ElementId> Ids(JsonElement args)
|
|
13
|
+
{
|
|
14
|
+
var ids = JsonArgs.GetLongArray(args, "element_ids");
|
|
15
|
+
if (ids.Count is < 1 or > 200 || ids.Any(id => id <= 0) || ids.Distinct().Count() != ids.Count)
|
|
16
|
+
throw new ArgumentException("element_ids must contain 1–200 distinct positive IDs.");
|
|
17
|
+
return ids.Select(id => new ElementId(id)).ToList();
|
|
18
|
+
}
|
|
19
|
+
public static double LengthScale(JsonElement args) => JsonArgs.GetString(args, "unit") switch
|
|
20
|
+
{
|
|
21
|
+
"millimeters" => 1 / 304.8, "centimeters" => 1 / 30.48, "meters" => 1 / 0.3048,
|
|
22
|
+
"feet" => 1, "inches" => 1 / 12.0,
|
|
23
|
+
_ => throw new ArgumentException("unit must be millimeters, centimeters, meters, feet or inches."),
|
|
24
|
+
};
|
|
25
|
+
public static double Number(JsonElement args, string name)
|
|
26
|
+
{
|
|
27
|
+
if (!args.TryGetProperty(name, out var value) || value.ValueKind != JsonValueKind.Number || !value.TryGetDouble(out double number) || !double.IsFinite(number))
|
|
28
|
+
throw new ArgumentException($"{name} must be a finite number.");
|
|
29
|
+
return number;
|
|
30
|
+
}
|
|
31
|
+
public static XYZ Vector(JsonElement args, string name)
|
|
32
|
+
{
|
|
33
|
+
if (!args.TryGetProperty(name, out var values) || values.ValueKind != JsonValueKind.Array || values.GetArrayLength() != 3)
|
|
34
|
+
throw new ArgumentException($"{name} must contain exactly three finite numbers.");
|
|
35
|
+
var numbers = values.EnumerateArray().Select(v => v.ValueKind == JsonValueKind.Number && v.TryGetDouble(out double n) && double.IsFinite(n) ? n : throw new ArgumentException($"{name} must contain finite numbers.")).ToArray();
|
|
36
|
+
return new XYZ(numbers[0], numbers[1], numbers[2]);
|
|
37
|
+
}
|
|
38
|
+
public static object Snapshot(Element element)
|
|
39
|
+
{
|
|
40
|
+
static double[] Point(XYZ p) => new[] { p.X, p.Y, p.Z };
|
|
41
|
+
object? location = element.Location switch
|
|
42
|
+
{
|
|
43
|
+
LocationPoint p => new { kind = "point", point = Point(p.Point) },
|
|
44
|
+
LocationCurve c when c.Curve.IsBound => new { kind = "curve_endpoints", start = Point(c.Curve.GetEndPoint(0)), end = Point(c.Curve.GetEndPoint(1)) } as object,
|
|
45
|
+
_ => null,
|
|
46
|
+
};
|
|
47
|
+
return new { id = element.Id.Value, unique_id = element.UniqueId, name = element.Name, type_id = element.GetTypeId().Value, location };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -13,10 +13,17 @@ namespace RevitBridge.Tools
|
|
|
13
13
|
internal sealed class OpenView : ITool
|
|
14
14
|
{
|
|
15
15
|
public string Name => "open_view";
|
|
16
|
+
public IReadOnlyList<string> DocumentKinds => DocumentKind.Both;
|
|
17
|
+
public IReadOnlyList<string> Keywords => new[] { "open", "show", "activate", "switch view", "go to sheet", "display" };
|
|
18
|
+
public IReadOnlyList<ToolLimit> Limits => new[]
|
|
19
|
+
{
|
|
20
|
+
new ToolLimit("Creating views", "tool", "manage_views"),
|
|
21
|
+
};
|
|
22
|
+
public string? Verification => "none";
|
|
16
23
|
public string Label => "Open View";
|
|
17
24
|
public string Description => "Activate a view or sheet in the Revit UI, like double-clicking it in the Project Browser. Identify the target by view_id (an id from get_elements category Views/Sheets or from view/sheet creation) or by name (exact view name, sheet number like 'A-101', or sheet number - name; case-insensitive). Activation is queued and completes the instant this call returns control to Revit — a capture_view immediately after may still show the previous active view. View templates and internal views cannot be opened.";
|
|
18
|
-
public bool Write => false;
|
|
19
|
-
public IReadOnlyList<string> Effects => new[] { "ui" };
|
|
25
|
+
public bool Write => false;
|
|
26
|
+
public IReadOnlyList<string> Effects => new[] { "ui" };
|
|
20
27
|
|
|
21
28
|
public object ParametersSchema => new
|
|
22
29
|
{
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
using Autodesk.Revit.DB;
|
|
2
|
+
|
|
3
|
+
namespace RevitBridge.Tools
|
|
4
|
+
{
|
|
5
|
+
/// <summary>
|
|
6
|
+
/// A caller's parameter reference: <c>guid:<GUID></c> for a shared parameter, a
|
|
7
|
+
/// BuiltInParameter name (SHOUTY_SNAKE_CASE or <c>BuiltInParameter.NAME</c>), or a
|
|
8
|
+
/// localized display name. One parsing rule for every tool.
|
|
9
|
+
/// </summary>
|
|
10
|
+
internal readonly record struct ParameterReference(string Input, BuiltInParameter? BuiltIn, Guid? SharedGuid)
|
|
11
|
+
{
|
|
12
|
+
public bool IsDisplayName => BuiltIn is null && SharedGuid is null;
|
|
13
|
+
|
|
14
|
+
public static ParameterReference Parse(string input)
|
|
15
|
+
{
|
|
16
|
+
string text = input.Trim();
|
|
17
|
+
if (text.StartsWith("guid:", StringComparison.OrdinalIgnoreCase))
|
|
18
|
+
return Guid.TryParse(text["guid:".Length..], out var guid)
|
|
19
|
+
? new ParameterReference(text, null, guid)
|
|
20
|
+
: throw new ArgumentException($"Invalid shared parameter guid: {text}");
|
|
21
|
+
string enumName = text.StartsWith("BuiltInParameter.", StringComparison.OrdinalIgnoreCase)
|
|
22
|
+
? text["BuiltInParameter.".Length..]
|
|
23
|
+
: text;
|
|
24
|
+
// BuiltInParameter names are SHOUTY_SNAKE_CASE; require an underscore or
|
|
25
|
+
// all-caps so plain display names like "Comments" never collide.
|
|
26
|
+
bool looksLikeEnumName = enumName.Length > 0 && char.IsLetter(enumName[0]) && !enumName.Contains(' ')
|
|
27
|
+
&& (enumName.Contains('_') || enumName.All(c => !char.IsLetter(c) || char.IsUpper(c)));
|
|
28
|
+
if (looksLikeEnumName && Enum.TryParse<BuiltInParameter>(enumName, true, out var parsed) && parsed != BuiltInParameter.INVALID)
|
|
29
|
+
return new ParameterReference(text, parsed, null);
|
|
30
|
+
return new ParameterReference(text, null, null);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// <summary>
|
|
35
|
+
/// The single parameter-resolution policy for every tool (inv:parameter-ambiguity).
|
|
36
|
+
/// Exact identities resolve to at most one parameter. A display name returns every
|
|
37
|
+
/// parameter with that name (exact case first, then case-insensitive), because Revit
|
|
38
|
+
/// elements can carry several same-named parameters and <c>LookupParameter</c> would
|
|
39
|
+
/// silently return an arbitrary one. Writes and filters use <see cref="FindSingle"/>,
|
|
40
|
+
/// which rejects ambiguity with the candidates' exact identities; projections use
|
|
41
|
+
/// <see cref="FindAll"/> and report ambiguity. Missing (null) is distinct from empty.
|
|
42
|
+
/// </summary>
|
|
43
|
+
internal static class ParameterResolver
|
|
44
|
+
{
|
|
45
|
+
public static IReadOnlyList<Parameter> FindAll(Element element, ParameterReference reference)
|
|
46
|
+
{
|
|
47
|
+
if (reference.BuiltIn is { } builtIn)
|
|
48
|
+
return One(element.get_Parameter(builtIn));
|
|
49
|
+
if (reference.SharedGuid is { } guid)
|
|
50
|
+
return One(element.get_Parameter(guid));
|
|
51
|
+
// GetParameters is Revit's native exact-name lookup returning every match.
|
|
52
|
+
var exact = element.GetParameters(reference.Input);
|
|
53
|
+
if (exact.Count > 0)
|
|
54
|
+
return Distinct(exact);
|
|
55
|
+
var folded = new List<Parameter>();
|
|
56
|
+
foreach (Parameter parameter in element.Parameters)
|
|
57
|
+
if (string.Equals(parameter.Definition?.Name, reference.Input, StringComparison.OrdinalIgnoreCase))
|
|
58
|
+
folded.Add(parameter);
|
|
59
|
+
return Distinct(folded);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// <summary>The one matching parameter, null when missing; throws when ambiguous.</summary>
|
|
63
|
+
public static Parameter? FindSingle(Element element, ParameterReference reference)
|
|
64
|
+
{
|
|
65
|
+
var matches = FindAll(element, reference);
|
|
66
|
+
return matches.Count switch
|
|
67
|
+
{
|
|
68
|
+
0 => null,
|
|
69
|
+
1 => matches[0],
|
|
70
|
+
_ => throw new ArgumentException(AmbiguityMessage(element, reference, matches)),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public static string AmbiguityMessage(Element element, ParameterReference reference, IReadOnlyList<Parameter> matches)
|
|
75
|
+
=> $"Parameter '{reference.Input}' is ambiguous on element {element.Id.Value}: {matches.Count} parameters share that display name "
|
|
76
|
+
+ $"({string.Join("; ", matches.Select(Identity))}). Nothing was chosen. Use one of these exact identities instead.";
|
|
77
|
+
|
|
78
|
+
/// <summary>The language-independent identity a caller can pass back.</summary>
|
|
79
|
+
public static string Identity(Parameter parameter)
|
|
80
|
+
{
|
|
81
|
+
if (parameter.IsShared)
|
|
82
|
+
return $"guid:{parameter.GUID}";
|
|
83
|
+
if (parameter.Definition is InternalDefinition definition && definition.BuiltInParameter != BuiltInParameter.INVALID)
|
|
84
|
+
return definition.BuiltInParameter.ToString();
|
|
85
|
+
return $"'{parameter.Definition?.Name}' (parameter id {parameter.Id.Value}, no language-independent identity)";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private static IReadOnlyList<Parameter> One(Parameter? parameter)
|
|
89
|
+
=> parameter is null ? Array.Empty<Parameter>() : new[] { parameter };
|
|
90
|
+
|
|
91
|
+
private static IReadOnlyList<Parameter> Distinct(IEnumerable<Parameter> parameters)
|
|
92
|
+
=> parameters.GroupBy(parameter => parameter.Id.Value).Select(group => group.First()).ToList();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -1,63 +1,70 @@
|
|
|
1
|
-
using System.Text.Json;
|
|
2
|
-
using Autodesk.Revit.DB;
|
|
3
|
-
|
|
4
|
-
namespace RevitBridge.Tools;
|
|
5
|
-
|
|
6
|
-
internal sealed class QuerySpatialElements : ITool
|
|
7
|
-
{
|
|
8
|
-
public string Name => "query_spatial_elements";
|
|
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
|
-
var
|
|
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
|
-
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal sealed class QuerySpatialElements : ITool
|
|
7
|
+
{
|
|
8
|
+
public string Name => "query_spatial_elements";
|
|
9
|
+
public IReadOnlyList<string> DocumentKinds => DocumentKind.ProjectOnly;
|
|
10
|
+
public IReadOnlyList<string> Keywords => new[] { "inside", "within", "contained", "intersect", "bounding box", "spatial", "near", "clash candidates", "overlap" };
|
|
11
|
+
public IReadOnlyList<ToolLimit> Limits => new[]
|
|
12
|
+
{
|
|
13
|
+
new ToolLimit("Solid-intersection clash detection", "api", "ElementIntersectsElementFilter; ElementIntersectsSolidFilter"),
|
|
14
|
+
new ToolLimit("Elements inside linked models", "tool", "get_linked_elements with host_bounds"),
|
|
15
|
+
};
|
|
16
|
+
public string Label => "Query Spatial Elements";
|
|
17
|
+
public string Tier => "advanced";
|
|
18
|
+
public string Description => "Find host elements whose model axis-aligned bounding boxes intersect or lie inside an explicit region. Coordinates use document internal axes and the required length unit. query uses get_elements filters, covering the whole scope (maximum 10,000 candidates). Includes touching boundaries; elements without bounds are counted separately. Bounds may include nonphysical geometry, so these are approximate candidates, not solid intersections or clashes. Linked contents are not traversed. Results are ordered by element ID with paging.";
|
|
19
|
+
public object ParametersSchema => new
|
|
20
|
+
{
|
|
21
|
+
type = "object", properties = new
|
|
22
|
+
{
|
|
23
|
+
query = ElementQueryScope.Schema(),
|
|
24
|
+
min = ModelEditInputs.VectorSchema("Region minimum in document internal axes, in unit."),
|
|
25
|
+
max = ModelEditInputs.VectorSchema("Region maximum in document internal axes, in unit."),
|
|
26
|
+
unit = ModelEditInputs.LengthUnitSchema,
|
|
27
|
+
relation = new { type = "string", @enum = new[] { "intersects", "inside" } },
|
|
28
|
+
offset = new { type = "integer", minimum = 0 }, limit = new { type = "integer", minimum = 1, maximum = 200 },
|
|
29
|
+
}, required = new[] { "min", "max", "unit" },
|
|
30
|
+
};
|
|
31
|
+
public object Execute(JsonElement args, ToolContext context)
|
|
32
|
+
{
|
|
33
|
+
var doc = context.Document ?? throw new NoActiveDocumentException();
|
|
34
|
+
double scale = ModelEditInputs.LengthScale(args);
|
|
35
|
+
var region = new SpatialBounds(ModelEditInputs.Vector(args, "min").Multiply(scale), ModelEditInputs.Vector(args, "max").Multiply(scale));
|
|
36
|
+
if (region.Min.X > region.Max.X || region.Min.Y > region.Max.Y || region.Min.Z > region.Max.Z)
|
|
37
|
+
throw new ArgumentException("Each min coordinate must be less than or equal to max.");
|
|
38
|
+
string relation = JsonArgs.GetString(args, "relation") ?? "intersects";
|
|
39
|
+
if (relation is not ("intersects" or "inside")) throw new ArgumentException("relation must be intersects or inside.");
|
|
40
|
+
var query = ElementQueryScope.Parse(args);
|
|
41
|
+
JsonElement RunQuery() => JsonSerializer.SerializeToElement(((ToolOutput)new GetElements().Execute(JsonSerializer.SerializeToElement(query), context)!).Payload);
|
|
42
|
+
query["count_only"] = true;
|
|
43
|
+
var counted = RunQuery();
|
|
44
|
+
int count = counted.GetProperty("total_count").GetInt32();
|
|
45
|
+
if (count > 10000) throw new ArgumentException($"Query has {count} candidates; narrow query to at most 10,000.");
|
|
46
|
+
object? warnings = counted.TryGetProperty("warnings", out var warning) ? warning.Clone() : null;
|
|
47
|
+
query["count_only"] = false; query["limit"] = 1000;
|
|
48
|
+
var matches = new List<(Element Element, SpatialBounds Bounds)>();
|
|
49
|
+
int withoutBounds = 0;
|
|
50
|
+
for (int start = 0; start < count; start += 1000)
|
|
51
|
+
{
|
|
52
|
+
query["offset"] = start;
|
|
53
|
+
foreach (var row in RunQuery().GetProperty("elements").EnumerateArray())
|
|
54
|
+
{
|
|
55
|
+
var element = doc.GetElement(new ElementId(row.GetProperty("id").GetInt64()));
|
|
56
|
+
var bounds = SpatialBounds.Of(element);
|
|
57
|
+
if (bounds == null) { withoutBounds++; continue; }
|
|
58
|
+
if (relation == "inside" ? region.Contains(bounds) : region.Intersects(bounds)) matches.Add((element, bounds));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
int offset = Math.Max(0, JsonArgs.GetInt(args, "offset", 0)), limit = Math.Clamp(JsonArgs.GetInt(args, "limit", 100), 1, 200);
|
|
62
|
+
var elements = matches.OrderBy(x => x.Element.Id.Value).Skip(offset).Take(limit).Select(x => new
|
|
63
|
+
{
|
|
64
|
+
id = x.Element.Id.Value, unique_id = x.Element.UniqueId, name = x.Element.Name, category = x.Element.Category?.Name, bounds = x.Bounds.Describe(scale),
|
|
65
|
+
}).ToArray();
|
|
66
|
+
return new { method = "axis_aligned_bounding_boxes", approximate = true, coordinate_system = "document_internal", unit = JsonArgs.GetString(args, "unit"),
|
|
67
|
+
relation, region = region.Describe(scale), candidate_count = count, without_bounds_count = withoutBounds, warnings,
|
|
68
|
+
total_count = matches.Count, offset, returned_count = elements.Length, next_offset = offset + elements.Length < matches.Count ? (int?)(offset + elements.Length) : null, elements };
|
|
69
|
+
}
|
|
70
|
+
}
|