pi-revit 0.3.0 → 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 +394 -351
- package/README.md +438 -25
- package/bin/pi-revit.js +3 -5
- package/extensions/pi-revit/index.ts +151 -92
- 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 +58 -57
- package/scripts/build.ps1 +9 -3
- package/scripts/check-sdk.ps1 +66 -0
- package/scripts/deploy.ps1 +16 -4
- package/skills/pi-revit/SKILL.md +181 -17
- 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 +79 -29
- package/src/Revit/OperationStore.cs +178 -0
- package/src/Revit/ToolRegistry.cs +26 -4
- 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 +2 -2
- package/src/Revit/Tools/ElementQueryScope.cs +27 -0
- package/src/Revit/Tools/ExecuteCsharp.cs +23 -13
- package/src/Revit/Tools/ExportDocuments.cs +2 -1
- package/src/Revit/Tools/GetElementDetails.cs +32 -2
- package/src/Revit/Tools/GetElementRelationships.cs +76 -0
- package/src/Revit/Tools/GetElements.cs +21 -7
- 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/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 +2 -1
- 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/TransformElements.cs +58 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using System.Text.Json.Nodes;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal static class ElementQueryScope
|
|
7
|
+
{
|
|
8
|
+
public static JsonObject Schema()
|
|
9
|
+
{
|
|
10
|
+
var schema = JsonSerializer.SerializeToNode(new GetElements().ParametersSchema)!.AsObject();
|
|
11
|
+
foreach (string key in new[] { "count_only", "fields", "offset", "limit", "parameter_names", "include_type_parameters" })
|
|
12
|
+
schema["properties"]!.AsObject().Remove(key);
|
|
13
|
+
schema["description"] = "Filter the whole matching scope. Query paging and projections are not supported here.";
|
|
14
|
+
schema["additionalProperties"] = false;
|
|
15
|
+
return schema;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public static JsonObject Parse(JsonElement args)
|
|
19
|
+
{
|
|
20
|
+
var query = args.TryGetProperty("query", out var input) ? JsonNode.Parse(input.GetRawText()) as JsonObject : new JsonObject();
|
|
21
|
+
if (query == null) throw new ArgumentException("query must be an object.");
|
|
22
|
+
var allowed = Schema()["properties"]!.AsObject();
|
|
23
|
+
foreach (var property in query)
|
|
24
|
+
if (!allowed.ContainsKey(property.Key)) throw new ArgumentException($"query does not support '{property.Key}'. It always uses the whole matching scope.");
|
|
25
|
+
return query;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -20,11 +20,12 @@ namespace RevitBridge.Tools
|
|
|
20
20
|
/// </summary>
|
|
21
21
|
public sealed class ScriptGlobals
|
|
22
22
|
{
|
|
23
|
-
internal ScriptGlobals(Document document, UIDocument uiDocument, UIApplication uiApplication, Action<object?> dump)
|
|
23
|
+
internal ScriptGlobals(Document document, UIDocument uiDocument, UIApplication uiApplication, JsonElement inputValues, Action<object?> dump)
|
|
24
24
|
{
|
|
25
25
|
doc = document;
|
|
26
26
|
uidoc = uiDocument;
|
|
27
|
-
uiapp = uiApplication;
|
|
27
|
+
uiapp = uiApplication;
|
|
28
|
+
inputs = inputValues;
|
|
28
29
|
Dump = dump;
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -35,7 +36,10 @@ namespace RevitBridge.Tools
|
|
|
35
36
|
public UIDocument uidoc { get; }
|
|
36
37
|
|
|
37
38
|
/// <summary>The Revit UI application.</summary>
|
|
38
|
-
public UIApplication uiapp { get; }
|
|
39
|
+
public UIApplication uiapp { get; }
|
|
40
|
+
|
|
41
|
+
/// <summary>Structured inputs supplied separately from the script source.</summary>
|
|
42
|
+
public JsonElement inputs { get; }
|
|
39
43
|
|
|
40
44
|
/// <summary>Records a value into the result's dumps[] (safely projected at call time).</summary>
|
|
41
45
|
public Action<object?> Dump { get; }
|
|
@@ -59,19 +63,21 @@ namespace RevitBridge.Tools
|
|
|
59
63
|
|
|
60
64
|
public string Name => "execute_csharp";
|
|
61
65
|
public string Label => "Execute C#";
|
|
62
|
-
public string Description => "Compile and run a C# script on the Revit API thread against the open model — the escape hatch for anything without a dedicated tool: element creation, deletion, geometry edits (move/copy/rotate), views, sheets, schedules, tagging, families, links, worksets. Globals: doc (Document), uidoc (UIDocument), uiapp (UIApplication), and Dump(value) to record intermediate values into the result's dumps[]. Default imports: System, System.Linq, System.Collections.Generic, Autodesk.Revit.DB, Autodesk.Revit.UI — add using directives at the top for sub-namespaces (e.g. using Autodesk.Revit.DB.Architecture;). The entire run is wrapped in ONE transaction named 'execute_csharp': committed on success; on failure rollback is attempted and its confirmed status is reported (do not open your own Transaction; sub-transactions are fine). The script's final expression or return statement becomes returnValue; return primitives, strings, or anonymous objects/lists — Revit API values are projected to safe shapes (Element -> {id,name,category,typeName,levelId}, ElementId -> number, XYZ -> {x,y,z}, Parameter -> {name,value,displayValue}; other API objects become strings) with depth and item caps, so never rely on raw API objects round-tripping. Lengths are in internal units (decimal feet) — convert with UnitUtils. Prefer collector-level filtering (FilteredElementCollector .OfCategory/.OfClass/.WhereElementIsNotElementType) and bounded loops: the call budget is 120s and Revit cannot be interrupted mid-script. Scripts must be fully synchronous — await/async is rejected at compile time, and blocking on tasks (Task.Result/.Wait()) can freeze Revit. Modal dialogs raised while running are auto-dismissed and reported in suppressedDialogs — unrecognized dialogs are answered dismissively (Cancel/Close/No) rather than confirmed, so an operation that raises a confirmation prompt may be cancelled; check suppressedDialogs when a result looks incomplete. Verify unfamiliar signatures with search_api_docs first.";
|
|
63
|
-
public bool Write => true;
|
|
66
|
+
public string Description => "Compile and run a C# script on the Revit API thread against the open model — the escape hatch for anything without a dedicated tool: element creation, deletion, geometry edits (move/copy/rotate), views, sheets, schedules, tagging, families, links, worksets. Globals: doc (Document), uidoc (UIDocument), uiapp (UIApplication), inputs (System.Text.Json.JsonElement, defaults to an empty object), and Dump(value) to record intermediate values into the result's dumps[]. Default imports: System, System.Linq, System.Collections.Generic, Autodesk.Revit.DB, Autodesk.Revit.UI — add using directives at the top for sub-namespaces (e.g. using Autodesk.Revit.DB.Architecture;). The entire run is wrapped in ONE transaction named 'execute_csharp': committed on success; on failure rollback is attempted and its confirmed status is reported (do not open your own Transaction; sub-transactions are fine). The script's final expression or return statement becomes returnValue; return primitives, strings, or anonymous objects/lists — Revit API values are projected to safe shapes (Element -> {id,name,category,typeName,levelId}, ElementId -> number, XYZ -> {x,y,z}, Parameter -> {name,value,displayValue}; other API objects become strings) with depth and item caps, so never rely on raw API objects round-tripping. Lengths are in internal units (decimal feet) — convert with UnitUtils. Prefer collector-level filtering (FilteredElementCollector .OfCategory/.OfClass/.WhereElementIsNotElementType) and bounded loops: the call budget is 120s and Revit cannot be interrupted mid-script. Scripts must be fully synchronous — await/async is rejected at compile time, and blocking on tasks (Task.Result/.Wait()) can freeze Revit. Modal dialogs raised while running are auto-dismissed and reported in suppressedDialogs — unrecognized dialogs are answered dismissively (Cancel/Close/No) rather than confirmed, so an operation that raises a confirmation prompt may be cancelled; check suppressedDialogs when a result looks incomplete. Verify unfamiliar signatures with search_api_docs first.";
|
|
67
|
+
public bool Write => true;
|
|
68
|
+
public IReadOnlyList<string> Effects => new[] { "model", "ui", "files", "external" };
|
|
64
69
|
|
|
65
70
|
public object ParametersSchema => new
|
|
66
71
|
{
|
|
67
72
|
type = "object",
|
|
68
73
|
properties = new
|
|
69
74
|
{
|
|
70
|
-
code = new
|
|
75
|
+
code = new
|
|
71
76
|
{
|
|
72
77
|
type = "string",
|
|
73
|
-
description = "C# script body (top-level statements; using directives allowed at the top). Globals doc/uidoc/uiapp and Dump(value) are in scope. The final expression or a return statement is the result.",
|
|
74
|
-
},
|
|
78
|
+
description = "C# script body (top-level statements; using directives allowed at the top). Globals doc/uidoc/uiapp/inputs and Dump(value) are in scope. The final expression or a return statement is the result.",
|
|
79
|
+
},
|
|
80
|
+
inputs = new { type = "object", description = "Optional structured JSON inputs, available as the inputs JsonElement global. Default empty object. Values are never interpolated into source code." },
|
|
75
81
|
expected_document = new
|
|
76
82
|
{
|
|
77
83
|
type = "string",
|
|
@@ -96,8 +102,11 @@ namespace RevitBridge.Tools
|
|
|
96
102
|
DocumentGuard.CheckExpectedDocument(args, doc);
|
|
97
103
|
|
|
98
104
|
string code = JsonArgs.GetString(args, "code") ?? string.Empty;
|
|
99
|
-
if (string.IsNullOrWhiteSpace(code))
|
|
100
|
-
throw new ArgumentException("code must be a non-empty C# script.");
|
|
105
|
+
if (string.IsNullOrWhiteSpace(code))
|
|
106
|
+
throw new ArgumentException("code must be a non-empty C# script.");
|
|
107
|
+
var inputValues = args.TryGetProperty("inputs", out var suppliedInputs) ? suppliedInputs.Clone() : JsonSerializer.SerializeToElement(new { });
|
|
108
|
+
if (inputValues.ValueKind != JsonValueKind.Object) throw new ArgumentException("inputs must be a JSON object.");
|
|
109
|
+
if (inputValues.GetRawText().Length > 100000) throw new ArgumentException("inputs must not exceed 100,000 JSON characters.");
|
|
101
110
|
|
|
102
111
|
var stopwatch = Stopwatch.StartNew();
|
|
103
112
|
|
|
@@ -109,7 +118,7 @@ namespace RevitBridge.Tools
|
|
|
109
118
|
RejectAsyncCode(script.GetCompilation());
|
|
110
119
|
|
|
111
120
|
var dumps = new List<object?>();
|
|
112
|
-
var globals = new ScriptGlobals(doc, uidoc, uiapp, value =>
|
|
121
|
+
var globals = new ScriptGlobals(doc, uidoc, uiapp, inputValues, value =>
|
|
113
122
|
{
|
|
114
123
|
if (dumps.Count < MaxDumps)
|
|
115
124
|
dumps.Add(Project(value, 0));
|
|
@@ -194,7 +203,8 @@ namespace RevitBridge.Tools
|
|
|
194
203
|
Assembly.Load("netstandard"),
|
|
195
204
|
typeof(Enumerable).Assembly, // System.Linq
|
|
196
205
|
typeof(Regex).Assembly, // System.Text.RegularExpressions
|
|
197
|
-
typeof(Console).Assembly, // System.Console
|
|
206
|
+
typeof(Console).Assembly, // System.Console
|
|
207
|
+
typeof(JsonElement).Assembly, // System.Text.Json
|
|
198
208
|
typeof(Document).Assembly, // RevitAPI
|
|
199
209
|
typeof(UIApplication).Assembly) // RevitAPIUI
|
|
200
210
|
.WithImports("System", "System.Linq", "System.Collections.Generic", "Autodesk.Revit.DB", "Autodesk.Revit.UI")
|
|
@@ -209,7 +219,7 @@ namespace RevitBridge.Tools
|
|
|
209
219
|
});
|
|
210
220
|
string more = errors.Count > MaxReportedErrors ? $"\n … +{errors.Count - MaxReportedErrors} more error(s)" : string.Empty;
|
|
211
221
|
return $"C# compilation failed with {errors.Count} error(s):\n{string.Join("\n", lines)}{more}\n"
|
|
212
|
-
+ "Globals: doc, uidoc, uiapp, Dump(value). Default imports: System, System.Linq, System.Collections.Generic, "
|
|
222
|
+
+ "Globals: doc, uidoc, uiapp, inputs (JsonElement), Dump(value). Default imports: System, System.Linq, System.Collections.Generic, "
|
|
213
223
|
+ "Autodesk.Revit.DB, Autodesk.Revit.UI — add using directives for other namespaces.";
|
|
214
224
|
}
|
|
215
225
|
|
|
@@ -22,7 +22,8 @@ namespace RevitBridge.Tools
|
|
|
22
22
|
public string Name => "export_documents";
|
|
23
23
|
public string Label => "Export Documents";
|
|
24
24
|
public string Description => "Export documents from the open Revit model. format 'pdf'/'dwg'/'png' export the given sheet/view ids to files: pdf combines everything into one file by default (combine=false writes one PDF per sheet/view, named by Revit's naming rule); png renders 2048 px wide; ifc exports the whole model, or just what one given view shows. Files sort themselves per model: with output_dir omitted they land in Documents\\pi-revit\\Models\\<model title>--<identity hash>\\exports, derived from the document being exported (pass output_dir only for a different explicit target); file_name_prefix sets the base file name (default: the document title; Revit appends view/sheet suffixes for multi-file exports). Returns the produced file paths with sizes. Find sheet/view ids with get_elements (category 'Sheets' or 'Views') first.";
|
|
25
|
-
public bool Write => true;
|
|
25
|
+
public bool Write => true;
|
|
26
|
+
public IReadOnlyList<string> Effects => new[] { "model", "files" };
|
|
26
27
|
public string Tier => "advanced";
|
|
27
28
|
|
|
28
29
|
public object ParametersSchema => new
|
|
@@ -10,7 +10,7 @@ namespace RevitBridge.Tools
|
|
|
10
10
|
|
|
11
11
|
public string Name => "get_element_details";
|
|
12
12
|
public string Label => "Get Element Details";
|
|
13
|
-
public string Description => "Inspect one or more elements by id: parameter VALUES (name, internal value, formatted displayValue, storage type, display unit where applicable, read-only/shared flags), plus optional type parameters, location (point/curve), bounding box, and materials. Internal numeric values are Revit internal units (feet-based); displayValue is formatted in the document's display units and 'unit' names that display unit.
|
|
13
|
+
public string Description => "Inspect one or more elements by id: parameter VALUES (name, internal value, formatted displayValue, storage type, display unit where applicable, read-only/shared flags), plus optional type parameters, location (point/curve), bounding box, and materials. Internal numeric values are Revit internal units (feet-based); displayValue is formatted in the document's display units and 'unit' names that display unit. Use get_elements parameter_names to project a small set of values alongside a query.";
|
|
14
14
|
|
|
15
15
|
public object ParametersSchema => new
|
|
16
16
|
{
|
|
@@ -167,7 +167,37 @@ namespace RevitBridge.Tools
|
|
|
167
167
|
return total;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
internal static IReadOnlyList<object> ProjectParameters(Document doc, Element element, IReadOnlyList<string> names, bool includeType)
|
|
171
|
+
{
|
|
172
|
+
var result = new List<object>();
|
|
173
|
+
void Project(Element source, bool isType)
|
|
174
|
+
{
|
|
175
|
+
foreach (string input in names.Distinct(StringComparer.OrdinalIgnoreCase))
|
|
176
|
+
{
|
|
177
|
+
IEnumerable<Parameter> matches;
|
|
178
|
+
if (input.StartsWith("guid:", StringComparison.OrdinalIgnoreCase))
|
|
179
|
+
{
|
|
180
|
+
if (!Guid.TryParse(input[5..], out var guid)) throw new ArgumentException($"Invalid shared parameter identity: {input}");
|
|
181
|
+
var parameter = source.get_Parameter(guid);
|
|
182
|
+
matches = parameter == null ? Array.Empty<Parameter>() : new[] { parameter };
|
|
183
|
+
}
|
|
184
|
+
else if (Enum.TryParse<BuiltInParameter>(input, true, out var builtIn) && Enum.IsDefined(builtIn) && builtIn != BuiltInParameter.INVALID)
|
|
185
|
+
{
|
|
186
|
+
var parameter = source.get_Parameter(builtIn);
|
|
187
|
+
matches = parameter == null ? Array.Empty<Parameter>() : new[] { parameter };
|
|
188
|
+
}
|
|
189
|
+
else
|
|
190
|
+
matches = source.Parameters.Cast<Parameter>().Where(p => string.Equals(p.Definition?.Name, input, StringComparison.OrdinalIgnoreCase));
|
|
191
|
+
var values = matches.Select(p => DescribeParameter(doc, p, p.Definition?.Name ?? "", isType)).ToArray();
|
|
192
|
+
result.Add(new { requested = input, isType, found = values.Length > 0, ambiguous = values.Length > 1, matches = values });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
Project(element, false);
|
|
196
|
+
if (includeType && doc.GetElement(element.GetTypeId()) is { } type) Project(type, true);
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
internal static Dictionary<string, object?> DescribeParameter(Document doc, Parameter parameter, string name, bool isType)
|
|
171
201
|
{
|
|
172
202
|
bool hasValue = parameter.HasValue;
|
|
173
203
|
var row = new Dictionary<string, object?>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal sealed class GetElementRelationships : ITool
|
|
7
|
+
{
|
|
8
|
+
public string Name => "get_element_relationships";
|
|
9
|
+
public string Label => "Get Element Relationships";
|
|
10
|
+
public string Tier => "advanced";
|
|
11
|
+
public string Description => "Inspect host-document element relationships: type, level, owning view, host, family parent/subcomponents, group or assembly membership, joined geometry and logical dependents. Dependents are not a complete deletion-impact prediction. Pagination applies independently to each relationship; links are not traversed.";
|
|
12
|
+
private static readonly string[] Kinds = { "type", "level", "owner_view", "host", "parent", "subcomponents", "group", "assembly", "members", "joined", "dependents" };
|
|
13
|
+
public object ParametersSchema => new
|
|
14
|
+
{
|
|
15
|
+
type = "object",
|
|
16
|
+
properties = new
|
|
17
|
+
{
|
|
18
|
+
element_id = new { type = "integer" },
|
|
19
|
+
relationships = new { type = "array", items = new { type = "string", @enum = Kinds } },
|
|
20
|
+
offset = new { type = "integer", minimum = 0 },
|
|
21
|
+
limit = new { type = "integer", minimum = 1, maximum = 200 },
|
|
22
|
+
},
|
|
23
|
+
required = new[] { "element_id" },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
public object Execute(JsonElement args, ToolContext context)
|
|
27
|
+
{
|
|
28
|
+
var doc = context.Document ?? throw new NoActiveDocumentException();
|
|
29
|
+
var id = JsonArgs.GetLong(args, "element_id") ?? throw new ArgumentException("element_id is required.");
|
|
30
|
+
var element = doc.GetElement(new ElementId(id)) ?? throw new ArgumentException("element_id does not exist in the active document.");
|
|
31
|
+
var kinds = (IReadOnlyList<string>?)JsonArgs.GetStringArray(args, "relationships") ?? Kinds;
|
|
32
|
+
if (kinds.Any(kind => !Kinds.Contains(kind))) throw new ArgumentException("Unknown relationship kind.");
|
|
33
|
+
int offset = Math.Max(0, JsonArgs.GetInt(args, "offset", 0));
|
|
34
|
+
int limit = Math.Clamp(JsonArgs.GetInt(args, "limit", 100), 1, 200);
|
|
35
|
+
var result = new Dictionary<string, object?>();
|
|
36
|
+
foreach (var kind in kinds.Distinct())
|
|
37
|
+
{
|
|
38
|
+
IEnumerable<ElementId> ids = kind switch
|
|
39
|
+
{
|
|
40
|
+
"type" => One(element.GetTypeId()),
|
|
41
|
+
"level" => One(element.LevelId),
|
|
42
|
+
"owner_view" => One(element.OwnerViewId),
|
|
43
|
+
"host" => One((element as FamilyInstance)?.Host?.Id),
|
|
44
|
+
"parent" => One((element as FamilyInstance)?.SuperComponent?.Id),
|
|
45
|
+
"subcomponents" => element is FamilyInstance family ? family.GetSubComponentIds() : Array.Empty<ElementId>(),
|
|
46
|
+
"group" => One(element.GroupId),
|
|
47
|
+
"assembly" => One(element.AssemblyInstanceId),
|
|
48
|
+
"members" => element is Group group ? group.GetMemberIds()
|
|
49
|
+
: element is AssemblyInstance assembly ? assembly.GetMemberIds() : Array.Empty<ElementId>(),
|
|
50
|
+
"joined" => Joined(doc, element),
|
|
51
|
+
"dependents" => element.GetDependentElements(null),
|
|
52
|
+
_ => Array.Empty<ElementId>(),
|
|
53
|
+
};
|
|
54
|
+
var all = ids.Where(x => x != ElementId.InvalidElementId).DistinctBy(x => x.Value).OrderBy(x => x.Value).ToList();
|
|
55
|
+
var rows = all.Skip(offset).Take(limit).Select(x =>
|
|
56
|
+
{
|
|
57
|
+
var related = doc.GetElement(x);
|
|
58
|
+
return new { id = x.Value, name = related?.Name, category = related?.Category?.Name, unique_id = related?.UniqueId };
|
|
59
|
+
}).ToArray();
|
|
60
|
+
result[kind] = new { total_count = all.Count, returned_count = rows.Length, offset,
|
|
61
|
+
has_more = offset + rows.Length < all.Count,
|
|
62
|
+
next_offset = offset + rows.Length < all.Count ? (int?)(offset + rows.Length) : null, elements = rows };
|
|
63
|
+
}
|
|
64
|
+
return new { document_id = DocumentGuard.GetIdentity(doc), element_id = id, relationships = result };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private static IEnumerable<ElementId> One(ElementId? id)
|
|
68
|
+
=> id == null || id == ElementId.InvalidElementId ? Array.Empty<ElementId>() : new[] { id };
|
|
69
|
+
|
|
70
|
+
private static IEnumerable<ElementId> Joined(Document doc, Element element)
|
|
71
|
+
{
|
|
72
|
+
// JoinGeometryUtils rejects family documents; surface that restriction explicitly.
|
|
73
|
+
if (doc.IsFamilyDocument) throw new ArgumentException("joined relationships require a project document. Request other relationship kinds for families.");
|
|
74
|
+
return JoinGeometryUtils.GetJoinedElements(doc, element);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -21,7 +21,7 @@ namespace RevitBridge.Tools
|
|
|
21
21
|
|
|
22
22
|
public string Name => "get_elements";
|
|
23
23
|
public string Label => "Get Elements";
|
|
24
|
-
public string Description => "Query Revit elements: scope by category (display name like 'Walls' or enum name like 'OST_Walls'), element class, level, type id, or active view; filter by parameter rules; paginate with offset/limit or just count with count_only. Returns identity fields
|
|
24
|
+
public string Description => "Query Revit elements: scope by category (display name like 'Walls' or enum name like 'OST_Walls'), element class, level, type id, or active view; filter by parameter rules; paginate with offset/limit or just count with count_only. Returns identity fields and optional parameter_names projections; include_type_parameters also reads the requested values from each type. Rules with an explicit BuiltInParameter or shared GUID can evaluate inside Revit's collector; display-name rules, regex rules, and unsupported quick filters run as a per-element scan. A display name can identify different parameters even within one category or class. Scope queries to reduce the scan, or use an explicit parameter identity when appropriate. Numeric rule values are interpreted in the document's display units for that parameter unless 'unit' is given.";
|
|
25
25
|
|
|
26
26
|
public object ParametersSchema => new
|
|
27
27
|
{
|
|
@@ -72,12 +72,18 @@ namespace RevitBridge.Tools
|
|
|
72
72
|
level = new { type = "string", description = "Only elements hosted on this level (level name or id)." },
|
|
73
73
|
type_id = new { type = "integer", description = "Only instances of this element type id (see get_element_types)." },
|
|
74
74
|
in_active_view = new { type = "boolean", description = "Only elements visible in the active view. Default false." },
|
|
75
|
-
count_only = new { type = "boolean", description = "Return only total_count, no element rows. Default false." },
|
|
75
|
+
count_only = new { type = "boolean", description = "Return only total_count, no element rows. Default false." },
|
|
76
|
+
parameter_names = new
|
|
77
|
+
{
|
|
78
|
+
type = "array", maxItems = 20, items = new { type = "string" },
|
|
79
|
+
description = "Optional parameter projections for each returned element: display names, BuiltInParameter names or guid:<GUID>. Every matching parameter is reported, including missing/ambiguous matches. Numeric values use internal units with separate display values. Ignored by count_only.",
|
|
80
|
+
},
|
|
81
|
+
include_type_parameters = new { type = "boolean", description = "Also project requested parameters from each element's type. Default false." },
|
|
76
82
|
fields = new
|
|
77
83
|
{
|
|
78
84
|
type = "array",
|
|
79
85
|
items = new { type = "string", @enum = new[] { "id", "name", "category", "typeName", "levelId" } },
|
|
80
|
-
description = "Identity fields per row; default all five. id is always included.
|
|
86
|
+
description = "Identity fields per row; default all five. id is always included. Use parameter_names for optional values alongside these fields.",
|
|
81
87
|
},
|
|
82
88
|
offset = new { type = "integer", description = "Pagination offset. Default 0." },
|
|
83
89
|
limit = new { type = "integer", description = "Max rows to return (1-1000). Default 200." },
|
|
@@ -89,7 +95,7 @@ namespace RevitBridge.Tools
|
|
|
89
95
|
public IReadOnlyList<string>? PromptGuidelines => new[]
|
|
90
96
|
{
|
|
91
97
|
"Use get_elements to list or count elements of any category or class (walls, doors, rooms, sheets, views, ...); use the returned ids with the other Revit tools.",
|
|
92
|
-
"get_elements
|
|
98
|
+
"get_elements can project up to 20 parameter_names per row; use get_element_details for full element inspection.",
|
|
93
99
|
};
|
|
94
100
|
|
|
95
101
|
public object? Execute(JsonElement args, ToolContext context)
|
|
@@ -103,7 +109,10 @@ namespace RevitBridge.Tools
|
|
|
103
109
|
|| string.Equals(JsonArgs.GetString(args, "mode"), "count", StringComparison.OrdinalIgnoreCase);
|
|
104
110
|
int offset = Math.Max(0, JsonArgs.GetInt(args, "offset", 0));
|
|
105
111
|
int limit = Math.Clamp(JsonArgs.GetInt(args, "limit", DefaultLimit), 1, MaxLimit);
|
|
106
|
-
var fields = ResolveFields(args);
|
|
112
|
+
var fields = ResolveFields(args);
|
|
113
|
+
var parameterNames = JsonArgs.GetStringArray(args, "parameter_names");
|
|
114
|
+
if (parameterNames?.Count > 20) throw new ArgumentException("parameter_names allows at most 20 parameter identities.");
|
|
115
|
+
bool includeTypeParameters = JsonArgs.GetBool(args, "include_type_parameters", false);
|
|
107
116
|
|
|
108
117
|
ElementId? categoryId = string.IsNullOrWhiteSpace(categoryInput) ? null : CategoryResolver.Resolve(doc, categoryInput);
|
|
109
118
|
Type? elementClass = string.IsNullOrWhiteSpace(classInput) ? null : ElementClassResolver.Resolve(classInput);
|
|
@@ -158,8 +167,13 @@ namespace RevitBridge.Tools
|
|
|
158
167
|
{
|
|
159
168
|
if (postPredicate != null && !postPredicate(element))
|
|
160
169
|
continue;
|
|
161
|
-
if (rows != null && total >= offset && rows.Count < limit)
|
|
162
|
-
|
|
170
|
+
if (rows != null && total >= offset && rows.Count < limit)
|
|
171
|
+
{
|
|
172
|
+
var row = ElementIdentity.Build(doc, element, fields);
|
|
173
|
+
if (parameterNames is { Count: > 0 })
|
|
174
|
+
row["parameters"] = GetElementDetails.ProjectParameters(doc, element, parameterNames, includeTypeParameters);
|
|
175
|
+
rows.Add(row);
|
|
176
|
+
}
|
|
163
177
|
total++;
|
|
164
178
|
}
|
|
165
179
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using System.Text.Json.Nodes;
|
|
3
|
+
using Autodesk.Revit.DB;
|
|
4
|
+
|
|
5
|
+
namespace RevitBridge.Tools;
|
|
6
|
+
|
|
7
|
+
internal sealed class GetLinkedElements : ITool
|
|
8
|
+
{
|
|
9
|
+
public string Name => "get_linked_elements";
|
|
10
|
+
public string Label => "Get Linked Elements";
|
|
11
|
+
public string Tier => "advanced";
|
|
12
|
+
public string Description => "Query one loaded Revit link with get_elements filters and pagination. Use link_instance_id and expected_linked_document_id from get_linked_models. Returned element IDs belong to the linked document and cannot be passed to host write tools. Optional bounds are axis-aligned in host coordinates, in internal feet. Only direct links are supported; active-view scoping is unavailable.";
|
|
13
|
+
public object ParametersSchema
|
|
14
|
+
{
|
|
15
|
+
get
|
|
16
|
+
{
|
|
17
|
+
var schema = JsonSerializer.SerializeToNode(new GetElements().ParametersSchema)!.AsObject();
|
|
18
|
+
var properties = schema["properties"]!.AsObject();
|
|
19
|
+
properties.Remove("in_active_view");
|
|
20
|
+
properties["link_instance_id"] = new JsonObject { ["type"] = "integer" };
|
|
21
|
+
properties["expected_linked_document_id"] = new JsonObject { ["type"] = "string" };
|
|
22
|
+
properties["include_bounds"] = new JsonObject { ["type"] = "boolean" };
|
|
23
|
+
schema["required"] = new JsonArray("link_instance_id", "expected_linked_document_id");
|
|
24
|
+
return schema;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public object Execute(JsonElement args, ToolContext context)
|
|
29
|
+
{
|
|
30
|
+
var host = context.Document ?? throw new NoActiveDocumentException();
|
|
31
|
+
var id = JsonArgs.GetLong(args, "link_instance_id") ?? throw new ArgumentException("link_instance_id is required.");
|
|
32
|
+
var link = host.GetElement(new ElementId(id)) as RevitLinkInstance
|
|
33
|
+
?? throw new ArgumentException("link_instance_id is not a Revit link instance in the active host document.");
|
|
34
|
+
var linked = link.GetLinkDocument() ?? throw new ArgumentException("The link is unloaded. Load it in Revit before querying it.");
|
|
35
|
+
string identity = DocumentGuard.GetIdentity(linked);
|
|
36
|
+
if (!string.Equals(JsonArgs.GetString(args, "expected_linked_document_id"), identity, StringComparison.Ordinal))
|
|
37
|
+
throw new ArgumentException("The linked document identity is missing or stale. Read get_linked_models again.");
|
|
38
|
+
if (JsonArgs.GetBool(args, "in_active_view", false))
|
|
39
|
+
throw new ArgumentException("in_active_view is not supported for linked queries.");
|
|
40
|
+
|
|
41
|
+
var result = (ToolOutput)new GetElements().Execute(args, new ToolContext(linked, null))!;
|
|
42
|
+
var payload = JsonSerializer.SerializeToNode(result.Payload)!.AsObject();
|
|
43
|
+
payload["host_document_id"] = DocumentGuard.GetIdentity(host);
|
|
44
|
+
payload["linked_document_id"] = identity;
|
|
45
|
+
payload["link_instance_id"] = id;
|
|
46
|
+
payload["coordinate_unit"] = "ft";
|
|
47
|
+
if (payload["elements"] is JsonArray elements)
|
|
48
|
+
{
|
|
49
|
+
var transform = link.GetTotalTransform();
|
|
50
|
+
foreach (var row in elements)
|
|
51
|
+
{
|
|
52
|
+
var element = linked.GetElement(new ElementId(row!["id"]!.GetValue<long>()));
|
|
53
|
+
row["unique_id"] = element.UniqueId;
|
|
54
|
+
row["reference"] = new JsonObject
|
|
55
|
+
{
|
|
56
|
+
["host_document_id"] = DocumentGuard.GetIdentity(host),
|
|
57
|
+
["link_instance_id"] = id,
|
|
58
|
+
["linked_document_id"] = identity,
|
|
59
|
+
["element_id"] = element.Id.Value,
|
|
60
|
+
};
|
|
61
|
+
if (JsonArgs.GetBool(args, "include_bounds", false))
|
|
62
|
+
row["host_bounds"] = JsonSerializer.SerializeToNode(HostBounds(element.get_BoundingBox(null), transform));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return new ToolOutput(payload, result.CompactText);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
internal static object? HostBounds(BoundingBoxXYZ? box, Transform placement)
|
|
69
|
+
{
|
|
70
|
+
if (box == null) return null;
|
|
71
|
+
var corners = new List<XYZ>(8);
|
|
72
|
+
foreach (double x in new[] { box.Min.X, box.Max.X })
|
|
73
|
+
foreach (double y in new[] { box.Min.Y, box.Max.Y })
|
|
74
|
+
foreach (double z in new[] { box.Min.Z, box.Max.Z })
|
|
75
|
+
corners.Add(placement.OfPoint(box.Transform.OfPoint(new XYZ(x, y, z))));
|
|
76
|
+
return new
|
|
77
|
+
{
|
|
78
|
+
min = new[] { corners.Min(p => p.X), corners.Min(p => p.Y), corners.Min(p => p.Z) },
|
|
79
|
+
max = new[] { corners.Max(p => p.X), corners.Max(p => p.Y), corners.Max(p => p.Z) },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal sealed class GetLinkedModels : ITool
|
|
7
|
+
{
|
|
8
|
+
public string Name => "get_linked_models";
|
|
9
|
+
public string Label => "Get Linked Models";
|
|
10
|
+
public string Tier => "advanced";
|
|
11
|
+
public string Description => "List placed Revit links, including unloaded instances, exact linked document identities and their transforms into host coordinates. Nested links are not traversed. Coordinates and transform origins use internal feet.";
|
|
12
|
+
public object ParametersSchema => new
|
|
13
|
+
{
|
|
14
|
+
type = "object",
|
|
15
|
+
properties = new
|
|
16
|
+
{
|
|
17
|
+
offset = new { type = "integer", minimum = 0 },
|
|
18
|
+
limit = new { type = "integer", minimum = 1, maximum = 1000 },
|
|
19
|
+
},
|
|
20
|
+
required = Array.Empty<string>(),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
public object Execute(JsonElement args, ToolContext context)
|
|
24
|
+
{
|
|
25
|
+
var doc = context.Document ?? throw new NoActiveDocumentException();
|
|
26
|
+
int offset = Math.Max(0, JsonArgs.GetInt(args, "offset", 0));
|
|
27
|
+
int limit = Math.Clamp(JsonArgs.GetInt(args, "limit", 100), 1, 1000);
|
|
28
|
+
var links = new FilteredElementCollector(doc).OfClass(typeof(RevitLinkInstance))
|
|
29
|
+
.Cast<RevitLinkInstance>().OrderBy(x => x.Id.Value).ToList();
|
|
30
|
+
var rows = links.Skip(offset).Take(limit).Select(link =>
|
|
31
|
+
{
|
|
32
|
+
var linked = link.GetLinkDocument();
|
|
33
|
+
var transform = link.GetTotalTransform();
|
|
34
|
+
return new
|
|
35
|
+
{
|
|
36
|
+
link_instance_id = link.Id.Value,
|
|
37
|
+
unique_id = link.UniqueId,
|
|
38
|
+
name = link.Name,
|
|
39
|
+
type_id = link.GetTypeId().Value,
|
|
40
|
+
loaded = linked != null,
|
|
41
|
+
linked_document_id = linked == null ? null : DocumentGuard.GetIdentity(linked),
|
|
42
|
+
document_title = linked?.Title,
|
|
43
|
+
transform = new
|
|
44
|
+
{
|
|
45
|
+
origin = Coordinates(transform.Origin),
|
|
46
|
+
basis_x = Coordinates(transform.BasisX),
|
|
47
|
+
basis_y = Coordinates(transform.BasisY),
|
|
48
|
+
basis_z = Coordinates(transform.BasisZ),
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}).ToArray();
|
|
52
|
+
return new
|
|
53
|
+
{
|
|
54
|
+
document_id = DocumentGuard.GetIdentity(doc),
|
|
55
|
+
coordinate_unit = "ft",
|
|
56
|
+
total_count = links.Count,
|
|
57
|
+
returned_count = rows.Length,
|
|
58
|
+
offset,
|
|
59
|
+
has_more = offset + rows.Length < links.Count,
|
|
60
|
+
next_offset = offset + rows.Length < links.Count ? (int?)(offset + rows.Length) : null,
|
|
61
|
+
links = rows,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
internal static double[] Coordinates(XYZ point) => new[] { point.X, point.Y, point.Z };
|
|
66
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal sealed class GetModelCoordinates : ITool
|
|
7
|
+
{
|
|
8
|
+
public string Name => "get_model_coordinates";
|
|
9
|
+
public string Label => "Get Model Coordinates";
|
|
10
|
+
public string Tier => "advanced";
|
|
11
|
+
public string Description => "Read project/survey base points, active project location and site coordinates. Optionally report shared coordinates for up to 100 points expressed along document internal axes. Required unit applies to input points and every returned length; angles and latitude/longitude are degrees. The active location's Revit GetProjectPosition defines the shared mapping. List project locations with paging. Does not infer a GIS coordinate reference system or modify/acquire coordinates.";
|
|
12
|
+
public object ParametersSchema => new
|
|
13
|
+
{
|
|
14
|
+
type = "object", properties = new
|
|
15
|
+
{
|
|
16
|
+
unit = ModelEditInputs.LengthUnitSchema,
|
|
17
|
+
points = new { type = "array", maxItems = 100, items = ModelEditInputs.VectorSchema("Point along document internal axes, in unit.") },
|
|
18
|
+
offset = new { type = "integer", minimum = 0 }, limit = new { type = "integer", minimum = 1, maximum = 100 },
|
|
19
|
+
}, required = new[] { "unit" },
|
|
20
|
+
};
|
|
21
|
+
public object Execute(JsonElement args, ToolContext context)
|
|
22
|
+
{
|
|
23
|
+
var doc = context.Document ?? throw new NoActiveDocumentException();
|
|
24
|
+
if (doc.IsFamilyDocument) throw new ArgumentException("Project coordinates require a project document.");
|
|
25
|
+
double scale = ModelEditInputs.LengthScale(args);
|
|
26
|
+
var active = doc.ActiveProjectLocation;
|
|
27
|
+
object Position(ProjectPosition position) => new { east_west = position.EastWest / scale, north_south = position.NorthSouth / scale, elevation = position.Elevation / scale, angle_degrees = position.Angle * 180 / Math.PI };
|
|
28
|
+
object? Base(BasePoint? point) => point == null ? null : new { id = point.Id.Value, internal_position = SpatialBounds.Coordinates(point.Position, scale), shared_position = SpatialBounds.Coordinates(point.SharedPosition, scale) };
|
|
29
|
+
var points = new List<object>();
|
|
30
|
+
if (args.TryGetProperty("points", out var values))
|
|
31
|
+
{
|
|
32
|
+
if (values.ValueKind != JsonValueKind.Array || values.GetArrayLength() > 100) throw new ArgumentException("points must be an array of at most 100 XYZ points.");
|
|
33
|
+
foreach (var value in values.EnumerateArray())
|
|
34
|
+
{
|
|
35
|
+
var wrapper = JsonSerializer.SerializeToElement(new { point = value });
|
|
36
|
+
var point = ModelEditInputs.Vector(wrapper, "point").Multiply(scale);
|
|
37
|
+
points.Add(new { internal_position = SpatialBounds.Coordinates(point, scale), shared_position = Position(active.GetProjectPosition(point)) });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
var locations = doc.ProjectLocations.Cast<ProjectLocation>().OrderBy(x => x.Id.Value).ToList();
|
|
41
|
+
int offset = Math.Max(0, JsonArgs.GetInt(args, "offset", 0)), limit = Math.Clamp(JsonArgs.GetInt(args, "limit", 50), 1, 100);
|
|
42
|
+
var page = locations.Skip(offset).Take(limit).Select(x => new { id = x.Id.Value, name = x.Name, active = x.Id == active.Id, internal_origin_in_shared_coordinates = Position(x.GetProjectPosition(XYZ.Zero)) }).ToArray();
|
|
43
|
+
var site = doc.SiteLocation;
|
|
44
|
+
return new { unit = JsonArgs.GetString(args, "unit"), angle_unit = "degrees", point_input_coordinates = "document_internal", active_project_location = new { id = active.Id.Value, name = active.Name },
|
|
45
|
+
project_base_point = Base(BasePoint.GetProjectBasePoint(doc)), survey_point = Base(BasePoint.GetSurveyPoint(doc)),
|
|
46
|
+
site = new { name = site.Name, latitude_degrees = site.Latitude * 180 / Math.PI, longitude_degrees = site.Longitude * 180 / Math.PI, time_zone = site.TimeZone },
|
|
47
|
+
total_locations = locations.Count, offset, next_offset = offset + page.Length < locations.Count ? (int?)(offset + page.Length) : null, locations = page, points };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
using System.Text.Json;
|
|
2
|
+
using Autodesk.Revit.DB;
|
|
3
|
+
|
|
4
|
+
namespace RevitBridge.Tools;
|
|
5
|
+
|
|
6
|
+
internal sealed class GetScheduleFields : ITool
|
|
7
|
+
{
|
|
8
|
+
public string Name => "get_schedule_fields";
|
|
9
|
+
public string Label => "Get Schedule Fields";
|
|
10
|
+
public string Tier => "advanced";
|
|
11
|
+
public string Description => "List fields eligible to be added to one existing schedule, with paging and localized-name filtering. A field is identified by the pair parameter_id + field_type; negative built-in parameter IDs are valid. These identities differ from schedule-local field_id values returned by get_schedules. Existing fields are marked included. Count may appear in the returned fields; manage_schedules accepts its discovered pair or field_type Count without a parameter_id. Calculated/combined fields are not exposed here.";
|
|
12
|
+
public object ParametersSchema => new
|
|
13
|
+
{
|
|
14
|
+
type = "object", properties = new
|
|
15
|
+
{
|
|
16
|
+
schedule_id = new { type = "integer", minimum = 1 }, name_filter = new { type = "string" },
|
|
17
|
+
offset = new { type = "integer", minimum = 0 }, limit = new { type = "integer", minimum = 1, maximum = 200 },
|
|
18
|
+
}, required = new[] { "schedule_id" },
|
|
19
|
+
};
|
|
20
|
+
public object Execute(JsonElement args, ToolContext context)
|
|
21
|
+
{
|
|
22
|
+
var doc = context.Document ?? throw new NoActiveDocumentException();
|
|
23
|
+
var schedule = doc.GetElement(new ElementId(JsonArgs.GetLong(args, "schedule_id") ?? 0)) as ViewSchedule ?? throw new ArgumentException("schedule_id is not a schedule.");
|
|
24
|
+
if (schedule.IsTemplate || schedule.IsTitleblockRevisionSchedule) throw new ArgumentException("Use a regular schedule, not a template or titleblock revision schedule.");
|
|
25
|
+
int offset = JsonArgs.GetInt(args, "offset", 0), limit = JsonArgs.GetInt(args, "limit", 100);
|
|
26
|
+
if (offset < 0 || limit is < 1 or > 200) throw new ArgumentException("Invalid paging range.");
|
|
27
|
+
string needle = JsonArgs.GetString(args, "name_filter") ?? "";
|
|
28
|
+
var definition = schedule.Definition;
|
|
29
|
+
var included = definition.GetFieldOrder().Select(id => definition.GetField(id)).Select(f => (f.ParameterId.Value, f.FieldType)).ToHashSet();
|
|
30
|
+
var fields = definition.GetSchedulableFields().Select(f => new
|
|
31
|
+
{
|
|
32
|
+
parameter_id = f.ParameterId.Value, field_type = f.FieldType.ToString(), name = f.GetName(doc), included = included.Contains((f.ParameterId.Value, f.FieldType)),
|
|
33
|
+
}).Where(f => f.name.Contains(needle, StringComparison.OrdinalIgnoreCase)).OrderBy(f => f.parameter_id).ThenBy(f => f.field_type, StringComparer.Ordinal).ToArray();
|
|
34
|
+
var page = fields.Skip(offset).Take(limit).ToArray();
|
|
35
|
+
return new { schedule_id = schedule.Id.Value, total_count = fields.Length, offset, returned_count = page.Length, next_offset = offset + page.Length < fields.Length ? (int?)(offset + page.Length) : null, fields = page };
|
|
36
|
+
}
|
|
37
|
+
}
|