diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Abstractions/Contracts.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Abstractions/Contracts.cs index 69e3649..2eedab3 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Abstractions/Contracts.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Abstractions/Contracts.cs @@ -70,6 +70,8 @@ public sealed record ElevationAnnotationOperation(string StableId, AnnotationOpe public sealed record ElevationAnnotationPlan(IReadOnlyList Operations); public sealed record LineConnectionCandidate(string StableId, string LineNumber, Point3Dto FirstEndpoint, Point3Dto SecondEndpoint, Bounds3Dto Bounds, AnnotationPoint Position, double Rotation); public sealed record LineConnectionGroup(string LineNumber, IReadOnlyList Members, AnnotationPoint Placement, double Rotation); +public sealed record LineNumberOwnedRow(string SourceStableId, int MarkerNumber, string Text, AnnotationPoint MarkerAttributePosition); +public sealed record LineNumberRowMutation(string SourceStableId, int PreviousMarkerNumber, int MarkerNumber, string Text, AnnotationPoint? MarkerAttributePosition); public sealed record LineNumberTextAsset(string Layer, string TextStyle, double Size, string TextValue, Point2Dto Position); public sealed record LineNumberLineAsset(string Layer, Point2Dto Start, Point2Dto End); public sealed record LineNumberMarkerAsset(string Layer, double MaskScaleFactor, IReadOnlyList Texts, IReadOnlyList Lines); diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Core/LineNumberEditPlanner.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Core/LineNumberEditPlanner.cs new file mode 100644 index 0000000..1372f81 --- /dev/null +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Core/LineNumberEditPlanner.cs @@ -0,0 +1,24 @@ +using PRecAnnot.Abstractions; + +namespace PRecAnnot.Core; + +public static class LineNumberEditPlanner +{ + public static IReadOnlyList Renumber(IEnumerable rows, int newStart) + { + if (newStart <= 0) throw new ArgumentOutOfRangeException(nameof(newStart), "LINE_NUMBER_START_MUST_BE_POSITIVE"); + var ordered = rows.OrderBy(value => value.MarkerNumber).ThenBy(value => value.SourceStableId, StringComparer.Ordinal).ToArray(); + if (ordered.Length == 0) throw new ArgumentException("LINE_NUMBER_LIST_EMPTY", nameof(rows)); + if (ordered.Select(value => value.MarkerNumber).Distinct().Count() != ordered.Length) throw new InvalidOperationException("LINE_NUMBER_LIST_DUPLICATE_MARKER_NUMBER"); + return ordered.Select((value, index) => new LineNumberRowMutation(value.SourceStableId, value.MarkerNumber, checked(newStart + index), value.Text, null)).ToArray(); + } + + public static LineNumberRowMutation EditText(LineNumberOwnedRow row, string text) + { + if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("LINE_NUMBER_TEXT_EMPTY", nameof(text)); + return new(row.SourceStableId, row.MarkerNumber, row.MarkerNumber, text, null); + } + + public static LineNumberRowMutation ResetMarkerAttributePosition(LineNumberOwnedRow row, Point2Dto configuredPosition) => + new(row.SourceStableId, row.MarkerNumber, row.MarkerNumber, row.Text, new(configuredPosition.X, configuredPosition.Y)); +} diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/LineNumberAnnotationWriter.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/LineNumberAnnotationWriter.cs index 14c2368..507f4a0 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/LineNumberAnnotationWriter.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/LineNumberAnnotationWriter.cs @@ -87,6 +87,50 @@ internal sealed class LineNumberAnnotationWriter return new(1, 0, 0); } + public LineNumberApplyResult ApplyEdits(Document document, IReadOnlyList mutations) + { + if (mutations.Count == 0) return new(0, 0, 1); + using var documentLock = document.LockDocument(); + var database = document.Database; + using var transaction = database.TransactionManager.StartTransaction(); + var blocks = (BlockTable)transaction.GetObject(database.BlockTableId, OpenMode.ForRead); + var paper = (BlockTableRecord)transaction.GetObject(blocks[BlockTableRecord.PaperSpace], OpenMode.ForRead); + var updated = 0; + foreach (var mutation in mutations) + { + var marker = FindOwnedMarker(transaction, paper, mutation.SourceStableId) ?? throw new InvalidOperationException("LINE_NUMBER_OWNED_MARKER_NOT_FOUND"); + foreach (ObjectId id in marker.AttributeCollection) + { + var attribute = (AttributeReference)transaction.GetObject(id, OpenMode.ForWrite); + if (!string.Equals(attribute.Tag, MarkerAttributeTag, StringComparison.OrdinalIgnoreCase)) continue; + attribute.TextString = mutation.MarkerNumber.ToString(System.Globalization.CultureInfo.InvariantCulture); + if (mutation.MarkerAttributePosition is { } reset) attribute.Position = new Point3d(reset.X, reset.Y, reset.Z).TransformBy(marker.BlockTransform); + } + foreach (ObjectId entityId in paper) + { + if (transaction.GetObject(entityId, OpenMode.ForRead) is not BlockReference list || !list.Name.StartsWith("LN_LIST", StringComparison.OrdinalIgnoreCase)) continue; + foreach (ObjectId attributeId in list.AttributeCollection) + { + var attribute = (AttributeReference)transaction.GetObject(attributeId, OpenMode.ForWrite); + if (string.Equals(attribute.Tag, $"NUMBER{mutation.PreviousMarkerNumber}", StringComparison.OrdinalIgnoreCase)) + { + attribute.Tag = $"NUMBER{mutation.MarkerNumber}"; + attribute.TextString = mutation.MarkerNumber.ToString(System.Globalization.CultureInfo.InvariantCulture); + } + else if (string.Equals(attribute.Tag, $"LINENUMBER{mutation.PreviousMarkerNumber}", StringComparison.OrdinalIgnoreCase)) + { + attribute.Tag = $"LINENUMBER{mutation.MarkerNumber}"; + attribute.TextString = mutation.Text.Replace("\n", "\\P", StringComparison.Ordinal); + attribute.UpdateMTextAttribute(); + } + } + } + updated++; + } + transaction.Commit(); + return new(0, updated, 0); + } + private static void ValidateAssets(Transaction transaction, Database database, LineNumberMarkerAsset marker) { var layers = (LayerTable)transaction.GetObject(database.LayerTableId, OpenMode.ForRead); @@ -169,6 +213,12 @@ internal sealed class LineNumberAnnotationWriter attribute.SetAttributeFromBlock(source, reference.BlockTransform); attribute.TextString = values[source.Tag]; attribute.UpdateMTextAttribute(); + var row = rows.Single(value => source.Tag.EndsWith(value.MarkerNumber.ToString(System.Globalization.CultureInfo.InvariantCulture), StringComparison.Ordinal)); + attribute.XData = new ResultBuffer( + new TypedValue((int)DxfCode.ExtendedDataRegAppName, ApplicationName), + new TypedValue((int)DxfCode.ExtendedDataAsciiString, row.StableId), + new TypedValue((int)DxfCode.ExtendedDataAsciiString, source.Tag.StartsWith("NUMBER", StringComparison.OrdinalIgnoreCase) ? "MARKER" : "LINENUMBER"), + new TypedValue((int)DxfCode.ExtendedDataInteger32, row.MarkerNumber)); reference.AttributeCollection.AppendAttribute(attribute); transaction.AddNewlyCreatedDBObject(attribute, true); } diff --git a/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/LineNumberEditPlannerTests.cs b/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/LineNumberEditPlannerTests.cs new file mode 100644 index 0000000..6ae3a62 --- /dev/null +++ b/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/LineNumberEditPlannerTests.cs @@ -0,0 +1,27 @@ +using PRecAnnot.Abstractions; +using PRecAnnot.Core; + +namespace PRecAnnot.Core.Tests; + +public sealed class LineNumberEditPlannerTests +{ + [Fact] + public void RenumbersLegacyListContiguouslyFromPositiveStart() + { + var rows = new[] { new LineNumberOwnedRow("b", 7, "L-2", new()), new LineNumberOwnedRow("a", 5, "L-1", new()) }; + var result = LineNumberEditPlanner.Renumber(rows, 10); + Assert.Equal(new[] { 10, 11 }, result.Select(value => value.MarkerNumber)); + Assert.Equal(new[] { 5, 7 }, result.Select(value => value.PreviousMarkerNumber)); + } + + [Fact] public void RejectsNonPositiveStart() => Assert.Throws(() => LineNumberEditPlanner.Renumber([new("a", 1, "L", new())], 0)); + [Fact] public void RejectsDuplicateExistingMarkerNumbers() => Assert.Throws(() => LineNumberEditPlanner.Renumber([new("a", 1, "L", new()), new("b", 1, "L2", new())], 2)); + + [Fact] + public void PlansTextEditAndConfiguredMarkerPositionReset() + { + var row = new LineNumberOwnedRow("a", 3, "old", new(9, 9)); + Assert.Equal("new", LineNumberEditPlanner.EditText(row, "new").Text); + Assert.Equal(new AnnotationPoint(-4, 4.4), LineNumberEditPlanner.ResetMarkerAttributePosition(row, new(-4, 4.4)).MarkerAttributePosition); + } +}