diff --git a/Plant3D2026/P_Rec_Annot/docs/evidence/ortho-dry-run-summary.json b/Plant3D2026/P_Rec_Annot/docs/evidence/ortho-dry-run-summary.json new file mode 100644 index 0000000..0abe051 --- /dev/null +++ b/Plant3D2026/P_Rec_Annot/docs/evidence/ortho-dry-run-summary.json @@ -0,0 +1,24 @@ +{ + "executionMode": "DryRun", + "runtimeExecuted": false, + "plantProjectDetected": null, + "orthoDetection": "NotExecuted", + "viewportCount": null, + "dataLinksAvailable": null, + "objectsRead": null, + "plan": { + "create": null, + "update": null, + "skip": null, + "conflict": null, + "unsupported": null + }, + "planDeterministic": true, + "dbmodBefore": null, + "dbmodAfter": null, + "dwgModified": null, + "dcfModified": null, + "projectXmlModified": null, + "unhandledExceptions": null, + "blocker": "No controlled test Ortho DWG/project path was supplied or discovered; runtime DryRun was not claimed." +} diff --git a/Plant3D2026/P_Rec_Annot/docs/migration-dashboard.md b/Plant3D2026/P_Rec_Annot/docs/migration-dashboard.md index 05a97b1..d71e6b2 100644 --- a/Plant3D2026/P_Rec_Annot/docs/migration-dashboard.md +++ b/Plant3D2026/P_Rec_Annot/docs/migration-dashboard.md @@ -10,7 +10,9 @@ The first real Plant 3D 2026 build is complete against the workstation assemblie | Ribbon | Implemented with `Autodesk.Windows`; idempotent tab/panel guard | | Adapter | Compiled against real 2026 assemblies | | Bundle | Local smoke package built and checked for Autodesk DLL exclusion | -| Runtime | Not executed in AutoCAD/Plant process | +| Runtime smoke | Manual NETLOAD confirmed on Plant 3D 2026; automatic AppLoader and Ortho DryRun remain unverified | +| Ortho read path | Compiled against `OrthoDocUtils`, `OrthoProject`, `DwgSheetDefinitions`, `DwgViewDefs`, and `DataLinksManager`; runtime fixture pending | +| Dry-run plan | Deterministic Core builder with DTO boundary and Apply guard; no annotation writes | | Production deploy | NOT PERFORMED | -Fixed API assumptions: foundation-only lifecycle became `IExtensionApplication`; command descriptors became actual `CommandMethod` registrations; Plant project detection uses `PlantApplication.CurrentProject`; Ribbon uses `ComponentManager.Ribbon`, `RibbonTab`, `RibbonPanelSource`, and `RibbonButton`. Remaining blockers are runtime loading, Ortho API/data-link mapping, safe DWG transaction implementation, and golden fixture validation. +Fixed API assumptions: foundation-only lifecycle became `IExtensionApplication`; command descriptors became actual `CommandMethod` registrations; Ortho detection uses `OrthoDocUtils.IsValidOrthoDrawing` plus the Ortho project part; viewport metadata uses `DwgSheetDefinitions`/`DwgViewDefs`; object properties use read-only `DataLinksManager.SelectAcPpRowIds` and `GetAllProperties`; Core receives only immutable DTOs. Remaining blockers are a controlled Ortho fixture, geometry/object-to-viewport mapping, legacy source provenance, and future Apply implementation. diff --git a/Plant3D2026/P_Rec_Annot/docs/pxp-aav-analysis.md b/Plant3D2026/P_Rec_Annot/docs/pxp-aav-analysis.md new file mode 100644 index 0000000..3aeb16a --- /dev/null +++ b/Plant3D2026/P_Rec_Annot/docs/pxp-aav-analysis.md @@ -0,0 +1,9 @@ +# PXP-AAV read-path analysis + +Status: traced to the existing command registration and the shared dry-run plan path; annotation writes are not implemented in this slice. + +| RuleId | LegacyCommand | LegacyProject | LegacyFile | ObservedBehavior | TargetUseCase | TargetClass | Test | Status | +|---|---|---|---|---|---|---|---|---| +| PXP-AAV-001 | PXP-AAV | legacy audit | `docs/legacy-rule-provenance.csv` | legacy provenance is recorded, but the requested legacy checkout is absent on this workstation | read objects visible to an Ortho view and build a dry-run plan | adapter DTO -> `AnnotationPlan` | `AnnotationPlan_InputOrderDoesNotChangeResult` | partial | + +The implementation deliberately does not claim behavioral equivalence. The next required input is a read-only copy of the legacy source or a golden test Ortho drawing with expected diagnostics. diff --git a/Plant3D2026/P_Rec_Annot/docs/pxp-oa-analysis.md b/Plant3D2026/P_Rec_Annot/docs/pxp-oa-analysis.md new file mode 100644 index 0000000..df2960c --- /dev/null +++ b/Plant3D2026/P_Rec_Annot/docs/pxp-oa-analysis.md @@ -0,0 +1,9 @@ +# PXP-OA read-path analysis + +Status: command is a real `CommandMethod` and uses the same safe context/read-plan pipeline. No Ortho annotation is created, updated, or deleted. + +| RuleId | LegacyCommand | LegacyProject | LegacyFile | ObservedBehavior | TargetUseCase | TargetClass | Test | Status | +|---|---|---|---|---|---|---|---|---| +| PXP-OA-001 | PXP-OA | legacy audit | `docs/legacy-rule-provenance.csv` | legacy provenance is available, but source checkout was not found locally | Ortho context and DataLinks read diagnostics | adapter DTO -> `AnnotationPlan` | `ApplyMode_IsBlocked` | partial | + +Exact object filters, annotation text, placement rules, and existing-annotation detection remain unconfirmed until the legacy source or a controlled golden fixture is supplied. diff --git a/Plant3D2026/P_Rec_Annot/docs/runtime-test-plan.md b/Plant3D2026/P_Rec_Annot/docs/runtime-test-plan.md index 4a701ab..ccf76f5 100644 --- a/Plant3D2026/P_Rec_Annot/docs/runtime-test-plan.md +++ b/Plant3D2026/P_Rec_Annot/docs/runtime-test-plan.md @@ -12,3 +12,8 @@ Copy-Item -Recurse -Force .\Plant3D2026\P_Rec_Annot\artifacts\P_Rec_Annot.bundle In a controlled Plant 3D 2026 session, verify that the entry assembly loads, the `Промхимпроект` tab and `Аннотации` panel occur once, and the `Аннотировать` button invokes `PHP_REC_ANNOT`. Open a Plant project and active DWG, run all three commands, verify the project guard and diagnostic-only behavior, and confirm no unhandled exception. Repeat initialization and confirm no Ribbon duplicate. DWG mutation, Ortho viewport/data-link behavior, undo, repeat-run idempotence, and golden-DWG comparison remain runtime blockers for the implementation wave. Deploy status: NOT PERFORMED. +# Current slice: Ortho read / dry-run + +`P_REC_ANNOT_RUNTIME_MODE=Smoke` reads context only. `DryRun` reads Ortho view metadata and DataLinks properties, builds an immutable deterministic `AnnotationPlan`, and never applies it. `Apply` is blocked with `APPLY_MODE_NOT_IMPLEMENTED`; unknown values resolve to `DryRun`. + +The read adapter uses `OrthoDocUtils.IsValidOrthoDrawing`, `OrthoProject.DwgSheetDefinitions`, `IDwgViewDef`, and read-only `DataLinksManager` methods. The adapter does not expose AutoCAD or Plant objects to Core and contains no write calls. A runtime DryRun result is not published until a controlled test Ortho DWG is available; see `docs/evidence/ortho-dry-run-summary.json`. 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 85006a1..46b0d8a 100644 --- a/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs +++ b/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs @@ -86,4 +86,61 @@ public sealed class FoundationTests var references = typeof(AnnotationRequest).Assembly.GetReferencedAssemblies(); Assert.DoesNotContain(references, x => x.Name?.StartsWith("Autodesk", StringComparison.OrdinalIgnoreCase) == true || x.Name?.StartsWith("Ac", StringComparison.OrdinalIgnoreCase) == true); } + + [Fact] public void OrthoDetection_NoPlantProject_ReturnsNotOrtho() => Assert.Equal(OrthoDetectionStatus.NotOrtho, OrthoDetectionClassifier.Classify(false, false, false).Status); + [Fact] public void OrthoDetection_NormalDwg_ReturnsNotOrtho() => Assert.Equal(OrthoDetectionStatus.NotOrtho, OrthoDetectionClassifier.Classify(true, true, false).Status); + [Fact] public void OrthoDetection_ConfirmedOrtho_ReturnsConfirmed() => Assert.Equal(OrthoDetectionStatus.Confirmed, OrthoDetectionClassifier.Classify(true, true, true).Status); + [Fact] public void OrthoDetection_AmbiguousMetadata_ReturnsAmbiguous() => Assert.Equal(OrthoDetectionStatus.Ambiguous, OrthoDetectionClassifier.Classify(true, true, false, true).Status); + + [Fact] public void AnnotationPlan_InputOrderDoesNotChangeResult() + { + var viewport = new OrthoViewportInfo { Id = "vp", DisplayName = "A", SourceDrawingName = "test.dwg" }; + var objects = new[] { Snapshot("b", "Pipe", "L-2"), Snapshot("a", "Valve", "V-1") }; + var builder = new AnnotationPlanBuilder(); + var first = builder.Build(new AnnotationPlanningRequest { CommandName = "PHP_REC_ANNOT", Viewport = viewport, Objects = objects }); + var second = builder.Build(new AnnotationPlanningRequest { CommandName = "PHP_REC_ANNOT", Viewport = viewport, Objects = objects.Reverse().ToArray() }); + Assert.Equal(first.PlanId, second.PlanId); + Assert.Equal(first.Summary, second.Summary); + Assert.Equal(first.Operations.Select(x => x.SourceSnapshotId), second.Operations.Select(x => x.SourceSnapshotId)); + } + + [Fact] public void AnnotationPlan_MissingLineNumber_IsSkipped() + { + var plan = Build(Snapshot("a", "Pipe", null, null)); + Assert.Equal(AnnotationOperationKind.Skip, Assert.Single(plan.Operations).Kind); + } + + [Fact] public void AnnotationPlan_UnsupportedObject_IsMarkedUnsupported() + { + var plan = Build(Snapshot("a", "", "L-1")); + Assert.Equal(AnnotationOperationKind.Unsupported, Assert.Single(plan.Operations).Kind); + } + + [Fact] public void AnnotationPlan_ExistingAnnotation_BecomesUpdate() + { + var viewport = new OrthoViewportInfo { Id = "vp", DisplayName = "A", SourceDrawingName = "test.dwg" }; + var item = Snapshot("a", "Pipe", "L-1"); + var plan = new AnnotationPlanBuilder().Build(new AnnotationPlanningRequest { CommandName = "PHP_REC_ANNOT", Viewport = viewport, Objects = new[] { item }, Existing = new[] { new ExistingAnnotation("a", "old", new(0, 0)) } }); + Assert.Equal(AnnotationOperationKind.Update, Assert.Single(plan.Operations).Kind); + } + + [Fact] public void AnnotationPlan_DuplicateCandidate_BecomesConflict() + { + var plan = Build(Snapshot("a", "Pipe", "L-1"), Snapshot("a", "Pipe", "L-1")); + Assert.Equal(AnnotationOperationKind.Conflict, plan.Operations.Last().Kind); + } + + [Fact] public void ApplyMode_IsBlocked() => Assert.Throws(() => new ExecutionModeWriteGuard(PRecAnnotExecutionMode.Apply).EnsureWriteAllowed("test")); + + [Fact] public void DryRunPath_HasNoForWriteOperations() + { + var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "PlantReadPath.cs")); + Assert.DoesNotContain("ForWrite", source); + Assert.DoesNotContain("AppendEntity", source); + Assert.DoesNotContain("AcceptChanges", source); + Assert.DoesNotContain("SetProperties", source); + } + + private static AnnotationPlan Build(params PlantObjectSnapshot[] objects) => new AnnotationPlanBuilder().Build(new AnnotationPlanningRequest { CommandName = "PHP_REC_ANNOT", Viewport = new OrthoViewportInfo { Id = "vp", DisplayName = "A", SourceDrawingName = "test.dwg" }, Objects = objects }); + private static PlantObjectSnapshot Snapshot(string id, string type, string? line, string? tag = "TAG") => new() { SnapshotId = id, ObjectClass = type, LineNumber = line, Tag = tag, Geometry = new GeometrySnapshot { PaperPosition = new Point2Dto(1, 2) } }; }