using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Geometry; using PRecAnnot.Abstractions; namespace PRecAnnot.Plant3D2026; internal sealed class LineConnectionAnnotationWriter { private const string ApplicationName = "PXP-ALC"; public LineConnectionApplyResult Replace(Document document, string viewportStableId, IReadOnlyList plans, LineConnectionAssets assets) { var insertable = plans.Where(plan => plan.Reasons.Count == 0).OrderBy(plan => plan.Position.X).ThenByDescending(plan => plan.Position.Y).ToArray(); 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 layers = (LayerTable)transaction.GetObject(database.LayerTableId, OpenMode.ForRead); var styles = (TextStyleTable)transaction.GetObject(database.TextStyleTableId, OpenMode.ForRead); if (!blocks.Has(assets.BlockName)) throw new InvalidOperationException("LINE_CONNECTION_BLOCK_MISSING:" + assets.BlockName); if (!layers.Has(assets.PolylineLayer)) throw new InvalidOperationException("LINE_CONNECTION_POLYLINE_LAYER_MISSING:" + assets.PolylineLayer); if (!layers.Has(assets.TextLayer)) throw new InvalidOperationException("LINE_CONNECTION_TEXT_LAYER_MISSING:" + assets.TextLayer); if (!styles.Has(assets.TextStyle)) throw new InvalidOperationException("LINE_CONNECTION_TEXT_STYLE_MISSING:" + assets.TextStyle); EnsureRegApp(transaction, database); var paper = (BlockTableRecord)transaction.GetObject(blocks[BlockTableRecord.PaperSpace], OpenMode.ForWrite); var deleted = 0; foreach (ObjectId id in paper) { var value = transaction.GetObject(id, OpenMode.ForRead); var data = value.GetXDataForApplication(ApplicationName)?.AsArray(); if (data is null || !data.Any(item => item.TypeCode == (int)DxfCode.ExtendedDataAsciiString && string.Equals(item.Value?.ToString(), viewportStableId, StringComparison.Ordinal))) continue; value.UpgradeOpen(); value.Erase(); deleted++; } var definition = (BlockTableRecord)transaction.GetObject(blocks[assets.BlockName], OpenMode.ForRead); foreach (var plan in insertable) { using var reference = new BlockReference(new Point3d(plan.Position.X, plan.Position.Y, plan.Position.Z), definition.ObjectId) { LayerId = layers[assets.PolylineLayer], Rotation = plan.Rotation }; reference.XData = Ownership(viewportStableId, plan); paper.AppendEntity(reference); transaction.AddNewlyCreatedDBObject(reference, true); var values = new[] { plan.FirstText ?? string.Empty, plan.SecondText ?? string.Empty }; var index = 0; foreach (ObjectId entityId in definition) { if (transaction.GetObject(entityId, OpenMode.ForRead) is not AttributeDefinition source || source.Constant) continue; using var attribute = new AttributeReference(); attribute.SetAttributeFromBlock(source, reference.BlockTransform); attribute.TextString = values[Math.Min(index++, 1)].Replace("\n", "\\P", StringComparison.Ordinal); attribute.LayerId = layers[assets.TextLayer]; attribute.TextStyleId = styles[assets.TextStyle]; attribute.UpdateMTextAttribute(); reference.AttributeCollection.AppendAttribute(attribute); transaction.AddNewlyCreatedDBObject(attribute, true); } if (plan.ConnectorPosition is { } connector) { using var leader = new Polyline(3) { LayerId = layers[assets.PolylineLayer], XData = Ownership(viewportStableId, plan) }; leader.AddVertexAt(0, new Point2d(connector.X, connector.Y), 0, 0, 0); // Legacy PXP-ALC uses an orthogonal two-segment leader. Its // elbow shares the annotation Y coordinate for a stable route. leader.AddVertexAt(1, new Point2d(connector.X, plan.Position.Y), 0, 0, 0); leader.AddVertexAt(2, new Point2d(plan.Position.X, plan.Position.Y), 0, 0, 0); paper.AppendEntity(leader); transaction.AddNewlyCreatedDBObject(leader, true); } } transaction.Commit(); return new(deleted, insertable.Length); } private static ResultBuffer Ownership(string viewport, LineConnectionBoundaryPlan plan) => new( new TypedValue((int)DxfCode.ExtendedDataRegAppName, ApplicationName), new TypedValue((int)DxfCode.ExtendedDataAsciiString, viewport), new TypedValue((int)DxfCode.ExtendedDataAsciiString, plan.StableId), new TypedValue((int)DxfCode.ExtendedDataAsciiString, "LINENUMBER1"), new TypedValue((int)DxfCode.ExtendedDataAsciiString, plan.FirstText ?? string.Empty), new TypedValue((int)DxfCode.ExtendedDataAsciiString, "LINENUMBER2"), new TypedValue((int)DxfCode.ExtendedDataAsciiString, plan.SecondText ?? string.Empty)); 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); } }