Files
autodesk/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyRuntime.cs
T

114 lines
6.0 KiB
C#

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<FirstLimitedApplyRuntimePermit>(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<DynamicBlockReferenceProperty>()
.SingleOrDefault(value => string.Equals(value.PropertyName, "V", StringComparison.Ordinal));
if (property is null || !property.GetAllowedValues().OfType<string>().Contains(visibilityState, StringComparer.Ordinal))
throw new InvalidOperationException("ANNOTATION_VISIBILITY_STATE_UNSUPPORTED");
property.Value = visibilityState;
}
}