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.
Files changed (119) hide show
  1. package/AGENTS.md +167 -0
  2. package/CHANGELOG.md +465 -430
  3. package/README.md +604 -548
  4. package/bin/pi-revit.js +9 -9
  5. package/docs/architecture.md +271 -0
  6. package/docs/evaluation.md +434 -0
  7. package/docs/invariants.json +147 -0
  8. package/extensions/pi-revit/completion-monitor.ts +55 -0
  9. package/extensions/pi-revit/contracts.ts +146 -0
  10. package/extensions/pi-revit/discovery.ts +93 -0
  11. package/extensions/pi-revit/index.ts +342 -255
  12. package/extensions/pi-revit/instance-router.ts +86 -86
  13. package/extensions/pi-revit/platform-prompt.ts +40 -0
  14. package/extensions/pi-revit/scope-monitor.ts +114 -0
  15. package/extensions/pi-revit/script-library.ts +144 -144
  16. package/extensions/pi-revit/tool-catalog.ts +113 -14
  17. package/extensions/pi-revit/tool-documentation.ts +72 -0
  18. package/extensions/pi-revit/tool-schema.ts +8 -0
  19. package/package.json +8 -2
  20. package/scripts/build.ps1 +9 -9
  21. package/scripts/check-sdk.ps1 +66 -66
  22. package/scripts/check-tool-documentation.mjs +287 -0
  23. package/scripts/deploy.ps1 +16 -16
  24. package/scripts/generate-contracts.mjs +80 -0
  25. package/scripts/lib/platform.mjs +226 -0
  26. package/scripts/test-extension.mjs +15 -0
  27. package/skills/pi-revit/SKILL.md +30 -218
  28. package/skills/pi-revit/contracts.generated.json +3524 -0
  29. package/skills/pi-revit/references/execution-rules.md +41 -0
  30. package/skills/pi-revit/references/model-audit-export.md +38 -27
  31. package/skills/pi-revit/references/operation-recovery.md +33 -0
  32. package/skills/pi-revit/references/room-documentation.md +37 -26
  33. package/skills/pi-revit/references/tool-index.md +89 -0
  34. package/skills/pi-revit/references/tools/capture_view.md +62 -0
  35. package/skills/pi-revit/references/tools/change_element_types.md +65 -0
  36. package/skills/pi-revit/references/tools/create_tags.md +85 -0
  37. package/skills/pi-revit/references/tools/delete_elements.md +66 -0
  38. package/skills/pi-revit/references/tools/execute_csharp.md +81 -0
  39. package/skills/pi-revit/references/tools/export_documents.md +75 -0
  40. package/skills/pi-revit/references/tools/find_revit_tools.md +96 -0
  41. package/skills/pi-revit/references/tools/get_element_details.md +66 -0
  42. package/skills/pi-revit/references/tools/get_element_relationships.md +61 -0
  43. package/skills/pi-revit/references/tools/get_element_types.md +67 -0
  44. package/skills/pi-revit/references/tools/get_elements.md +87 -0
  45. package/skills/pi-revit/references/tools/get_linked_elements.md +79 -0
  46. package/skills/pi-revit/references/tools/get_linked_models.md +57 -0
  47. package/skills/pi-revit/references/tools/get_model_coordinates.md +64 -0
  48. package/skills/pi-revit/references/tools/get_model_health.md +53 -0
  49. package/skills/pi-revit/references/tools/get_model_overview.md +57 -0
  50. package/skills/pi-revit/references/tools/get_revit_operation.md +54 -0
  51. package/skills/pi-revit/references/tools/get_schedule_fields.md +62 -0
  52. package/skills/pi-revit/references/tools/get_schedules.md +71 -0
  53. package/skills/pi-revit/references/tools/manage_element_sets.md +92 -0
  54. package/skills/pi-revit/references/tools/manage_revit_instances.md +63 -0
  55. package/skills/pi-revit/references/tools/manage_revit_scripts.md +109 -0
  56. package/skills/pi-revit/references/tools/manage_schedules.md +90 -0
  57. package/skills/pi-revit/references/tools/manage_selection.md +66 -0
  58. package/skills/pi-revit/references/tools/manage_sheet_placements.md +82 -0
  59. package/skills/pi-revit/references/tools/manage_sheets.md +71 -0
  60. package/skills/pi-revit/references/tools/manage_views.md +95 -0
  61. package/skills/pi-revit/references/tools/measure_geometry.md +71 -0
  62. package/skills/pi-revit/references/tools/open_view.md +59 -0
  63. package/skills/pi-revit/references/tools/ping.md +41 -0
  64. package/skills/pi-revit/references/tools/query_spatial_elements.md +74 -0
  65. package/skills/pi-revit/references/tools/read_revit_result.md +53 -0
  66. package/skills/pi-revit/references/tools/search_api_docs.md +65 -0
  67. package/skills/pi-revit/references/tools/set_parameters.md +75 -0
  68. package/skills/pi-revit/references/tools/summarize_elements.md +64 -0
  69. package/skills/pi-revit/references/tools/transform_elements.md +79 -0
  70. package/skills/pi-revit/references/visual-verification.md +36 -0
  71. package/skills/pi-revit/tool-manifest.json +338 -0
  72. package/src/Revit/BridgeServer.cs +93 -87
  73. package/src/Revit/OperationStore.cs +178 -178
  74. package/src/Revit/ToolRegistry.cs +88 -57
  75. package/src/Revit/Tools/CaptureView.cs +10 -2
  76. package/src/Revit/Tools/ChangeElementTypes.cs +74 -60
  77. package/src/Revit/Tools/ChangeSet.cs +39 -0
  78. package/src/Revit/Tools/CreateTags.cs +107 -95
  79. package/src/Revit/Tools/DeleteElements.cs +53 -44
  80. package/src/Revit/Tools/DocumentGuard.cs +74 -64
  81. package/src/Revit/Tools/ElementNames.cs +103 -0
  82. package/src/Revit/Tools/ElementQueryScope.cs +27 -27
  83. package/src/Revit/Tools/ElementTraits.cs +53 -0
  84. package/src/Revit/Tools/ExecuteCsharp.cs +54 -45
  85. package/src/Revit/Tools/ExportDocuments.cs +129 -121
  86. package/src/Revit/Tools/GetElementDetails.cs +37 -41
  87. package/src/Revit/Tools/GetElementRelationships.cs +82 -76
  88. package/src/Revit/Tools/GetElementTypes.cs +8 -0
  89. package/src/Revit/Tools/GetElements.cs +75 -86
  90. package/src/Revit/Tools/GetLinkedElements.cs +89 -82
  91. package/src/Revit/Tools/GetLinkedModels.cs +73 -66
  92. package/src/Revit/Tools/GetModelCoordinates.cs +56 -49
  93. package/src/Revit/Tools/GetModelHealth.cs +7 -0
  94. package/src/Revit/Tools/GetModelOverview.cs +185 -158
  95. package/src/Revit/Tools/GetScheduleFields.cs +44 -37
  96. package/src/Revit/Tools/GetSchedules.cs +96 -89
  97. package/src/Revit/Tools/InheritedState.Summary.cs +57 -0
  98. package/src/Revit/Tools/InheritedState.cs +144 -0
  99. package/src/Revit/Tools/ManageElementSets.cs +114 -106
  100. package/src/Revit/Tools/ManageSchedules.cs +174 -164
  101. package/src/Revit/Tools/ManageSelection.cs +45 -37
  102. package/src/Revit/Tools/ManageSheetPlacements.cs +113 -97
  103. package/src/Revit/Tools/ManageSheets.cs +72 -63
  104. package/src/Revit/Tools/ManageViews.cs +115 -100
  105. package/src/Revit/Tools/MeasureGeometry.cs +60 -54
  106. package/src/Revit/Tools/ModelChanges.cs +154 -0
  107. package/src/Revit/Tools/ModelEditBatch.cs +105 -102
  108. package/src/Revit/Tools/ModelEditInputs.cs +49 -49
  109. package/src/Revit/Tools/OpenView.cs +9 -2
  110. package/src/Revit/Tools/ParameterResolver.cs +94 -0
  111. package/src/Revit/Tools/QuerySpatialElements.cs +70 -63
  112. package/src/Revit/Tools/SearchApiDocs.cs +72 -4
  113. package/src/Revit/Tools/SetParameters.cs +60 -79
  114. package/src/Revit/Tools/SpatialBounds.cs +30 -30
  115. package/src/Revit/Tools/SummarizeElements.cs +94 -87
  116. package/src/Revit/Tools/ToolContract.cs +48 -0
  117. package/src/Revit/Tools/ToolSupport.cs +5 -1
  118. package/src/Revit/Tools/TransformElements.cs +72 -57
  119. package/workspace/AGENTS.md +26 -20
@@ -1,89 +1,96 @@
1
- using System.Text.Json;
2
- using Autodesk.Revit.DB;
3
-
4
- namespace RevitBridge.Tools;
5
-
6
- internal sealed class GetSchedules : ITool
7
- {
8
- public string Name => "get_schedules";
9
- public string Label => "Get Schedules";
10
- public string Tier => "advanced";
11
- public string Description => "List schedules, or read one schedule's field definitions and displayed body cells by schedule_id. Cell text uses Revit formatting and may contain group headings/totals; rows are not guaranteed to correspond to individual elements. Row and column pagination are independent. Templates and revision schedules are excluded from the list.";
12
- public object ParametersSchema => new
13
- {
14
- type = "object",
15
- properties = new
16
- {
17
- schedule_id = new { type = "integer" },
18
- name_filter = new { type = "string" },
19
- offset = new { type = "integer", minimum = 0, description = "List offset or body row offset." },
20
- limit = new { type = "integer", minimum = 1, maximum = 200 },
21
- column_offset = new { type = "integer", minimum = 0 },
22
- column_limit = new { type = "integer", minimum = 1, maximum = 50 },
23
- },
24
- required = Array.Empty<string>(),
25
- };
26
-
27
- public object Execute(JsonElement args, ToolContext context)
28
- {
29
- var doc = context.Document ?? throw new NoActiveDocumentException();
30
- int offset = Math.Max(0, JsonArgs.GetInt(args, "offset", 0));
31
- int limit = Math.Clamp(JsonArgs.GetInt(args, "limit", 50), 1, 200);
32
- if (JsonArgs.GetLong(args, "schedule_id") is not { } id)
33
- {
34
- string needle = JsonArgs.GetString(args, "name_filter") ?? "";
35
- var schedules = new FilteredElementCollector(doc).OfClass(typeof(ViewSchedule)).Cast<ViewSchedule>()
36
- .Where(s => !s.IsTemplate && !s.IsTitleblockRevisionSchedule && s.Name.Contains(needle, StringComparison.OrdinalIgnoreCase))
37
- .OrderBy(s => s.Id.Value).ToList();
38
- var rows = schedules.Skip(offset).Take(limit).Select(s => new
39
- {
40
- id = s.Id.Value, name = s.Name, unique_id = s.UniqueId,
41
- category_id = s.Definition.CategoryId.Value,
42
- is_itemized = s.Definition.IsItemized,
43
- field_count = s.Definition.GetFieldCount(),
44
- }).ToArray();
45
- return new { total_count = schedules.Count, returned_count = rows.Length, offset,
46
- has_more = offset + rows.Length < schedules.Count,
47
- next_offset = offset + rows.Length < schedules.Count ? (int?)(offset + rows.Length) : null, schedules = rows };
48
- }
49
-
50
- var schedule = doc.GetElement(new ElementId(id)) as ViewSchedule
51
- ?? throw new ArgumentException("schedule_id is not a schedule in the active document.");
52
- if (schedule.IsTemplate) throw new ArgumentException("Schedule templates do not contain displayed body rows.");
53
- using var table = schedule.GetTableData();
54
- using var body = table.GetSectionData(SectionType.Body);
55
- int columnOffset = Math.Max(0, JsonArgs.GetInt(args, "column_offset", 0));
56
- int columnLimit = Math.Clamp(JsonArgs.GetInt(args, "column_limit", 50), 1, 50);
57
- int columnCount = Math.Min(columnLimit, Math.Max(0, body.NumberOfColumns - columnOffset));
58
- int rowCount = Math.Min(limit, Math.Max(0, body.NumberOfRows - offset));
59
- var cells = Enumerable.Range(0, rowCount).Select(row => new
60
- {
61
- row_index = body.FirstRowNumber + offset + row,
62
- cells = Enumerable.Range(0, columnCount).Select(col => schedule.GetCellText(SectionType.Body,
63
- body.FirstRowNumber + offset + row, body.FirstColumnNumber + columnOffset + col)).ToArray(),
64
- }).ToArray();
65
- var fields = schedule.Definition.GetFieldOrder().Select(fieldId =>
66
- {
67
- var field = schedule.Definition.GetField(fieldId);
68
- return new { field_id = fieldId.IntegerValue, parameter_id = field.ParameterId.Value,
69
- name = field.GetName(), heading = field.ColumnHeading, hidden = field.IsHidden, field_type = field.FieldType.ToString(),
70
- spec_type_id = field.GetSpecTypeId().TypeId, grid_width_feet = field.GridColumnWidth, sheet_width_feet = field.SheetColumnWidth,
71
- can_filter_value = schedule.Definition.CanFilterByValue(fieldId), can_filter_substring = schedule.Definition.CanFilterBySubstring(fieldId) };
72
- }).ToArray();
73
- return new
74
- {
75
- schedule_id = id, name = schedule.Name, is_itemized = schedule.Definition.IsItemized, fields,
76
- sort_fields = schedule.Definition.GetSortGroupFields().Select(sort => new { field_id = sort.FieldId.IntegerValue, order = sort.SortOrder.ToString(), show_header = sort.ShowHeader }).ToArray(),
77
- filters = schedule.Definition.GetFilters().Select(filter => new { field_id = filter.FieldId.IntegerValue, comparison = filter.FilterType.ToString(),
78
- value = filter.IsStringValue ? (object)filter.GetStringValue() : filter.IsDoubleValue ? filter.GetDoubleValue() : filter.IsIntegerValue ? filter.GetIntegerValue() : filter.IsElementIdValue ? filter.GetElementIdValue().Value : null,
79
- value_unit = filter.IsDoubleValue ? "internal" : null }).ToArray(),
80
- total_rows = body.NumberOfRows, total_columns = body.NumberOfColumns,
81
- offset, returned_rows = rowCount,
82
- next_offset = offset + rowCount < body.NumberOfRows ? (int?)(offset + rowCount) : null,
83
- column_offset = columnOffset, returned_columns = columnCount,
84
- first_column_index = body.FirstColumnNumber + columnOffset,
85
- next_column_offset = columnOffset + columnCount < body.NumberOfColumns ? (int?)(columnOffset + columnCount) : null,
86
- rows = cells,
87
- };
88
- }
89
- }
1
+ using System.Text.Json;
2
+ using Autodesk.Revit.DB;
3
+
4
+ namespace RevitBridge.Tools;
5
+
6
+ internal sealed class GetSchedules : ITool
7
+ {
8
+ public string Name => "get_schedules";
9
+ public IReadOnlyList<string> DocumentKinds => DocumentKind.ProjectOnly;
10
+ public IReadOnlyList<string> Keywords => new[] { "schedule", "quantities", "takeoff", "table", "schedule cells", "schedule values", "bill of quantities" };
11
+ public IReadOnlyList<ToolLimit> Limits => new[]
12
+ {
13
+ new ToolLimit("Titleblock revision schedules and schedule templates", "api", "ViewSchedule.IsTitleblockRevisionSchedule; ViewSchedule.GetTableData"),
14
+ new ToolLimit("Graphical layout of a schedule on a sheet", "tool", "capture_view"),
15
+ };
16
+ public string Label => "Get Schedules";
17
+ public string Tier => "advanced";
18
+ public string Description => "List schedules, or read one schedule's field definitions and displayed body cells by schedule_id. Cell text uses Revit formatting and may contain group headings/totals; rows are not guaranteed to correspond to individual elements. Row and column pagination are independent. Templates and revision schedules are excluded from the list.";
19
+ public object ParametersSchema => new
20
+ {
21
+ type = "object",
22
+ properties = new
23
+ {
24
+ schedule_id = new { type = "integer" },
25
+ name_filter = new { type = "string" },
26
+ offset = new { type = "integer", minimum = 0, description = "List offset or body row offset." },
27
+ limit = new { type = "integer", minimum = 1, maximum = 200 },
28
+ column_offset = new { type = "integer", minimum = 0 },
29
+ column_limit = new { type = "integer", minimum = 1, maximum = 50 },
30
+ },
31
+ required = Array.Empty<string>(),
32
+ };
33
+
34
+ public object Execute(JsonElement args, ToolContext context)
35
+ {
36
+ var doc = context.Document ?? throw new NoActiveDocumentException();
37
+ int offset = Math.Max(0, JsonArgs.GetInt(args, "offset", 0));
38
+ int limit = Math.Clamp(JsonArgs.GetInt(args, "limit", 50), 1, 200);
39
+ if (JsonArgs.GetLong(args, "schedule_id") is not { } id)
40
+ {
41
+ string needle = JsonArgs.GetString(args, "name_filter") ?? "";
42
+ var schedules = new FilteredElementCollector(doc).OfClass(typeof(ViewSchedule)).Cast<ViewSchedule>()
43
+ .Where(s => !s.IsTemplate && !s.IsTitleblockRevisionSchedule && s.Name.Contains(needle, StringComparison.OrdinalIgnoreCase))
44
+ .OrderBy(s => s.Id.Value).ToList();
45
+ var rows = schedules.Skip(offset).Take(limit).Select(s => new
46
+ {
47
+ id = s.Id.Value, name = s.Name, unique_id = s.UniqueId,
48
+ category_id = s.Definition.CategoryId.Value,
49
+ is_itemized = s.Definition.IsItemized,
50
+ field_count = s.Definition.GetFieldCount(),
51
+ }).ToArray();
52
+ return new { total_count = schedules.Count, returned_count = rows.Length, offset,
53
+ has_more = offset + rows.Length < schedules.Count,
54
+ next_offset = offset + rows.Length < schedules.Count ? (int?)(offset + rows.Length) : null, schedules = rows };
55
+ }
56
+
57
+ var schedule = doc.GetElement(new ElementId(id)) as ViewSchedule
58
+ ?? throw new ArgumentException("schedule_id is not a schedule in the active document.");
59
+ if (schedule.IsTemplate) throw new ArgumentException("Schedule templates do not contain displayed body rows.");
60
+ using var table = schedule.GetTableData();
61
+ using var body = table.GetSectionData(SectionType.Body);
62
+ int columnOffset = Math.Max(0, JsonArgs.GetInt(args, "column_offset", 0));
63
+ int columnLimit = Math.Clamp(JsonArgs.GetInt(args, "column_limit", 50), 1, 50);
64
+ int columnCount = Math.Min(columnLimit, Math.Max(0, body.NumberOfColumns - columnOffset));
65
+ int rowCount = Math.Min(limit, Math.Max(0, body.NumberOfRows - offset));
66
+ var cells = Enumerable.Range(0, rowCount).Select(row => new
67
+ {
68
+ row_index = body.FirstRowNumber + offset + row,
69
+ cells = Enumerable.Range(0, columnCount).Select(col => schedule.GetCellText(SectionType.Body,
70
+ body.FirstRowNumber + offset + row, body.FirstColumnNumber + columnOffset + col)).ToArray(),
71
+ }).ToArray();
72
+ var fields = schedule.Definition.GetFieldOrder().Select(fieldId =>
73
+ {
74
+ var field = schedule.Definition.GetField(fieldId);
75
+ return new { field_id = fieldId.IntegerValue, parameter_id = field.ParameterId.Value,
76
+ name = field.GetName(), heading = field.ColumnHeading, hidden = field.IsHidden, field_type = field.FieldType.ToString(),
77
+ spec_type_id = field.GetSpecTypeId().TypeId, grid_width_feet = field.GridColumnWidth, sheet_width_feet = field.SheetColumnWidth,
78
+ can_filter_value = schedule.Definition.CanFilterByValue(fieldId), can_filter_substring = schedule.Definition.CanFilterBySubstring(fieldId) };
79
+ }).ToArray();
80
+ return new
81
+ {
82
+ schedule_id = id, name = schedule.Name, is_itemized = schedule.Definition.IsItemized, fields,
83
+ sort_fields = schedule.Definition.GetSortGroupFields().Select(sort => new { field_id = sort.FieldId.IntegerValue, order = sort.SortOrder.ToString(), show_header = sort.ShowHeader }).ToArray(),
84
+ filters = schedule.Definition.GetFilters().Select(filter => new { field_id = filter.FieldId.IntegerValue, comparison = filter.FilterType.ToString(),
85
+ value = filter.IsStringValue ? (object)filter.GetStringValue() : filter.IsDoubleValue ? filter.GetDoubleValue() : filter.IsIntegerValue ? filter.GetIntegerValue() : filter.IsElementIdValue ? filter.GetElementIdValue().Value : null,
86
+ value_unit = filter.IsDoubleValue ? "internal" : null }).ToArray(),
87
+ total_rows = body.NumberOfRows, total_columns = body.NumberOfColumns,
88
+ offset, returned_rows = rowCount,
89
+ next_offset = offset + rowCount < body.NumberOfRows ? (int?)(offset + rowCount) : null,
90
+ column_offset = columnOffset, returned_columns = columnCount,
91
+ first_column_index = body.FirstColumnNumber + columnOffset,
92
+ next_column_offset = columnOffset + columnCount < body.NumberOfColumns ? (int?)(columnOffset + columnCount) : null,
93
+ rows = cells,
94
+ };
95
+ }
96
+ }
@@ -0,0 +1,57 @@
1
+ namespace RevitBridge.Tools
2
+ {
3
+ /// <summary>The Revit-free part of InheritedState: limits, the facts record and the summary. Tested offline.</summary>
4
+ internal static partial class InheritedState
5
+ {
6
+ public const int ListCap = 30;
7
+ public const int SampleCap = 10;
8
+ public static readonly TimeSpan ScanBudget = TimeSpan.FromSeconds(4);
9
+
10
+ public const string ViewCheck = "Inherited from the source view. Compare this state with the request: show anything the request asks to show, or disclose what stays hidden, and report what was inherited.";
11
+ public const string NewViewCheck = "A new view starts with this visibility state; a duplicate inherits its source's state. Compare it with the request before reporting the view as complete.";
12
+ public const string ElementCheck = "Carried over from the source object. Check these values against the request; a copied Mark or Comment usually needs a new value.";
13
+
14
+ /// <summary>Facts read from one view. Lists hold category identities as "OST_Name|Localized name".</summary>
15
+ internal sealed record ViewFacts(
16
+ long? SourceId, long? TemplateId, string? TemplateName,
17
+ IReadOnlyList<string> HiddenCategories, int HiddenElementCount, IReadOnlyList<long> HiddenElementSample,
18
+ int ElementOverrideCount, bool ElementScanComplete, int CategoryOverrideCount,
19
+ IReadOnlyList<(long Id, string Name, bool Visible)> Filters, bool TemporaryHideIsolate,
20
+ string? DisplayStyle, string? DetailLevel, bool CropActive, bool? SectionBoxActive, bool? Perspective);
21
+
22
+ /// <summary>Pure summary of view facts: only non-empty sections, capped lists, and the check the caller must make.</summary>
23
+ public static Dictionary<string, object?> Summarize(ViewFacts facts, string check)
24
+ {
25
+ var state = new Dictionary<string, object?>();
26
+ if (facts.SourceId is long source) state["derived_from"] = source;
27
+ if (facts.TemplateId is long template && template > 0) state["template"] = new Dictionary<string, object?> { ["id"] = template, ["name"] = facts.TemplateName };
28
+ if (facts.HiddenCategories.Count > 0)
29
+ state["hidden_categories"] = new Dictionary<string, object?>
30
+ {
31
+ ["count"] = facts.HiddenCategories.Count,
32
+ ["categories"] = facts.HiddenCategories.Take(ListCap).ToList(),
33
+ ["truncated"] = facts.HiddenCategories.Count > ListCap,
34
+ };
35
+ if (facts.HiddenElementCount > 0 || !facts.ElementScanComplete)
36
+ state["hidden_elements"] = new Dictionary<string, object?>
37
+ {
38
+ ["count"] = facts.HiddenElementCount,
39
+ ["sample_ids"] = facts.HiddenElementSample.Take(SampleCap).ToList(),
40
+ ["complete"] = facts.ElementScanComplete,
41
+ };
42
+ if (facts.ElementOverrideCount > 0) state["element_overrides"] = facts.ElementOverrideCount;
43
+ if (facts.CategoryOverrideCount > 0) state["category_overrides"] = facts.CategoryOverrideCount;
44
+ if (facts.Filters.Count > 0)
45
+ state["filters"] = facts.Filters.Take(ListCap).Select(f => new Dictionary<string, object?> { ["id"] = f.Id, ["name"] = f.Name, ["visible"] = f.Visible }).ToList();
46
+ if (facts.TemporaryHideIsolate) state["temporary_hide_isolate"] = true;
47
+ var display = new Dictionary<string, object?> { ["display_style"] = facts.DisplayStyle, ["detail_level"] = facts.DetailLevel, ["crop_box_active"] = facts.CropActive };
48
+ if (facts.SectionBoxActive is bool box) display["section_box_active"] = box;
49
+ if (facts.Perspective is bool perspective) display["perspective"] = perspective;
50
+ state["display"] = display;
51
+ if (!facts.ElementScanComplete)
52
+ state["scan_note"] = $"The hidden-element scan stopped after {ScanBudget.TotalSeconds:0} s; counts are lower bounds.";
53
+ state["check"] = check;
54
+ return state;
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,144 @@
1
+ using System.Diagnostics;
2
+ using Autodesk.Revit.DB;
3
+
4
+ namespace RevitBridge.Tools
5
+ {
6
+ /// <summary>
7
+ /// Shared report of the state an object carries when it is derived from an existing one
8
+ /// (inv:derived-state-reported). A duplicated view keeps its source's hidden categories and
9
+ /// elements, filters, overrides and template; a copied element keeps its instance values; a
10
+ /// retyped element keeps its per-view graphics. Any tool that duplicates, copies, mirrors or
11
+ /// retypes reports this in its result, so the caller can compare it with the request instead
12
+ /// of relying on an image. The gate in scripts/check-tool-documentation.mjs makes every tool
13
+ /// that creates from an existing object use this class. Reading and summarizing are separate:
14
+ /// Summarize is pure and tested offline; the Read* methods only collect facts from Revit.
15
+ /// </summary>
16
+ internal static partial class InheritedState
17
+ {
18
+ /// <summary>Inherited state of a view derived from source (null when unknown), for a result.</summary>
19
+ public static Dictionary<string, object?> OfView(View view, long? sourceId, string check = ViewCheck)
20
+ => Summarize(ReadView(view, sourceId), check);
21
+
22
+ /// <summary>Collect facts from Revit. The element scan is bounded by ScanBudget.</summary>
23
+ public static ViewFacts ReadView(View view, long? sourceId)
24
+ {
25
+ var doc = view.Document;
26
+ var hiddenCategories = new List<string>();
27
+ int categoryOverrides = 0;
28
+ foreach (Category category in doc.Settings.Categories)
29
+ {
30
+ try
31
+ {
32
+ if (view.CanCategoryBeHidden(category.Id) && view.GetCategoryHidden(category.Id))
33
+ hiddenCategories.Add($"{category.BuiltInCategory}|{category.Name}");
34
+ if (view.AreGraphicsOverridesAllowed() && view.IsCategoryOverridable(category.Id) && IsNonDefault(view.GetCategoryOverrides(category.Id)))
35
+ categoryOverrides++;
36
+ }
37
+ catch (Autodesk.Revit.Exceptions.ArgumentException) { }
38
+ }
39
+
40
+ int hidden = 0, overridden = 0;
41
+ var sample = new List<long>();
42
+ bool complete = true;
43
+ var clock = Stopwatch.StartNew();
44
+ var candidates = new FilteredElementCollector(doc).WhereElementIsNotElementType().WhereElementIsViewIndependent()
45
+ .Concat(new FilteredElementCollector(doc).OwnedByView(view.Id));
46
+ foreach (var element in candidates)
47
+ {
48
+ if (clock.Elapsed > ScanBudget) { complete = false; break; }
49
+ try
50
+ {
51
+ if (element.CanBeHidden(view) && element.IsHidden(view))
52
+ {
53
+ hidden++;
54
+ if (sample.Count < SampleCap) sample.Add(element.Id.Value);
55
+ }
56
+ if (view.AreGraphicsOverridesAllowed() && IsNonDefault(view.GetElementOverrides(element.Id))) overridden++;
57
+ }
58
+ catch (Autodesk.Revit.Exceptions.ArgumentException) { }
59
+ catch (Autodesk.Revit.Exceptions.InvalidOperationException) { }
60
+ }
61
+
62
+ var filters = new List<(long, string, bool)>();
63
+ try
64
+ {
65
+ foreach (var id in view.GetFilters())
66
+ filters.Add((id.Value, doc.GetElement(id)?.Name ?? "", view.GetFilterVisibility(id)));
67
+ }
68
+ catch (Autodesk.Revit.Exceptions.InvalidOperationException) { }
69
+
70
+ var template = view.ViewTemplateId;
71
+ var view3D = view as View3D;
72
+ return new ViewFacts(sourceId, template.Value > 0 ? template.Value : null, template.Value > 0 ? doc.GetElement(template)?.Name : null,
73
+ hiddenCategories, hidden, sample, overridden, complete, categoryOverrides, filters, Safe(() => view.IsTemporaryHideIsolateActive()),
74
+ Safe(() => view.DisplayStyle.ToString()), Safe(() => view.DetailLevel.ToString()), Safe(() => view.CropBoxActive),
75
+ view3D == null ? null : Safe(() => view3D.IsSectionBoxActive), view3D?.IsPerspective);
76
+ }
77
+
78
+ /// <summary>Some view kinds (schedules, sheets) reject graphics properties; report them as unset.</summary>
79
+ private static T? Safe<T>(Func<T> read)
80
+ {
81
+ try { return read(); }
82
+ catch (Autodesk.Revit.Exceptions.InvalidOperationException) { return default; }
83
+ catch (Autodesk.Revit.Exceptions.ArgumentException) { return default; }
84
+ }
85
+
86
+ /// <summary>True when an override changes anything from the view's normal graphics.</summary>
87
+ public static bool IsNonDefault(OverrideGraphicSettings o) =>
88
+ o.Halftone || o.Transparency != 0 || o.DetailLevel != ViewDetailLevel.Undefined
89
+ || o.ProjectionLineColor.IsValid || o.ProjectionLineWeight != OverrideGraphicSettings.InvalidPenNumber
90
+ || o.CutLineColor.IsValid || o.CutLineWeight != OverrideGraphicSettings.InvalidPenNumber
91
+ || o.SurfaceForegroundPatternId != ElementId.InvalidElementId || o.CutForegroundPatternId != ElementId.InvalidElementId
92
+ || !o.IsSurfaceForegroundPatternVisible || !o.IsCutForegroundPatternVisible;
93
+
94
+ /// <summary>
95
+ /// Inherited state of an element derived from source: its special traits, identity-like
96
+ /// values that were copied (Mark, Comments), and views that hide or override it (bounded).
97
+ /// Per-view hiding and overrides are keyed by element ID, so a copy (new ID) never inherits
98
+ /// them; pass viewGraphics only when the element keeps its ID, as a retyped element does.
99
+ /// Views get the view report instead. Returns null when nothing was carried over.
100
+ /// </summary>
101
+ public static Dictionary<string, object?>? OfElement(Element derived, long? sourceId, bool viewGraphics = false)
102
+ {
103
+ if (derived is View view) return OfView(view, sourceId);
104
+ var state = new Dictionary<string, object?>();
105
+ if (sourceId is long source && source != derived.Id.Value) state["derived_from"] = source;
106
+ if (ElementTraits.For(derived) is { } traits) state["traits"] = traits;
107
+ var carried = new Dictionary<string, object?>();
108
+ foreach (var (key, parameter) in new[] { ("mark", BuiltInParameter.ALL_MODEL_MARK), ("comments", BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS) })
109
+ if (derived.get_Parameter(parameter)?.AsString() is { Length: > 0 } value) carried[key] = value;
110
+ if (carried.Count > 0) state["carried_values"] = carried;
111
+ if (viewGraphics && ViewGraphicsOf(derived) is { } graphics) state["view_graphics"] = graphics;
112
+ if (state.Count == 0 || (state.Count == 1 && state.ContainsKey("derived_from"))) return null;
113
+ state["check"] = ElementCheck;
114
+ return state;
115
+ }
116
+
117
+ /// <summary>Views that hide or override this element, within ScanBudget.</summary>
118
+ private static Dictionary<string, object?>? ViewGraphicsOf(Element element)
119
+ {
120
+ var clock = Stopwatch.StartNew();
121
+ var hiddenIn = new List<long>();
122
+ int overriddenIn = 0;
123
+ bool complete = true;
124
+ foreach (var view in new FilteredElementCollector(element.Document).OfClass(typeof(View)).Cast<View>())
125
+ {
126
+ if (clock.Elapsed > ScanBudget) { complete = false; break; }
127
+ if (view.IsTemplate) continue;
128
+ try
129
+ {
130
+ if (element.CanBeHidden(view) && element.IsHidden(view)) hiddenIn.Add(view.Id.Value);
131
+ if (view.AreGraphicsOverridesAllowed() && IsNonDefault(view.GetElementOverrides(element.Id))) overriddenIn++;
132
+ }
133
+ catch (Autodesk.Revit.Exceptions.ArgumentException) { }
134
+ catch (Autodesk.Revit.Exceptions.InvalidOperationException) { }
135
+ }
136
+ if (hiddenIn.Count == 0 && overriddenIn == 0 && complete) return null;
137
+ return new Dictionary<string, object?>
138
+ {
139
+ ["hidden_in_views"] = hiddenIn.Count, ["hidden_in_view_ids"] = hiddenIn.Take(SampleCap).ToList(),
140
+ ["overridden_in_views"] = overriddenIn, ["complete"] = complete,
141
+ };
142
+ }
143
+ }
144
+ }