From bc481d437890606f321d1a301616ae77f327b3cb Mon Sep 17 00:00:00 2001 From: codex Date: Tue, 4 Aug 2026 09:37:24 +0300 Subject: [PATCH] fix: execute automation commands in AutoCAD command context --- .../FirstLimitedApplyAutomation.cs | 31 ++++++++++++------- .../PRecAnnot.Core.Tests/FoundationTests.cs | 11 ++++--- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs index df7853e..c3be6fc 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs @@ -264,23 +264,30 @@ internal sealed class FirstLimitedApplyAutomationCoordinator catch (System.Exception ex) { FailFromCallback("ContextVerification", ex, "AUTOMATION_CONTEXT_FAILED"); } } - private void Queue(string command) - { - if (IsTerminal || document is null) return; - transitions.Add("CommandQueued:" + command); - document.SendStringToExecute(command + "\n", true, true, false); - } + private void Queue(string command) => _ = DispatchCommandAsync(command, false); private void QueueReadinessBarrier() + { + _ = DispatchCommandAsync("PHP_REC_AUTOMATION_READY", true); + } + private async Task DispatchCommandAsync(string command, bool readiness) { if (IsTerminal || document is null) return; - var active = AcadApplication.DocumentManager.MdiActiveDocument; - if (active is null || !string.Equals(active.Name, document.Name, StringComparison.OrdinalIgnoreCase)) + var expected = document; + transitions.Add(readiness ? "ReadinessCommandDispatchRequested" : "CommandDispatchRequested:" + command); + try { - Complete(false, "AUTOMATION_READINESS_DOCUMENT_NOT_ACTIVE"); - return; + await AcadApplication.DocumentManager.ExecuteInCommandContextAsync(async _ => + { + transitions.Add("CommandContextEntered:" + command); + var active = AcadApplication.DocumentManager.MdiActiveDocument; + if (active is null || !string.Equals(active.Name, expected.Name, StringComparison.OrdinalIgnoreCase)) + throw new InvalidOperationException("AUTOMATION_COMMAND_DOCUMENT_NOT_ACTIVE"); + transitions.Add(readiness ? "ReadinessBarrierQueued" : "CommandQueued:" + command); + await active.Editor.CommandAsync(command); + transitions.Add("CommandInvocationReturned:" + command); + }, null); } - transitions.Add("ReadinessBarrierQueued"); - document.SendStringToExecute("PHP_REC_AUTOMATION_READY\n", true, false, false); + catch (System.Exception exception) { FailFromCallback("CommandDispatcher", exception, "AUTOMATION_COMMAND_CONTEXT_FAILED"); } } private void OnCommandEnded(object sender, CommandEventArgs e) { diff --git a/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs b/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs index 69a0435..c97c598 100644 --- a/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs +++ b/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs @@ -113,11 +113,12 @@ public sealed class FoundationTests [Fact] public void QueueReadinessBarrier_RequiresActiveFixtureAndUsesSafeDispatch() { var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs")); - var barrier = source[source.IndexOf("private void QueueReadinessBarrier", StringComparison.Ordinal)..source.IndexOf("private void OnCommandEnded", StringComparison.Ordinal)]; - Assert.Contains("MdiActiveDocument", barrier); - Assert.Contains("AUTOMATION_READINESS_DOCUMENT_NOT_ACTIVE", barrier); - Assert.Contains("PHP_REC_AUTOMATION_READY\\n\", true, false, false", barrier); - Assert.True(barrier.IndexOf("AUTOMATION_READINESS_DOCUMENT_NOT_ACTIVE", StringComparison.Ordinal) < barrier.IndexOf("SendStringToExecute", StringComparison.Ordinal)); + var dispatcher = source[source.IndexOf("private async Task DispatchCommandAsync", StringComparison.Ordinal)..source.IndexOf("private void OnCommandEnded", StringComparison.Ordinal)]; + Assert.Contains("ExecuteInCommandContextAsync", dispatcher); + Assert.Contains("MdiActiveDocument", dispatcher); + Assert.Contains("AUTOMATION_COMMAND_DOCUMENT_NOT_ACTIVE", dispatcher); + Assert.Contains("Editor.CommandAsync", dispatcher); + Assert.DoesNotContain("SendStringToExecute", dispatcher); } [Fact] public void AutomationControlledExit_QueuesNativeQuitAfterPublicCommand()