feat: port p-rec-annot line-number refresh pipeline

This commit is contained in:
codex
2026-08-07 09:19:56 +03:00
parent 34cdfa225c
commit 95ae9dfd18
4 changed files with 98 additions and 1 deletions
@@ -40,7 +40,7 @@ public sealed class EntryPoint : IExtensionApplication
public void OrthoAnnotateCommand() => RunDiagnostic("PXP-OA"); public void OrthoAnnotateCommand() => RunDiagnostic("PXP-OA");
[CommandMethod("PXP-LN")] public void AnnotateLineNumbersCommand() => RunLineNumberPreview(); [CommandMethod("PXP-LN")] public void AnnotateLineNumbersCommand() => RunLineNumberPreview();
[CommandMethod("PXP-ULN")] public void UpdateLineNumbersCommand() => RunDiagnostic("PXP-ULN"); [CommandMethod("PXP-ULN")] public void UpdateLineNumbersCommand() => LineNumberRefreshCommand.Run();
[CommandMethod("PXP-ESN")] public void EditLineNumberStartCommand() => LineNumberEditCommands.Renumber(); [CommandMethod("PXP-ESN")] public void EditLineNumberStartCommand() => LineNumberEditCommands.Renumber();
[CommandMethod("PXP-ELN")] public void EditLineNumbersCommand() => LineNumberEditCommands.EditText(); [CommandMethod("PXP-ELN")] public void EditLineNumbersCommand() => LineNumberEditCommands.EditText();
[CommandMethod("PXP-RMP")] public void ResetMarkerPositionCommand() => LineNumberEditCommands.ResetMarkerPosition(); [CommandMethod("PXP-RMP")] public void ResetMarkerPositionCommand() => LineNumberEditCommands.ResetMarkerPosition();
@@ -60,6 +60,26 @@ internal sealed class LineNumberOwnedAnnotationReader
return new(stableId, markerNumber, text, new(localPosition.X, localPosition.Y, localPosition.Z)); return new(stableId, markerNumber, text, new(localPosition.X, localPosition.Y, localPosition.Z));
} }
public IReadOnlyList<LineNumberOwnedRow> ReadMarkers(Document document)
{
using var transaction = document.Database.TransactionManager.StartOpenCloseTransaction();
var blocks = (BlockTable)transaction.GetObject(document.Database.BlockTableId, OpenMode.ForRead);
var paper = (BlockTableRecord)transaction.GetObject(blocks[BlockTableRecord.PaperSpace], OpenMode.ForRead);
var rows = new List<LineNumberOwnedRow>();
foreach (ObjectId id in paper)
{
if (transaction.GetObject(id, OpenMode.ForRead) is not BlockReference marker || !HasRole(marker, "MARKER")) continue;
var values = marker.GetXDataForApplication(ApplicationName)?.AsArray() ?? [];
var strings = values.Where(value => value.TypeCode == 1000).Select(value => value.Value as string).ToArray();
var stableId = strings.FirstOrDefault(value => !string.Equals(value, "MARKER", StringComparison.OrdinalIgnoreCase));
var markerNumber = values.Where(value => value.TypeCode == 1071).Select(value => Convert.ToInt32(value.Value)).FirstOrDefault();
var text = strings.LastOrDefault() ?? string.Empty;
if (string.IsNullOrWhiteSpace(stableId) || markerNumber <= 0) continue;
rows.Add(new(stableId, markerNumber, text, default));
}
return rows.OrderBy(value => value.MarkerNumber).ThenBy(value => value.SourceStableId, StringComparer.Ordinal).ToArray();
}
private static bool HasRole(BlockReference reference, string role) => private static bool HasRole(BlockReference reference, string role) =>
reference.GetXDataForApplication(ApplicationName)?.AsArray().Any(value => reference.GetXDataForApplication(ApplicationName)?.AsArray().Any(value =>
value.TypeCode == 1000 && string.Equals(value.Value as string, role, StringComparison.OrdinalIgnoreCase)) == true; value.TypeCode == 1000 && string.Equals(value.Value as string, role, StringComparison.OrdinalIgnoreCase)) == true;
@@ -0,0 +1,64 @@
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.ProcessPower.PlantInstance;
using PRecAnnot.Abstractions;
using PRecAnnot.Core;
using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application;
namespace PRecAnnot.Plant3D2026;
// PXP-ULN refreshes the source-backed text of already-owned annotations. The
// legacy command drives the same operation across selected Ortho drawings; the
// modern command keeps the mutation pipeline scoped to the active controlled
// drawing until the Ortho drawing enumerator is ported.
internal static class LineNumberRefreshCommand
{
public static void Run()
{
var document = AcadApplication.DocumentManager.MdiActiveDocument;
if (document is null) return;
try
{
var configuration = LineNumberRuntimeConfigurationReader.Read();
var definition = configuration.Annotations.Annotations.OrderBy(value => value.Nickname, StringComparer.Ordinal).First();
var manager = PlantApplication.ProjectPart("Piping")?.DataLinksManager ?? throw new InvalidOperationException("LINE_NUMBER_PIPING_DATALINKS_UNAVAILABLE");
var owned = new LineNumberOwnedAnnotationReader().ReadMarkers(document);
if (owned.Count == 0) throw new InvalidOperationException("LINE_NUMBER_OWNED_MARKERS_NOT_FOUND");
var candidates = owned.Select(row => ReadCandidate(document.Database, row, manager, configuration.Assets)).ToArray();
var preview = new LineNumberCommandPreviewService(
new LineNumberCommandPlanner(new LineNumberAnnotationFormatter()),
new LineNumberFieldResolver(new PlantDataLinksPropertyReader(manager)))
.Preview(definition, candidates, owned.Select(row => new ExistingLineNumberAnnotation(row.SourceStableId, row.SourceStableId, row.Text)));
if (!preview.Success) throw new InvalidOperationException(preview.FailureCode);
var rows = owned.ToDictionary(row => row.SourceStableId, StringComparer.OrdinalIgnoreCase);
var apply = preview.Plan!.Operations.Where(operation => operation.Kind is AnnotationOperationKind.Create or AnnotationOperationKind.Update)
.Select(operation => new LineNumberApplyPreflight(operation.StableId, rows[operation.StableId].MarkerNumber, operation.Text!, operation.Position, operation.Rotation, operation.Layer, operation.TextStyle))
.OrderBy(operation => operation.MarkerNumber).ToArray();
var writer = new LineNumberAnnotationWriter();
var markerResult = writer.Apply(document, apply, configuration.Assets);
var mutations = apply.Select(item => new LineNumberRowMutation(item.StableId, rows[item.StableId].MarkerNumber, item.MarkerNumber, item.Text, null)).ToArray();
var listResult = writer.ApplyEdits(document, mutations);
document.Editor.WriteMessage($"\nPXP-ULN: updated markers={markerResult.Updated}; lists={listResult.Updated}; already-current={markerResult.Unchanged}.");
RuntimeDiagnostics.Write("LineNumberRefreshCompleted", commandName: "PXP-ULN", resultStatus: markerResult.Updated > 0 ? "Updated" : "AlreadyCurrent", document: document,
details: new Dictionary<string, object?> { ["UpdatedMarkers"] = markerResult.Updated, ["UpdatedLists"] = listResult.Updated, ["AlreadyCurrent"] = markerResult.Unchanged, ["Scope"] = "ActiveDrawing" });
}
catch (System.Exception exception)
{
RuntimeDiagnostics.Write("LineNumberRefreshFailed", "ERROR", commandName: "PXP-ULN", resultStatus: exception.Message, exception: exception, document: document);
document.Editor.WriteMessage($"\nPXP-ULN: {exception.Message}");
}
}
private static LineNumberCandidate ReadCandidate(Database database, LineNumberOwnedRow row, Autodesk.ProcessPower.DataLinks.DataLinksManager manager, AnnotateLineNumbersAssets assets)
{
const string prefix = "acad:";
if (!row.SourceStableId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("LINE_NUMBER_SOURCE_ID_UNSUPPORTED");
if (!long.TryParse(row.SourceStableId[prefix.Length..], System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out var handle))
throw new InvalidOperationException("LINE_NUMBER_SOURCE_HANDLE_INVALID");
var id = database.GetObjectId(false, new Handle(handle), 0);
var read = new PlantLineNumberCandidateReader(manager).Read(id, assets.Marker.Layer, assets.Marker.Texts[0].TextStyle);
if (!read.Success) throw new InvalidOperationException(read.FailureCode);
return read.Candidate!;
}
}
@@ -41,4 +41,17 @@ public sealed class LineNumberEditCommandRegistrationTests
var commands = File.ReadAllText(Path.Combine(ProjectRoot, "LineNumberEditCommands.cs")); var commands = File.ReadAllText(Path.Combine(ProjectRoot, "LineNumberEditCommands.cs"));
Assert.Equal(3, System.Text.RegularExpressions.Regex.Matches(commands, "ApplyEdits\\(document").Count); Assert.Equal(3, System.Text.RegularExpressions.Regex.Matches(commands, "ApplyEdits\\(document").Count);
} }
[Fact]
public void UpdateCommand_UsesOwnedSourceRefreshInsteadOfDiagnosticOnlyHandler()
{
var entry = File.ReadAllText(Path.Combine(ProjectRoot, "EntryPoint.cs"));
var declaration = entry.Split('\n').Single(value => value.Contains("CommandMethod(\"PXP-ULN\")", StringComparison.Ordinal));
Assert.Contains("LineNumberRefreshCommand.Run", declaration);
Assert.DoesNotContain("RunDiagnostic", declaration);
var refresh = File.ReadAllText(Path.Combine(ProjectRoot, "LineNumberRefreshCommand.cs"));
Assert.Contains("PlantLineNumberCandidateReader", refresh);
Assert.Contains("LineNumberCommandPreviewService", refresh);
Assert.Contains("ApplyEdits", refresh);
}
} }