using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Geometry; using PRecAnnot.Abstractions; namespace PRecAnnot.Plant3D2026; internal sealed class LineNumberOwnedAnnotationReader { private const string ApplicationName = "PXP-LN"; public IReadOnlyList ReadList(Document document, ObjectId listId) { using var transaction = document.Database.TransactionManager.StartOpenCloseTransaction(); if (transaction.GetObject(listId, OpenMode.ForRead) is not BlockReference list || !HasRole(list, "LIST")) throw new InvalidOperationException("LINE_NUMBER_LIST_NOT_SELECTED"); var rows = new Dictionary(); foreach (ObjectId id in list.AttributeCollection) { var attribute = (AttributeReference)transaction.GetObject(id, OpenMode.ForRead); var ownership = ReadOwnership(attribute); if (ownership is null) continue; rows.TryGetValue(ownership.Value.MarkerNumber, out var row); rows[ownership.Value.MarkerNumber] = ownership.Value.Role == "LINENUMBER" ? (ownership.Value.StableId, attribute.TextString.Replace("\\P", "\n", StringComparison.Ordinal)) : (ownership.Value.StableId ?? row.StableId, row.Text); } var result = rows.OrderBy(value => value.Key) .Select(value => new LineNumberOwnedRow( value.Value.StableId ?? throw new InvalidOperationException("LINE_NUMBER_ROW_STABLE_ID_MISSING"), value.Key, value.Value.Text ?? throw new InvalidOperationException("LINE_NUMBER_ROW_TEXT_MISSING"), default)) .ToArray(); if (result.Length == 0) throw new InvalidOperationException("LINE_NUMBER_LIST_EMPTY"); return result; } public LineNumberOwnedRow ReadMarker(Document document, ObjectId markerId) { using var transaction = document.Database.TransactionManager.StartOpenCloseTransaction(); if (transaction.GetObject(markerId, OpenMode.ForRead) is not BlockReference marker) throw new InvalidOperationException("LINE_NUMBER_MARKER_NOT_SELECTED"); var values = marker.GetXDataForApplication(ApplicationName)?.AsArray() ?? []; var stableId = values.Where(value => value.TypeCode == 1000).Select(value => value.Value as string) .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 = values.Where(value => value.TypeCode == 1000).Select(value => value.Value as string).LastOrDefault() ?? string.Empty; if (string.IsNullOrWhiteSpace(stableId) || markerNumber <= 0) throw new InvalidOperationException("LINE_NUMBER_MARKER_OWNERSHIP_MISSING"); var localPosition = Point3d.Origin; foreach (ObjectId id in marker.AttributeCollection) { var attribute = (AttributeReference)transaction.GetObject(id, OpenMode.ForRead); if (!string.Equals(attribute.Tag, "NUMBER", StringComparison.OrdinalIgnoreCase)) continue; localPosition = attribute.Position.TransformBy(marker.BlockTransform.Inverse()); break; } return new(stableId, markerNumber, text, new(localPosition.X, localPosition.Y, localPosition.Z)); } public IReadOnlyList 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(); 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) => reference.GetXDataForApplication(ApplicationName)?.AsArray().Any(value => value.TypeCode == 1000 && string.Equals(value.Value as string, role, StringComparison.OrdinalIgnoreCase)) == true; private static (string? StableId, string Role, int MarkerNumber)? ReadOwnership(AttributeReference attribute) { var values = attribute.GetXDataForApplication(ApplicationName)?.AsArray(); if (values is null) return null; var strings = values.Where(value => value.TypeCode == 1000).Select(value => value.Value as string).ToArray(); var role = strings.FirstOrDefault(value => value is "MARKER" or "LINENUMBER"); var number = values.Where(value => value.TypeCode == 1071).Select(value => Convert.ToInt32(value.Value)).FirstOrDefault(); return role is null || number <= 0 ? null : (strings.FirstOrDefault(value => value != role), role, number); } }