pi-revit 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +388 -351
  2. package/README.md +426 -22
  3. package/extensions/pi-revit/index.ts +246 -187
  4. package/extensions/pi-revit/instance-router.ts +86 -0
  5. package/extensions/pi-revit/script-library.ts +146 -0
  6. package/extensions/pi-revit/tool-catalog.ts +67 -0
  7. package/package.json +59 -59
  8. package/skills/pi-revit/SKILL.md +196 -32
  9. package/skills/pi-revit/references/model-audit-export.md +29 -0
  10. package/skills/pi-revit/references/room-documentation.md +28 -0
  11. package/src/Revit/BridgeServer.cs +87 -37
  12. package/src/Revit/OperationStore.cs +178 -0
  13. package/src/Revit/ToolRegistry.cs +57 -35
  14. package/src/Revit/Tools/CaptureView.cs +2 -1
  15. package/src/Revit/Tools/ChangeElementTypes.cs +60 -0
  16. package/src/Revit/Tools/CreateTags.cs +95 -0
  17. package/src/Revit/Tools/DeleteElements.cs +44 -0
  18. package/src/Revit/Tools/DocumentGuard.cs +64 -64
  19. package/src/Revit/Tools/ElementQueryScope.cs +27 -0
  20. package/src/Revit/Tools/ExecuteCsharp.cs +45 -35
  21. package/src/Revit/Tools/ExportDocuments.cs +121 -120
  22. package/src/Revit/Tools/FailureGuard.cs +26 -26
  23. package/src/Revit/Tools/GetElementDetails.cs +42 -12
  24. package/src/Revit/Tools/GetElementRelationships.cs +76 -0
  25. package/src/Revit/Tools/GetElements.cs +37 -23
  26. package/src/Revit/Tools/GetLinkedElements.cs +82 -0
  27. package/src/Revit/Tools/GetLinkedModels.cs +66 -0
  28. package/src/Revit/Tools/GetModelCoordinates.cs +49 -0
  29. package/src/Revit/Tools/GetModelOverview.cs +2 -2
  30. package/src/Revit/Tools/GetScheduleFields.cs +37 -0
  31. package/src/Revit/Tools/GetSchedules.cs +89 -0
  32. package/src/Revit/Tools/ManageElementSets.cs +106 -0
  33. package/src/Revit/Tools/ManageSchedules.cs +164 -0
  34. package/src/Revit/Tools/ManageSelection.cs +37 -36
  35. package/src/Revit/Tools/ManageSheetPlacements.cs +97 -0
  36. package/src/Revit/Tools/ManageSheets.cs +63 -0
  37. package/src/Revit/Tools/ManageViews.cs +100 -0
  38. package/src/Revit/Tools/MeasureGeometry.cs +54 -0
  39. package/src/Revit/Tools/ModelEditBatch.cs +102 -0
  40. package/src/Revit/Tools/ModelEditInputs.cs +49 -0
  41. package/src/Revit/Tools/OpenView.cs +2 -1
  42. package/src/Revit/Tools/QuerySpatialElements.cs +63 -0
  43. package/src/Revit/Tools/SetParameters.cs +43 -93
  44. package/src/Revit/Tools/SpatialBounds.cs +30 -0
  45. package/src/Revit/Tools/SummarizeElements.cs +87 -0
  46. package/src/Revit/Tools/ToolSupport.cs +1 -1
  47. package/src/Revit/Tools/TransformElements.cs +58 -0
  48. package/workspace/AGENTS.md +45 -45
@@ -1,63 +1,227 @@
1
1
  ---
2
2
  name: pi-revit
3
- description: Work with the open Autodesk Revit model through the Revit bridge tools (ping, get_model_overview, get_elements, get_element_details, get_element_types, manage_selection, open_view, set_parameters, search_api_docs, execute_csharp, capture_view, export_documents, get_model_health) and retrieve saved results with read_revit_result. Use when the user asks about the Revit project, its elements, parameters, selection, or wants to change, script, capture, or export the model.
3
+ description: Work with the open Autodesk Revit model through the Revit bridge tools (ping, get_model_overview, get_model_coordinates, get_elements, summarize_elements, manage_element_sets, get_element_details, get_element_types, get_linked_models, get_linked_elements, query_spatial_elements, measure_geometry, get_schedules, get_schedule_fields, manage_schedules, create_tags, get_element_relationships, manage_selection, open_view, set_parameters, transform_elements, delete_elements, change_element_types, manage_views, manage_sheets, manage_sheet_placements, search_api_docs, execute_csharp, capture_view, export_documents, get_model_health), select local sessions with manage_revit_instances, manage reusable code with manage_revit_scripts, retrieve saved results with read_revit_result, and check operation receipts with get_revit_operation. Use when the user asks about the Revit project, linked models, schedules, elements, parameters, selection, or wants to change, script, capture, or export the model.
4
4
  ---
5
5
 
6
6
  # Revit
7
7
 
8
- Work with the live Revit model. The bridge targets Revit 2025, 2026, and 2027; the 0.3.0 changes were tested live on Revit 2025. Bridge document tools require Revit running with a project open; `ping` and `search_api_docs` work without a document. `read_revit_result` reads an already saved result locally without contacting Revit.
8
+ Work with the live Revit model. The bridge targets Revit 2025, 2026, and 2027; the 0.3.0 changes were tested live on Revit 2025. Bridge document tools require Revit running with a project open; `ping` and `search_api_docs` work without a document. `read_revit_result` reads an already saved result locally without contacting Revit. `get_revit_operation` contacts the running bridge without needing an open document or waiting for the model thread.
9
9
 
10
10
  ## Tool selection
11
11
 
12
12
  | Task | Tool |
13
13
  |------|------|
14
- | Bridge alive? Which Revit version? | `ping` |
15
- | Orientation: project info, units, levels, grids, category counts | `get_model_overview` |
16
- | List or count elements of ANY category (walls, doors, rooms, sheets, views, ...) | `get_elements` |
14
+ | Bridge alive? Which Revit version? | `ping` |
15
+ | List local Revit sessions or choose the intended instance | `manage_revit_instances` (native extension tool) |
16
+ | Find and activate specialist tools | `find_revit_tools` (local catalogue) |
17
+ | Orientation: project info, units, levels, grids, category counts | `get_model_overview` |
18
+ | Read base points, site/project locations, or map internal points to active shared coordinates | `get_model_coordinates` (advanced) |
19
+ | List or count elements of ANY category (walls, doors, rooms, sheets, views, ...) | `get_elements` |
20
+ | Count a whole query scope by category, type, level, or parameter value | `summarize_elements` (advanced) |
21
+ | Retain and reread a temporary snapshot of matching host elements | `manage_element_sets` (advanced) |
17
22
  | Read parameter VALUES, location, bounding box, materials of specific elements | `get_element_details` |
18
- | List element types / family symbols; "used vs merely loaded" | `get_element_types` |
23
+ | List element types / family symbols; "used vs merely loaded" | `get_element_types` |
24
+ | List placed Revit links, load status, document identities, placement transforms | `get_linked_models` (advanced) |
25
+ | Query or count elements inside one loaded Revit link | `get_linked_elements` (advanced) |
26
+ | Find host elements whose bounding boxes intersect or fit inside a region | `query_spatial_elements` (advanced) |
27
+ | Measure explicit points or approximate separation of element bounding boxes | `measure_geometry` (advanced) |
28
+ | List schedules or read their fields and displayed cells | `get_schedules` (advanced) |
29
+ | Discover fields eligible for an existing schedule | `get_schedule_fields` (advanced) |
30
+ | Create or configure a regular schedule | `manage_schedules` (advanced) |
31
+ | Create element, room, space, or area tags in one view | `create_tags` (advanced) |
32
+ | Inspect an element's host, type, members, joined geometry, or dependents | `get_element_relationships` (advanced) |
19
33
  | Read or change the user's selection; zoom; temporary isolate | `manage_selection` |
20
34
  | Put a view or sheet on the user's screen (activate it) | `open_view` |
21
- | Write parameter values; rename anything (levels, views, sheets, types) | `set_parameters` |
35
+ | Write or preview parameter values and renames; optional atomic batch | `set_parameters` |
36
+ | Move, copy, or rotate a selection together | `transform_elements` (advanced) |
37
+ | Preview or perform deletion, including Revit's reported deletion cascade | `delete_elements` (advanced) |
38
+ | Change element types with per-target results | `change_element_types` (advanced) |
39
+ | Create plans, isometric 3D views, or sections; duplicate or update views | `manage_views` (advanced) |
40
+ | Create, rename, or renumber drawing sheets | `manage_sheets` (advanced) |
41
+ | List, place, or move views and schedules on sheets | `manage_sheet_placements` (advanced) |
22
42
  | Look up Revit API classes/members/signatures | `search_api_docs` |
23
- | Everything else (create, delete, move, views, sheets, tagging, ...) | `execute_csharp` |
43
+ | Other model tasks (create model elements, custom annotation, ...) | `execute_csharp` |
44
+ | Save, inspect, and run an exact reusable script version; inspect run history | `manage_revit_scripts` (native extension tool) |
24
45
  | PNG snapshot of a view (visual QA) | `capture_view` (advanced) |
25
46
  | PDF/DWG/PNG/IFC file export | `export_documents` (advanced) |
26
- | Warnings / model quality audit | `get_model_health` (advanced) |
27
- | Continue a saved large tool result | `read_revit_result` (local, no Revit call) |
47
+ | Warnings / model quality audit | `get_model_health` (advanced) |
48
+ | Continue a saved large tool result | `read_revit_result` (local, no Revit call) |
49
+ | Check progress or recover a timed-out call's retained result | `get_revit_operation` (native extension tool, local bridge) |
28
50
 
29
- Workflow guidance:
51
+ ## Recipes
52
+
53
+ Read the matching reference when the user requests a complete workflow:
54
+
55
+ - [Room documentation](references/room-documentation.md): plan/section views, room tags, schedules, and sheet placement with previews and visual checks.
56
+ - [Model audit and export](references/model-audit-export.md): counts, health checks, reusable sets, parameter review, and traceable model exports.
57
+
58
+ Workflow guidance:
59
+
60
+ - Specialist tools start inactive. Use `find_revit_tools` with search words or exact `names` to activate matching tools, then call them normally. Browsing without a query lists the catalogue without activation. Activation preserves other Pi tools and runs no model operation.
30
61
 
31
- - Call `get_model_overview` for the intended open model before changing it. Copy `project.documentId` unchanged into `expected_document_id` for `set_parameters`, `execute_csharp`, `export_documents`, `open_view`, and selection/zoom changes. `manage_selection` also requires it whenever `isolate_in_view: true`, even with action `get`. Pure reads may omit it; a supplied ID is always checked. A matching legacy `expected_document` title alone is insufficient.
32
- - The ID belongs to one open document in one bridge session. Refresh it after closing/reopening the model or restarting Revit. If the guard rejects a call, activate the intended model and obtain its overview again; do not blindly substitute the currently active model's ID.
33
- - `get_elements` is the listing/counting primitive (`count_only: true` for bare counts). It returns identity fields only (id, name, category, typeName, levelId); read parameter values with `get_element_details`. Prefer a `category` or `of_class` scope when filtering by a parameter's display name.
62
+ - Call `get_model_overview` for the intended open model before changing it. Copy `project.documentId` unchanged into `expected_document_id` for all model writes and previews (`set_parameters`, `transform_elements`, `delete_elements`, `change_element_types`, `manage_views`, `manage_sheets`, `manage_sheet_placements`, `manage_schedules`, `create_tags`, `execute_csharp`), `export_documents`, `open_view`, and selection/zoom changes. `manage_sheet_placements` requires it even for `list`; its tool-level metadata allows writes. `manage_selection` also requires it whenever `isolate_in_view: true`, even with action `get`. Other pure reads may omit it; a supplied ID is always checked. A matching legacy `expected_document` title alone is insufficient.
63
+ - The ID belongs to one open document in one bridge session. Refresh it after closing/reopening the model or restarting Revit. If the guard rejects a call, activate the intended model and obtain its overview again; do not blindly substitute the currently active model's ID.
64
+ - `get_elements` is the listing/counting primitive (`count_only: true` for bare counts). It returns identity fields (id, name, category, typeName, levelId), with optional `parameter_names` projections for up to 20 parameter identities. Use `get_element_details` for full inspection. Prefer a `category` or `of_class` scope when filtering by a parameter's display name.
34
65
  - The selection pipeline is `get_elements` -> ids -> `manage_selection` (action `set`); there is no inline filter on selection.
35
- - `set_parameters` handles bulk parameter writes and renames (the `Name` parameter covers levels, views, sheets, types). It can commit a partially successful batch: inspect every failed update, `commitWarnings`, and the reported transaction outcome before describing what changed.
66
+ - `set_parameters` handles bulk parameter writes and renames (the `Name` parameter covers levels, views, sheets, types). Default batches can commit partial successes; `atomic: true` rolls the whole batch back if any update fails. Use `preview: true` to validate a proposed batch and roll back its model changes. Inspect `committed`, `succeeded`, `proposed`, `failed`, `commitWarnings`, and `commit_validation_performed` before describing the outcome.
36
67
  - Parameter display names are LOCALIZED: in a non-English Revit UI, `Mark`, `Comments`, and every other display name appear under their translated names. When a display-name lookup or `parameter_names` filter finds nothing, or the document may be non-English, use the language-independent `BuiltInParameter` enum name instead (e.g. `ALL_MODEL_MARK` for Mark, `ALL_MODEL_INSTANCE_COMMENTS` for Comments) — `set_parameters`, `get_element_details.parameter_names`, and `get_elements` filter rules all accept them, and `get_element_details` reports each parameter's `builtInParameter` name for discovery.
37
68
  - Before writing `execute_csharp` code, verify unfamiliar classes/members with `search_api_docs` (works with no document open; first query builds the index and takes a few seconds). The top match carries its remarks, parameter docs, and returns inline, and every public API enum value is searchable — trust the result over guessing or web search; narrow the query to promote a different match into the top slot.
38
- - `export_documents` defaults to `Documents\pi-revit\Models\<model title>--<identity hash>\exports`. Use its returned `outputDir` and file paths as authoritative; do not construct a destination from the title or opaque `project.documentId`. Saved paths and cloud/server identities determine the folder; unsaved/unavailable identities use a session fallback. Save As can select a new folder. Pass `output_dir` when the user requests a different destination. Existing title-only folders remain untouched.
39
- - Large tool payloads return a `result_id`, `file_path`, and continuation instructions. Call `read_revit_result` with that ID and `offset: 0`, then follow `next_offset` until `has_more` is false. Concatenate `text` fragments in order; each fragment is not a standalone JSON result. Offsets count UTF-16 code units. IDs last for the current extension instance; after reload, use the returned absolute file path with `read` while the file exists. Retrieval does not replace the original query's pagination or remove its limits.
69
+ - `export_documents` defaults to `Documents\pi-revit\Models\<model title>--<identity hash>\exports`. Use its returned `outputDir` and file paths as authoritative; do not construct a destination from the title or opaque `project.documentId`. Saved paths and cloud/server identities determine the folder; unsaved/unavailable identities use a session fallback. Save As can select a new folder. Pass `output_dir` when the user requests a different destination. Existing title-only folders remain untouched.
70
+ - Large tool payloads return a `result_id`, `file_path`, and continuation instructions. Call `read_revit_result` with that ID and `offset: 0`, then follow `next_offset` until `has_more` is false. Concatenate `text` fragments in order; each fragment is not a standalone JSON result. Offsets count UTF-16 code units. IDs last for the current extension instance; after reload, use the returned absolute file path with `read` while the file exists. Retrieval does not replace the original query's pagination or remove its limits.
71
+
72
+ ## Required visual proof
73
+
74
+ - After creating or changing sheets, drawings, views, tags, schedule layouts, geometry placement, or other visible results, capture the actual result with `capture_view` or an image export. An API success response alone does not establish visual correctness.
75
+ - Open the returned image with Pi's image-capable read tool and inspect its contents. Check that the requested result is visible, correctly positioned, readable, and free of unintended clipping or overlaps. Use before/after captures when needed to demonstrate movement, rotation, layout changes, or isolation.
76
+ - Frame the real Revit view before capture. A mostly blank image with a tiny drawing does not count as proof. Use a readable full-sheet image plus close-ups when necessary; place schedules on a sheet when a graphical capture of their layout is needed.
77
+ - Retain the pictures with the task's evidence and show or link them in the final result with short captions describing what was verified. Do not fabricate images or infer appearance from filenames, dimensions, or successful export alone.
78
+ - If image capture or inspection is unavailable, explicitly report visual verification as blocked or incomplete. Do not claim the visual work is fully verified. Capturing or exporting evidence does not authorize saving the Revit model.
79
+
80
+ ## Revit instance selection
81
+
82
+ - `manage_revit_instances` defaults to `action: "list"` and returns reachable sessions with opaque `bridge_id`, process/version details, selection status, and operation-tracking support. Use `action: "select"` with the exact returned ID. Selection applies to this Pi extension session; it does not activate a Revit document.
83
+ - The first sole instance binds automatically. If several instances are available before binding, select explicitly before model calls. Once bound, the session never silently falls back. If the selected bridge closes or restarts, list again and explicitly select the intended available identity, even if only one remains. Selection refreshes the tool catalogue; check `tool_catalog_ready` and retry `ping` if discovery is pending. Read `get_model_overview` afterward for a fresh exact document ID.
84
+ - Current bridges publish separate discovery files in `%APPDATA%\RevitBridge\instances\<bridgeId>.json`; the legacy `bridge.json` file is also read. Older bridges without generation IDs receive opaque hash selectors. Copy selectors from the list unchanged. Multiple older bridges cannot all be discovered through the one legacy file; deploy the current add-in to each instance for independent discovery.
85
+ - `get_revit_operation` and identical retries with `_operation_id` always resolve the original bridge from the operation ID, regardless of the selected target for new calls. They do not change that selection. If the original session is unavailable, the request fails without redirecting it. Selecting a new session cannot recover an old session's lost receipts.
86
+
87
+ ## Project coordinates
88
+
89
+ - `get_model_coordinates` requires a project document and explicit length `unit`. It returns project/survey base points, active project location, site data, and project-location pages (`offset`/`limit`, default 50, maximum 100). Optional `points` contains up to 100 `[x,y,z]` positions in document internal axes. The active location's `GetProjectPosition` maps them to shared east/west, north/south, and elevation values. All returned lengths use the requested unit; angles and latitude/longitude use degrees. Paging applies to locations, not input points.
90
+ - This is Revit's active shared-coordinate mapping. Do not infer a GIS coordinate reference system, datum, or projected map units from site latitude/longitude or base-point values. The tool does not acquire, publish, or modify coordinates.
40
91
 
41
- ## execute_csharp playbook
92
+ ## Spatial queries and measurements
93
+
94
+ - `query_spatial_elements` reads host elements using model axis-aligned bounding boxes. Supply region `min`/`max` vectors and explicit length `unit` in document internal axes. `relation: "intersects"` is the default; `"inside"` requires the entire element box to fit inside the region. Both include touching boundaries. Linked contents are not traversed.
95
+ - Its `query` accepts the whole-scope element filters, with no query-level paging or projections. Narrow it to at most 10,000 candidates before spatial testing; the cap applies even when few elements would match the region. Results sort by element ID and use outer `offset`/`limit` (default 100, maximum 200). Follow `next_offset` and inspect `candidate_count`, `without_bounds_count`, and warnings. Elements without a model box are counted separately and omitted from matches.
96
+ - `measure_geometry` with `mode: "point_distance"` requires explicit `point_a` and `point_b`. It returns exact Euclidean distance between those supplied points and signed `delta` from A to B, not a distance between element surfaces. `mode: "bounding_box_gap"` instead requires `element_a_id` and `element_b_id`, both in the host; missing boxes fail the call. Do not mix point inputs with element IDs or pass `clearance` in point mode.
97
+ - Both spatial tools require `unit` from `millimeters`, `centimeters`, `meters`, `feet`, or `inches`; it applies to all coordinate/distance inputs and outputs. Measurement's optional nonnegative `clearance` is only a box-proximity threshold. `box_gap_below_clearance` uses strict less-than, while `boxes_overlap_or_touch` includes contact.
98
+ - Bounding boxes may include nonphysical geometry. Their gap is only a lower bound on geometry separation; a zero gap or a threshold hit is a candidate for review, not a confirmed clash or physical clearance failure. Preserve the returned `method` and `approximate` labels in reports and do not turn box matches into verified collision claims.
99
+
100
+ ## Parameter projections, summaries, and element sets
101
+
102
+ - `get_elements.parameter_names` accepts display names, built-in parameter names, or `guid:<GUID>`. Each projection reports `requested`, `isType`, `found`, `ambiguous`, and every matching parameter in `matches`. Missing and duplicate display-name matches are explicit; do not silently select one ambiguous match. `include_type_parameters: true` adds the same requested parameters from the type, where one exists. `count_only` returns no projections.
103
+ - Parameter `value` is the raw Revit value; measurable numeric values use internal units. `displayValue` is separately formatted for the document. This differs from numeric query filter inputs and `set_parameters` inputs, which use the supplied `unit` or the document's display units.
104
+ - `summarize_elements` and `manage_element_sets` creation accept a `query` containing category, class, level, type, active-view, and parameter-filter scope. The query always covers all matches: query-level paging, `count_only`, fields, and projections are rejected. Both tools require at most 10,000 matches; narrow larger scopes. An omitted query means all host instances, subject to this cap.
105
+ - `summarize_elements` groups by `category`, `typeName`, `levelId`, or `parameter`. Parameter grouping requires `parameter`; `type_parameter: true` uses the type instead of the instance. Groups use exact raw values, including internal numeric units, and distinguish a missing parameter from a present parameter with a null value. Ambiguous parameter names are rejected. The outer `offset`/`limit` pages groups only (default 100, maximum 500); `total_elements` covers the whole matching scope.
106
+ - `manage_element_sets` actions are `create`, `list`, `read`, and `forget`. Sets retain membership and element identities without changing the model or selection. Each expires 30 minutes after creation; reads do not extend its lifetime. At most 32 sets are retained across the bridge session. A set belongs to the exact open document and does not survive closing/reopening that document or restarting the bridge. `list` shows only sets for the active document.
107
+ - Read a set with its `set_id`, using `offset`/`limit` (default 100, maximum 1000). Membership stays fixed; filters are not rerun, but returned values are current. Optional `parameter_names` and `include_type_parameters` use the same projection contract as `get_elements`. Check `missing` for deleted or identity-changed members before passing returned IDs to another tool. Follow `next_offset`, which advances by `visited_count`, including missing members; `returned_count` can be smaller.
108
+
109
+ ## Parameter previews and atomic batches
110
+
111
+ - `set_parameters` accepts 1–200 updates, each isolated in a subtransaction. `preview` and `atomic` are independent and default to false. Preview still requires the intended model's `expected_document_id`.
112
+ - A preview commits an eligible transaction to exercise Revit's commit checks, then rolls back its enclosing transaction group. If an atomic batch has a failed update, or no updates are accepted, it rolls back without attempting commit. `commit_validation_performed` distinguishes these cases; do not claim commit validation when it is false. A returned preview result confirms group rollback; on an error, inspect the reported cleanup outcome.
113
+ - `succeeded` contains only committed updates. Previewed or otherwise rolled-back accepted steps appear in `proposed`, with `updated: 0` and `committed: false`; they are not changes left in the model. Both lists include zero-based input `index` and observed per-step `before`/`after` values. Repeated writes to the same parameter form a sequence, so intermediate `after` values are not a final-model snapshot. Read the element again when final values matter, and inspect all `failed` entries and commit warnings.
114
+
115
+ ## Transform, delete, and change types
116
+
117
+ - Activate these tools with `find_revit_tools`. All three use host-document IDs and require `expected_document_id`, including previews. Inspect the common `committed`, `succeeded`, `proposed`, `failed`, `commitWarnings`, and `commit_validation_performed` fields. `updated` counts committed steps: a transform or deletion is one step for the whole selection, not one per element.
118
+ - `transform_elements` accepts 1–200 distinct `element_ids` and `action: "move"`, `"copy"`, or `"rotate"`. Supply `unit` explicitly: `millimeters`, `centimeters`, `meters`, `feet`, or `inches`. Move/copy uses `translation: [x,y,z]`; rotation uses `axis_origin`, a nonzero dimensionless `axis_direction`, and signed right-hand `angle_degrees`. Coordinates use the document's internal origin and axes, not a view or shared-coordinate frame. Returned location snapshots use feet regardless of input units.
119
+ - Transform applies the entire selection in one step. A failure rolls back that step; move/rotate rejects pinned requested elements, and the tool never unpins anything. Constraints or hosting can affect other elements. Snapshots do not provide a complete audit of those dependent effects. In copy previews, `created_ids` are temporary and marked `created_ids_are_temporary`; never reuse them after rollback.
120
+ - `delete_elements` accepts 1–200 distinct requested IDs and deletes them together. Pinned requested elements are rejected. A preview returns the complete `deleted_ids` set reported by `Document.Delete`, including `dependent_ids`, then rolls back. More than 10,000 deleted IDs causes rollback. This list does not audit surviving elements that Revit may modify.
121
+ - Optionally pass the full preview `deleted_ids` as `expected_deleted_ids` on a later deletion. The tool compares sets before commit and rolls back if they differ. This checks the deletion membership, not every element property or dependent effect. Use a new operation ID for the actual deletion because its arguments differ from the preview; retain the original ID only for an identical retry.
122
+ - `change_element_types` accepts 1–200 `updates` containing `element_id`/`type_id` pairs, with each target appearing once. Invalid types and pinned targets fail individually. Default batches can commit other targets; `atomic: true` rolls all back on any failure. After a committed change, use `resulting_id` and `unique_id`, since Revit can replace an element. Replacement IDs in `proposed` are temporary after either preview or atomic rollback and must not be reused. Constraints can affect connected or hosted elements beyond the returned target snapshots.
123
+
124
+ ## Views, sheets, and placements
125
+
126
+ - Activate `manage_views`, `manage_sheets`, and `manage_sheet_placements` with `find_revit_tools`. They require `expected_document_id` and support `preview: true` for edits. Each edit is one step using the common transaction result fields. Created preview IDs are temporary (`id_is_temporary: true`); do not reuse them. Use `delete_elements` for removal and `open_view` to activate a committed view or sheet.
127
+ - `manage_views` supports `create_plan`, `create_3d`, `create_section`, `duplicate`, and `update`. Discover compatible `view_family_type_id` values with `get_element_types` using `of_class: "ViewFamilyType"`; use `get_elements` for levels and existing views. Plan creation also requires `level_id`; 3D creation is isometric. Duplicate/update requires `view_id`. Duplication options are `duplicate` (default), `with_detailing`, and `dependent`, subject to the view's supported options.
128
+ - A section requires `unit`, `origin`, nonzero orthogonal `viewing_direction` and `up` vectors, and positive `width`, `height`, and `depth`. Its origin and dimensions use the explicit length unit in the document's internal coordinate frame; directions are dimensionless. Width/height extend symmetrically about the origin, with depth extending along the viewing direction. This is not a sheet-coordinate operation.
129
+ - View edits can apply `name`, `scale` (1–24000), and compatible `view_template_id` together. Use template ID `-1` only when intentionally removing a template. An incompatible template or template-controlled scale fails the step; the tool does not remove a template automatically to change scale.
130
+ - `manage_sheets` creation requires `name` and `number`; optional `titleblock_type_id` selects a loaded titleblock `FamilySymbol`, and omission creates a sheet without one. Discover titleblock types with `get_element_types` and category `OST_TitleBlocks`. Update requires `sheet_id` and accepts name/number; it rejects `titleblock_type_id`. Use `change_element_types` on an existing titleblock instance to change its type.
131
+ - `manage_sheet_placements` `list` requires `sheet_id` and returns viewports and schedule instances with `offset`/`limit` (default 100, maximum 200) and `next_offset`. Listing does not edit the model, but the tool is classified as write-capable, so the exact document guard applies to every action.
132
+ - Placement `place` requires `sheet_id`, `view_id`, `position: [x,y,0]`, and explicit `unit`. `move` uses `placement_id`, position, and unit; pinned placements are rejected. Coordinates are paper-space sheet coordinates: never multiply them by view scale. A viewport's position is its box center excluding the label; a schedule's position is its insertion point. Returned positions always use feet and identify `position_kind`.
133
+ - Optional viewport `rotation` is `none`, `clockwise`, or `counterclockwise`. Omit rotation entirely for schedules, including when no rotation is intended. Revit validates placement eligibility; placeholder sheets cannot receive content, and a view already placed elsewhere may be rejected.
134
+
135
+ ## Schedule authoring and tags
136
+
137
+ - `manage_schedules` creates or configures a regular schedule as one atomic step. Creation requires `category` and `name`; `area_scheme_id` is available for area schedules. Configure requires `schedule_id`; category and area scheme are creation-only. Use `is_itemized` to control whether instances remain separate. Templates, revision/embedded schedules, and calculated/combined-field authoring are outside this tool.
138
+ - Discover eligible additions with `get_schedule_fields` on an existing schedule: use optional localized `name_filter` and `offset`/`limit` (default 100, maximum 200). Each field is identified by the pair `parameter_id` and case-sensitive `field_type`; negative built-in parameter IDs are valid. `included` reports existing pairs. These are not the schedule-local `field_id` values returned by `get_schedules`.
139
+ - `add_fields` uses the eligible pair and supports heading, hidden state, and width. When Count appears in field discovery, pass its returned `parameter_id` and `field_type` unchanged. Count also supports `{ "field_type": "Count" }` without a parameter ID. A supplied pair must be eligible for that schedule; do not guess its parameter ID. `update_fields`, `sort_fields`, and `filters` instead use actual schedule-local `field_id` values from `get_schedules`; never guess them from column positions or parameter IDs. Read newly committed fields before configuring their sort/filter rules. A preview's new schedule and added field IDs are temporary and cannot be reused.
140
+ - Add/update arrays each allow 50 fields. Width changes require an explicit length `unit` and apply to both grid and sheet widths. Supplied `sort_fields` (maximum 4) or `filters` (maximum 8) replace the entire corresponding list; omission preserves it, and `[]` clears it. Sort entries support `descending` and `show_header`.
141
+ - Filters require `field_id`, `comparison` (`equals`, `not_equals`, `contains`, `greater_than`, or `less_than`), `value_type` (`string`, `number`, `integer`, or `element_id`), and matching `value`. Measured `number` values require an explicit compatible `unit`; omit units for unitless numbers. Use `get_schedules` field `spec_type_id`, `can_filter_value`, and `can_filter_substring` to inspect capabilities. Its numeric filter values use internal units and its comparison names are Revit enum names, not the write schema's comparison strings. Returned grid/sheet widths use feet.
142
+ - `create_tags` requires `kind` (`element`, `room`, `space`, or `area`), one `view_id`, loaded tag `FamilySymbol` `tag_type_id`, explicit length `unit`, and 1–100 targets containing `element_id` and `head_position: [x,y,z]`. Positions use the document's internal coordinates and always describe the tag head, including with `leader: true`; returned head positions use feet. Targets must belong to the host document; linked targets and face/subelement references are unsupported.
143
+ - Element tags accept `orientation: "horizontal"` (default) or `"vertical"`; omit orientation for spatial tags. Room/space/area tags require a compatible plan view and positions at the spatial element's level. Templates, perspective views, and unlocked 3D views cannot host these independent tags. Discover a compatible loaded tag type before creating tags.
144
+ - Schedule edits and tag creation require exact document targeting and support commit-validated preview rollback. Tag batches default to per-target partial success; `atomic: true` rolls all back on any failure. Inspect all common transaction fields. All tag IDs in `proposed` are temporary after preview or atomic rollback; only reuse IDs from committed `succeeded` entries.
145
+
146
+ ## Links, schedules, and relationships
147
+
148
+ - Start linked queries with `get_linked_models`. It lists direct link instances, including unloaded links, with `link_instance_id`, `loaded`, and `linked_document_id`. Pass the returned linked identity unchanged as `expected_linked_document_id` to `get_linked_elements`; use the host overview's ID for `expected_document_id`. Refresh link discovery after a link is unloaded or reloaded. Nested links are not traversed.
149
+ - `get_linked_elements` accepts the same category, class, level, type, parameter filters, `count_only`, and `offset`/`limit` as `get_elements`, but does not support `in_active_view`. Level and type IDs belong to the linked document. Preserve each row's full `reference`, including the link instance: two placements of the same linked model can have different host coordinates. Linked element IDs must not be passed to host selection or write tools.
150
+ - With `include_bounds: true`, `get_linked_elements` returns `host_bounds` in internal feet, or null where no bounding box exists. These are axis-aligned host bounds calculated from all eight transformed box corners, not exact element geometry. Link transform origins are also in feet; the basis vectors describe orientation and scale.
151
+ - `get_schedules` without `schedule_id` lists schedules, optionally narrowed by `name_filter`; templates and titleblock revision schedules are excluded from this list. Supply a returned ID to read field definitions, specification and width metadata, filter capabilities, current sort/filter rules, and formatted body cells. Body rows may include headings, grouped entries, and totals: neither a row index nor a cell value establishes an element ID. Hidden field definitions do not correspond one-to-one to displayed columns.
152
+ - Schedule body reads have two independent continuations: `next_offset` for rows and `next_column_offset` for columns. Read all column pages at each row offset before advancing the rows. List/body pages default to 50 entries (maximum 200); column pages default to 50 (maximum 50). Use returned row and column indices rather than guessing their origin.
153
+ - `get_element_relationships` reads one host element. Choose `relationships` to narrow the result; each relationship has its own count and `next_offset`, using the supplied `offset`/`limit` (default 100, maximum 200). `host`, `parent`, and `subcomponents` describe family instances; `members` describes groups or assemblies. Links are not traversed. Logical `dependents` are not a complete prediction of what deletion would remove. In a family document, request specific kinds that exclude `joined`.
154
+ - Follow each tool's returned pagination separately from `read_revit_result`. Link-instance pages default to 100 (maximum 1000); linked-element pages default to 200 (maximum 1000).
155
+
156
+ ## execute_csharp playbook
42
157
 
43
- - Globals: `doc` (Document), `uidoc` (UIDocument), `uiapp` (UIApplication), and `Dump(value)` to record intermediates into the result's `dumps[]`.
44
- - The script runs inside one backend-owned transaction. Do not start another transaction on `doc` (sub-transactions are allowed). The tool checks commit status and attempts rollback on script failure; read its actual outcome instead of assuming rollback succeeded. Result projection can fail after a successful commit and report `returnValueError`. Filesystem and UI effects are separate from model rollback.
158
+ - Globals: `doc` (Document), `uidoc` (UIDocument), `uiapp` (UIApplication), `inputs` (`System.Text.Json.JsonElement`), and `Dump(value)` to record intermediates into the result's `dumps[]`. Pass an optional JSON `inputs` object separately from `code`; omission gives an empty object. Read values with `inputs.GetProperty(...)` and validate them in the script. Inputs are not interpolated into source and may contain at most 100,000 JSON characters.
159
+ - The script runs inside one backend-owned transaction. Do not start another transaction on `doc` (sub-transactions are allowed). The tool checks commit status and attempts rollback on script failure; read its actual outcome instead of assuming rollback succeeded. Result projection can fail after a successful commit and report `returnValueError`. Filesystem and UI effects are separate from model rollback.
45
160
  - Scripts must be fully synchronous: `await`/`async` is rejected at compile time; never block on `Task.Result`/`.Wait()`.
46
161
  - Return primitives, strings, or anonymous objects/lists; raw Revit API objects are projected to compact shapes (Element -> `{id,name,category,typeName,levelId}`, ElementId -> number, XYZ -> `{x,y,z}`).
47
162
  - Lengths are internal units (decimal feet) — convert with `UnitUtils.ConvertToInternalUnits`/`ConvertFromInternalUnits`.
48
- - Common pitfalls: call `FamilySymbol.Activate()` before `NewFamilyInstance`; use collector-level filtering (`OfCategory`/`OfClass`/`WhereElementIsNotElementType`) and bounded loops. The budget is 120s and Revit cannot be interrupted mid-script. The dialog guard attempts dismissive responses and reports `suppressedDialogs`; it cannot guarantee handling every modal dialog.
163
+ - Common pitfalls: call `FamilySymbol.Activate()` before `NewFamilyInstance`; use collector-level filtering (`OfCategory`/`OfClass`/`WhereElementIsNotElementType`) and bounded loops. The budget is 120s and Revit cannot be interrupted mid-script. The dialog guard attempts dismissive responses and reports `suppressedDialogs`; it cannot guarantee handling every modal dialog.
49
164
  - `capture_view` returns a `filePath` to a temp PNG, never image data — open it with the read tool to actually see it.
50
165
 
51
- ## Failure modes
52
-
53
- - **Identity rejected**: no tool action was performed. Verify the intended active model, refresh `project.documentId`, and pass `expected_document_id` unchanged.
54
- - **Partial UI/export effects**: selection or zoom can already have changed when isolation fails. Export errors can leave incomplete files, and IFC commit warnings are returned. Inspect the reported effects and output paths; model rollback does not remove files or reverse earlier UI actions.
55
- - **Large-result save failed**: Revit may already have completed the operation. Verify its effects before retrying a write.
166
+ ## Reusable scripts
167
+
168
+ - `manage_revit_scripts` supports `save`, `list`, `read`, `run`, and `history`. The local library is `%APPDATA%\pi-revit\scripts`, with immutable versions and run records. Save requires `name`, `description`, `code`, and `input_types`, and never executes the code. Names contain 1–64 lowercase letters, digits, underscores, or hyphens, starting with a letter. The returned 64-character SHA-256 `version` identifies the complete saved definition, including source, description, and input declarations.
169
+ - Read the exact `name`/`version` to inspect its code before running it; there is no implicit latest version. Run requires that exact saved hash, `expected_document_id`, and matching `inputs`. Saved content is integrity-checked before use. Library runs require a bridge with operation receipts; saving/reading/listing/history do not require a model.
170
+ - `input_types` declares at most 40 named required inputs of kind `string`, `number`, `integer`, `boolean`, `object`, or `array`. Extra inputs are rejected. This validates top-level JSON kinds only; scripts must validate nested contents, units, ranges, element identities, and other domain rules. `inputs` reaches the script as a separate `JsonElement`, not substituted source text.
171
+ - A library run has the same unrestricted model/UI/file/external effects and one-transaction behavior as `execute_csharp`, including its synchronous execution rule and timeout. There is no library preview mode or automatic model saving. Inspect the code's intended effects and honor the user's save instructions; saving a script definition is not saving the model.
172
+ - `list` and `history` support optional exact-name filtering and outer `offset`/`limit` (default 20, maximum 100). History records script version, document identity, input hash, timestamps, and the operation/bridge receipt identifiers, not raw input values or results. `prepared` is not proof of execution, `response_received` is not proof of a successful model edit, and `outcome_unconfirmed` needs receipt inspection. If interrupted, check `get_revit_operation` before retrying the same version, document, and inputs with the original `_operation_id`.
173
+
174
+ For example, save a numeric echo/check definition with `manage_revit_scripts`:
175
+
176
+ ```json
177
+ {
178
+ "action": "save",
179
+ "name": "check_number",
180
+ "description": "Echo a supplied number and report whether it is nonnegative.",
181
+ "code": "var value = inputs.GetProperty(\"value\").GetDouble(); return new { value, nonnegative = value >= 0 };",
182
+ "input_types": { "value": "number" }
183
+ }
184
+ ```
185
+
186
+ Read the returned version and inspect its source:
187
+
188
+ ```json
189
+ { "action": "read", "name": "check_number", "version": "<exact version from save>" }
190
+ ```
191
+
192
+ Then explicitly run that version against the intended open model, replacing both placeholders with the actual returned identities:
193
+
194
+ ```json
195
+ {
196
+ "action": "run",
197
+ "name": "check_number",
198
+ "version": "<exact version from save>",
199
+ "expected_document_id": "<project.documentId from the current overview>",
200
+ "inputs": { "value": 12.5 }
201
+ }
202
+ ```
203
+
204
+ This example's source only returns the supplied value and a comparison; it performs no model edit or save. The run still uses the normal script transaction and operation receipt.
205
+
206
+ ## Operation receipts and retries
207
+
208
+ - With a supporting bridge, every bridge tool call automatically receives an operation ID, included in its response or request error. Use `get_revit_operation` with `operation_id` to inspect it without queueing a model action. Receipts are held by the bridge, not the Pi session; the original bridge must remain reachable. Receipt reads and identical retries route to that original session even if another instance is now selected.
209
+ - States are `queued`, `running`, `succeeded`, `failed`, `expired_before_start`, `result_unavailable`, or `unknown`. `expired_before_start` confirms no tool action began. `failed` and `result_unavailable` do not establish rollback; inspect the result and actual effects. `succeeded` describes call completion, not a committed model edit: a preview can succeed with `committed: false` and rolled-back `proposed` changes.
210
+ - Retry only an identical request with the exact `_operation_id` added to its original tool arguments. The bridge waits for or returns that operation's response without executing it again. Preserve the original tool, document identity, and every argument; a mismatch is rejected. Omitting `_operation_id` creates a new operation. Do not use a new ID to bypass an unresolved earlier outcome.
211
+ - Full results are bounded to 128 completed receipts and 32 MiB in total. Eviction leaves the ID and outcome reserved as a receipt record for the rest of that bridge session, with `result_available: false`; retrying cannot recreate the evicted result or rerun the action. At 10,000 records, new tracked calls are rejected while existing receipts remain queryable.
212
+ - Restarting the bridge clears receipts and changes its identity. An old operation ID cannot be replayed in the new session. An unavailable original bridge or an `unknown` receipt is not proof that the operation never ran. Changing selection does not redirect old receipts or retries. Inspect the original bridge/model before issuing a new edit. An older bridge without tracking provides no receipt guarantee.
213
+
214
+ ## Failure modes
215
+
216
+ - **Identity rejected**: no tool action was performed. Verify the intended active model, refresh `project.documentId`, and pass `expected_document_id` unchanged.
217
+ - **Several instances / selected session unavailable**: use `manage_revit_instances` to list and explicitly select the intended current bridge, then refresh its model overview. Calls are not redirected automatically. For an old operation receipt, restore access to its original bridge or inspect the original model; choosing another session does not establish the old outcome.
218
+ - **Partial UI/export effects**: selection or zoom can already have changed when isolation fails. Export errors can leave incomplete files, and IFC commit warnings are returned. Inspect the reported effects and output paths; model rollback does not remove files or reverse earlier UI actions.
219
+ - **Large-result save failed**: Revit may already have completed the operation. Check `get_revit_operation` using its operation ID to recover a retained bridge result. Verify its effects before issuing a new write.
56
220
  - **Bridge not reachable** ("Revit bridge is not available" / "Could not reach the Revit bridge"): Revit is not running or the add-in did not load. Ask the user to start Revit, then retry `ping`.
57
221
  - **HTTP 409 / "No active Revit document is open."** (`hasActiveDocument: false`): Revit is running but no project is open. Ask the user to open a project, then retry. This fails immediately; do not wait or retry blindly.
58
- - **Timeout** ("Revit did not answer within Ns", 30s default / 120s for execute_csharp, capture_view, export_documents): Revit is busy or showing a modal dialog. An already-started tool still runs to completion in Revit — verify model state (e.g. `get_elements`) before re-issuing a write.
59
- - **Cancelled**: same caveat — the bridge cannot abort queued or running work, so verify model state before retrying a write tool.
60
-
61
- ## Upgrading from 0.2.x
62
-
63
- Version 0.3.0 requires exact document IDs for the operations above. Update the Pi package and deploy the matching add-in with Revit closed, then restart Revit and start a fresh Pi session so the new schemas and instructions are loaded. Obtain a fresh overview before writes. `ping` reports an installed/loaded version mismatch. Setup preserves existing workspace `AGENTS.md`; merge these targeting, result-reading, and folder rules into an older workspace's instructions when needed.
222
+ - **Timeout** ("Revit did not answer within Ns", 30s default / 120s for execute_csharp, capture_view, export_documents): Revit is busy or showing a modal dialog. An already-started tool can still complete. Check `get_revit_operation`; reuse the exact `_operation_id` and original arguments for an identical retry. If no receipt is available, verify model state before a new write.
223
+ - **Cancelled**: client cancellation does not cancel bridge work. Check the operation receipt and follow the same retry rules; a queued call may still start before its deadline, and running work cannot be interrupted.
224
+
225
+ ## Upgrading from 0.2.x
226
+
227
+ Version 0.3.0 requires exact document IDs for the operations above. Update the Pi package and deploy the matching add-in with Revit closed, then restart Revit and start a fresh Pi session so the new schemas and instructions are loaded. Obtain a fresh overview before writes. `ping` reports an installed/loaded version mismatch. Setup preserves existing workspace `AGENTS.md`; merge these targeting, result-reading, and folder rules into an older workspace's instructions when needed.
@@ -0,0 +1,29 @@
1
+ # Model audit and export
2
+
3
+ Use this workflow to establish model counts and data quality, inspect findings, and produce requested exports with a record of the model and scope used. An audit does not imply permission to repair the model or save its Revit file.
4
+
5
+ ## Establish an auditable scope
6
+
7
+ 1. Select the intended Revit session with `manage_revit_instances` when needed, then call `get_model_overview`. Record the returned model identity, title, available persistent path/identity, Revit/add-in versions, and the inspection time. The opaque document ID identifies this open session; it is not a permanent project identifier or an export-folder key.
8
+ 2. Activate `summarize_elements`, `manage_element_sets`, `get_model_health`, and the required inspection/export tools with `find_revit_tools`. State the categories, levels, views, and parameter rules being audited. Pass `expected_document_id` on reads as well when keeping the audit tied to one model matters.
9
+ 3. Use `get_elements` for raw counts and parameter projections. Up to 20 `parameter_names` can be requested per row, with optional type parameters. Report missing and ambiguous matches separately from empty or null values. Prefer built-in identities or shared GUIDs for stable parameter targeting. Raw measurable values use internal units; retain `displayValue` and unit context for human-readable findings.
10
+ 4. Use `summarize_elements` for counts by category, type, level, or exact parameter value. The query covers all matches and rejects query-level paging. Narrow scopes above 10,000 candidates; outer pages contain groups, not partial element counts. Record each query and avoid adding overlapping scopes as though they were disjoint.
11
+
12
+ ## Review health and retain findings
13
+
14
+ 5. Call `get_model_health` for warning groups and model structure. Inspect truncation flags: it returns at most 100 warning groups and 20 element examples per group. A truncated example list is not the complete affected set, and a successful health read does not certify every model-quality rule.
15
+ 6. For a reusable filtered scope, create a set with `manage_element_sets`. Record its query, `set_id`, count, and expiry. Sets retain at most 10,000 members, expire 30 minutes after creation, and belong to the exact open document/bridge session. They store membership, not frozen parameter values. Repeated reads show current values and report deleted or identity-changed members in `missing`; follow `next_offset` using visited membership, not only returned rows.
16
+ 7. Inspect specific findings with `get_element_details` and `get_element_relationships`. For coordinate checks, use `get_model_coordinates` with explicit units and record the active project location; its shared mapping does not identify a GIS reference system. Spatial tools may narrow review candidates, but axis-aligned boxes do not prove clashes or clearance failures. Preserve each tool's method, units, limits, warnings, and approximation status in the findings.
17
+ 8. Retrieve oversized payloads with `read_revit_result` and follow every continuation. This recovers the current tool payload; it does not expand the original query page or remove warning/projection limits.
18
+
19
+ ## Apply only requested corrections
20
+
21
+ 9. If corrections are authorized, use `set_parameters` with exact document identity, explicit numeric input units, and `preview: true` to inspect proposed before/after values. Use `atomic: true` where the batch must succeed together. Check `commit_validation_performed`, failures, and warnings before applying the intended edit as a new operation. Only committed `succeeded` entries represent retained changes; proposed entries were rolled back.
22
+ 10. Reread the affected members and repeat relevant counts/health checks after changes. Distinguish a changed query membership from changed values within the original set. Preserve before/after evidence when reporting the correction; no call should save the Revit file unless the user requested that separately.
23
+
24
+ ## Export and record what was produced
25
+
26
+ 11. Discover and inspect the intended sheet/view IDs before `export_documents`. PDF/DWG/PNG require 1–100 explicit IDs. IFC exports the whole model by default or can use one supplied view; record that distinction. PDF combines outputs by default. Export has file effects, and IFC can also write model metadata inside its transaction; it is not a rollback-only preview.
27
+ 12. Omit `output_dir` to use the identity-derived model destination unless the user requests another location. Treat returned `outputDir` and `files[].path` as authoritative. Do not reconstruct a folder from model title or session ID. Use the verified model folder for accompanying audit notes according to workspace rules; never trigger an unnecessary export just to discover a folder.
28
+ 13. Record export time, format, requested IDs/scope, model identity evidence, operation ID, returned paths and sizes, and commit warnings. These identify what the export call reported. Directory-change detection can be confused by unrelated simultaneous writers, so avoid sharing the output folder with concurrent work.
29
+ 14. Open or inspect the produced artifact as appropriate to the deliverable. Existence and file size do not establish drawing quality or IFC schema/geometry validity. If export fails, report observed partial files; rollback does not remove them. For a timeout, inspect its original operation receipt before retrying with the same `_operation_id` and identical arguments. State remaining uncertainty explicitly rather than claiming an unverified export succeeded.
@@ -0,0 +1,28 @@
1
+ # Room documentation
2
+
3
+ Use this workflow to prepare a room plan or section, tags, a schedule, and a drawing sheet. Match the user's requested deliverables and model-saving instructions. Creation and placement calls change the open document; a tool commit does not save the Revit file.
4
+
5
+ ## Establish the room and drawing resources
6
+
7
+ 1. If needed, use `manage_revit_instances` to select the intended bridge. Call `get_model_overview` and retain its exact `project.documentId`. Pass it as `expected_document_id` throughout, including previews and placement listing. After a bridge restart or document reopen, select the intended session and refresh the overview.
8
+ 2. Activate the required tools with `find_revit_tools`. Query rooms using `get_elements` with category `OST_Rooms`. Project room number/name parameters using built-in identities discovered through `get_element_details`. Follow query pages and distinguish missing values from empty ones. Select the intended room by its identity, not a name alone.
9
+ 3. Read the room's level, location, bounds, and relevant parameters with `get_element_details`. Check that it is placed and suitable for documentation. Record units: detail coordinates are internal feet. A box is only an enclosure and does not establish the room's exact boundary or a valid tag location.
10
+ 4. Discover existing views/levels with `get_elements`, view-family types using `get_element_types` with `of_class: "ViewFamilyType"`, and compatible room-tag/titleblock symbols with `get_element_types`. Reuse suitable loaded resources; do not invent type IDs.
11
+
12
+ ## Build and check the views
13
+
14
+ 5. Use `manage_views` to duplicate an appropriate plan, or create a plan with the room's `level_id` and a compatible `view_family_type_id`. Choose a clear name and supported scale/template. A new floor plan is not automatically cropped to the room. Confirm the actual view extent before describing it as a room drawing.
15
+ 6. If a section is requested, use `create_section` with an explicit internal-coordinate origin, orthogonal view/up vectors, dimensions, and length unit. Derive the extent from the intended room and drawing requirements; do not assume its bounding-box faces are finished wall faces. Preview each proposed edit, inspect failures and `commit_validation_performed`, then apply the intended edit with a new operation ID. Preview-created IDs are temporary.
16
+ 7. Read the committed view ID, then use `create_tags` with `kind: "room"`, a compatible plan view and loaded room-tag type. Place `head_position` at the room's level in internal coordinates using the specified unit. It always refers to the tag head, including when a leader is enabled. Omit `orientation` for room tags. Preview, then create the intended tags; check all per-target results. Use `atomic: true` when the requested tag batch must succeed together.
17
+
18
+ ## Add the schedule and sheet
19
+
20
+ 8. Reuse a suitable existing schedule or create a regular `OST_Rooms` schedule with `manage_schedules`. On the committed schedule, call `get_schedule_fields` to discover eligible `parameter_id`/`field_type` pairs for room number, name, area, and any requested data. Add those fields, then read `get_schedules` for actual schedule-local `field_id` values.
21
+ 9. Configure headings, explicit-unit widths, itemization, sorting, and any room-number filter with `manage_schedules`. Supplied filter/sort arrays replace their entire lists. Use measured numeric filter units explicitly. Verify all row and column pages; grouped rows and totals are not element IDs. Do not reuse field IDs created only in a preview.
22
+ 10. Create or update the sheet with `manage_sheets`, using the requested name/number and an optional loaded titleblock type. Preview first when assessing the proposed layout changes, then retain the committed sheet ID.
23
+ 11. Place committed views and the schedule with `manage_sheet_placements`. Specify paper-space `[x,y,0]` coordinates and units; never multiply by view scale. A viewport position is its box center excluding its label, while a schedule position is its insertion point. Omit schedule rotation. Use `list` to inspect placements and `move` to refine them; previews produce no reusable placement IDs.
24
+
25
+ ## Verify and deliver
26
+
27
+ 12. Activate the sheet with `open_view`, capture it with `capture_view`, and open the returned PNG using Pi's image-capable read tool. Check tag legibility, view extent, scale, schedule contents, titleblock data, and overlaps. Numeric placement alone does not confirm visual layout quality.
28
+ 13. If export is requested, export the committed sheet ID through `export_documents` and report the returned paths. Respect the user's file-saving instruction separately from export. Summarize committed views, tags, schedules, and sheets, along with remaining failures or warnings. If a call times out, check `get_revit_operation` and retry only the identical request with its original `_operation_id` until its outcome is known.
@@ -39,7 +39,8 @@ namespace RevitBridge
39
39
  private readonly CommandQueue _queue;
40
40
  private readonly ToolRegistry _registry;
41
41
  private readonly string _revitVersion;
42
- private readonly Func<bool> _hasOpenDocument;
42
+ private readonly Func<bool> _hasOpenDocument;
43
+ private readonly OperationStore _operations = new();
43
44
 
44
45
  /// <summary>How often the bridge re-checks that bridge.json still points somewhere
45
46
  /// live (see EnsureBridgeInfo). Cheap: one small file read per tick.</summary>
@@ -117,17 +118,23 @@ namespace RevitBridge
117
118
 
118
119
  public static string BridgeInfoPath() => Path.Combine(BridgeInfoDirectory(), "bridge.json");
119
120
 
120
- private void WriteBridgeInfo()
121
- {
122
- Directory.CreateDirectory(BridgeInfoDirectory());
123
- File.WriteAllText(BridgeInfoPath(), JsonSerializer.Serialize(new
121
+ private void WriteBridgeInfo()
122
+ {
123
+ Directory.CreateDirectory(BridgeInfoDirectory());
124
+ string json = JsonSerializer.Serialize(new
124
125
  {
125
126
  baseUrl = $"http://127.0.0.1:{_port}",
126
127
  token = _token,
127
128
  pid = Environment.ProcessId,
128
- revitVersion = _revitVersion,
129
+ revitVersion = _revitVersion,
130
+ bridgeId = _operations.Generation,
131
+ supportsOperationTracking = true,
129
132
  startedAtUtc = DateTime.UtcNow.ToString("o"),
130
- }, new JsonSerializerOptions { WriteIndented = true }));
133
+ }, new JsonSerializerOptions { WriteIndented = true });
134
+ string instances = Path.Combine(BridgeInfoDirectory(), "instances");
135
+ Directory.CreateDirectory(instances);
136
+ File.WriteAllText(Path.Combine(instances, _operations.Generation + ".json"), json);
137
+ File.WriteAllText(BridgeInfoPath(), json);
131
138
  }
132
139
 
133
140
  /// <summary>
@@ -186,8 +193,9 @@ namespace RevitBridge
186
193
  }
187
194
  }
188
195
 
189
- private static void DeleteBridgeInfoIfOwned()
190
- {
196
+ private void DeleteBridgeInfoIfOwned()
197
+ {
198
+ try { File.Delete(Path.Combine(BridgeInfoDirectory(), "instances", _operations.Generation + ".json")); } catch { }
191
199
  try
192
200
  {
193
201
  string path = BridgeInfoPath();
@@ -337,7 +345,10 @@ namespace RevitBridge
337
345
  private async Task<(int Status, object Response)> RouteAsync(string method, string path, Dictionary<string, string> query, string body, CancellationToken token)
338
346
  {
339
347
  if (method == "GET" && path == "/ping")
340
- return (200, new { ok = true, service = "revit-bridge", revitVersion = _revitVersion, pid = Environment.ProcessId, addinVersion = AddinVersion });
348
+ return (200, new { ok = true, service = "revit-bridge", revitVersion = _revitVersion, pid = Environment.ProcessId, addinVersion = AddinVersion, bridgeId = _operations.Generation, supportsOperationTracking = true });
349
+
350
+ if (method == "GET" && path.StartsWith("/operations/", StringComparison.Ordinal))
351
+ return (200, _operations.Status(WebUtility.UrlDecode(path["/operations/".Length..])));
341
352
 
342
353
  if (method == "GET" && path == "/tools")
343
354
  {
@@ -378,14 +389,6 @@ namespace RevitBridge
378
389
  if (tool is null)
379
390
  return (404, new { error = true, message = $"Unknown tool: {name}" });
380
391
 
381
- // Pre-check before enqueueing: with zero documents open Revit does not
382
- // pump ExternalEvents, so a queued call would hang instead of failing.
383
- // The in-queue NoActiveDocumentException below remains as the backstop
384
- // for a document closing between this check and execution. Tools that
385
- // never touch the Revit API (RequiresDocument = false) skip the gate.
386
- if (tool.RequiresDocument && !_hasOpenDocument())
387
- return (409, new { error = true, hasActiveDocument = false, message = "No active Revit document is open." });
388
-
389
392
  JsonElement args;
390
393
  try
391
394
  {
@@ -399,34 +402,81 @@ namespace RevitBridge
399
402
  return (400, new { error = true, message = $"Invalid JSON tool arguments: {ex.Message}" });
400
403
  }
401
404
 
402
- int timeoutMs = ResolveTimeoutMs(query);
403
-
404
- try
405
+ int timeoutMs = ResolveTimeoutMs(query);
406
+
407
+ if (query.TryGetValue("operation_id", out string? operationId))
408
+ {
409
+ try
410
+ {
411
+ var (entry, isNew) = _operations.Reserve(operationId, name, args, TimeSpan.FromMilliseconds(timeoutMs));
412
+ var completion = _operations.Wait(entry);
413
+ if (isNew) _ = CompleteTrackedOperationAsync(entry, tool, args, timeoutMs);
414
+ var reply = await completion;
415
+ return (reply.Status, reply.Response);
416
+ }
417
+ catch (ArgumentException error) { return (409, new { error = true, message = error.Message, operation_id = operationId }); }
418
+ catch (InvalidOperationException error) { return (503, new { error = true, message = error.Message, operation_id = operationId }); }
419
+ }
420
+ var result = await RunToolAsync(tool, args, timeoutMs, null);
421
+ return (result.Status, result.Response);
422
+ }
423
+
424
+ private async Task CompleteTrackedOperationAsync(OperationStore.Entry entry, ITool tool, JsonElement args, int timeoutMs)
425
+ {
426
+ var reply = await RunToolAsync(tool, args, timeoutMs, entry);
427
+ _operations.Complete(entry, new OperationStore.Reply(reply.Status, reply.Response, reply.Outcome));
428
+ }
429
+
430
+ private async Task<(int Status, object Response, string? Outcome)> RunToolAsync(ITool tool, JsonElement args, int timeoutMs, OperationStore.Entry? operation)
431
+ {
432
+ string name = tool.Name;
433
+ // Cached receipts remain accessible even when the active document closes.
434
+ if (tool.RequiresDocument && !_hasOpenDocument())
435
+ return (409, new { error = true, hasActiveDocument = false, message = "No active Revit document is open." }, null);
436
+
437
+ void StartOperation()
438
+ {
439
+ if (operation != null && !_operations.TryStart(operation)) throw new TimeoutException("Operation expired before execution; no tool action was performed.");
440
+ }
441
+
442
+ try
405
443
  {
406
444
  object? output = tool.RequiresDocument
407
445
  ? await _queue.RunAsync(uiApp =>
408
- {
409
- var document = uiApp.ActiveUIDocument?.Document ?? throw new NoActiveDocumentException();
410
- RevitBridge.Tools.DocumentGuard.CheckForTool(args, document, tool.Name);
411
- return tool.Execute(args, new ToolContext(document, uiApp));
446
+ {
447
+ StartOperation();
448
+ var document = uiApp.ActiveUIDocument?.Document ?? throw new NoActiveDocumentException();
449
+ RevitBridge.Tools.DocumentGuard.CheckForTool(args, document, tool.Name, tool.Write);
450
+ return tool.Execute(args, new ToolContext(document, uiApp));
412
451
  }, TimeSpan.FromMilliseconds(timeoutMs))
413
452
  // RequiresDocument = false tools never touch the Revit API, so they
414
453
  // run right here on the server task instead of the CommandQueue.
415
- : tool.Execute(args, new ToolContext(null, null));
416
-
417
- return (200, BuildToolResponse(name, output));
454
+ : ExecuteWithoutDocument();
455
+
456
+ try { return (200, BuildToolResponse(name, output), null); }
457
+ catch (Exception error)
458
+ {
459
+ return (500, new { error = true, toolName = name, result_unavailable = true,
460
+ message = "The tool finished, but its response could not be constructed. Model or other effects may already have occurred; inspect them before retrying. " + error.Message }, "result_unavailable");
461
+ }
462
+
463
+ object? ExecuteWithoutDocument()
464
+ {
465
+ StartOperation();
466
+ return tool.Execute(args, new ToolContext(null, null));
467
+ }
418
468
  }
419
469
  catch (NoActiveDocumentException ex)
420
470
  {
421
- return (409, new { error = true, hasActiveDocument = false, message = ex.Message });
471
+ return (409, new { error = true, hasActiveDocument = false, message = ex.Message }, null);
422
472
  }
423
473
  catch (ArgumentException ex)
424
474
  {
425
- return (400, new { error = true, toolName = name, message = ex.Message });
475
+ return (400, new { error = true, toolName = name, message = ex.Message }, null);
426
476
  }
427
477
  catch (Exception ex)
428
478
  {
429
- return (500, new { error = true, toolName = name, message = ex.Message });
479
+ return (500, new { error = true, toolName = name, message = ex.Message }, null);
430
480
  }
431
481
  }
432
482
 
@@ -436,9 +486,9 @@ namespace RevitBridge
436
486
  ? Math.Clamp(value, 1_000, 600_000)
437
487
  : 30_000;
438
488
 
439
- /// <summary>Complete bounded JSON for model context; the full payload always
440
- /// remains in details for the extension's saved-result retrieval. ToolOutput
441
- /// compact text is a display summary, not a substitute for requested data.</summary>
489
+ /// <summary>Complete bounded JSON for model context; the full payload always
490
+ /// remains in details for the extension's saved-result retrieval. ToolOutput
491
+ /// compact text is a display summary, not a substitute for requested data.</summary>
442
492
  private static object BuildToolResponse(string toolName, object? output)
443
493
  {
444
494
  object? payload = output;
@@ -449,11 +499,11 @@ namespace RevitBridge
449
499
  compact = toolOutput.CompactText;
450
500
  }
451
501
 
452
- string text = JsonSerializer.Serialize(payload);
502
+ string text = JsonSerializer.Serialize(payload);
453
503
  bool truncated = text.Length > MaxContentChars;
454
504
  if (truncated)
455
505
  {
456
- text = $"Result exceeds the {MaxContentChars}-character inline limit. The complete value is in details.payload; the Pi extension saves it locally and provides read_revit_result for bounded retrieval.";
506
+ text = $"Result exceeds the {MaxContentChars}-character inline limit. The complete value is in details.payload; the Pi extension saves it locally and provides read_revit_result for bounded retrieval.";
457
507
  }
458
508
 
459
509
  return new
@@ -461,7 +511,7 @@ namespace RevitBridge
461
511
  success = true,
462
512
  toolName,
463
513
  content = new[] { new { type = "text", text } },
464
- details = new { payload, summary = compact, contentTruncated = truncated },
514
+ details = new { payload, summary = compact, contentTruncated = truncated },
465
515
  isError = false,
466
516
  };
467
517
  }