pi-revit 0.2.10 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,40 @@ Format follows [Keep a Changelog](https://keepachangelog.com/); version headers
7
7
  Every published version gets an entry with **Added** / **Changed** / **Fixed** sections
8
8
  describing what the user will notice — not internal refactors.
9
9
 
10
+ ## [0.2.12] - 2026-07-22
11
+
12
+ ### Added
13
+ - New tool `open_view`: activates a view or sheet in the Revit UI — the equivalent of
14
+ double-clicking it in the Project Browser. Identify the target by `view_id` or by
15
+ `name` (view name, sheet number like "A-101", or "number - name"). Uses Revit's
16
+ queued `RequestViewChange`, which is explicitly legal from the bridge's ExternalEvent
17
+ context; the activation completes the instant the call returns. Ends the
18
+ "please double-click the sheet yourself" gap after sheet/view creation.
19
+
20
+ Requires redeploying the Revit add-in (`scripts\deploy.ps1` with Revit closed, then
21
+ restart Revit).
22
+
23
+ ## [0.2.11] - 2026-07-22
24
+
25
+ ### Fixed
26
+ - `search_api_docs`: signature queries now accept .NET type names and qualified names —
27
+ `Wall.Create(Document, Curve, ElementId, Boolean` and `...(System.String` match the
28
+ rendered `bool` / `string`, and `(Autodesk.Revit.DB.Document` matches `Document`.
29
+ Parameter types in the query are reduced exactly the way the index renders them
30
+ (namespace stripped, CLR name → C# keyword, case-insensitive).
31
+ - `search_api_docs`: Creation-factory calls resolve — `Document.Create.NewRoom(Level, UV`
32
+ finds `Document.NewRoom`, and `Document.Create.NewFamilyInstance` finds the
33
+ `ItemFactoryBase` overloads (factory members documented on a base class). A note in the
34
+ result explains the rewrite. Applies only to the literal `Document.Create.` /
35
+ `Application.Create.` prefixes; ordinary members like `Wall.Create` are untouched, and
36
+ nonsense like `Document.Create.Banana` still honestly returns nothing.
37
+
38
+ Both were observed live: pi stumbled on these five times across two modeling sessions.
39
+ The benchmark gained six regression probes for them.
40
+
41
+ Requires redeploying the Revit add-in (`scripts\deploy.ps1` with Revit closed, then
42
+ restart Revit).
43
+
10
44
  ## [0.2.10] - 2026-07-22
11
45
 
12
46
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-revit",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "description": "Native Pi connector for Autodesk Revit. Run npx.cmd -y pi-revit for the full Windows install.",
5
5
  "author": "Ahmad Altahlawi",
6
6
  "license": "MIT",
@@ -80,7 +80,8 @@ class Member:
80
80
  @property
81
81
  def short_sig_prefix(s):
82
82
  shorts = [short_type(p) for p in s.params]
83
- return f"{s.typename}.{s.member}(" + ", ".join(shorts)
83
+ head = s.typename if s.member == "#ctor" else f"{s.typename}.{s.member}"
84
+ return f"{head}(" + ", ".join(shorts)
84
85
 
85
86
  def text_of(el):
86
87
  if el is None: return None
@@ -161,7 +162,8 @@ for m in sampleB:
161
162
  matches, _ = search(canonical)
162
163
  B["n"] += 1
163
164
  sigs = [x.get("signature","") for x in matches]
164
- ok = bool(sigs and sigs[0].startswith(f"{m.typename}.{m.member}("))
165
+ want = f"new {m.typename}(" if m.member == "#ctor" else f"{m.typename}.{m.member}("
166
+ ok = bool(sigs and sigs[0].startswith(want))
165
167
  B["top1_sig"] += ok
166
168
  variants = [canonical.replace(", ", ","),
167
169
  canonical.replace(", ", " , "),
@@ -200,6 +202,13 @@ probe("case-insensitive", "wall.create(document, curve")
200
202
  probe("Double vs double", "UnitUtils.ConvertToInternalUnits(Double, ForgeTypeId")
201
203
  probe("trailing garbage brackets", "Wall.Create(Document, Curve]]", True) # observational
202
204
  probe("double periods", "Wall..Create", True) # observational
205
+ # 0.2.11: CLR alias + namespace reduction in signature queries, Creation-factory rewrite
206
+ probe("CLR alias Boolean", "Wall.Create(Document, Curve, ElementId, Boolean")
207
+ probe("qualified param type", "Wall.Create(Autodesk.Revit.DB.Document, Curve")
208
+ probe("System-qualified alias", "ParameterFilterElement.Create(Document, System.String")
209
+ probe("factory rewrite", "Document.Create.NewRoom(Level, UV")
210
+ probe("factory inherited member", "Document.Create.NewFamilyInstance")
211
+ probe("factory honesty control", "Document.Create.Banana", expect_nonzero=False)
203
212
  print("C done", flush=True)
204
213
  R["C complex-syntax"] = C
205
214
 
@@ -1,52 +1,53 @@
1
- ---
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, set_parameters, search_api_docs, execute_csharp, capture_view, export_documents, get_model_health). Use when the user asks about the Revit project, its elements, parameters, selection, or wants to change, script, capture, or export the model.
4
- ---
5
-
6
- # Revit
7
-
8
- Work with the live Revit model. The tools call a headless bridge add-in inside Revit (2025, 2026, or 2027); Revit must be running with a project open (only `ping` and `search_api_docs` work without a document).
9
-
10
- ## Tool selection
11
-
12
- | Task | Tool |
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` |
17
- | 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` |
19
- | Read or change the user's selection; zoom; temporary isolate | `manage_selection` |
20
- | Write parameter values; rename anything (levels, views, sheets, types) | `set_parameters` |
21
- | Look up Revit API classes/members/signatures | `search_api_docs` |
22
- | Everything else (create, delete, move, views, sheets, tagging, ...) | `execute_csharp` |
23
- | PNG snapshot of a view (visual QA) | `capture_view` (advanced) |
24
- | PDF/DWG/PNG/IFC file export | `export_documents` (advanced) |
25
- | Warnings / model quality audit | `get_model_health` (advanced) |
26
-
27
- Workflow guidance:
28
-
29
- - Call `get_model_overview` first when starting work on an unfamiliar model — one call returns project metadata, units, levels, grids, and category counts.
30
- - `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.
31
- - The selection pipeline is `get_elements` -> ids -> `manage_selection` (action `set`); there is no inline filter on selection.
32
- - `set_parameters` is the home for bulk parameter writes AND renames (the `Name` parameter covers levels, views, sheets, types). One transaction per batch; per-element failures are reported. Pass `expected_document` (the model title) when several models are open or the session is long — it makes the write fail cleanly instead of landing in a different active document.
33
- - Parameter display names are LOCALIZED: in a non-English Revit UI, `Mark` is `Kennzeichen` (German), `マーク` (Japanese), etc. 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.
34
- - 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.
35
- - `export_documents` files its output under `Documents\pi-revit\Models\<model title>\exports` automatically when `output_dir` is omitted — keyed to the exported document, so it lands right even across many models. Pass `output_dir` only when the user names a different target.
36
-
37
- ## execute_csharp playbook
38
-
39
- - Globals: `doc` (Document), `uidoc` (UIDocument), `uiapp` (UIApplication), and `Dump(value)` to record intermediates into the result's `dumps[]`.
40
- - The transaction is automatic: the whole script runs inside ONE backend-owned transaction — committed on success, rolled back on any exception. Do not open your own `Transaction` (sub-transactions are fine).
41
- - Scripts must be fully synchronous: `await`/`async` is rejected at compile time; never block on `Task.Result`/`.Wait()`.
42
- - 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}`).
43
- - Lengths are internal units (decimal feet) — convert with `UnitUtils.ConvertToInternalUnits`/`ConvertFromInternalUnits`.
44
- - 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; modal dialogs are auto-dismissed and reported in `suppressedDialogs`.
45
- - `capture_view` returns a `filePath` to a temp PNG, never image data — open it with the read tool to actually see it.
46
-
47
- ## Failure modes
48
-
49
- - **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`.
50
- - **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.
51
- - **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.
52
- - **Cancelled**: same caveat — the bridge cannot abort queued or running work, so verify model state before retrying a write tool.
1
+ ---
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). Use when the user asks about the Revit project, its elements, parameters, selection, or wants to change, script, capture, or export the model.
4
+ ---
5
+
6
+ # Revit
7
+
8
+ Work with the live Revit model. The tools call a headless bridge add-in inside Revit (2025, 2026, or 2027); Revit must be running with a project open (only `ping` and `search_api_docs` work without a document).
9
+
10
+ ## Tool selection
11
+
12
+ | Task | Tool |
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` |
17
+ | 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` |
19
+ | Read or change the user's selection; zoom; temporary isolate | `manage_selection` |
20
+ | 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` |
22
+ | Look up Revit API classes/members/signatures | `search_api_docs` |
23
+ | Everything else (create, delete, move, views, sheets, tagging, ...) | `execute_csharp` |
24
+ | PNG snapshot of a view (visual QA) | `capture_view` (advanced) |
25
+ | PDF/DWG/PNG/IFC file export | `export_documents` (advanced) |
26
+ | Warnings / model quality audit | `get_model_health` (advanced) |
27
+
28
+ Workflow guidance:
29
+
30
+ - Call `get_model_overview` first when starting work on an unfamiliar model — one call returns project metadata, units, levels, grids, and category counts.
31
+ - `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.
32
+ - The selection pipeline is `get_elements` -> ids -> `manage_selection` (action `set`); there is no inline filter on selection.
33
+ - `set_parameters` is the home for bulk parameter writes AND renames (the `Name` parameter covers levels, views, sheets, types). One transaction per batch; per-element failures are reported. Pass `expected_document` (the model title) when several models are open or the session is long — it makes the write fail cleanly instead of landing in a different active document.
34
+ - Parameter display names are LOCALIZED: in a non-English Revit UI, `Mark` is `Kennzeichen` (German), `マーク` (Japanese), etc. 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.
35
+ - 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.
36
+ - `export_documents` files its output under `Documents\pi-revit\Models\<model title>\exports` automatically when `output_dir` is omitted — keyed to the exported document, so it lands right even across many models. Pass `output_dir` only when the user names a different target.
37
+
38
+ ## execute_csharp playbook
39
+
40
+ - Globals: `doc` (Document), `uidoc` (UIDocument), `uiapp` (UIApplication), and `Dump(value)` to record intermediates into the result's `dumps[]`.
41
+ - The transaction is automatic: the whole script runs inside ONE backend-owned transaction — committed on success, rolled back on any exception. Do not open your own `Transaction` (sub-transactions are fine).
42
+ - Scripts must be fully synchronous: `await`/`async` is rejected at compile time; never block on `Task.Result`/`.Wait()`.
43
+ - 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}`).
44
+ - Lengths are internal units (decimal feet) — convert with `UnitUtils.ConvertToInternalUnits`/`ConvertFromInternalUnits`.
45
+ - 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; modal dialogs are auto-dismissed and reported in `suppressedDialogs`.
46
+ - `capture_view` returns a `filePath` to a temp PNG, never image data — open it with the read tool to actually see it.
47
+
48
+ ## Failure modes
49
+
50
+ - **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`.
51
+ - **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.
52
+ - **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.
53
+ - **Cancelled**: same caveat — the bridge cannot abort queued or running work, so verify model state before retrying a write tool.
@@ -71,6 +71,7 @@ namespace RevitBridge
71
71
  registry.Add(new GetElementTypes());
72
72
  registry.Add(new GetElementDetails());
73
73
  registry.Add(new ManageSelection());
74
+ registry.Add(new OpenView());
74
75
  registry.Add(new SetParameters());
75
76
  registry.Add(new CaptureView());
76
77
  registry.Add(new ExportDocuments());
@@ -0,0 +1,103 @@
1
+ using System.Text.Json;
2
+ using Autodesk.Revit.DB;
3
+
4
+ namespace RevitBridge.Tools
5
+ {
6
+ /// <summary>
7
+ /// Activates a view or sheet in the Revit UI — the API equivalent of double-clicking
8
+ /// it in the Project Browser. Uses UIDocument.RequestViewChange, which is explicitly
9
+ /// permitted from an ExternalEvent callback (where all bridge tools run) as long as no
10
+ /// transaction is open; the tool opens none. The activation is asynchronous by design:
11
+ /// Revit performs it the moment control returns from the bridge call.
12
+ /// </summary>
13
+ internal sealed class OpenView : ITool
14
+ {
15
+ public string Name => "open_view";
16
+ public string Label => "Open View";
17
+ public string Description => "Activate a view or sheet in the Revit UI, like double-clicking it in the Project Browser. Identify the target by view_id (an id from get_elements category Views/Sheets or from view/sheet creation) or by name (exact view name, sheet number like 'A-101', or sheet number - name; case-insensitive). Activation is queued and completes the instant this call returns control to Revit — a capture_view immediately after may still show the previous active view. View templates and internal views cannot be opened.";
18
+ public bool Write => false;
19
+
20
+ public object ParametersSchema => new
21
+ {
22
+ type = "object",
23
+ properties = new
24
+ {
25
+ view_id = new { type = "integer", description = "Id of the view or sheet to activate." },
26
+ name = new { type = "string", description = "Exact view name, sheet number, or sheet 'number - name' (case-insensitive). Used when view_id is absent." },
27
+ },
28
+ };
29
+
30
+ public string? PromptSnippet => "Activate a Revit view or sheet in the UI by id or name (like double-clicking it in the Project Browser).";
31
+ public IReadOnlyList<string>? PromptGuidelines => new[]
32
+ {
33
+ "Use open_view to put a view or sheet on the user's screen (e.g. after creating a sheet); activation completes right after the call, so capture_view in the SAME call batch may still show the old view.",
34
+ };
35
+
36
+ public object? Execute(JsonElement args, ToolContext context)
37
+ {
38
+ var doc = context.Document ?? throw new NoActiveDocumentException();
39
+ var uiapp = context.UIApplication ?? throw new NoActiveDocumentException();
40
+ var uidoc = uiapp.ActiveUIDocument ?? throw new NoActiveDocumentException();
41
+
42
+ long? viewId = JsonArgs.GetLong(args, "view_id");
43
+ string? name = JsonArgs.GetString(args, "name");
44
+ if (viewId is null && string.IsNullOrWhiteSpace(name))
45
+ throw new ArgumentException("Pass view_id or name to identify the view or sheet to open.");
46
+
47
+ View view = viewId is { } id ? ResolveById(doc, id) : ResolveByName(doc, name!.Trim());
48
+
49
+ try
50
+ {
51
+ uidoc.RequestViewChange(view);
52
+ }
53
+ catch (Autodesk.Revit.Exceptions.ArgumentException ex)
54
+ {
55
+ throw new ArgumentException($"View '{view.Name}' (id {view.Id.Value}) cannot be activated: {ex.Message}");
56
+ }
57
+ catch (Autodesk.Revit.Exceptions.InvalidOperationException ex)
58
+ {
59
+ throw new InvalidOperationException(
60
+ $"Revit refused to queue the view change to '{view.Name}': {ex.Message} "
61
+ + "The document may be mid-edit; retry after the current operation finishes.");
62
+ }
63
+
64
+ string kind = view is ViewSheet sheet ? $"sheet {sheet.SheetNumber}" : view.ViewType.ToString();
65
+ return new ToolOutput(new
66
+ {
67
+ requestedViewId = view.Id.Value,
68
+ viewName = view.Name,
69
+ viewType = view.ViewType.ToString(),
70
+ }, $"Queued activation of {kind} '{view.Name}' (id {view.Id.Value}); it opens as soon as Revit regains control.");
71
+ }
72
+
73
+ private static View ResolveById(Document doc, long id)
74
+ {
75
+ var view = doc.GetElement(new ElementId(id)) as View
76
+ ?? throw new ArgumentException($"Element {id} is not a view or sheet (or does not exist). Find view ids with get_elements, category 'Views' or 'Sheets'.");
77
+ if (view.IsTemplate)
78
+ throw new ArgumentException($"View '{view.Name}' (id {id}) is a view template; templates cannot be opened.");
79
+ return view;
80
+ }
81
+
82
+ private static View ResolveByName(Document doc, string name)
83
+ {
84
+ var candidates = new FilteredElementCollector(doc)
85
+ .OfClass(typeof(View))
86
+ .Cast<View>()
87
+ .Where(view => !view.IsTemplate)
88
+ .Where(view =>
89
+ string.Equals(view.Name, name, StringComparison.OrdinalIgnoreCase)
90
+ || (view is ViewSheet sheet
91
+ && (string.Equals(sheet.SheetNumber, name, StringComparison.OrdinalIgnoreCase)
92
+ || string.Equals($"{sheet.SheetNumber} - {sheet.Name}", name, StringComparison.OrdinalIgnoreCase))))
93
+ .ToList();
94
+
95
+ if (candidates.Count == 1)
96
+ return candidates[0];
97
+ if (candidates.Count == 0)
98
+ throw new ArgumentException($"No view or sheet named '{name}'. Find the exact name or sheet number with get_elements, category 'Views' or 'Sheets'.");
99
+ string list = string.Join("; ", candidates.Take(8).Select(view => $"'{view.Name}' (id {view.Id.Value}, {view.ViewType})"));
100
+ throw new ArgumentException($"'{name}' matches {candidates.Count} views: {list}. Pass view_id to disambiguate.");
101
+ }
102
+ }
103
+ }
@@ -81,7 +81,7 @@ namespace RevitBridge.Tools
81
81
  if (index.Members.Count == 0)
82
82
  throw new InvalidOperationException("The Revit API documentation index is empty. " + string.Join(" ", index.Warnings));
83
83
 
84
- var (top, total) = Search(index, query, kindFilter, maxResults);
84
+ var (top, total, rewriteNote) = Search(index, query, kindFilter, maxResults);
85
85
 
86
86
  var matches = new List<Dictionary<string, object?>>(top.Count);
87
87
  for (int i = 0; i < top.Count; i++)
@@ -116,22 +116,56 @@ namespace RevitBridge.Tools
116
116
  indexedMembers = index.Members.Count,
117
117
  sources = index.SourceFiles,
118
118
  warnings = index.Warnings.Count > 0 ? index.Warnings : null,
119
- }, BuildMarkdown(query, top, total, index));
119
+ }, BuildMarkdown(query, top, total, index) + (rewriteNote is null ? string.Empty : $"\nNote: {rewriteNote}"));
120
120
  }
121
121
 
122
122
  // ----------------------------------------------------------------- search
123
123
 
124
124
  private static readonly char[] WordSeparators = { '.', ' ', '_', '(', ')', ',', ':', '-', '/' };
125
125
 
126
- private static (List<ApiMember> Top, int Total) Search(DocIndex index, string query, char? kindFilter, int maxResults)
126
+ private static (List<ApiMember> Top, int Total, string? RewriteNote) Search(DocIndex index, string query, char? kindFilter, int maxResults)
127
127
  {
128
128
  // Signatures are rendered exactly one way ("Name(Type, Type)"): normalize the
129
129
  // query's spacing around commas and parentheses so 'Wall.Create(Document,Curve'
130
130
  // and 'Wall.Create( Document, Curve' hit the same rendered text instead of
131
- // failing on typography.
132
- string q = query.ToLowerInvariant();
131
+ // failing on typography. Parameter types are additionally reduced the same way
132
+ // the renderer reduces them (namespaces stripped, CLR names -> C# keywords), so
133
+ // 'Wall.Create(Document, Curve, ElementId, Boolean' and
134
+ // '...(System.String' match the rendered 'bool' / 'string'.
135
+ string q = NormalizeSignatureQuery(query).ToLowerInvariant();
133
136
  q = Regex.Replace(q, @"\s*,\s*", ", ");
134
137
  q = Regex.Replace(q, @"\(\s+", "(");
138
+
139
+ // The Creation-factory pattern: code says doc.Create.NewRoom(...) but the docs
140
+ // live on Autodesk.Revit.Creation.Document (rendered 'Document.NewRoom') or a
141
+ // base class like ItemFactoryBase. Try the factory rewrite, then the bare
142
+ // member, before giving up. Deterministic rewrites of an exact idiom — never
143
+ // applied unless the literal 'document.create.' / 'application.create.' prefix
144
+ // is present, so ordinary names like Wall.Create are untouched.
145
+ var candidates = new List<(string Query, string? Note)> { (q, null) };
146
+ foreach (string factory in new[] { "document.create.", "application.create." })
147
+ {
148
+ if (!q.StartsWith(factory, StringComparison.Ordinal))
149
+ continue;
150
+ string owner = factory[..(factory.IndexOf('.') + 1)]; // "document."
151
+ string rest = q[factory.Length..];
152
+ candidates.Add((owner + rest, $"'{owner}Create.*' is the Creation factory — matched as '{owner}{rest}'."));
153
+ string bareMember = rest.Split('(')[0];
154
+ if (bareMember.Length > 0)
155
+ candidates.Add((bareMember, $"'{owner}Create.{bareMember}' is a Creation-factory call; its docs live on the factory class (e.g. ItemFactoryBase) — matched by member name '{bareMember}'."));
156
+ }
157
+
158
+ foreach (var (candidate, note) in candidates)
159
+ {
160
+ var (top, total) = RunScoring(index, candidate, kindFilter, maxResults);
161
+ if (total > 0)
162
+ return (top, total, note);
163
+ }
164
+ return (new List<ApiMember>(), 0, null);
165
+ }
166
+
167
+ private static (List<ApiMember> Top, int Total) RunScoring(DocIndex index, string q, char? kindFilter, int maxResults)
168
+ {
135
169
  string[] words = q.Split(WordSeparators, StringSplitOptions.RemoveEmptyEntries);
136
170
 
137
171
  var scored = new List<(ApiMember Member, int Score)>();
@@ -155,6 +189,65 @@ namespace RevitBridge.Tools
155
189
  return (top, scored.Count);
156
190
  }
157
191
 
192
+ /// <summary>Reduces the parameter part of a signature query exactly the way the
193
+ /// renderer reduces signatures: each identifier keeps only its last dot-segment
194
+ /// and CLR primitive names map to C# keywords (case-insensitively — queries say
195
+ /// 'Boolean' or 'boolean'; signatures render 'bool'). The member path before the
196
+ /// first '(' is left untouched.</summary>
197
+ private static string NormalizeSignatureQuery(string query)
198
+ {
199
+ int paren = query.IndexOf('(');
200
+ if (paren < 0)
201
+ return query;
202
+
203
+ string tail = query[(paren + 1)..];
204
+ var result = new StringBuilder(tail.Length);
205
+ int i = 0;
206
+ while (i < tail.Length)
207
+ {
208
+ char c = tail[i];
209
+ if (char.IsLetter(c) || c == '_')
210
+ {
211
+ int start = i, lastDot = -1;
212
+ while (i < tail.Length && (char.IsLetterOrDigit(tail[i]) || tail[i] is '_' or '.'))
213
+ {
214
+ if (tail[i] == '.')
215
+ lastDot = i;
216
+ i++;
217
+ }
218
+ string identifier = tail[(lastDot >= 0 ? lastDot + 1 : start)..i];
219
+ result.Append(QueryTypeAliases.TryGetValue(identifier, out string? keyword) ? keyword : identifier);
220
+ }
221
+ else
222
+ {
223
+ result.Append(c);
224
+ i++;
225
+ }
226
+ }
227
+ return query[..(paren + 1)] + result;
228
+ }
229
+
230
+ /// <summary>Case-insensitive inverse of MapTypeKeyword for query text.</summary>
231
+ private static readonly Dictionary<string, string> QueryTypeAliases = new(StringComparer.OrdinalIgnoreCase)
232
+ {
233
+ ["String"] = "string",
234
+ ["Boolean"] = "bool",
235
+ ["Int32"] = "int",
236
+ ["Int64"] = "long",
237
+ ["Int16"] = "short",
238
+ ["Double"] = "double",
239
+ ["Single"] = "float",
240
+ ["Object"] = "object",
241
+ ["Void"] = "void",
242
+ ["Byte"] = "byte",
243
+ ["SByte"] = "sbyte",
244
+ ["Char"] = "char",
245
+ ["Decimal"] = "decimal",
246
+ ["UInt16"] = "ushort",
247
+ ["UInt32"] = "uint",
248
+ ["UInt64"] = "ulong",
249
+ };
250
+
158
251
  private static int Score(ApiMember member, string q, string[] words)
159
252
  {
160
253
  // A query with a parenthesis targets a specific overload by signature,