using System.Text.Json; using Autodesk.Revit.DB; namespace RevitBridge.Tools { /// /// Activates a view or sheet in the Revit UI — the API equivalent of double-clicking /// it in the Project Browser. Uses UIDocument.RequestViewChange, which is explicitly /// permitted from an ExternalEvent callback (where all bridge tools run) as long as no /// transaction is open; the tool opens none. The activation is asynchronous by design: /// Revit performs it the moment control returns from the bridge call. /// internal sealed class OpenView : ITool { public string Name => "open_view"; public string Label => "Open View"; 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."; public bool Write => false; public object ParametersSchema => new { type = "object", properties = new { view_id = new { type = "integer", description = "Id of the view or sheet to activate." }, name = new { type = "string", description = "Exact view name, sheet number, or sheet 'number - name' (case-insensitive). Used when view_id is absent." }, }, }; public string? PromptSnippet => "Activate a Revit view or sheet in the UI by id or name (like double-clicking it in the Project Browser)."; public IReadOnlyList? PromptGuidelines => new[] { "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.", }; public object? Execute(JsonElement args, ToolContext context) { var doc = context.Document ?? throw new NoActiveDocumentException(); var uiapp = context.UIApplication ?? throw new NoActiveDocumentException(); var uidoc = uiapp.ActiveUIDocument ?? throw new NoActiveDocumentException(); long? viewId = JsonArgs.GetLong(args, "view_id"); string? name = JsonArgs.GetString(args, "name"); if (viewId is null && string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Pass view_id or name to identify the view or sheet to open."); View view = viewId is { } id ? ResolveById(doc, id) : ResolveByName(doc, name!.Trim()); try { uidoc.RequestViewChange(view); } catch (Autodesk.Revit.Exceptions.ArgumentException ex) { throw new ArgumentException($"View '{view.Name}' (id {view.Id.Value}) cannot be activated: {ex.Message}"); } catch (Autodesk.Revit.Exceptions.InvalidOperationException ex) { throw new InvalidOperationException( $"Revit refused to queue the view change to '{view.Name}': {ex.Message} " + "The document may be mid-edit; retry after the current operation finishes."); } string kind = view is ViewSheet sheet ? $"sheet {sheet.SheetNumber}" : view.ViewType.ToString(); return new ToolOutput(new { requestedViewId = view.Id.Value, viewName = view.Name, viewType = view.ViewType.ToString(), }, $"Queued activation of {kind} '{view.Name}' (id {view.Id.Value}); it opens as soon as Revit regains control."); } private static View ResolveById(Document doc, long id) { var view = doc.GetElement(new ElementId(id)) as View ?? 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'."); if (view.IsTemplate) throw new ArgumentException($"View '{view.Name}' (id {id}) is a view template; templates cannot be opened."); return view; } private static View ResolveByName(Document doc, string name) { var candidates = new FilteredElementCollector(doc) .OfClass(typeof(View)) .Cast() .Where(view => !view.IsTemplate) .Where(view => string.Equals(view.Name, name, StringComparison.OrdinalIgnoreCase) || (view is ViewSheet sheet && (string.Equals(sheet.SheetNumber, name, StringComparison.OrdinalIgnoreCase) || string.Equals($"{sheet.SheetNumber} - {sheet.Name}", name, StringComparison.OrdinalIgnoreCase)))) .ToList(); if (candidates.Count == 1) return candidates[0]; if (candidates.Count == 0) throw new ArgumentException($"No view or sheet named '{name}'. Find the exact name or sheet number with get_elements, category 'Views' or 'Sheets'."); string list = string.Join("; ", candidates.Take(8).Select(view => $"'{view.Name}' (id {view.Id.Value}, {view.ViewType})")); throw new ArgumentException($"'{name}' matches {candidates.Count} views: {list}. Pass view_id to disambiguate."); } } }