using System.Text.Json; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Geometry; using PRecAnnot.Abstractions; namespace PRecAnnot.Plant3D2026; // This file is deliberately runtime-only. Core receives only the anonymised // permit and canonical operation; no AutoCAD handle, database or project path // crosses the adapter boundary. internal sealed record FirstLimitedApplyRuntimePermit { public required bool AllowApply { get; init; } public required string FixtureMarkerPath { get; init; } public required string TargetSnapshotId { get; init; } public required string ExpectedPlanId { get; init; } public required string VisibilityState { get; init; } } internal static class FirstLimitedApplyPermitReader { private const string MarkerContent = "P_REC_ANNOT_TEST_FIXTURE=FIRST_LIMITED_APPLY"; public static FirstLimitedApplyRuntimePermit ReadFor(Document document) { var path = Environment.GetEnvironmentVariable("P_REC_ANNOT_APPLY_PERMIT_PATH"); if (string.IsNullOrWhiteSpace(path) || !Path.IsPathFullyQualified(path) || path.StartsWith("\\\\", StringComparison.Ordinal)) throw new InvalidOperationException("APPLY_PERMIT_PATH_REQUIRED"); if (!File.Exists(path)) throw new InvalidOperationException("APPLY_PERMIT_MISSING"); var permit = JsonSerializer.Deserialize(File.ReadAllText(path), new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); if (permit is null || !permit.AllowApply || string.IsNullOrWhiteSpace(permit.TargetSnapshotId) || string.IsNullOrWhiteSpace(permit.ExpectedPlanId) || string.IsNullOrWhiteSpace(permit.VisibilityState)) throw new InvalidOperationException("APPLY_TEST_FIXTURE_OR_PERMIT_REQUIRED"); if (!Path.IsPathFullyQualified(permit.FixtureMarkerPath) || permit.FixtureMarkerPath.StartsWith("\\\\", StringComparison.Ordinal) || !File.Exists(permit.FixtureMarkerPath)) throw new InvalidOperationException("APPLY_TEST_FIXTURE_OR_PERMIT_REQUIRED"); if (!string.Equals(File.ReadAllText(permit.FixtureMarkerPath).Trim(), MarkerContent, StringComparison.Ordinal)) throw new InvalidOperationException("APPLY_TEST_FIXTURE_OR_PERMIT_REQUIRED"); var root = Path.GetDirectoryName(Path.GetFullPath(permit.FixtureMarkerPath))!; var drawing = Path.GetFullPath(document.Name); if (!drawing.StartsWith(root + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("APPLY_TEST_FIXTURE_OR_PERMIT_REQUIRED"); return permit; } } internal enum PipeAnnotationWriteOutcome { Created, AlreadyExists } internal sealed class FirstLimitedPipeAnnotationWriter { private const string BlockName = "PIPE"; private const string XdataApplication = "PXP-OA"; public PipeAnnotationWriteOutcome Write(Document document, CreatePipeAnnotationOperation operation, string visibilityState) { var database = document.Database; using var transaction = database.TransactionManager.StartTransaction(); var table = (BlockTable)transaction.GetObject(database.BlockTableId, OpenMode.ForRead); if (!table.Has(BlockName)) throw new InvalidOperationException("ANNOTATION_TEMPLATE_BLOCK_MISSING"); var paper = (BlockTableRecord)transaction.GetObject(table[BlockTableRecord.PaperSpace], OpenMode.ForWrite); if (HasOwnedAnnotation(transaction, paper, operation.DuplicateKey)) return PipeAnnotationWriteOutcome.AlreadyExists; EnsureRegApp(transaction, database); using var annotation = new BlockReference( new Point3d(operation.Placement.X, operation.Placement.Y, operation.Placement.Z), table[BlockName]); paper.AppendEntity(annotation); transaction.AddNewlyCreatedDBObject(annotation, true); SetConfirmedVisibility(annotation, visibilityState); annotation.XData = new ResultBuffer( new TypedValue((int)DxfCode.ExtendedDataRegAppName, XdataApplication), new TypedValue((int)DxfCode.ExtendedDataAsciiString, operation.DuplicateKey), new TypedValue((int)DxfCode.ExtendedDataAsciiString, operation.RuleId)); transaction.Commit(); return PipeAnnotationWriteOutcome.Created; } private static bool HasOwnedAnnotation(Transaction transaction, BlockTableRecord paper, string duplicateKey) { foreach (ObjectId id in paper) { if (transaction.GetObject(id, OpenMode.ForRead) is not BlockReference reference) continue; using var xdata = reference.GetXDataForApplication(XdataApplication); if (xdata?.AsArray().Any(value => value.TypeCode == (int)DxfCode.ExtendedDataAsciiString && string.Equals(value.Value as string, duplicateKey, StringComparison.Ordinal)) == true) return true; } return false; } private static void EnsureRegApp(Transaction transaction, Database database) { var apps = (RegAppTable)transaction.GetObject(database.RegAppTableId, OpenMode.ForRead); if (apps.Has(XdataApplication)) return; apps.UpgradeOpen(); using var record = new RegAppTableRecord { Name = XdataApplication }; apps.Add(record); transaction.AddNewlyCreatedDBObject(record, true); } // The "V" property and allowed values are the confirmed legacy PXP-OA // dynamic-block contract. Failure is pre-commit and leaves no entity. private static void SetConfirmedVisibility(BlockReference annotation, string visibilityState) { var property = annotation.DynamicBlockReferencePropertyCollection .Cast() .SingleOrDefault(value => string.Equals(value.PropertyName, "V", StringComparison.Ordinal)); if (property is null || !property.GetAllowedValues().OfType().Contains(visibilityState, StringComparer.Ordinal)) throw new InvalidOperationException("ANNOTATION_VISIBILITY_STATE_UNSUPPORTED"); property.Value = visibilityState; } }