diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Abstractions/Contracts.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Abstractions/Contracts.cs index 016b1aa..eb4eb6a 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Abstractions/Contracts.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Abstractions/Contracts.cs @@ -61,6 +61,7 @@ public sealed record LineNumberCommandOperation(string StableId, AnnotationOpera public sealed record LineNumberCommandPlan(IReadOnlyList Operations); public sealed record LineNumberApplyPreflight(string StableId, int MarkerNumber, string Text, AnnotationPoint Position, double Rotation, string Layer, string TextStyle); public sealed record LineNumberApplyResult(int Created, int Updated, int Unchanged); +public sealed record LineNumberListApplyPreflight(string StableId, AnnotationPoint Position, double Rotation, IReadOnlyList Rows); 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.Plant3D2026/LineNumberAnnotationWriter.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/LineNumberAnnotationWriter.cs index c640554..14c2368 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/LineNumberAnnotationWriter.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/LineNumberAnnotationWriter.cs @@ -47,6 +47,46 @@ internal sealed class LineNumberAnnotationWriter return new(created, updated, unchanged); } + // Legacy PXP-LN materialises its selected marker/text pairs as a paper-space + // LN_LIST block. The list ownership key makes repeat Apply update that + // aggregate rather than append another list. + public LineNumberApplyResult ApplyList(Document document, LineNumberListApplyPreflight plan, AnnotateLineNumbersAssets assets) + { + ArgumentNullException.ThrowIfNull(document); + ArgumentNullException.ThrowIfNull(plan); + if (plan.Rows.Count == 0) return new(0, 0, 1); + if (plan.Rows.Select(row => row.MarkerNumber).Distinct().Count() != plan.Rows.Count) + throw new InvalidOperationException("LINE_NUMBER_LIST_DUPLICATE_MARKER_NUMBER"); + + using var documentLock = document.LockDocument(); + var database = document.Database; + using var transaction = database.TransactionManager.StartTransaction(); + var blocks = (BlockTable)transaction.GetObject(database.BlockTableId, OpenMode.ForRead); + ValidateListAssets(transaction, database, assets.List); + EnsureRegApp(transaction, database); + var paper = (BlockTableRecord)transaction.GetObject(blocks[BlockTableRecord.PaperSpace], OpenMode.ForWrite); + var existing = FindOwnedMarker(transaction, paper, plan.StableId); + if (existing is not null) + { + var changed = UpdateList(transaction, existing, plan); + transaction.Commit(); + return new(0, changed ? 1 : 0, changed ? 0 : 1); + } + + var definition = CreateListDefinition(transaction, database, blocks, plan, assets.List); + using var reference = new BlockReference(new Point3d(plan.Position.X, plan.Position.Y, plan.Position.Z), definition) + { + Rotation = plan.Rotation, + Layer = assets.List.Layer, + XData = ListOwnership(plan.StableId) + }; + paper.AppendEntity(reference); + transaction.AddNewlyCreatedDBObject(reference, true); + AppendListAttributes(transaction, definition, reference, plan.Rows); + transaction.Commit(); + return new(1, 0, 0); + } + private static void ValidateAssets(Transaction transaction, Database database, LineNumberMarkerAsset marker) { var layers = (LayerTable)transaction.GetObject(database.LayerTableId, OpenMode.ForRead); @@ -58,6 +98,105 @@ internal sealed class LineNumberAnnotationWriter if (marker.Texts.Count == 0) throw new InvalidOperationException("LINE_NUMBER_MARKER_TEXT_ASSET_MISSING"); } + private static void ValidateListAssets(Transaction transaction, Database database, LineNumberListAsset list) + { + var layers = (LayerTable)transaction.GetObject(database.LayerTableId, OpenMode.ForRead); + var styles = (TextStyleTable)transaction.GetObject(database.TextStyleTableId, OpenMode.ForRead); + foreach (var layer in new[] { list.Layer, list.MarkerText.Layer, list.LineNumberText.Layer, list.Line.Layer }.Distinct(StringComparer.OrdinalIgnoreCase)) + if (!layers.Has(layer)) throw new InvalidOperationException($"LINE_NUMBER_LAYER_MISSING:{layer}"); + foreach (var style in new[] { list.MarkerText.TextStyle, list.LineNumberText.TextStyle }.Distinct(StringComparer.OrdinalIgnoreCase)) + if (!styles.Has(style)) throw new InvalidOperationException($"LINE_NUMBER_TEXT_STYLE_MISSING:{style}"); + if (list.LineHeight <= 0) throw new InvalidOperationException("LINE_NUMBER_LIST_HEIGHT_INVALID"); + } + + private static ObjectId CreateListDefinition(Transaction transaction, Database database, BlockTable blocks, LineNumberListApplyPreflight plan, LineNumberListAsset list) + { + blocks.UpgradeOpen(); + var baseName = $"LN_LIST{plan.Rows.Count}"; + var name = baseName; + for (var suffix = 1; blocks.Has(name); suffix++) name = $"{baseName}_{suffix}"; + using var definition = new BlockTableRecord { Name = name, Origin = Point3d.Origin }; + var id = blocks.Add(definition); + transaction.AddNewlyCreatedDBObject(definition, true); + var layers = (LayerTable)transaction.GetObject(database.LayerTableId, OpenMode.ForRead); + var styles = (TextStyleTable)transaction.GetObject(database.TextStyleTableId, OpenMode.ForRead); + for (var index = 0; index < plan.Rows.Count; index++) + { + var row = plan.Rows[index]; + AppendListAttribute(transaction, definition, row.MarkerNumber.ToString(System.Globalization.CultureInfo.InvariantCulture), $"NUMBER{row.MarkerNumber}", "Номер маркера:", list.MarkerText, index, list.LineHeight, list.MaskScaleFactor, layers, styles); + AppendListAttribute(transaction, definition, row.Text.Replace("\n", "(|)", StringComparison.Ordinal), $"LINENUMBER{row.MarkerNumber}", "Номер линии:", list.LineNumberText, index, list.LineHeight, list.MaskScaleFactor, layers, styles); + using var horizontal = new Line( + Shift(list.Line.Start, index, list.LineHeight), + Shift(list.Line.End, index, list.LineHeight)) { LayerId = layers[list.Line.Layer] }; + definition.AppendEntity(horizontal); + transaction.AddNewlyCreatedDBObject(horizontal, true); + } + using var vertical = new Line( + new Point3d(list.Line.Start.X, list.Line.Start.Y + list.LineHeight, 0), + Shift(list.Line.Start, plan.Rows.Count - 1, list.LineHeight)) { LayerId = layers[list.Line.Layer] }; + definition.AppendEntity(vertical); + transaction.AddNewlyCreatedDBObject(vertical, true); + return id; + } + + private static void AppendListAttribute(Transaction transaction, BlockTableRecord definition, string text, string tag, string prompt, LineNumberTextAsset asset, int row, double lineHeight, double maskScale, LayerTable layers, TextStyleTable styles) + { + var position = new Point3d(asset.Position.X, asset.Position.Y - lineHeight * row, 0); + using var attribute = new AttributeDefinition(position, text, tag, prompt, styles[asset.TextStyle]) { Height = asset.Size, IsMTextAttributeDefinition = true, LayerId = layers[asset.Layer] }; + var mtext = attribute.MTextAttributeDefinition; + mtext.BackgroundFill = true; + mtext.BackgroundScaleFactor = maskScale; + mtext.UseBackgroundColor = true; + attribute.MTextAttributeDefinition = mtext; + definition.AppendEntity(attribute); + transaction.AddNewlyCreatedDBObject(attribute, true); + } + + private static Point3d Shift(Point2Dto source, int row, double height) => new(source.X, source.Y - height * row, 0); + + private static void AppendListAttributes(Transaction transaction, ObjectId definitionId, BlockReference reference, IReadOnlyList rows) + { + var values = rows.SelectMany(row => new[] + { + new KeyValuePair($"NUMBER{row.MarkerNumber}", row.MarkerNumber.ToString(System.Globalization.CultureInfo.InvariantCulture)), + new KeyValuePair($"LINENUMBER{row.MarkerNumber}", row.Text.Replace("\n", "\\P", StringComparison.Ordinal)) + }).ToDictionary(value => value.Key, value => value.Value, StringComparer.OrdinalIgnoreCase); + var definition = (BlockTableRecord)transaction.GetObject(definitionId, OpenMode.ForRead); + foreach (ObjectId id in definition) + { + if (transaction.GetObject(id, OpenMode.ForRead) is not AttributeDefinition source || source.Constant) continue; + using var attribute = new AttributeReference(); + attribute.SetAttributeFromBlock(source, reference.BlockTransform); + attribute.TextString = values[source.Tag]; + attribute.UpdateMTextAttribute(); + reference.AttributeCollection.AppendAttribute(attribute); + transaction.AddNewlyCreatedDBObject(attribute, true); + } + } + + private static bool UpdateList(Transaction transaction, BlockReference reference, LineNumberListApplyPreflight plan) + { + var position = new Point3d(plan.Position.X, plan.Position.Y, plan.Position.Z); + var changed = reference.Position != position || reference.Rotation != plan.Rotation; + reference.UpgradeOpen(); + reference.Position = position; + reference.Rotation = plan.Rotation; + var values = plan.Rows.SelectMany(row => new[] + { + new KeyValuePair($"NUMBER{row.MarkerNumber}", row.MarkerNumber.ToString(System.Globalization.CultureInfo.InvariantCulture)), + new KeyValuePair($"LINENUMBER{row.MarkerNumber}", row.Text.Replace("\n", "\\P", StringComparison.Ordinal)) + }).ToDictionary(value => value.Key, value => value.Value, StringComparer.OrdinalIgnoreCase); + foreach (ObjectId id in reference.AttributeCollection) + { + var attribute = (AttributeReference)transaction.GetObject(id, OpenMode.ForWrite); + if (!values.TryGetValue(attribute.Tag, out var value)) continue; + changed |= !string.Equals(attribute.TextString, value, StringComparison.Ordinal); + attribute.TextString = value; + attribute.UpdateMTextAttribute(); + } + return changed; + } + private static ObjectId EnsureMarkerDefinition(Transaction transaction, Database database, BlockTable blocks, LineNumberMarkerAsset marker) { if (blocks.Has(MarkerBlockName)) return blocks[MarkerBlockName]; @@ -129,6 +268,11 @@ internal sealed class LineNumberAnnotationWriter return null; } + private static ResultBuffer ListOwnership(string stableId) => new( + new TypedValue((int)DxfCode.ExtendedDataRegAppName, ApplicationName), + new TypedValue((int)DxfCode.ExtendedDataAsciiString, stableId), + new TypedValue((int)DxfCode.ExtendedDataAsciiString, "LIST")); + private static bool UpdateMarker(Transaction transaction, BlockReference marker, LineNumberApplyPreflight item, string layer) { var position = new Point3d(item.Position.X, item.Position.Y, item.Position.Z);