pi-revit 0.3.1 → 0.4.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/CHANGELOG.md +388 -351
- package/README.md +426 -22
- package/extensions/pi-revit/index.ts +246 -187
- package/extensions/pi-revit/instance-router.ts +86 -0
- package/extensions/pi-revit/script-library.ts +146 -0
- package/extensions/pi-revit/tool-catalog.ts +67 -0
- package/package.json +59 -59
- package/skills/pi-revit/SKILL.md +196 -32
- package/skills/pi-revit/references/model-audit-export.md +29 -0
- package/skills/pi-revit/references/room-documentation.md +28 -0
- package/src/Revit/BridgeServer.cs +87 -37
- package/src/Revit/OperationStore.cs +178 -0
- package/src/Revit/ToolRegistry.cs +57 -35
- package/src/Revit/Tools/CaptureView.cs +2 -1
- package/src/Revit/Tools/ChangeElementTypes.cs +60 -0
- package/src/Revit/Tools/CreateTags.cs +95 -0
- package/src/Revit/Tools/DeleteElements.cs +44 -0
- package/src/Revit/Tools/DocumentGuard.cs +64 -64
- package/src/Revit/Tools/ElementQueryScope.cs +27 -0
- package/src/Revit/Tools/ExecuteCsharp.cs +45 -35
- package/src/Revit/Tools/ExportDocuments.cs +121 -120
- package/src/Revit/Tools/FailureGuard.cs +26 -26
- package/src/Revit/Tools/GetElementDetails.cs +42 -12
- package/src/Revit/Tools/GetElementRelationships.cs +76 -0
- package/src/Revit/Tools/GetElements.cs +37 -23
- package/src/Revit/Tools/GetLinkedElements.cs +82 -0
- package/src/Revit/Tools/GetLinkedModels.cs +66 -0
- package/src/Revit/Tools/GetModelCoordinates.cs +49 -0
- package/src/Revit/Tools/GetModelOverview.cs +2 -2
- package/src/Revit/Tools/GetScheduleFields.cs +37 -0
- package/src/Revit/Tools/GetSchedules.cs +89 -0
- package/src/Revit/Tools/ManageElementSets.cs +106 -0
- package/src/Revit/Tools/ManageSchedules.cs +164 -0
- package/src/Revit/Tools/ManageSelection.cs +37 -36
- package/src/Revit/Tools/ManageSheetPlacements.cs +97 -0
- package/src/Revit/Tools/ManageSheets.cs +63 -0
- package/src/Revit/Tools/ManageViews.cs +100 -0
- package/src/Revit/Tools/MeasureGeometry.cs +54 -0
- package/src/Revit/Tools/ModelEditBatch.cs +102 -0
- package/src/Revit/Tools/ModelEditInputs.cs +49 -0
- package/src/Revit/Tools/OpenView.cs +2 -1
- package/src/Revit/Tools/QuerySpatialElements.cs +63 -0
- package/src/Revit/Tools/SetParameters.cs +43 -93
- package/src/Revit/Tools/SpatialBounds.cs +30 -0
- package/src/Revit/Tools/SummarizeElements.cs +87 -0
- package/src/Revit/Tools/ToolSupport.cs +1 -1
- package/src/Revit/Tools/TransformElements.cs +58 -0
- package/workspace/AGENTS.md +45 -45
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using System.Text.Json.Nodes;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal sealed class SummarizeElements : ITool
|
|
7
|
+
{
|
|
8
|
+
private const int MaxElements = 10000;
|
|
9
|
+
public string Name => "summarize_elements";
|
|
10
|
+
public string Label => "Summarize Elements";
|
|
11
|
+
public string Tier => "advanced";
|
|
12
|
+
public string Description => "Count matching host-model elements grouped by category, type, level or one parameter value. Accepts get_elements category/class/level/type/filter scopes. At most 10,000 matches; narrow larger queries. Parameter grouping uses exact raw values, with a distinct missing-value group. Ambiguous display-name parameters are rejected. Counts cover the entire matching scope, not just the returned groups page.";
|
|
13
|
+
public object ParametersSchema
|
|
14
|
+
{
|
|
15
|
+
get
|
|
16
|
+
{
|
|
17
|
+
var query = ElementQueryScope.Schema();
|
|
18
|
+
return new
|
|
19
|
+
{
|
|
20
|
+
type = "object",
|
|
21
|
+
properties = new
|
|
22
|
+
{
|
|
23
|
+
query,
|
|
24
|
+
group_by = new { type = "string", @enum = new[] { "category", "typeName", "levelId", "parameter" } },
|
|
25
|
+
parameter = new { type = "string", description = "Required for group_by=parameter. Name, built-in identity or guid:<GUID>." },
|
|
26
|
+
type_parameter = new { type = "boolean", description = "Group by the parameter on the element type instead of the instance." },
|
|
27
|
+
offset = new { type = "integer", minimum = 0 },
|
|
28
|
+
limit = new { type = "integer", minimum = 1, maximum = 500 },
|
|
29
|
+
},
|
|
30
|
+
required = new[] { "group_by" },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public object Execute(JsonElement args, ToolContext context)
|
|
36
|
+
{
|
|
37
|
+
string groupBy = JsonArgs.GetString(args, "group_by") ?? "";
|
|
38
|
+
if (groupBy is not ("category" or "typeName" or "levelId" or "parameter")) throw new ArgumentException("Unknown group_by value.");
|
|
39
|
+
string? parameter = JsonArgs.GetString(args, "parameter");
|
|
40
|
+
if (groupBy == "parameter" && string.IsNullOrWhiteSpace(parameter)) throw new ArgumentException("parameter is required for parameter grouping.");
|
|
41
|
+
bool typeParameter = JsonArgs.GetBool(args, "type_parameter", false);
|
|
42
|
+
var query = ElementQueryScope.Parse(args);
|
|
43
|
+
JsonElement RunQuery() => JsonSerializer.SerializeToElement(((ToolOutput)new GetElements().Execute(JsonSerializer.SerializeToElement(query), context)!).Payload);
|
|
44
|
+
query["count_only"] = true;
|
|
45
|
+
var countResult = RunQuery();
|
|
46
|
+
int total = countResult.GetProperty("total_count").GetInt32();
|
|
47
|
+
object? warnings = countResult.TryGetProperty("warnings", out var queryWarnings) ? queryWarnings.Clone() : null;
|
|
48
|
+
if (total > MaxElements) throw new ArgumentException($"Query matches {total} elements; narrow the scope to at most {MaxElements}.");
|
|
49
|
+
query["count_only"] = false;
|
|
50
|
+
query["limit"] = 1000;
|
|
51
|
+
if (groupBy == "parameter")
|
|
52
|
+
{
|
|
53
|
+
query["parameter_names"] = new JsonArray(parameter);
|
|
54
|
+
query["include_type_parameters"] = typeParameter;
|
|
55
|
+
}
|
|
56
|
+
var counts = new Dictionary<string, (object? Value, bool Missing, int Count)>();
|
|
57
|
+
for (int position = 0; position < total; position += 1000)
|
|
58
|
+
{
|
|
59
|
+
query["offset"] = position;
|
|
60
|
+
foreach (var row in RunQuery().GetProperty("elements").EnumerateArray())
|
|
61
|
+
{
|
|
62
|
+
object? value = null;
|
|
63
|
+
bool missing = false;
|
|
64
|
+
if (groupBy == "parameter")
|
|
65
|
+
{
|
|
66
|
+
var projection = row.GetProperty("parameters").EnumerateArray().FirstOrDefault(p => p.GetProperty("isType").GetBoolean() == typeParameter);
|
|
67
|
+
missing = projection.ValueKind == JsonValueKind.Undefined || !projection.GetProperty("found").GetBoolean();
|
|
68
|
+
if (!missing)
|
|
69
|
+
{
|
|
70
|
+
if (projection.GetProperty("ambiguous").GetBoolean()) throw new ArgumentException($"Parameter '{parameter}' is ambiguous on element {row.GetProperty("id")}; use a built-in identity or shared GUID.");
|
|
71
|
+
value = projection.GetProperty("matches")[0].GetProperty("value").Clone();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else if (row.TryGetProperty(groupBy, out var field)) value = field.Clone();
|
|
75
|
+
string key = (missing ? "missing:" : "value:") + JsonSerializer.Serialize(value);
|
|
76
|
+
counts[key] = counts.TryGetValue(key, out var previous) ? (value, missing, previous.Count + 1) : (value, missing, 1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
int offset = Math.Max(0, JsonArgs.GetInt(args, "offset", 0));
|
|
80
|
+
int limit = Math.Clamp(JsonArgs.GetInt(args, "limit", 100), 1, 500);
|
|
81
|
+
var groups = counts.OrderByDescending(pair => pair.Value.Count).ThenBy(pair => pair.Key, StringComparer.Ordinal).Skip(offset).Take(limit)
|
|
82
|
+
.Select(pair => new { value = pair.Value.Value, missing = pair.Value.Missing, count = pair.Value.Count }).ToArray();
|
|
83
|
+
return new { total_elements = total, total_groups = counts.Count, group_by = groupBy, parameter, type_parameter = typeParameter,
|
|
84
|
+
numeric_values = "Revit internal units", warnings, offset, returned_count = groups.Length,
|
|
85
|
+
next_offset = offset + groups.Length < counts.Count ? (int?)(offset + groups.Length) : null, groups };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal sealed class TransformElements : ITool
|
|
7
|
+
{
|
|
8
|
+
public string Name => "transform_elements";
|
|
9
|
+
public string Label => "Transform Elements";
|
|
10
|
+
public string Tier => "advanced";
|
|
11
|
+
public bool Write => true;
|
|
12
|
+
public string Description => "Move, copy or rotate 1–200 elements together in the active document. Coordinates use the document internal origin and axes; unit is required. Rotation uses a right-handed axis and angle_degrees. The whole selection succeeds or rolls back as one step; pinned elements are never automatically unpinned. Revit may move constrained/hosted dependents too. preview=true validates commit then rolls back; any created IDs in a preview are temporary and must not be reused. Snapshots describe requested elements, not a complete dependent-change audit.";
|
|
13
|
+
public object ParametersSchema => new
|
|
14
|
+
{
|
|
15
|
+
type = "object", properties = new
|
|
16
|
+
{
|
|
17
|
+
action = new { type = "string", @enum = new[] { "move", "copy", "rotate" } },
|
|
18
|
+
element_ids = ModelEditInputs.IdsSchema,
|
|
19
|
+
unit = ModelEditInputs.LengthUnitSchema,
|
|
20
|
+
translation = ModelEditInputs.VectorSchema("Move/copy displacement [x,y,z] in unit."),
|
|
21
|
+
axis_origin = ModelEditInputs.VectorSchema("Rotation axis origin [x,y,z] in unit, relative to internal origin."),
|
|
22
|
+
axis_direction = ModelEditInputs.VectorSchema("Nonzero rotation axis direction [x,y,z], dimensionless."),
|
|
23
|
+
angle_degrees = new { type = "number", description = "Signed right-hand rotation angle in degrees." },
|
|
24
|
+
preview = ModelEditInputs.PreviewSchema,
|
|
25
|
+
}, required = new[] { "action", "element_ids", "unit" },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
public object Execute(JsonElement args, ToolContext context)
|
|
29
|
+
{
|
|
30
|
+
var doc = context.Document ?? throw new NoActiveDocumentException();
|
|
31
|
+
var ids = ModelEditInputs.Ids(args);
|
|
32
|
+
string action = JsonArgs.GetString(args, "action") ?? "";
|
|
33
|
+
if (action is not ("move" or "copy" or "rotate")) throw new ArgumentException("action must be move, copy or rotate.");
|
|
34
|
+
double scale = ModelEditInputs.LengthScale(args);
|
|
35
|
+
XYZ? translation = action != "rotate" ? ModelEditInputs.Vector(args, "translation").Multiply(scale) : null;
|
|
36
|
+
XYZ? origin = action == "rotate" ? ModelEditInputs.Vector(args, "axis_origin").Multiply(scale) : null;
|
|
37
|
+
XYZ? direction = action == "rotate" ? ModelEditInputs.Vector(args, "axis_direction") : null;
|
|
38
|
+
double angle = action == "rotate" ? ModelEditInputs.Number(args, "angle_degrees") * Math.PI / 180 : 0;
|
|
39
|
+
if (direction != null && direction.GetLength() < 1e-12) throw new ArgumentException("axis_direction must be nonzero.");
|
|
40
|
+
var result = ModelEditBatch.Run(doc, Name, args, new[] { new ModelEditBatch.Step(
|
|
41
|
+
new() { ["action"] = action, ["element_ids"] = ids.Select(x => x.Value).ToArray() }, () =>
|
|
42
|
+
{
|
|
43
|
+
var elements = ids.Select(id => doc.GetElement(id) ?? throw new ArgumentException($"Element {id.Value} not found.")).ToArray();
|
|
44
|
+
if (action != "copy" && elements.Any(e => e.Pinned)) throw new ArgumentException("Selection contains pinned elements; no elements were unpinned.");
|
|
45
|
+
var before = elements.Select(ModelEditInputs.Snapshot).ToArray();
|
|
46
|
+
ICollection<ElementId> outputIds = ids;
|
|
47
|
+
if (action == "move") ElementTransformUtils.MoveElements(doc, ids, translation!);
|
|
48
|
+
else if (action == "copy") outputIds = ElementTransformUtils.CopyElements(doc, ids, translation!);
|
|
49
|
+
else ElementTransformUtils.RotateElements(doc, ids, Line.CreateUnbound(origin!, direction!.Normalize()), angle);
|
|
50
|
+
doc.Regenerate();
|
|
51
|
+
return new() { ["before"] = before, ["after"] = outputIds.Select(doc.GetElement).Where(e => e != null).Select(ModelEditInputs.Snapshot).ToArray(),
|
|
52
|
+
["created_ids"] = action == "copy" ? outputIds.Select(id => id.Value).ToArray() : Array.Empty<long>(),
|
|
53
|
+
["coordinate_system"] = "document_internal", ["snapshot_unit"] = "feet",
|
|
54
|
+
["created_ids_are_temporary"] = action == "copy" && JsonArgs.GetBool(args, "preview", false) };
|
|
55
|
+
}) });
|
|
56
|
+
return result.Payload;
|
|
57
|
+
}
|
|
58
|
+
}
|
package/workspace/AGENTS.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# pi-revit workspace
|
|
2
2
|
|
|
3
3
|
This folder tree is the working area for Pi + Revit sessions. The pi-revit tools talk to the
|
|
4
|
-
Revit bridge add-in, targeting Revit 2025, 2026, and 2027. The 0.3.0 changes were
|
|
5
|
-
tested live on Revit 2025. Bridge document tools require a project open; `ping` and
|
|
6
|
-
`search_api_docs` work without a document. `read_revit_result` reads saved local
|
|
7
|
-
results without contacting Revit.
|
|
4
|
+
Revit bridge add-in, targeting Revit 2025, 2026, and 2027. The 0.3.0 changes were
|
|
5
|
+
tested live on Revit 2025. Bridge document tools require a project open; `ping` and
|
|
6
|
+
`search_api_docs` work without a document. `read_revit_result` reads saved local
|
|
7
|
+
results without contacting Revit.
|
|
8
8
|
|
|
9
9
|
## File rules — where every file goes
|
|
10
10
|
|
|
@@ -16,57 +16,57 @@ Documents\pi-revit\
|
|
|
16
16
|
├─ AGENTS.md <- this file
|
|
17
17
|
├─ pi-revit.cmd <- double-click launcher
|
|
18
18
|
└─ Models\
|
|
19
|
-
└─ <model title>--<identity hash>\ <- selected automatically for the exported document
|
|
20
|
-
├─ model.txt <- document identity/path marker written by the add-in
|
|
19
|
+
└─ <model title>--<identity hash>\ <- selected automatically for the exported document
|
|
20
|
+
├─ model.txt <- document identity/path marker written by the add-in
|
|
21
21
|
├─ exports\ <- export_documents output (its default)
|
|
22
22
|
├─ captures\ <- view snapshots worth keeping
|
|
23
23
|
└─ scripts\ <- generated scripts and analysis for that model
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
1. **Let exports sort themselves**: call `export_documents` without `output_dir` — files land
|
|
27
|
-
in an identity-derived model folder automatically. Treat the returned `outputDir`
|
|
28
|
-
and file paths as authoritative; do not derive the hash from the model title or
|
|
29
|
-
opaque `project.documentId`. Save As can change the destination. Existing title-only
|
|
30
|
-
folders remain untouched. Pass
|
|
27
|
+
in an identity-derived model folder automatically. Treat the returned `outputDir`
|
|
28
|
+
and file paths as authoritative; do not derive the hash from the model title or
|
|
29
|
+
opaque `project.documentId`. Save As can change the destination. Existing title-only
|
|
30
|
+
folders remain untouched. Pass
|
|
31
31
|
`output_dir` only when the user names a different target.
|
|
32
32
|
2. **Anything else you produce about a model goes into that model's folder**: view captures the
|
|
33
|
-
user wants to keep in that verified model folder's `captures` subfolder
|
|
34
|
-
(`capture_view` writes to temp), and scripts and analysis in its `scripts`
|
|
35
|
-
subfolder. For a default export, the model folder is the parent of returned
|
|
36
|
-
`outputDir`. If no folder has been established, identify it from existing model
|
|
37
|
-
markers or choose an explicit destination for the task; do not trigger an
|
|
38
|
-
unnecessary export or guess a title-only folder. Create subfolders on first use.
|
|
33
|
+
user wants to keep in that verified model folder's `captures` subfolder
|
|
34
|
+
(`capture_view` writes to temp), and scripts and analysis in its `scripts`
|
|
35
|
+
subfolder. For a default export, the model folder is the parent of returned
|
|
36
|
+
`outputDir`. If no folder has been established, identify it from existing model
|
|
37
|
+
markers or choose an explicit destination for the task; do not trigger an
|
|
38
|
+
unnecessary export or guess a title-only folder. Create subfolders on first use.
|
|
39
39
|
3. **Never create files loose in the workspace root.** The root holds `AGENTS.md`,
|
|
40
40
|
`pi-revit.cmd`, `Models\`, and Pi's own session data — nothing else, ever.
|
|
41
41
|
|
|
42
42
|
## Tool habits
|
|
43
43
|
|
|
44
|
-
- Start unfamiliar models with `get_model_overview`; use `get_elements` for any listing or
|
|
45
|
-
counting; read parameter values with `get_element_details`.
|
|
46
|
-
- Copy the intended model's `project.documentId` unchanged into `expected_document_id`
|
|
47
|
-
for `set_parameters`, `execute_csharp`, `export_documents`, `open_view`, and
|
|
48
|
-
selection/zoom changes. Any `manage_selection` call with `isolate_in_view: true`
|
|
49
|
-
also requires it, including action `get`. Pure reads may omit it; supplied IDs
|
|
50
|
-
are checked. Legacy `expected_document` titles alone do not satisfy the guard.
|
|
51
|
-
After a rejection, verify the intended model before refreshing its ID. Closing
|
|
52
|
-
and reopening a model or restarting Revit invalidates earlier IDs.
|
|
53
|
-
- Before `execute_csharp`, verify unfamiliar API signatures with `search_api_docs`.
|
|
54
|
-
- Write tools (`set_parameters`, `execute_csharp`) change the real model — state clearly what
|
|
55
|
-
was changed. `set_parameters` commits partial successes: always check its `failed` list.
|
|
56
|
-
Inspect `commitWarnings` and the actual transaction outcome. A failed
|
|
57
|
-
`execute_csharp` attempts rollback; do not assume rollback succeeded. A
|
|
58
|
-
`returnValueError` can follow a committed edit. Earlier UI actions and created
|
|
59
|
-
files can persist after failure. Read reported effects before retrying.
|
|
60
|
-
- When a result returns `result_id`, use `read_revit_result` from offset zero and
|
|
61
|
-
follow `next_offset` until `has_more` is false. Join its `text` fragments in order;
|
|
62
|
-
offsets count UTF-16 code units. The returned absolute file path remains a
|
|
63
|
-
fallback for `read` after extension reload while the file exists. Tool query
|
|
64
|
-
pagination and limits still apply separately.
|
|
65
|
-
|
|
66
|
-
## Upgrade notes
|
|
67
|
-
|
|
68
|
-
After installing 0.3.0, deploy the matching add-in with Revit closed, restart Revit,
|
|
69
|
-
and start a fresh Pi session to load the new tool schemas. Obtain a new overview
|
|
70
|
-
before changing a document. Setup preserves existing `AGENTS.md`; older workspaces
|
|
71
|
-
need these exact-ID, saved-result, and identity-derived folder rules merged into
|
|
72
|
-
their existing instructions.
|
|
44
|
+
- Start unfamiliar models with `get_model_overview`; use `get_elements` for any listing or
|
|
45
|
+
counting; read parameter values with `get_element_details`.
|
|
46
|
+
- Copy the intended model's `project.documentId` unchanged into `expected_document_id`
|
|
47
|
+
for `set_parameters`, `execute_csharp`, `export_documents`, `open_view`, and
|
|
48
|
+
selection/zoom changes. Any `manage_selection` call with `isolate_in_view: true`
|
|
49
|
+
also requires it, including action `get`. Pure reads may omit it; supplied IDs
|
|
50
|
+
are checked. Legacy `expected_document` titles alone do not satisfy the guard.
|
|
51
|
+
After a rejection, verify the intended model before refreshing its ID. Closing
|
|
52
|
+
and reopening a model or restarting Revit invalidates earlier IDs.
|
|
53
|
+
- Before `execute_csharp`, verify unfamiliar API signatures with `search_api_docs`.
|
|
54
|
+
- Write tools (`set_parameters`, `execute_csharp`) change the real model — state clearly what
|
|
55
|
+
was changed. `set_parameters` commits partial successes: always check its `failed` list.
|
|
56
|
+
Inspect `commitWarnings` and the actual transaction outcome. A failed
|
|
57
|
+
`execute_csharp` attempts rollback; do not assume rollback succeeded. A
|
|
58
|
+
`returnValueError` can follow a committed edit. Earlier UI actions and created
|
|
59
|
+
files can persist after failure. Read reported effects before retrying.
|
|
60
|
+
- When a result returns `result_id`, use `read_revit_result` from offset zero and
|
|
61
|
+
follow `next_offset` until `has_more` is false. Join its `text` fragments in order;
|
|
62
|
+
offsets count UTF-16 code units. The returned absolute file path remains a
|
|
63
|
+
fallback for `read` after extension reload while the file exists. Tool query
|
|
64
|
+
pagination and limits still apply separately.
|
|
65
|
+
|
|
66
|
+
## Upgrade notes
|
|
67
|
+
|
|
68
|
+
After installing 0.3.0, deploy the matching add-in with Revit closed, restart Revit,
|
|
69
|
+
and start a fresh Pi session to load the new tool schemas. Obtain a new overview
|
|
70
|
+
before changing a document. Setup preserves existing `AGENTS.md`; older workspaces
|
|
71
|
+
need these exact-ID, saved-result, and identity-derived folder rules merged into
|
|
72
|
+
their existing instructions.
|