feat: port p-rec-annot line-number edit mutations

This commit is contained in:
codex
2026-08-07 09:04:07 +03:00
parent 7090321167
commit 91f316d79b
4 changed files with 103 additions and 0 deletions
@@ -70,6 +70,8 @@ public sealed record ElevationAnnotationOperation(string StableId, AnnotationOpe
public sealed record ElevationAnnotationPlan(IReadOnlyList<ElevationAnnotationOperation> Operations); public sealed record ElevationAnnotationPlan(IReadOnlyList<ElevationAnnotationOperation> Operations);
public sealed record LineConnectionCandidate(string StableId, string LineNumber, Point3Dto FirstEndpoint, Point3Dto SecondEndpoint, Bounds3Dto Bounds, AnnotationPoint Position, double Rotation); 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<LineConnectionCandidate> Members, AnnotationPoint Placement, double Rotation); public sealed record LineConnectionGroup(string LineNumber, IReadOnlyList<LineConnectionCandidate> 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 LineNumberTextAsset(string Layer, string TextStyle, double Size, string TextValue, Point2Dto Position);
public sealed record LineNumberLineAsset(string Layer, Point2Dto Start, Point2Dto End); public sealed record LineNumberLineAsset(string Layer, Point2Dto Start, Point2Dto End);
public sealed record LineNumberMarkerAsset(string Layer, double MaskScaleFactor, IReadOnlyList<LineNumberTextAsset> Texts, IReadOnlyList<LineNumberLineAsset> Lines); public sealed record LineNumberMarkerAsset(string Layer, double MaskScaleFactor, IReadOnlyList<LineNumberTextAsset> Texts, IReadOnlyList<LineNumberLineAsset> Lines);
@@ -0,0 +1,24 @@
using PRecAnnot.Abstractions;
namespace PRecAnnot.Core;
public static class LineNumberEditPlanner
{
public static IReadOnlyList<LineNumberRowMutation> Renumber(IEnumerable<LineNumberOwnedRow> 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));
}
@@ -87,6 +87,50 @@ internal sealed class LineNumberAnnotationWriter
return new(1, 0, 0); return new(1, 0, 0);
} }
public LineNumberApplyResult ApplyEdits(Document document, IReadOnlyList<LineNumberRowMutation> 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) private static void ValidateAssets(Transaction transaction, Database database, LineNumberMarkerAsset marker)
{ {
var layers = (LayerTable)transaction.GetObject(database.LayerTableId, OpenMode.ForRead); var layers = (LayerTable)transaction.GetObject(database.LayerTableId, OpenMode.ForRead);
@@ -169,6 +213,12 @@ internal sealed class LineNumberAnnotationWriter
attribute.SetAttributeFromBlock(source, reference.BlockTransform); attribute.SetAttributeFromBlock(source, reference.BlockTransform);
attribute.TextString = values[source.Tag]; attribute.TextString = values[source.Tag];
attribute.UpdateMTextAttribute(); 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); reference.AttributeCollection.AppendAttribute(attribute);
transaction.AddNewlyCreatedDBObject(attribute, true); transaction.AddNewlyCreatedDBObject(attribute, true);
} }
@@ -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<ArgumentOutOfRangeException>(() => LineNumberEditPlanner.Renumber([new("a", 1, "L", new())], 0));
[Fact] public void RejectsDuplicateExistingMarkerNumbers() => Assert.Throws<InvalidOperationException>(() => 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);
}
}