fix: retain prepared Ortho context for script PXP-OA

This commit is contained in:
codex
2026-08-04 14:25:44 +03:00
parent 3cecee4777
commit e707456ab6
3 changed files with 37 additions and 3 deletions
@@ -77,7 +77,9 @@ public sealed class EntryPoint : IExtensionApplication
// document lags behind MDI activation. Bind the planner to the // document lags behind MDI activation. Bind the planner to the
// command document captured above, which is also the observer's // command document captured above, which is also the observer's
// fixture identity. // fixture identity.
var context = new PlantRuntimeContextReader().ReadCurrentContext(document); var context = ScriptDrivenFirstApplyAutomation.TryGetPreparedContext(document, out var scriptContext)
? scriptContext
: new PlantRuntimeContextReader().ReadCurrentContext(document);
var details = new Dictionary<string, object?> var details = new Dictionary<string, object?>
{ {
["ExecutionMode"] = mode.ToString(), ["ExecutionMode"] = mode.ToString(),
@@ -113,7 +115,8 @@ public sealed class EntryPoint : IExtensionApplication
var applyPermit = (mode == PRecAnnotExecutionMode.Apply || hasLimitedApplyPermit) && document is not null var applyPermit = (mode == PRecAnnotExecutionMode.Apply || hasLimitedApplyPermit) && document is not null
? FirstLimitedApplyPermitReader.ReadFor(document) ? FirstLimitedApplyPermitReader.ReadFor(document)
: null; : null;
var automationTarget = FirstLimitedApplyAutomationCoordinator.SelectedTargetSnapshotId; var automationTarget = ScriptDrivenFirstApplyAutomation.SelectedTargetSnapshotId
?? FirstLimitedApplyAutomationCoordinator.SelectedTargetSnapshotId;
var limitedTarget = applyPermit?.TargetSnapshotId ?? automationTarget; var limitedTarget = applyPermit?.TargetSnapshotId ?? automationTarget;
if (mode == PRecAnnotExecutionMode.Apply && context.Viewports.Count != 1) if (mode == PRecAnnotExecutionMode.Apply && context.Viewports.Count != 1)
throw new InvalidOperationException("APPLY_REQUIRES_EXACTLY_ONE_VIEWPORT"); throw new InvalidOperationException("APPLY_REQUIRES_EXACTLY_ONE_VIEWPORT");
@@ -6,6 +6,7 @@ using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime; using Autodesk.AutoCAD.Runtime;
using Autodesk.ProcessPower.PlantInstance; using Autodesk.ProcessPower.PlantInstance;
using Autodesk.ProcessPower.ProjectManager; using Autodesk.ProcessPower.ProjectManager;
using PRecAnnot.Abstractions;
using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application; using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application;
namespace PRecAnnot.Plant3D2026; namespace PRecAnnot.Plant3D2026;
@@ -26,6 +27,11 @@ internal static class ScriptDrivenFirstApplyAutomation
private static readonly List<AutomationSentinelEvidence> sentinels = []; private static readonly List<AutomationSentinelEvidence> sentinels = [];
private static readonly List<AutomationCommandCorrelationEvidence> correlations = []; private static readonly List<AutomationCommandCorrelationEvidence> correlations = [];
private static PxpOaDiscoveryEvidence? discovery; private static PxpOaDiscoveryEvidence? discovery;
// Plant's implicit current-document lookup can lag between script commands.
// PREPARE has already proved this immutable context belongs to the live fixture;
// retain it for the two immediately following public PXP-OA commands only.
private static PlantRuntimeContext? preparedContext;
private static string? selectedTargetSnapshotId;
private static bool prepared; private static bool prepared;
private static bool terminal; private static bool terminal;
@@ -53,6 +59,12 @@ internal static class ScriptDrivenFirstApplyAutomation
var context = new PlantRuntimeContextReader().ReadCurrentContext(fixture); var context = new PlantRuntimeContextReader().ReadCurrentContext(fixture);
if (!context.HasPlantProject || !context.IsOrthoDrawing || context.Viewports.Count != 1) if (!context.HasPlantProject || !context.IsOrthoDrawing || context.Viewports.Count != 1)
throw new InvalidOperationException("AUTOMATION_ORTHO_CONTEXT_INVALID"); throw new InvalidOperationException("AUTOMATION_ORTHO_CONTEXT_INVALID");
var candidates = new PlantObjectReader().ReadObjects(context.Viewports.Single(), CancellationToken.None)
.Where(x => string.Equals(x.ObjectClass, "Pipe", StringComparison.OrdinalIgnoreCase) && x.MembershipStatus == ViewMembershipStatus.Resolved && x.Geometry.PaperPosition is not null)
.Select(x => x.SnapshotId).OrderBy(x => x, StringComparer.Ordinal).ToArray();
if (candidates.Length == 0) throw new InvalidOperationException("AUTOMATION_NO_ELIGIBLE_CANDIDATE");
preparedContext = context;
selectedTargetSnapshotId = candidates[0];
dbmodBaseline = Dbmod(); dbmodBaseline = Dbmod();
baseline = Counts(database); baseline = Counts(database);
prepared = true; prepared = true;
@@ -81,6 +93,18 @@ internal static class ScriptDrivenFirstApplyAutomation
runs[^1] = last with { PlanId = planId, Candidates = candidates, View = view, Create = create, Skip = skip, SourceRows = sourceRows, MembershipResolved = membershipResolved }; runs[^1] = last with { PlanId = planId, Candidates = candidates, View = view, Create = create, Skip = skip, SourceRows = sourceRows, MembershipResolved = membershipResolved };
} }
internal static bool TryGetPreparedContext(Document? commandDocument, out PlantRuntimeContext context)
{
context = null!;
if (!prepared || terminal || fixture is null || preparedContext is null || commandDocument is null || config is null) return false;
if (!SamePath(commandDocument.Name, config.OrthoDrawingPath) || !SamePath(fixture.Name, config.OrthoDrawingPath)) return false;
if (commandDocument.Database is null || database is null || !SamePath(commandDocument.Database.Filename, database.Filename)) return false;
context = preparedContext;
return true;
}
internal static string? SelectedTargetSnapshotId => prepared && !terminal ? selectedTargetSnapshotId : null;
internal static void FinalizePreview() internal static void FinalizePreview()
{ {
try try
@@ -149,7 +173,12 @@ internal static class ScriptDrivenFirstApplyAutomation
} }
private static void Guard(Action action) { try { if (!terminal) action(); } catch (System.Exception ex) { Fail("AUTOMATION_EVENT_HANDLER_FAILED", ex); } } private static void Guard(Action action) { try { if (!terminal) action(); } catch (System.Exception ex) { Fail("AUTOMATION_EVENT_HANDLER_FAILED", ex); } }
private static void Fail(string code, System.Exception exception) { if (terminal) return; terminal = true; transitions.Add("Failed:" + code); try { Write(false, code + ":" + exception); } finally { Unsubscribe(); } } private static void Fail(string code, System.Exception exception) { if (terminal) return; terminal = true; transitions.Add("Failed:" + code); try { Write(false, code + ":" + exception); } finally { Unsubscribe(); } }
private static void Unsubscribe() { if (fixture is null) return; try { fixture.CommandWillStart -= OnWillStart; fixture.CommandEnded -= OnEnded; fixture.CommandFailed -= OnFailed; fixture.CommandCancelled -= OnCancelled; } catch { } } private static void Unsubscribe()
{
if (fixture is not null) try { fixture.CommandWillStart -= OnWillStart; fixture.CommandEnded -= OnEnded; fixture.CommandFailed -= OnFailed; fixture.CommandCancelled -= OnCancelled; } catch { }
preparedContext = null;
selectedTargetSnapshotId = null;
}
private static int? Dbmod() { try { return Convert.ToInt32(AcadApplication.GetSystemVariable("DBMOD")); } catch { return null; } } private static int? Dbmod() { try { return Convert.ToInt32(AcadApplication.GetSystemVariable("DBMOD")); } catch { return null; } }
private static AutomationEntityCounts Counts(Database db) private static AutomationEntityCounts Counts(Database db)
{ {
@@ -34,4 +34,6 @@ public sealed class PxpOaRegistrationTests
[Fact] public void Observer_RecordsExactlyTwoSequentialRuns() => Assert.Contains("runs.Count < 2", Observer); [Fact] public void Observer_RecordsExactlyTwoSequentialRuns() => Assert.Contains("runs.Count < 2", Observer);
[Fact] public void Finalize_ReportsMissingPlanSeparatelyFromMissingRuns() => Assert.True(Observer.IndexOf("AUTOMATION_EXPECTS_EXACTLY_TWO_PXP_OA", StringComparison.Ordinal) < Observer.IndexOf("AUTOMATION_PLAN_IDS_DIFFER", StringComparison.Ordinal)); [Fact] public void Finalize_ReportsMissingPlanSeparatelyFromMissingRuns() => Assert.True(Observer.IndexOf("AUTOMATION_EXPECTS_EXACTLY_TWO_PXP_OA", StringComparison.Ordinal) < Observer.IndexOf("AUTOMATION_PLAN_IDS_DIFFER", StringComparison.Ordinal));
[Fact] public void ScriptDrivenPxpOa_BindsPlantContextToCommandDocument() => Assert.Contains("ReadCurrentContext(document)", Entry); [Fact] public void ScriptDrivenPxpOa_BindsPlantContextToCommandDocument() => Assert.Contains("ReadCurrentContext(document)", Entry);
[Fact] public void ScriptDrivenPxpOa_UsesPreparedFixtureContextBeforeImplicitLookup() => Assert.Contains("TryGetPreparedContext(document, out var scriptContext)", Entry);
[Fact] public void ScriptDrivenPrepare_SelectsCanonicalTargetAndRetainsReadOnlyContext() { Assert.Contains("preparedContext = context", Observer); Assert.Contains("selectedTargetSnapshotId = candidates[0]", Observer); }
} }