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,100 +1,115 @@
|
|
|
1
|
-
using System.Text.Json;
|
|
2
|
-
using Autodesk.Revit.DB;
|
|
3
|
-
|
|
4
|
-
namespace RevitBridge.Tools;
|
|
5
|
-
|
|
6
|
-
internal sealed class ManageViews : ITool
|
|
7
|
-
{
|
|
8
|
-
public string Name => "manage_views";
|
|
9
|
-
public string
|
|
10
|
-
public string
|
|
11
|
-
public
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal sealed class ManageViews : ITool
|
|
7
|
+
{
|
|
8
|
+
public string Name => "manage_views";
|
|
9
|
+
public IReadOnlyList<string> DocumentKinds => DocumentKind.Both;
|
|
10
|
+
public IReadOnlyList<string> Keywords => new[] { "view", "floor plan", "ceiling plan", "section", "3d view", "duplicate view", "dependent view", "view template", "scale", "create view" };
|
|
11
|
+
public IReadOnlyList<ToolLimit> Limits => new[]
|
|
12
|
+
{
|
|
13
|
+
new ToolLimit("Perspective (camera) 3D views", "api", "View3D.CreatePerspective; ViewOrientation3D"),
|
|
14
|
+
new ToolLimit("Elevation, drafting and legend views", "api", "ElevationMarker.CreateElevationMarker; ViewDrafting.Create"),
|
|
15
|
+
new ToolLimit("View visibility and graphics (hiding, overrides, crop regions)", "api", "View.SetCategoryHidden; View.HideElements; View.SetElementOverrides; View.CropBox"),
|
|
16
|
+
new ToolLimit("Deleting views", "tool", "delete_elements"),
|
|
17
|
+
};
|
|
18
|
+
public string? Verification => "capture";
|
|
19
|
+
public string Label => "Manage Views";
|
|
20
|
+
public string Tier => "advanced";
|
|
21
|
+
public bool Write => true;
|
|
22
|
+
public string Description => "Create a plan, isometric 3D view or section; duplicate or update an existing view. A duplicate's result reports inherited_state: the source's hidden categories and elements, filters, overrides and template it carries. A name another view of the same type already uses is rejected with that view's ID. Resolve view-family-type IDs with get_element_types (of_class ViewFamilyType), and level IDs with get_elements. A section uses document internal axes and explicit length units, orthogonal viewing_direction/up vectors, origin, width/height/depth. Duplication options are duplicate, with_detailing or dependent. Optional name, scale and view_template_id apply in the same transaction; -1 removes a template. Incompatible templates or template-controlled scale changes fail without removing the template. preview=true commit-validates then rolls back; created preview IDs are temporary. Use delete_elements to remove views.";
|
|
23
|
+
public object ParametersSchema => new
|
|
24
|
+
{
|
|
25
|
+
type = "object", properties = new
|
|
26
|
+
{
|
|
27
|
+
action = new { type = "string", @enum = new[] { "create_plan", "create_3d", "create_section", "duplicate", "update" } },
|
|
28
|
+
view_id = new { type = "integer", description = "Existing view for duplicate/update." },
|
|
29
|
+
view_family_type_id = new { type = "integer", description = "Compatible ViewFamilyType for creation." },
|
|
30
|
+
level_id = new { type = "integer", description = "Level for create_plan." },
|
|
31
|
+
duplicate_option = new { type = "string", @enum = new[] { "duplicate", "with_detailing", "dependent" } },
|
|
32
|
+
name = new { type = "string", minLength = 1 }, scale = new { type = "integer", minimum = 1, maximum = 24000 },
|
|
33
|
+
view_template_id = new { type = "integer", description = "Compatible template ID, or -1 to remove." },
|
|
34
|
+
unit = ModelEditInputs.LengthUnitSchema,
|
|
35
|
+
origin = ModelEditInputs.VectorSchema("Section origin in document internal coordinates, in unit."),
|
|
36
|
+
viewing_direction = ModelEditInputs.VectorSchema("Section viewing direction, dimensionless nonzero vector."),
|
|
37
|
+
up = ModelEditInputs.VectorSchema("Section up vector, orthogonal to viewing_direction."),
|
|
38
|
+
width = new { type = "number", exclusiveMinimum = 0 }, height = new { type = "number", exclusiveMinimum = 0 }, depth = new { type = "number", exclusiveMinimum = 0 },
|
|
39
|
+
preview = ModelEditInputs.PreviewSchema,
|
|
40
|
+
}, required = new[] { "action" },
|
|
41
|
+
};
|
|
42
|
+
public object Execute(JsonElement args, ToolContext context)
|
|
43
|
+
{
|
|
44
|
+
var doc = context.Document ?? throw new NoActiveDocumentException();
|
|
45
|
+
string action = JsonArgs.GetString(args, "action") ?? "";
|
|
46
|
+
if (action is not ("create_plan" or "create_3d" or "create_section" or "duplicate" or "update")) throw new ArgumentException("Unknown view action.");
|
|
47
|
+
return ModelEditBatch.Run(doc, Name, args, new[] { new ModelEditBatch.Step(new() { ["action"] = action }, () =>
|
|
48
|
+
{
|
|
49
|
+
ElementId Id(string key) => new(JsonArgs.GetLong(args, key) is long id && id > 0 ? id : throw new ArgumentException($"{key} must be a positive ID."));
|
|
50
|
+
View view;
|
|
51
|
+
View? source = null;
|
|
52
|
+
object? before = null;
|
|
53
|
+
if (action is "duplicate" or "update")
|
|
54
|
+
{
|
|
55
|
+
view = doc.GetElement(Id("view_id")) as View ?? throw new ArgumentException("view_id is not a view.");
|
|
56
|
+
before = Describe(view);
|
|
57
|
+
if (action == "duplicate")
|
|
58
|
+
{
|
|
59
|
+
var option = (JsonArgs.GetString(args, "duplicate_option") ?? "duplicate") switch
|
|
60
|
+
{
|
|
61
|
+
"duplicate" => ViewDuplicateOption.Duplicate, "with_detailing" => ViewDuplicateOption.WithDetailing,
|
|
62
|
+
"dependent" => ViewDuplicateOption.AsDependent, _ => throw new ArgumentException("Invalid duplicate_option."),
|
|
63
|
+
};
|
|
64
|
+
if (!view.CanViewBeDuplicated(option)) throw new ArgumentException("This view cannot be duplicated with that option.");
|
|
65
|
+
source = view;
|
|
66
|
+
view = (View)doc.GetElement(view.Duplicate(option));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else if (action == "create_plan") view = ViewPlan.Create(doc, Id("view_family_type_id"), Id("level_id"));
|
|
70
|
+
else if (action == "create_3d") view = View3D.CreateIsometric(doc, Id("view_family_type_id"));
|
|
71
|
+
else
|
|
72
|
+
{
|
|
73
|
+
double factor = ModelEditInputs.LengthScale(args);
|
|
74
|
+
var origin = ModelEditInputs.Vector(args, "origin").Multiply(factor);
|
|
75
|
+
var direction = ModelEditInputs.Vector(args, "viewing_direction");
|
|
76
|
+
var up = ModelEditInputs.Vector(args, "up");
|
|
77
|
+
if (direction.GetLength() < 1e-12 || up.GetLength() < 1e-12) throw new ArgumentException("Section directions must be nonzero.");
|
|
78
|
+
direction = direction.Normalize(); up = up.Normalize();
|
|
79
|
+
if (Math.Abs(direction.DotProduct(up)) > 1e-8) throw new ArgumentException("Section up and viewing_direction must be orthogonal.");
|
|
80
|
+
double width = ModelEditInputs.Number(args, "width") * factor, height = ModelEditInputs.Number(args, "height") * factor, depth = ModelEditInputs.Number(args, "depth") * factor;
|
|
81
|
+
if (width <= 1e-6 || height <= 1e-6 || depth <= 1e-6) throw new ArgumentException("Section dimensions must exceed 0.000001 feet.");
|
|
82
|
+
var transform = Transform.Identity;
|
|
83
|
+
transform.Origin = origin; transform.BasisZ = direction; transform.BasisY = up; transform.BasisX = up.CrossProduct(direction).Normalize();
|
|
84
|
+
using var box = new BoundingBoxXYZ { Transform = transform, Min = new XYZ(-width / 2, -height / 2, 0), Max = new XYZ(width / 2, height / 2, depth), Enabled = true };
|
|
85
|
+
view = ViewSection.CreateSection(doc, Id("view_family_type_id"), box);
|
|
86
|
+
}
|
|
87
|
+
if (args.TryGetProperty("name", out var name))
|
|
88
|
+
{
|
|
89
|
+
if (name.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace(name.GetString())) throw new ArgumentException("name must be nonempty.");
|
|
90
|
+
ElementNames.Assign(view, name.GetString()!);
|
|
91
|
+
}
|
|
92
|
+
if (args.TryGetProperty("view_template_id", out _))
|
|
93
|
+
{
|
|
94
|
+
long template = JsonArgs.GetLong(args, "view_template_id") ?? 0;
|
|
95
|
+
if (template != -1 && (template <= 0 || !view.IsValidViewTemplate(new ElementId(template)))) throw new ArgumentException("view_template_id is not compatible with this view.");
|
|
96
|
+
view.ViewTemplateId = new ElementId(template);
|
|
97
|
+
}
|
|
98
|
+
if (args.TryGetProperty("scale", out var scale))
|
|
99
|
+
{
|
|
100
|
+
if (!scale.TryGetInt32(out int value) || value is < 1 or > 24000) throw new ArgumentException("scale must be an integer from 1 to 24000.");
|
|
101
|
+
var parameter = view.get_Parameter(BuiltInParameter.VIEW_SCALE);
|
|
102
|
+
if (parameter == null) throw new ArgumentException("This view's scale is unavailable.");
|
|
103
|
+
if (doc.GetElement(view.ViewTemplateId) is View template && template.GetTemplateParameterIds().Except(template.GetNonControlledTemplateParameterIds()).Contains(parameter.Id))
|
|
104
|
+
throw new ArgumentException("This view's scale is controlled by its template.");
|
|
105
|
+
view.Scale = value;
|
|
106
|
+
}
|
|
107
|
+
doc.Regenerate();
|
|
108
|
+
var result = new Dictionary<string, object?> { ["before"] = before, ["view"] = Describe(view), ["created"] = action != "update", ["id_is_temporary"] = action != "update" && JsonArgs.GetBool(args, "preview", false) };
|
|
109
|
+
// A duplicate carries its source's visibility and graphics (inv:derived-state-reported).
|
|
110
|
+
if (source != null) result["inherited_state"] = InheritedState.OfView(view, source.Id.Value);
|
|
111
|
+
return result;
|
|
112
|
+
}) }).Payload;
|
|
113
|
+
}
|
|
114
|
+
private static object Describe(View view) => new { id = view.Id.Value, unique_id = view.UniqueId, name = view.Name, view_type = view.ViewType.ToString(), template_id = view.ViewTemplateId.Value, scale = view.Scale };
|
|
115
|
+
}
|
|
@@ -1,54 +1,60 @@
|
|
|
1
|
-
using System.Text.Json;
|
|
2
|
-
using Autodesk.Revit.DB;
|
|
3
|
-
|
|
4
|
-
namespace RevitBridge.Tools;
|
|
5
|
-
|
|
6
|
-
internal sealed class MeasureGeometry : ITool
|
|
7
|
-
{
|
|
8
|
-
public string Name => "measure_geometry";
|
|
9
|
-
public string
|
|
10
|
-
public string
|
|
11
|
-
public
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal sealed class MeasureGeometry : ITool
|
|
7
|
+
{
|
|
8
|
+
public string Name => "measure_geometry";
|
|
9
|
+
public IReadOnlyList<string> DocumentKinds => DocumentKind.Both;
|
|
10
|
+
public IReadOnlyList<string> Keywords => new[] { "distance", "measure", "gap", "clearance", "spacing", "dimension check" };
|
|
11
|
+
public IReadOnlyList<ToolLimit> Limits => new[]
|
|
12
|
+
{
|
|
13
|
+
new ToolLimit("Exact solid-to-solid distance or clash detection", "api", "ElementIntersectsElementFilter; ElementIntersectsSolidFilter; ReferenceIntersector"),
|
|
14
|
+
};
|
|
15
|
+
public string Label => "Measure Geometry";
|
|
16
|
+
public string Tier => "advanced";
|
|
17
|
+
public string Description => "Measure exact Euclidean distance between two explicit points, or approximate separation between two host elements' model axis-aligned bounding boxes. Required unit applies to point inputs, optional clearance threshold, and all output coordinates/distances. Points use document internal axes. Box gap is a lower bound on geometry separation: zero means enclosing boxes touch/overlap, not a confirmed clash. Bounds can include nonphysical geometry. An optional clearance threshold classifies only box proximity, never a physical clearance failure. Does not traverse linked contents.";
|
|
18
|
+
public object ParametersSchema => new
|
|
19
|
+
{
|
|
20
|
+
type = "object", properties = new
|
|
21
|
+
{
|
|
22
|
+
mode = new { type = "string", @enum = new[] { "point_distance", "bounding_box_gap" } }, unit = ModelEditInputs.LengthUnitSchema,
|
|
23
|
+
point_a = ModelEditInputs.VectorSchema("First point in document internal axes, in unit."), point_b = ModelEditInputs.VectorSchema("Second point in document internal axes, in unit."),
|
|
24
|
+
element_a_id = new { type = "integer", minimum = 1 }, element_b_id = new { type = "integer", minimum = 1 },
|
|
25
|
+
clearance = new { type = "number", minimum = 0, description = "Optional box-proximity threshold in unit, only for bounding_box_gap." },
|
|
26
|
+
}, required = new[] { "mode", "unit" },
|
|
27
|
+
};
|
|
28
|
+
public object Execute(JsonElement args, ToolContext context)
|
|
29
|
+
{
|
|
30
|
+
var doc = context.Document ?? throw new NoActiveDocumentException();
|
|
31
|
+
double scale = ModelEditInputs.LengthScale(args);
|
|
32
|
+
string? mode = JsonArgs.GetString(args, "mode"), unit = JsonArgs.GetString(args, "unit");
|
|
33
|
+
if (mode == "point_distance")
|
|
34
|
+
{
|
|
35
|
+
if (args.TryGetProperty("clearance", out _) || args.TryGetProperty("element_a_id", out _) || args.TryGetProperty("element_b_id", out _))
|
|
36
|
+
throw new ArgumentException("point_distance accepts points, not element IDs or clearance.");
|
|
37
|
+
var a = ModelEditInputs.Vector(args, "point_a").Multiply(scale); var b = ModelEditInputs.Vector(args, "point_b").Multiply(scale);
|
|
38
|
+
return new { method = mode, approximate = false, coordinate_system = "document_internal", unit, distance = a.DistanceTo(b) / scale,
|
|
39
|
+
delta = SpatialBounds.Coordinates(b - a, scale), point_a = SpatialBounds.Coordinates(a, scale), point_b = SpatialBounds.Coordinates(b, scale) };
|
|
40
|
+
}
|
|
41
|
+
if (mode != "bounding_box_gap") throw new ArgumentException("Unknown measurement mode.");
|
|
42
|
+
if (args.TryGetProperty("point_a", out _) || args.TryGetProperty("point_b", out _)) throw new ArgumentException("bounding_box_gap accepts element IDs, not points.");
|
|
43
|
+
Element Resolve(string key)
|
|
44
|
+
{
|
|
45
|
+
long id = JsonArgs.GetLong(args, key) ?? 0;
|
|
46
|
+
return id > 0 ? doc.GetElement(new ElementId(id)) ?? throw new ArgumentException($"{key} {id} was not found.") : throw new ArgumentException($"{key} must be positive.");
|
|
47
|
+
}
|
|
48
|
+
var elementA = Resolve("element_a_id"); var elementB = Resolve("element_b_id");
|
|
49
|
+
var boundsA = SpatialBounds.Of(elementA) ?? throw new ArgumentException($"Element {elementA.Id.Value} has no model bounding box.");
|
|
50
|
+
var boundsB = SpatialBounds.Of(elementB) ?? throw new ArgumentException($"Element {elementB.Id.Value} has no model bounding box.");
|
|
51
|
+
var gap = boundsA.Gap(boundsB); double distance = gap.GetLength() / scale;
|
|
52
|
+
double? clearance = args.TryGetProperty("clearance", out _) ? ModelEditInputs.Number(args, "clearance") : null;
|
|
53
|
+
if (clearance < 0) throw new ArgumentException("clearance cannot be negative.");
|
|
54
|
+
return new { method = mode, approximate = true, coordinate_system = "document_internal", unit, distance, axis_gaps = SpatialBounds.Coordinates(gap, scale),
|
|
55
|
+
boxes_overlap_or_touch = boundsA.Intersects(boundsB), clearance, box_gap_below_clearance = clearance.HasValue ? (bool?)(distance < clearance.Value) : null,
|
|
56
|
+
interpretation = "Bounding-box gap is a lower bound on geometry separation. Overlap or a gap below clearance identifies candidates for geometry review, not a confirmed clash or clearance failure.",
|
|
57
|
+
element_a = new { id = elementA.Id.Value, unique_id = elementA.UniqueId, bounds = boundsA.Describe(scale) },
|
|
58
|
+
element_b = new { id = elementB.Id.Value, unique_id = elementB.UniqueId, bounds = boundsB.Describe(scale) } };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using System.Text.Json.Nodes;
|
|
3
|
+
using Autodesk.Revit.DB;
|
|
4
|
+
using Autodesk.Revit.DB.Events;
|
|
5
|
+
|
|
6
|
+
namespace RevitBridge.Tools
|
|
7
|
+
{
|
|
8
|
+
/// <summary>
|
|
9
|
+
/// Records what one model-changing tool call did to the document (inv:model-changes-reported)
|
|
10
|
+
/// and merges a bounded report into its result as model_changes. The bridge dispatcher wraps
|
|
11
|
+
/// every tool that can write or has model effects, including execute_csharp, so present and
|
|
12
|
+
/// future tools report created, modified and deleted objects without tool-specific code. New
|
|
13
|
+
/// views are reported with their visibility state through InheritedState, which covers views
|
|
14
|
+
/// duplicated in custom code. The Pi extension builds its per-request ledger of objects created
|
|
15
|
+
/// by the agent from this report.
|
|
16
|
+
/// </summary>
|
|
17
|
+
internal sealed class ModelChangeRecorder : IDisposable
|
|
18
|
+
{
|
|
19
|
+
public const int ItemCap = 20;
|
|
20
|
+
public const int NewViewStateCap = 3;
|
|
21
|
+
private readonly Autodesk.Revit.ApplicationServices.Application _application;
|
|
22
|
+
private readonly Document _document;
|
|
23
|
+
private readonly ChangeSet _changes = new();
|
|
24
|
+
// Family types and parameters are not elements, so DocumentChanged cannot name them;
|
|
25
|
+
// a family document is compared before and after the call instead.
|
|
26
|
+
private readonly FamilySnapshot? _familyBefore;
|
|
27
|
+
|
|
28
|
+
private ModelChangeRecorder(Autodesk.Revit.ApplicationServices.Application application, Document document)
|
|
29
|
+
{
|
|
30
|
+
_application = application; _document = document;
|
|
31
|
+
_familyBefore = FamilySnapshot.Of(document);
|
|
32
|
+
_application.DocumentChanged += OnChanged;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// <summary>A recorder for tools that can change the model; null for everything else.</summary>
|
|
36
|
+
public static ModelChangeRecorder? For(ITool tool, Autodesk.Revit.ApplicationServices.Application application, Document document)
|
|
37
|
+
=> tool.Write || tool.Effects.Contains("model") ? new ModelChangeRecorder(application, document) : null;
|
|
38
|
+
|
|
39
|
+
private void OnChanged(object? sender, DocumentChangedEventArgs e)
|
|
40
|
+
{
|
|
41
|
+
if (!e.GetDocument().Equals(_document)) return;
|
|
42
|
+
_changes.Record(e.Operation.ToString(), Ids(e.GetAddedElementIds()), Ids(e.GetModifiedElementIds()), Ids(e.GetDeletedElementIds()));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private static IEnumerable<long> Ids(ICollection<ElementId> ids) => ids.Select(id => id.Value).ToList();
|
|
46
|
+
|
|
47
|
+
public void Dispose() => _application.DocumentChanged -= OnChanged;
|
|
48
|
+
|
|
49
|
+
/// <summary>
|
|
50
|
+
/// Merge model_changes into an object payload; other payload shapes are returned unchanged.
|
|
51
|
+
/// The tool has already run: a reporting failure must never turn its result into an error.
|
|
52
|
+
/// </summary>
|
|
53
|
+
public object? Attach(object? output)
|
|
54
|
+
{
|
|
55
|
+
try
|
|
56
|
+
{
|
|
57
|
+
object? payload = output is ToolOutput tool ? tool.Payload : output;
|
|
58
|
+
if (JsonSerializer.SerializeToNode(payload) is not JsonObject json) return output;
|
|
59
|
+
bool describesState = json.ToJsonString().Contains("\"inherited_state\"", StringComparison.Ordinal);
|
|
60
|
+
json["model_changes"] = JsonSerializer.SerializeToNode(Report(!describesState));
|
|
61
|
+
return output is ToolOutput original ? original with { Payload = json } : json;
|
|
62
|
+
}
|
|
63
|
+
catch (Exception error) when (error is not OutOfMemoryException)
|
|
64
|
+
{
|
|
65
|
+
return output;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public Dictionary<string, object?> Report(bool includeNewViewState)
|
|
70
|
+
{
|
|
71
|
+
// A read through a write-capable tool (a placement listing, a read-only script) changed
|
|
72
|
+
// nothing: say so in one field instead of empty lists on every call.
|
|
73
|
+
if (!_changes.Observed) return new() { ["observed"] = false };
|
|
74
|
+
var added = _changes.Added.Where(id => _document.GetElement(new ElementId(id)) != null).OrderBy(id => id).ToList();
|
|
75
|
+
var modified = _changes.Modified.Where(id => _document.GetElement(new ElementId(id)) != null).OrderBy(id => id).ToList();
|
|
76
|
+
var deleted = _changes.Deleted.Where(id => _document.GetElement(new ElementId(id)) == null).OrderBy(id => id).ToList();
|
|
77
|
+
var report = new Dictionary<string, object?>
|
|
78
|
+
{
|
|
79
|
+
["observed"] = _changes.Observed,
|
|
80
|
+
["added"] = Describe(added),
|
|
81
|
+
["modified"] = Describe(modified),
|
|
82
|
+
["deleted"] = new Dictionary<string, object?> { ["count"] = deleted.Count, ["ids"] = deleted.Take(ItemCap).ToList() },
|
|
83
|
+
};
|
|
84
|
+
if (_changes.RolledBack) report["rolled_back"] = true;
|
|
85
|
+
if (_familyBefore != null && FamilySnapshot.Of(_document) is { } familyAfter && FamilySnapshot.Diff(_familyBefore, familyAfter) is { } family)
|
|
86
|
+
report["family"] = family;
|
|
87
|
+
// Revit reports elements it only regenerated (analytical nodes, imports, the project's
|
|
88
|
+
// internal elements) as modified; round-3 agents relayed those as edits.
|
|
89
|
+
if (modified.Count > 0)
|
|
90
|
+
report["modified_note"] = "Modified includes elements Revit updated as a side effect of regeneration. Report as changed only what your edit targeted; reread anything else before claiming it changed.";
|
|
91
|
+
if (includeNewViewState)
|
|
92
|
+
{
|
|
93
|
+
var views = added.Select(id => _document.GetElement(new ElementId(id))).OfType<View>()
|
|
94
|
+
.Where(view => !view.IsTemplate && view is not ViewSheet && view is not ViewSchedule).Take(NewViewStateCap)
|
|
95
|
+
.Select(view => new Dictionary<string, object?> { ["id"] = view.Id.Value, ["name"] = view.Name, ["view_type"] = view.ViewType.ToString(),
|
|
96
|
+
["state"] = InheritedState.Summarize(InheritedState.ReadView(view, null), InheritedState.NewViewCheck) }).ToList();
|
|
97
|
+
if (views.Count > 0) report["new_views"] = views;
|
|
98
|
+
}
|
|
99
|
+
return report;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// <summary>A family document's types (with every value) and parameter names; null in a project.</summary>
|
|
103
|
+
internal sealed record FamilySnapshot(Dictionary<string, string> Types, HashSet<string> Parameters)
|
|
104
|
+
{
|
|
105
|
+
public static FamilySnapshot? Of(Document doc)
|
|
106
|
+
{
|
|
107
|
+
if (!doc.IsFamilyDocument) return null;
|
|
108
|
+
var manager = doc.FamilyManager;
|
|
109
|
+
var parameters = manager.Parameters.Cast<FamilyParameter>().OrderBy(p => p.Definition.Name).ToList();
|
|
110
|
+
string Value(FamilyType type, FamilyParameter parameter)
|
|
111
|
+
{
|
|
112
|
+
try { return type.HasValue(parameter) ? type.AsValueString(parameter) ?? type.AsString(parameter) ?? "" : ""; }
|
|
113
|
+
catch (Autodesk.Revit.Exceptions.ApplicationException) { return "?"; }
|
|
114
|
+
}
|
|
115
|
+
var types = new Dictionary<string, string>();
|
|
116
|
+
foreach (FamilyType type in manager.Types)
|
|
117
|
+
types[type.Name] = string.Join("|", parameters.Select(p => p.Definition.Name + "=" + Value(type, p)));
|
|
118
|
+
return new FamilySnapshot(types, parameters.Select(p => p.Definition.Name).ToHashSet());
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// <summary>Family types and parameters added, removed or changed; null when none changed.</summary>
|
|
122
|
+
public static Dictionary<string, object?>? Diff(FamilySnapshot before, FamilySnapshot after)
|
|
123
|
+
{
|
|
124
|
+
var added = after.Types.Keys.Except(before.Types.Keys).OrderBy(n => n).ToList();
|
|
125
|
+
var removed = before.Types.Keys.Except(after.Types.Keys).OrderBy(n => n).ToList();
|
|
126
|
+
var changed = before.Types.Keys.Where(n => after.Types.TryGetValue(n, out var v) && v != before.Types[n]).OrderBy(n => n).ToList();
|
|
127
|
+
var parametersAdded = after.Parameters.Except(before.Parameters).OrderBy(n => n).ToList();
|
|
128
|
+
var parametersRemoved = before.Parameters.Except(after.Parameters).OrderBy(n => n).ToList();
|
|
129
|
+
if (added.Count + removed.Count + changed.Count + parametersAdded.Count + parametersRemoved.Count == 0) return null;
|
|
130
|
+
return new Dictionary<string, object?>
|
|
131
|
+
{
|
|
132
|
+
["types_added"] = added, ["types_removed"] = removed, ["types_changed"] = changed,
|
|
133
|
+
["parameters_added"] = parametersAdded, ["parameters_removed"] = parametersRemoved,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private Dictionary<string, object?> Describe(IReadOnlyList<long> ids) => new()
|
|
139
|
+
{
|
|
140
|
+
["count"] = ids.Count,
|
|
141
|
+
["items"] = ids.Take(ItemCap).Select(id =>
|
|
142
|
+
{
|
|
143
|
+
var element = _document.GetElement(new ElementId(id));
|
|
144
|
+
return new Dictionary<string, object?> { ["id"] = id, ["name"] = SafeName(element), ["category"] = element?.Category?.Name, ["class"] = element?.GetType().Name };
|
|
145
|
+
}).ToList(),
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
private static string? SafeName(Element? element)
|
|
149
|
+
{
|
|
150
|
+
try { return element?.Name; }
|
|
151
|
+
catch (Autodesk.Revit.Exceptions.InvalidOperationException) { return null; }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|