pi-revit 0.3.1 → 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 +114 -42
- package/README.md +598 -138
- 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 +231 -85
- package/extensions/pi-revit/instance-router.ts +86 -0
- 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 +146 -0
- package/extensions/pi-revit/tool-catalog.ts +166 -0
- package/extensions/pi-revit/tool-documentation.ts +72 -0
- package/extensions/pi-revit/tool-schema.ts +8 -0
- package/package.json +65 -59
- 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 +39 -63
- 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 +40 -0
- package/skills/pi-revit/references/operation-recovery.md +33 -0
- package/skills/pi-revit/references/room-documentation.md +39 -0
- 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 +75 -19
- package/src/Revit/OperationStore.cs +178 -0
- package/src/Revit/ToolRegistry.cs +61 -8
- package/src/Revit/Tools/CaptureView.cs +9 -0
- package/src/Revit/Tools/ChangeElementTypes.cs +74 -0
- package/src/Revit/Tools/ChangeSet.cs +39 -0
- package/src/Revit/Tools/CreateTags.cs +107 -0
- package/src/Revit/Tools/DeleteElements.cs +53 -0
- package/src/Revit/Tools/DocumentGuard.cs +12 -2
- package/src/Revit/Tools/ElementNames.cs +103 -0
- package/src/Revit/Tools/ElementQueryScope.cs +27 -0
- package/src/Revit/Tools/ElementTraits.cs +53 -0
- package/src/Revit/Tools/ExecuteCsharp.cs +26 -7
- package/src/Revit/Tools/ExportDocuments.cs +9 -0
- package/src/Revit/Tools/FailureGuard.cs +26 -26
- package/src/Revit/Tools/GetElementDetails.cs +28 -2
- package/src/Revit/Tools/GetElementRelationships.cs +82 -0
- package/src/Revit/Tools/GetElementTypes.cs +8 -0
- package/src/Revit/Tools/GetElements.cs +58 -55
- package/src/Revit/Tools/GetLinkedElements.cs +89 -0
- package/src/Revit/Tools/GetLinkedModels.cs +73 -0
- package/src/Revit/Tools/GetModelCoordinates.cs +56 -0
- package/src/Revit/Tools/GetModelHealth.cs +7 -0
- package/src/Revit/Tools/GetModelOverview.cs +187 -160
- package/src/Revit/Tools/GetScheduleFields.cs +44 -0
- package/src/Revit/Tools/GetSchedules.cs +96 -0
- package/src/Revit/Tools/InheritedState.Summary.cs +57 -0
- package/src/Revit/Tools/InheritedState.cs +144 -0
- package/src/Revit/Tools/ManageElementSets.cs +114 -0
- package/src/Revit/Tools/ManageSchedules.cs +174 -0
- package/src/Revit/Tools/ManageSelection.cs +9 -0
- package/src/Revit/Tools/ManageSheetPlacements.cs +113 -0
- package/src/Revit/Tools/ManageSheets.cs +72 -0
- package/src/Revit/Tools/ManageViews.cs +115 -0
- package/src/Revit/Tools/MeasureGeometry.cs +60 -0
- package/src/Revit/Tools/ModelChanges.cs +154 -0
- package/src/Revit/Tools/ModelEditBatch.cs +105 -0
- package/src/Revit/Tools/ModelEditInputs.cs +49 -0
- package/src/Revit/Tools/OpenView.cs +8 -0
- package/src/Revit/Tools/ParameterResolver.cs +94 -0
- package/src/Revit/Tools/QuerySpatialElements.cs +70 -0
- package/src/Revit/Tools/SearchApiDocs.cs +72 -4
- package/src/Revit/Tools/SetParameters.cs +50 -119
- package/src/Revit/Tools/SpatialBounds.cs +30 -0
- package/src/Revit/Tools/SummarizeElements.cs +94 -0
- package/src/Revit/Tools/ToolContract.cs +48 -0
- package/src/Revit/Tools/ToolSupport.cs +4 -0
- package/src/Revit/Tools/TransformElements.cs +73 -0
- package/workspace/AGENTS.md +54 -48
|
@@ -0,0 +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
|
+
}
|
|
@@ -13,9 +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
25
|
public bool Write => false;
|
|
26
|
+
public IReadOnlyList<string> Effects => new[] { "ui" };
|
|
19
27
|
|
|
20
28
|
public object ParametersSchema => new
|
|
21
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
|
+
}
|
|
@@ -0,0 +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 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
|
+
}
|
|
@@ -33,8 +33,15 @@ namespace RevitBridge.Tools
|
|
|
33
33
|
private const int MaxExceptionsPerMember = 8;
|
|
34
34
|
|
|
35
35
|
public string Name => "search_api_docs";
|
|
36
|
+
public IReadOnlyList<string> DocumentKinds => DocumentKind.Both;
|
|
37
|
+
public IReadOnlyList<string> Keywords => new[] { "api", "revit api", "method", "class", "signature", "enum", "developer documentation" };
|
|
38
|
+
public IReadOnlyList<ToolLimit> Limits => new[]
|
|
39
|
+
{
|
|
40
|
+
new ToolLimit("Autodesk product Help or modeling guidance", "user", "Autodesk Revit Help or project standards"),
|
|
41
|
+
new ToolLimit("Proof that code compiles or works", "tool", "execute_csharp"),
|
|
42
|
+
};
|
|
36
43
|
public string Label => "Search API Docs";
|
|
37
|
-
public string Description => "Search the offline Revit API documentation (the RevitAPI.xml and RevitAPIUI.xml files shipped with Revit) for types, methods, constructors, properties, fields, and events; every public API enum is fully searchable by value name (values the XML leaves undocumented are synthesized from the API assemblies). query is a
|
|
44
|
+
public string Description => "Search the offline Revit API documentation (the RevitAPI.xml and RevitAPIUI.xml files shipped with Revit) for types, methods, constructors, properties, fields, and events; every public API enum is fully searchable by value name (values the XML leaves undocumented are synthesized from the API assemblies). query is a name or substring — e.g. 'FilteredElementCollector', 'Wall.Create', 'WALL_BASE_OFFSET' — ranked: exact name first, then prefix, then substring; dotted Type.Member queries match composites, and same-named overloads rank simplest-first. To target one overload, continue the query past a parenthesis with parameter types, e.g. 'Wall.Create(Document, Curve'. Returns signatures with summary, remarks, parameter docs, return docs, exception docs, and the Revit version a member was introduced in ('since'); the top match shows its full docs inline. To verify several members at once, separate them with ';' (up to 10, e.g. 'View3D.CreatePerspective; ViewOrientation3D', the form declared API limits use): each gets its best match with compact docs. Works with no document open. Use it to verify exact classes, members, and signatures before writing execute_csharp code. The first query builds the index (a few seconds); later queries are instant.";
|
|
38
45
|
|
|
39
46
|
public bool RequiresDocument => false;
|
|
40
47
|
|
|
@@ -46,7 +53,7 @@ namespace RevitBridge.Tools
|
|
|
46
53
|
query = new
|
|
47
54
|
{
|
|
48
55
|
type = "string",
|
|
49
|
-
description = "Type or member name to look up, e.g. FilteredElementCollector, Wall.Create, WALL_BASE_OFFSET. Substring and multi-word matches are ranked; Type.Member composites rank highest on exact match.",
|
|
56
|
+
description = "Type or member name to look up, e.g. FilteredElementCollector, Wall.Create, WALL_BASE_OFFSET; or up to 10 names separated by ';' to verify them in one call. Substring and multi-word matches are ranked; Type.Member composites rank highest on exact match.",
|
|
50
57
|
},
|
|
51
58
|
kind = new
|
|
52
59
|
{
|
|
@@ -57,7 +64,7 @@ namespace RevitBridge.Tools
|
|
|
57
64
|
max_results = new
|
|
58
65
|
{
|
|
59
66
|
type = "integer",
|
|
60
|
-
description = $"Maximum matches to return (1-{MaxResultsCap}, default {DefaultMaxResults}).",
|
|
67
|
+
description = $"Maximum matches to return (1-{MaxResultsCap}, default {DefaultMaxResults}; per member when several are named, default {DefaultResultsPerMember}).",
|
|
61
68
|
},
|
|
62
69
|
},
|
|
63
70
|
required = new[] { "query" },
|
|
@@ -66,7 +73,7 @@ namespace RevitBridge.Tools
|
|
|
66
73
|
public string? PromptSnippet => "Search the offline Revit API documentation for types, members, and signatures.";
|
|
67
74
|
public IReadOnlyList<string>? PromptGuidelines => new[]
|
|
68
75
|
{
|
|
69
|
-
"Before writing execute_csharp code, verify exact classes and member signatures with search_api_docs (e.g.
|
|
76
|
+
"Before writing execute_csharp code, verify exact classes and member signatures with search_api_docs; name all members you need in one query separated by ';' (e.g. 'View3D.CreatePerspective; ViewOrientation3D; View.CropBox').",
|
|
70
77
|
};
|
|
71
78
|
|
|
72
79
|
public object? Execute(JsonElement args, ToolContext context)
|
|
@@ -81,6 +88,12 @@ namespace RevitBridge.Tools
|
|
|
81
88
|
if (index.Members.Count == 0)
|
|
82
89
|
throw new InvalidOperationException("The Revit API documentation index is empty. " + string.Join(" ", index.Warnings));
|
|
83
90
|
|
|
91
|
+
var parts = SplitQueries(query);
|
|
92
|
+
if (parts.Count > MaxQueriesPerCall)
|
|
93
|
+
throw new ArgumentException($"query names {parts.Count} members; look up at most {MaxQueriesPerCall} per call.");
|
|
94
|
+
if (parts.Count > 1)
|
|
95
|
+
return ExecuteMany(index, parts, kindFilter, args.TryGetProperty("max_results", out _) ? maxResults : DefaultResultsPerMember);
|
|
96
|
+
|
|
84
97
|
var (top, total, rewriteNote) = Search(index, query, kindFilter, maxResults);
|
|
85
98
|
|
|
86
99
|
var matches = new List<Dictionary<string, object?>>(top.Count);
|
|
@@ -119,10 +132,65 @@ namespace RevitBridge.Tools
|
|
|
119
132
|
}, BuildMarkdown(query, top, total, index) + (rewriteNote is null ? string.Empty : $"\nNote: {rewriteNote}"));
|
|
120
133
|
}
|
|
121
134
|
|
|
135
|
+
private const int DefaultResultsPerMember = 3;
|
|
136
|
+
private const int ManyRemarksChars = 300;
|
|
137
|
+
private const int ManyParamDocChars = 160;
|
|
138
|
+
|
|
139
|
+
/// <summary>Several members in one call: the best match of each with compact docs, plus
|
|
140
|
+
/// the next candidates by signature only, so a script's members are verified together.</summary>
|
|
141
|
+
private static ToolOutput ExecuteMany(DocIndex index, IReadOnlyList<string> parts, char? kindFilter, int perQuery)
|
|
142
|
+
{
|
|
143
|
+
var lookups = LookUpMany(index, parts, kindFilter, perQuery);
|
|
144
|
+
var results = lookups.Select(lookup => new Dictionary<string, object?>
|
|
145
|
+
{
|
|
146
|
+
["query"] = lookup.Query,
|
|
147
|
+
["totalMatches"] = lookup.Total,
|
|
148
|
+
["best"] = lookup.Top.Count == 0 ? null : new Dictionary<string, object?>
|
|
149
|
+
{
|
|
150
|
+
["kind"] = KindLabel(lookup.Top[0]), ["name"] = lookup.Top[0].FullName, ["signature"] = lookup.Top[0].Signature, ["since"] = lookup.Top[0].Since,
|
|
151
|
+
["summary"] = lookup.Top[0].Summary, ["remarks"] = Cap(lookup.Top[0].Remarks, ManyRemarksChars),
|
|
152
|
+
["parameters"] = lookup.Top[0].Parameters?.Select(pair => new Dictionary<string, object?> { ["name"] = pair.Key, ["description"] = Cap(pair.Value, ManyParamDocChars) }).ToList(),
|
|
153
|
+
["returns"] = Cap(lookup.Top[0].Returns, ManyParamDocChars),
|
|
154
|
+
// Exceptions state when a member refuses (for example "not a project document").
|
|
155
|
+
["exceptions"] = lookup.Top[0].Exceptions?.Take(3).Select(pair => $"{pair.Key}: {Cap(FirstSentence(pair.Value), 140)}").ToList(),
|
|
156
|
+
},
|
|
157
|
+
["alternatives"] = lookup.Top.Skip(1).Select(member => new Dictionary<string, object?> { ["kind"] = KindLabel(member), ["signature"] = member.Signature }).ToList(),
|
|
158
|
+
["note"] = lookup.Total == 0 ? "No match. Try a shorter name or the type alone." : lookup.Note,
|
|
159
|
+
}).ToList();
|
|
160
|
+
var markdown = new StringBuilder($"Revit API docs for {parts.Count} members:");
|
|
161
|
+
foreach (var lookup in lookups)
|
|
162
|
+
markdown.Append(lookup.Top.Count == 0 ? $"\n- '{lookup.Query}': no match" : $"\n- '{lookup.Query}': **{lookup.Top[0].Signature}** — {FirstSentence(lookup.Top[0].Summary) ?? "(no summary)"}");
|
|
163
|
+
return new ToolOutput(new
|
|
164
|
+
{
|
|
165
|
+
query = string.Join("; ", parts),
|
|
166
|
+
lookups = parts.Count,
|
|
167
|
+
results,
|
|
168
|
+
indexedMembers = index.Members.Count,
|
|
169
|
+
sources = index.SourceFiles,
|
|
170
|
+
warnings = index.Warnings.Count > 0 ? index.Warnings : null,
|
|
171
|
+
}, markdown.ToString());
|
|
172
|
+
}
|
|
173
|
+
|
|
122
174
|
// ----------------------------------------------------------------- search
|
|
123
175
|
|
|
124
176
|
private static readonly char[] WordSeparators = { '.', ' ', '_', '(', ')', ',', ':', '-', '/' };
|
|
125
177
|
|
|
178
|
+
/// <summary>Most members one multi-member lookup ("A; B; C") may name.</summary>
|
|
179
|
+
private const int MaxQueriesPerCall = 10;
|
|
180
|
+
|
|
181
|
+
/// <summary>A query naming several members separated by ';' (the form declared API
|
|
182
|
+
/// limits use) is looked up member by member in one call; duplicates are dropped.</summary>
|
|
183
|
+
private static List<string> SplitQueries(string query) => query
|
|
184
|
+
.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
|
185
|
+
.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
|
|
186
|
+
|
|
187
|
+
private static List<(string Query, List<ApiMember> Top, int Total, string? Note)> LookUpMany(DocIndex index, IReadOnlyList<string> queries, char? kindFilter, int perQuery)
|
|
188
|
+
=> queries.Select(part =>
|
|
189
|
+
{
|
|
190
|
+
var (top, total, note) = Search(index, part, kindFilter, perQuery);
|
|
191
|
+
return (part, top, total, note);
|
|
192
|
+
}).ToList();
|
|
193
|
+
|
|
126
194
|
private static (List<ApiMember> Top, int Total, string? RewriteNote) Search(DocIndex index, string query, char? kindFilter, int maxResults)
|
|
127
195
|
{
|
|
128
196
|
// Signatures are rendered exactly one way ("Name(Type, Type)"): normalize the
|
|
@@ -18,8 +18,18 @@ namespace RevitBridge.Tools
|
|
|
18
18
|
private const int MaxUpdates = 200;
|
|
19
19
|
|
|
20
20
|
public string Name => "set_parameters";
|
|
21
|
+
public IReadOnlyList<string> DocumentKinds => DocumentKind.Both;
|
|
22
|
+
public IReadOnlyList<string> Keywords => new[] { "parameter", "set value", "edit", "change value", "rename", "mark", "comments", "bulk edit", "update" };
|
|
23
|
+
public IReadOnlyList<ToolLimit> Limits => new[]
|
|
24
|
+
{
|
|
25
|
+
new ToolLimit("Changing an element's type", "tool", "change_element_types"),
|
|
26
|
+
new ToolLimit("Element properties that are not parameters, such as pinned state", "api", "Element.Pinned"),
|
|
27
|
+
new ToolLimit("Moving or rotating elements", "tool", "transform_elements"),
|
|
28
|
+
new ToolLimit("Family parameters, family types and formulas in a family document", "api", DocumentKind.FamilyApi),
|
|
29
|
+
};
|
|
30
|
+
public string? Verification => "reread";
|
|
21
31
|
public string Label => "Set Parameters";
|
|
22
|
-
public string Description => "Write parameter values on elements — also the home for rename: parameter 'Name' covers levels, views, sheets, types, etc. (falls back to the element's Name property when the Name parameter is read-only).
|
|
32
|
+
public string Description => "Write parameter values on elements — also the home for rename: parameter 'Name' covers levels, views, sheets, types, etc. (falls back to the element's Name property when the Name parameter is read-only). Each update has its own subtransaction inside one model transaction. Default partial success commits valid updates; atomic=true rolls everything back if any update fails. preview=true validates commit inside a transaction group, then rolls the group back. Results include observed per-step before/after values and distinguish succeeded from proposed updates. parameter accepts a display name (Comments, Mark, Name), a BuiltInParameter enum name (e.g. ALL_MODEL_MARK), or guid:<GUID> for a shared parameter; type parameters live on the element type, so pass the type's id. Values are validated against the parameter's storage type; numeric values are interpreted in the document's display units for that parameter unless 'unit' (e.g. millimeters, feet, squareMeters) is given. Revit warnings raised at commit (e.g. duplicate Mark values) are auto-dismissed and listed in commitWarnings — mention them to the user; error-severity failures roll the whole transaction back.";
|
|
23
33
|
public bool Write => true;
|
|
24
34
|
|
|
25
35
|
public object ParametersSchema => new
|
|
@@ -27,6 +37,8 @@ namespace RevitBridge.Tools
|
|
|
27
37
|
type = "object",
|
|
28
38
|
properties = new
|
|
29
39
|
{
|
|
40
|
+
preview = new { type = "boolean", description = "Validate edits through Revit commit, then roll back the enclosing transaction group. Returns proposed before/after values; no model changes remain. Default false." },
|
|
41
|
+
atomic = new { type = "boolean", description = "Roll back all updates if any fails. Default false preserves successful updates. Each update is isolated in a subtransaction." },
|
|
30
42
|
updates = new
|
|
31
43
|
{
|
|
32
44
|
type = "array",
|
|
@@ -57,7 +69,7 @@ namespace RevitBridge.Tools
|
|
|
57
69
|
public IReadOnlyList<string>? PromptGuidelines => new[]
|
|
58
70
|
{
|
|
59
71
|
"Use set_parameters for parameter writes and renames (parameter 'Name'); pass 'unit' with numeric values unless the document's display units are intended.",
|
|
60
|
-
"set_parameters
|
|
72
|
+
"set_parameters defaults to partial success; use atomic=true for all-or-nothing batches and preview=true for commit-validated model rollback. Always inspect committed, proposed and failed.",
|
|
61
73
|
};
|
|
62
74
|
|
|
63
75
|
public object? Execute(JsonElement args, ToolContext context)
|
|
@@ -65,87 +77,32 @@ namespace RevitBridge.Tools
|
|
|
65
77
|
var doc = context.Document ?? throw new NoActiveDocumentException();
|
|
66
78
|
DocumentGuard.CheckExpectedDocument(args, doc);
|
|
67
79
|
var updates = ParseUpdates(args);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
using var transaction = new Transaction(doc, "set_parameters");
|
|
73
|
-
if (transaction.Start() != TransactionStatus.Started)
|
|
74
|
-
throw new InvalidOperationException("Unable to start the set_parameters transaction.");
|
|
75
|
-
var failureGuard = FailureGuard.Attach(transaction);
|
|
76
|
-
|
|
77
|
-
try
|
|
78
|
-
{
|
|
79
|
-
foreach (var update in updates)
|
|
80
|
+
var steps = updates.Select(update => new ModelEditBatch.Step(
|
|
81
|
+
new Dictionary<string, object?> { ["id"] = update.ElementId, ["parameter"] = update.ParameterInput },
|
|
82
|
+
() =>
|
|
80
83
|
{
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
string? newDisplayValue = Apply(doc, update);
|
|
84
|
-
succeeded.Add(new Dictionary<string, object?>
|
|
85
|
-
{
|
|
86
|
-
["id"] = update.ElementId,
|
|
87
|
-
["parameter"] = update.ParameterInput,
|
|
88
|
-
["newDisplayValue"] = newDisplayValue,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
catch (Exception ex)
|
|
84
|
+
var change = Apply(doc, update);
|
|
85
|
+
return new Dictionary<string, object?>
|
|
92
86
|
{
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
var status = transaction.Commit();
|
|
105
|
-
var finalStatus = transaction.GetStatus();
|
|
106
|
-
if (status != TransactionStatus.Committed || finalStatus != TransactionStatus.Committed)
|
|
107
|
-
throw new InvalidOperationException(
|
|
108
|
-
$"The set_parameters commit returned {status}; current transaction status is {finalStatus}."
|
|
109
|
-
+ failureGuard.DescribeErrors());
|
|
110
|
-
}
|
|
111
|
-
else
|
|
112
|
-
{
|
|
113
|
-
var status = transaction.RollBack();
|
|
114
|
-
if (status != TransactionStatus.RolledBack || transaction.GetStatus() != TransactionStatus.RolledBack)
|
|
115
|
-
throw new InvalidOperationException($"The set_parameters rollback returned {status}.");
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
catch (Exception ex)
|
|
119
|
-
{
|
|
120
|
-
throw new InvalidOperationException($"{ex.Message} {FailureGuard.RollBackAndDescribe(transaction)}", ex);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
int elementCount = succeeded.Select(row => row["id"]).Distinct().Count();
|
|
124
|
-
string failureSample = failed.Count > 0
|
|
125
|
-
? $" First failure: element {failed[0]["id"]} '{failed[0]["parameter"]}': {failed[0]["reason"]}"
|
|
126
|
-
: string.Empty;
|
|
127
|
-
string compact = failed.Count == 0
|
|
128
|
-
? $"Updated {succeeded.Count} parameter value(s) on {elementCount} element(s)."
|
|
129
|
-
: succeeded.Count == 0
|
|
130
|
-
? $"No updates applied; all {failed.Count} failed — transaction rolled back.{failureSample}"
|
|
131
|
-
: $"Updated {succeeded.Count} parameter value(s) on {elementCount} element(s); {failed.Count} failed.{failureSample}";
|
|
132
|
-
if (failureGuard.Warnings.Count > 0)
|
|
133
|
-
compact += $" {failureGuard.Warnings.Count} Revit warning(s) auto-dismissed at commit (see commitWarnings), e.g.: {failureGuard.Warnings[0]}";
|
|
134
|
-
|
|
135
|
-
return new ToolOutput(new
|
|
136
|
-
{
|
|
137
|
-
updated = succeeded.Count,
|
|
138
|
-
committed = succeeded.Count > 0,
|
|
139
|
-
succeeded,
|
|
140
|
-
failed,
|
|
141
|
-
commitWarnings = failureGuard.Warnings,
|
|
142
|
-
}, compact);
|
|
87
|
+
["before"] = change.Before, ["after"] = change.After,
|
|
88
|
+
["newDisplayValue"] = change.Display,
|
|
89
|
+
};
|
|
90
|
+
})).ToArray();
|
|
91
|
+
var result = ModelEditBatch.Run(doc, Name, args, steps);
|
|
92
|
+
string summary = result.Preview
|
|
93
|
+
? $"Preview rolled back: {result.Proposed.Count} proposed update(s), {result.Failed.Count} failure(s)."
|
|
94
|
+
: result.Committed
|
|
95
|
+
? $"Applied {result.Succeeded.Count} parameter update(s); {result.Failed.Count} failed."
|
|
96
|
+
: $"No updates applied; {result.Failed.Count} failed. Batch rolled back.";
|
|
97
|
+
return new ToolOutput(result.Payload, summary);
|
|
143
98
|
}
|
|
144
99
|
|
|
145
100
|
// ------------------------------------------------------------- application
|
|
146
101
|
|
|
147
|
-
|
|
148
|
-
|
|
102
|
+
private sealed record ValueChange(string? Display, object? Before, object? After);
|
|
103
|
+
|
|
104
|
+
/// <summary>Snapshots the concrete parameter or rename target actually written by this step.</summary>
|
|
105
|
+
private static ValueChange Apply(Document doc, Update update)
|
|
149
106
|
{
|
|
150
107
|
var element = doc.GetElement(new ElementId(update.ElementId))
|
|
151
108
|
?? throw new InvalidOperationException($"Element {update.ElementId} not found.");
|
|
@@ -168,6 +125,7 @@ namespace RevitBridge.Tools
|
|
|
168
125
|
throw new InvalidOperationException($"Parameter '{parameter.Definition?.Name}' is read-only on element {update.ElementId}.");
|
|
169
126
|
}
|
|
170
127
|
|
|
128
|
+
var before = GetElementDetails.DescribeParameter(doc, parameter, parameter.Definition?.Name ?? "", false);
|
|
171
129
|
bool accepted = parameter.StorageType switch
|
|
172
130
|
{
|
|
173
131
|
StorageType.String => parameter.Set(ValueAsString(update.Value)),
|
|
@@ -183,36 +141,28 @@ namespace RevitBridge.Tools
|
|
|
183
141
|
return SetElementName(element, update);
|
|
184
142
|
throw new InvalidOperationException($"Revit rejected the value for '{parameter.Definition?.Name}' on element {update.ElementId} (Parameter.Set returned false).");
|
|
185
143
|
}
|
|
186
|
-
return DescribeNewValue(parameter) ?? ValueAsString(update.Value)
|
|
144
|
+
return new ValueChange(DescribeNewValue(parameter) ?? ValueAsString(update.Value), before,
|
|
145
|
+
GetElementDetails.DescribeParameter(doc, parameter, parameter.Definition?.Name ?? "", false));
|
|
187
146
|
}
|
|
188
147
|
|
|
189
148
|
/// <summary>Rename fallback: the Element.Name property setter.</summary>
|
|
190
|
-
private static
|
|
149
|
+
private static ValueChange SetElementName(Element element, Update update)
|
|
191
150
|
{
|
|
192
151
|
string name = ValueAsString(update.Value);
|
|
193
152
|
if (string.IsNullOrWhiteSpace(name))
|
|
194
153
|
throw new InvalidOperationException("Renaming needs a non-empty string value.");
|
|
195
|
-
element.Name =
|
|
196
|
-
|
|
154
|
+
var before = new { value = element.Name, displayValue = element.Name, storageType = "String" };
|
|
155
|
+
ElementNames.Assign(element, name);
|
|
156
|
+
return new ValueChange(name, before, new { value = element.Name, displayValue = element.Name, storageType = "String" });
|
|
197
157
|
}
|
|
198
158
|
|
|
159
|
+
/// <summary>
|
|
160
|
+
/// Shared resolution policy (inv:parameter-ambiguity): a display name matching several
|
|
161
|
+
/// parameters on the element fails this update with the candidates' exact identities
|
|
162
|
+
/// instead of writing to an arbitrary one. Missing stays null.
|
|
163
|
+
/// </summary>
|
|
199
164
|
private static Parameter? FindParameter(Element element, Update update)
|
|
200
|
-
|
|
201
|
-
if (update.BuiltIn is { } builtIn)
|
|
202
|
-
return element.get_Parameter(builtIn);
|
|
203
|
-
if (update.SharedGuid is { } guid)
|
|
204
|
-
return element.get_Parameter(guid);
|
|
205
|
-
|
|
206
|
-
var direct = element.LookupParameter(update.ParameterInput);
|
|
207
|
-
if (direct != null)
|
|
208
|
-
return direct;
|
|
209
|
-
foreach (Parameter parameter in element.Parameters)
|
|
210
|
-
{
|
|
211
|
-
if (string.Equals(parameter.Definition?.Name, update.ParameterInput, StringComparison.OrdinalIgnoreCase))
|
|
212
|
-
return parameter;
|
|
213
|
-
}
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
165
|
+
=> ParameterResolver.FindSingle(element, new ParameterReference(update.ParameterInput, update.BuiltIn, update.SharedGuid));
|
|
216
166
|
|
|
217
167
|
private static string? DescribeNewValue(Parameter parameter)
|
|
218
168
|
{
|
|
@@ -354,26 +304,7 @@ namespace RevitBridge.Tools
|
|
|
354
304
|
if (!item.TryGetProperty("value", out var value))
|
|
355
305
|
throw new ArgumentException($"The update for element {elementId} ('{parameterInput}') needs a value.");
|
|
356
306
|
|
|
357
|
-
|
|
358
|
-
Guid? sharedGuid = null;
|
|
359
|
-
if (parameterInput.StartsWith("guid:", StringComparison.OrdinalIgnoreCase))
|
|
360
|
-
{
|
|
361
|
-
sharedGuid = Guid.TryParse(parameterInput["guid:".Length..], out var guid)
|
|
362
|
-
? guid
|
|
363
|
-
: throw new ArgumentException($"Invalid shared parameter guid: {parameterInput}");
|
|
364
|
-
}
|
|
365
|
-
else
|
|
366
|
-
{
|
|
367
|
-
string enumName = parameterInput.StartsWith("BuiltInParameter.", StringComparison.OrdinalIgnoreCase)
|
|
368
|
-
? parameterInput["BuiltInParameter.".Length..]
|
|
369
|
-
: parameterInput;
|
|
370
|
-
// BuiltInParameter names are SHOUTY_SNAKE_CASE; require an underscore or
|
|
371
|
-
// all-caps so plain display names like "Comments" never collide.
|
|
372
|
-
bool looksLikeEnumName = enumName.Length > 0 && char.IsLetter(enumName[0]) && !enumName.Contains(' ')
|
|
373
|
-
&& (enumName.Contains('_') || enumName.All(c => !char.IsLetter(c) || char.IsUpper(c)));
|
|
374
|
-
if (looksLikeEnumName && Enum.TryParse<BuiltInParameter>(enumName, true, out var parsed) && parsed != BuiltInParameter.INVALID)
|
|
375
|
-
builtIn = parsed;
|
|
376
|
-
}
|
|
307
|
+
var reference = ParameterReference.Parse(parameterInput);
|
|
377
308
|
|
|
378
309
|
updates.Add(new Update
|
|
379
310
|
{
|
|
@@ -381,8 +312,8 @@ namespace RevitBridge.Tools
|
|
|
381
312
|
ParameterInput = parameterInput,
|
|
382
313
|
Value = value,
|
|
383
314
|
Unit = JsonArgs.GetString(item, "unit"),
|
|
384
|
-
BuiltIn =
|
|
385
|
-
SharedGuid =
|
|
315
|
+
BuiltIn = reference.BuiltIn,
|
|
316
|
+
SharedGuid = reference.SharedGuid,
|
|
386
317
|
});
|
|
387
318
|
}
|
|
388
319
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
using Autodesk.Revit.DB;
|
|
2
|
+
|
|
3
|
+
namespace RevitBridge.Tools;
|
|
4
|
+
|
|
5
|
+
internal sealed record SpatialBounds(XYZ Min, XYZ Max)
|
|
6
|
+
{
|
|
7
|
+
public static SpatialBounds? Of(Element element)
|
|
8
|
+
{
|
|
9
|
+
var box = element.get_BoundingBox(null);
|
|
10
|
+
if (box == null) return null;
|
|
11
|
+
var corners = new List<XYZ>(8);
|
|
12
|
+
foreach (double x in new[] { box.Min.X, box.Max.X })
|
|
13
|
+
foreach (double y in new[] { box.Min.Y, box.Max.Y })
|
|
14
|
+
foreach (double z in new[] { box.Min.Z, box.Max.Z })
|
|
15
|
+
corners.Add(box.Transform.OfPoint(new XYZ(x, y, z)));
|
|
16
|
+
return new(new(corners.Min(p => p.X), corners.Min(p => p.Y), corners.Min(p => p.Z)),
|
|
17
|
+
new(corners.Max(p => p.X), corners.Max(p => p.Y), corners.Max(p => p.Z)));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public bool Intersects(SpatialBounds other) => Min.X <= other.Max.X && Max.X >= other.Min.X
|
|
21
|
+
&& Min.Y <= other.Max.Y && Max.Y >= other.Min.Y && Min.Z <= other.Max.Z && Max.Z >= other.Min.Z;
|
|
22
|
+
public bool Contains(SpatialBounds other) => Min.X <= other.Min.X && Max.X >= other.Max.X
|
|
23
|
+
&& Min.Y <= other.Min.Y && Max.Y >= other.Max.Y && Min.Z <= other.Min.Z && Max.Z >= other.Max.Z;
|
|
24
|
+
public XYZ Gap(SpatialBounds other) => new(
|
|
25
|
+
Math.Max(0, Math.Max(Min.X - other.Max.X, other.Min.X - Max.X)),
|
|
26
|
+
Math.Max(0, Math.Max(Min.Y - other.Max.Y, other.Min.Y - Max.Y)),
|
|
27
|
+
Math.Max(0, Math.Max(Min.Z - other.Max.Z, other.Min.Z - Max.Z)));
|
|
28
|
+
public static double[] Coordinates(XYZ p, double scale) => new[] { p.X / scale, p.Y / scale, p.Z / scale };
|
|
29
|
+
public object Describe(double scale) => new { min = Coordinates(Min, scale), max = Coordinates(Max, scale) };
|
|
30
|
+
}
|