using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Geometry; using PRecAnnot.Abstractions; namespace PRecAnnot.Plant3D2026; internal sealed class ElevationAnnotationWriter { private const string ApplicationName = "PXP-AE"; public IReadOnlyList ReadExisting(Document document) { var result = new List(); 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); foreach (ObjectId id in paper) { if (transaction.GetObject(id, OpenMode.ForRead) is not BlockReference reference) continue; var data = reference.GetXDataForApplication(ApplicationName)?.AsArray(); if (data is null) continue; var strings = data.Where(x => x.TypeCode == (int)DxfCode.ExtendedDataAsciiString).Select(x => x.Value?.ToString() ?? string.Empty).ToArray(); var source = Value(strings, "SOURCE"); var text = Value(strings, "ANNOTATIONTEXT"); if (source is null || text is null) continue; result.Add(new ExistingElevationAnnotation(reference.Handle.ToString(), source, text, new AnnotationPoint(reference.Position.X, reference.Position.Y, reference.Position.Z))); } return result.OrderBy(x => x.SourceStableId, StringComparer.Ordinal).ToArray(); } public ElevationApplyResult Apply(Document document, IReadOnlyList operations, ElevationAssets assets, bool manual = false) { using var documentLock = document.LockDocument(); using var transaction = document.Database.TransactionManager.StartTransaction(); var blocks = (BlockTable)transaction.GetObject(document.Database.BlockTableId, OpenMode.ForRead); var layers = (LayerTable)transaction.GetObject(document.Database.LayerTableId, OpenMode.ForRead); var styles = (TextStyleTable)transaction.GetObject(document.Database.TextStyleTableId, OpenMode.ForRead); if (!blocks.Has(assets.BlockName)) throw new InvalidOperationException("ELEVATION_BLOCK_MISSING:" + assets.BlockName); if (!layers.Has(assets.Layer)) throw new InvalidOperationException("ELEVATION_LAYER_MISSING:" + assets.Layer); if (!styles.Has(assets.TextStyle)) throw new InvalidOperationException("ELEVATION_TEXT_STYLE_MISSING:" + assets.TextStyle); EnsureRegApp(transaction, document.Database); var paper = (BlockTableRecord)transaction.GetObject(blocks[BlockTableRecord.PaperSpace], OpenMode.ForWrite); var definition = (BlockTableRecord)transaction.GetObject(blocks[assets.BlockName], OpenMode.ForRead); var owned = paper.Cast().Select(id => transaction.GetObject(id, OpenMode.ForRead)).OfType() .Select(reference => (reference, source: Source(reference))).Where(x => x.source is not null) .GroupBy(x => x.source!, StringComparer.OrdinalIgnoreCase).ToDictionary(x => x.Key, x => x.ToArray(), StringComparer.OrdinalIgnoreCase); var created = 0; var updated = 0; var unchanged = 0; foreach (var operation in operations.Where(x => x.Kind is AnnotationOperationKind.Create or AnnotationOperationKind.Update)) { if (operation.Text is null) continue; var matches = owned.GetValueOrDefault(operation.StableId, []); BlockReference reference; if (matches.Length == 0) { reference = new BlockReference(ToPoint(operation.Position), definition.ObjectId) { LayerId = layers[assets.Layer], Rotation = operation.Rotation }; paper.AppendEntity(reference); transaction.AddNewlyCreatedDBObject(reference, true); created++; AddAttributes(transaction, definition, reference, operation.Text, styles[assets.TextStyle]); } else { reference = matches[0].reference; reference.UpgradeOpen(); reference.Position = ToPoint(operation.Position); reference.Rotation = operation.Rotation; reference.LayerId = layers[assets.Layer]; updated++; foreach (ObjectId attributeId in reference.AttributeCollection) { var attribute = (AttributeReference)transaction.GetObject(attributeId, OpenMode.ForWrite); attribute.TextString = operation.Text + (attribute.Tag.Contains('*') ? "*" : string.Empty); attribute.TextStyleId = styles[assets.TextStyle]; attribute.UpdateMTextAttribute(); } } reference.XData = Ownership(operation.StableId, operation.Text, manual); ApplyLegacyDynamicProperties(reference); } unchanged += operations.Count(x => x.Kind == AnnotationOperationKind.Skip && x.Reasons.Contains("ALREADY_CURRENT")); transaction.Commit(); return new(created, updated, unchanged); } private static void AddAttributes(Transaction transaction, BlockTableRecord definition, BlockReference reference, string text, ObjectId style) { 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 = text + (attribute.Tag.Contains('*') ? "*" : string.Empty); attribute.TextStyleId = style; attribute.UpdateMTextAttribute(); reference.AttributeCollection.AppendAttribute(attribute); transaction.AddNewlyCreatedDBObject(attribute, true); } } private static void ApplyLegacyDynamicProperties(BlockReference reference) { if (!reference.IsDynamicBlock) return; foreach (DynamicBlockReferenceProperty property in reference.DynamicBlockReferencePropertyCollection) if (string.Equals(property.PropertyName, "V1", StringComparison.OrdinalIgnoreCase) && !property.ReadOnly) property.Value = "От объекта"; } private static string? Source(BlockReference reference) { var strings = reference.GetXDataForApplication(ApplicationName)?.AsArray().Where(x => x.TypeCode == (int)DxfCode.ExtendedDataAsciiString).Select(x => x.Value?.ToString() ?? string.Empty).ToArray(); return strings is null ? null : Value(strings, "SOURCE"); } private static string? Value(string[] values, string key) { var index = Array.FindIndex(values, x => string.Equals(x, key, StringComparison.Ordinal)); return index >= 0 && index + 1 < values.Length ? values[index + 1] : null; } private static Point3d ToPoint(AnnotationPoint point) => new(point.X, point.Y, point.Z); private static ResultBuffer Ownership(string source, string text, bool manual) => new(new TypedValue((int)DxfCode.ExtendedDataRegAppName, ApplicationName), new TypedValue((int)DxfCode.ExtendedDataAsciiString, "SOURCE"), new TypedValue((int)DxfCode.ExtendedDataAsciiString, source), new TypedValue((int)DxfCode.ExtendedDataAsciiString, "ANNOTATIONTEXT"), new TypedValue((int)DxfCode.ExtendedDataAsciiString, text), new TypedValue((int)DxfCode.ExtendedDataAsciiString, manual ? "MANUAL" : "AUTO")); private static void EnsureRegApp(Transaction transaction, Database database) { var table = (RegAppTable)transaction.GetObject(database.RegAppTableId, OpenMode.ForRead); if (table.Has(ApplicationName)) return; table.UpgradeOpen(); using var record = new RegAppTableRecord { Name = ApplicationName }; table.Add(record); transaction.AddNewlyCreatedDBObject(record, true); } }