diff --git a/Plant3D2026/P_Rec_Annot/bundle/P_Rec_Annot.bundle/PackageContents.xml b/Plant3D2026/P_Rec_Annot/bundle/P_Rec_Annot.bundle/PackageContents.xml index f696ce6..5c984e9 100644 --- a/Plant3D2026/P_Rec_Annot/bundle/P_Rec_Annot.bundle/PackageContents.xml +++ b/Plant3D2026/P_Rec_Annot/bundle/P_Rec_Annot.bundle/PackageContents.xml @@ -1,5 +1,5 @@ - + diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs index 8039378..9e14771 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs @@ -54,6 +54,7 @@ public sealed class EntryPoint : IExtensionApplication [CommandMethod("PXP-COV")] public void CheckOrthoViewportCommand() => OrthoMaintenanceCommands.CheckViewport(); [CommandMethod("PXP-UOB")] public void UpdateOrthoViewBlocksCommand() => PRecAnnot.Plant3D2026.UpdateOrthoViewBlocksCommand.Run(); [CommandMethod("PXP-RA", CommandFlags.Session)] public void RemoveOrthoAnnotationsCommand() => OrthoAnnotationRemovalCommand.Run(); + [CommandMethod("PXP-OSI", CommandFlags.Session)] public void ShowOrthoSourceInModelCommand() => OrthoSourceNavigationCommand.Run(); [CommandMethod("PXP-AC", CommandFlags.UsePickSet)] public void SwapAttributesCommand() => PRecAnnot.Plant3D2026.SwapAttributesCommand.Run(); [CommandMethod("PXP-IOD", CommandFlags.Session)] public void UpdateOrthoDrawingsCommand() => PRecAnnot.Plant3D2026.UpdateOrthoDrawingsCommand.Run(); [CommandMethod("mto-ma")] public void ShowManualAnnotationPaletteCommand() => ManualAnnotationPalette.Show(); diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/OrthoAnnotationWriter.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/OrthoAnnotationWriter.cs index 6354eb4..2dedb71 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/OrthoAnnotationWriter.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/OrthoAnnotationWriter.cs @@ -35,13 +35,13 @@ internal sealed class OrthoAnnotationWriter using var leader = new MLeader { LayerId = layers[assets.Layer], MLeaderStyle = leaders.GetAt(assets.MultiLeaderStyle), ContentType = ContentType.MTextContent, LeaderLineType = LeaderType.StraightLeader }; var leaderId = leader.AddLeader(); var lineId = leader.AddLeaderLine(leaderId); leader.AddFirstVertex(lineId, start); leader.AddLastVertex(lineId, land); using var text = new MText { Contents = classification.Text, TextStyleId = styles[assets.TextStyle], Location = land, BackgroundFill = true, UseBackgroundColor = true, BackgroundScaleFactor = 1.1 }; - leader.MText = text; leader.TextLocation = land; leader.XData = Ownership(viewport.Id, source.SnapshotId, classification); + leader.MText = text; leader.TextLocation = land; leader.XData = Ownership(viewport, source, classification); paper.AppendEntity(leader); transaction.AddNewlyCreatedDBObject(leader, true); } else { var definition = (BlockTableRecord)transaction.GetObject(blocks[assets.BubbleBlock], OpenMode.ForRead); - using var reference = new BlockReference(start, definition.ObjectId) { LayerId = layers[assets.Layer], XData = Ownership(viewport.Id, source.SnapshotId, classification) }; + using var reference = new BlockReference(start, definition.ObjectId) { LayerId = layers[assets.Layer], XData = Ownership(viewport, source, classification) }; paper.AppendEntity(reference); transaction.AddNewlyCreatedDBObject(reference, true); foreach (ObjectId id in definition) { @@ -58,6 +58,17 @@ internal sealed class OrthoAnnotationWriter transaction.Commit(); return deleted == 0 ? "Created" : "Updated"; } private static bool OwnedBy(Entity entity, string source) => entity.GetXDataForApplication(ApplicationName)?.AsArray().Any(x => x.TypeCode == (int)DxfCode.ExtendedDataAsciiString && string.Equals(x.Value?.ToString(), source, StringComparison.Ordinal)) == true; - private static ResultBuffer Ownership(string viewport, string source, OrthoAnnotationClassification value) => new(new TypedValue((int)DxfCode.ExtendedDataRegAppName, ApplicationName), new TypedValue((int)DxfCode.ExtendedDataAsciiString, "VIEWPORT"), new TypedValue((int)DxfCode.ExtendedDataAsciiString, viewport), new TypedValue((int)DxfCode.ExtendedDataAsciiString, "SOURCE"), new TypedValue((int)DxfCode.ExtendedDataAsciiString, source), new TypedValue((int)DxfCode.ExtendedDataAsciiString, "ANNOTATIONTEXT"), new TypedValue((int)DxfCode.ExtendedDataAsciiString, value.Text), new TypedValue((int)DxfCode.ExtendedDataAsciiString, value.Representation.ToString())); + private static ResultBuffer Ownership(OrthoViewportInfo viewport, PlantObjectSnapshot source, OrthoAnnotationClassification value) + { + source.Properties.TryGetValue("PRecAnnot.RowId", out var rowId); + return new ResultBuffer( + new TypedValue((int)DxfCode.ExtendedDataRegAppName, ApplicationName), + new TypedValue((int)DxfCode.ExtendedDataAsciiString, "VIEWPORT"), new TypedValue((int)DxfCode.ExtendedDataAsciiString, viewport.Id), + new TypedValue((int)DxfCode.ExtendedDataAsciiString, "SOURCE"), new TypedValue((int)DxfCode.ExtendedDataAsciiString, source.SnapshotId), + new TypedValue((int)DxfCode.ExtendedDataAsciiString, "SOURCE_DRAWING"), new TypedValue((int)DxfCode.ExtendedDataAsciiString, viewport.SourceDrawingName ?? string.Empty), + new TypedValue((int)DxfCode.ExtendedDataAsciiString, "SOURCE_ROW"), new TypedValue((int)DxfCode.ExtendedDataAsciiString, rowId ?? string.Empty), + new TypedValue((int)DxfCode.ExtendedDataAsciiString, "ANNOTATIONTEXT"), new TypedValue((int)DxfCode.ExtendedDataAsciiString, value.Text), + new TypedValue((int)DxfCode.ExtendedDataAsciiString, value.Representation.ToString())); + } 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); } } diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/OrthoSourceNavigationCommand.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/OrthoSourceNavigationCommand.cs new file mode 100644 index 0000000..1952a2a --- /dev/null +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/OrthoSourceNavigationCommand.cs @@ -0,0 +1,108 @@ +using Autodesk.AutoCAD.ApplicationServices; +using Autodesk.AutoCAD.DatabaseServices; +using Autodesk.AutoCAD.EditorInput; +using Autodesk.AutoCAD.Geometry; +using Autodesk.ProcessPower.DataLinks; +using Autodesk.ProcessPower.PlantInstance; +using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application; + +namespace PRecAnnot.Plant3D2026; + +// Source-backed replacement for OrthoAutoAnnotate.HighlightAndZoomPnPId. +// New PXP-OA annotations persist their project source drawing and DataLinks row +// in XData. Legacy annotations without that information remain untouched and +// fail safely rather than being migrated destructively. +internal static class OrthoSourceNavigationCommand +{ + private const string ApplicationName = "PXP-OA"; + + public static void Run() + { + var ortho = AcadApplication.DocumentManager.MdiActiveDocument; + if (ortho is null || PlantApplication.CurrentProject is null) return; + var pick = ortho.Editor.GetEntity(new PromptEntityOptions("\nSelect an Ortho annotation to show its model source: ") { AllowNone = false }); + if (pick.Status != PromptStatus.OK) return; + if (!TryReadSource(ortho.Database, pick.ObjectId, out var sourceDrawing, out var rowId)) + { + ortho.Editor.WriteMessage("\nThe selected annotation has no modern source identity; no drawing was changed."); + return; + } + + var target = AcadApplication.DocumentManager.Cast() + .FirstOrDefault(document => SamePath(document.Name, sourceDrawing)); + if (target is null) + { + var answer = ortho.Editor.GetKeywords(new PromptKeywordOptions($"\nOpen model drawing '{Path.GetFileName(sourceDrawing)}'? [Open/Cancel] : ") { Keywords = { "Open", "Cancel" }, AllowNone = true }); + if (answer.Status == PromptStatus.Cancel || string.Equals(answer.StringResult, "Cancel", StringComparison.OrdinalIgnoreCase)) return; + if (!File.Exists(sourceDrawing)) { ortho.Editor.WriteMessage("\nThe source drawing is unavailable in the active project."); return; } + try { target = AcadApplication.DocumentManager.Open(sourceDrawing, false); } + catch (Exception exception) { RuntimeDiagnostics.Write("OrthoSourceNavigation", "ERROR", resultStatus: "SOURCE_DRAWING_OPEN_FAILED", exception: exception); ortho.Editor.WriteMessage("\nUnable to open the source drawing."); return; } + } + + AcadApplication.DocumentManager.MdiActiveDocument = target; + var links = PlantApplication.ProjectPart("Piping")?.DataLinksManager; + if (links is null) { target.Editor.WriteMessage("\nPiping DataLinks are unavailable."); return; } + var id = FindEntityForRow(target.Database, links, rowId); + if (id.IsNull) { target.Editor.WriteMessage("\nThe source component no longer exists."); return; } + HighlightAndZoom(target, id); + RuntimeDiagnostics.Write("OrthoSourceNavigation", resultStatus: "SOURCE_OBJECT_HIGHLIGHTED", document: target); + } + + private static bool TryReadSource(Database database, ObjectId id, out string drawing, out int rowId) + { + drawing = string.Empty; rowId = 0; + try + { + using var transaction = database.TransactionManager.StartOpenCloseTransaction(); + if (transaction.GetObject(id, OpenMode.ForRead) is not Entity entity) return false; + using var data = entity.GetXDataForApplication(ApplicationName); + var values = data?.AsArray().Where(value => value.TypeCode == (int)DxfCode.ExtendedDataAsciiString).Select(value => value.Value?.ToString() ?? string.Empty).ToArray(); + if (values is null) return false; + for (var index = 0; index + 1 < values.Length; index++) + { + if (string.Equals(values[index], "SOURCE_DRAWING", StringComparison.Ordinal)) drawing = values[index + 1]; + if (string.Equals(values[index], "SOURCE_ROW", StringComparison.Ordinal)) int.TryParse(values[index + 1], out rowId); + } + return Path.IsPathRooted(drawing) && rowId > 0; + } + catch { return false; } + } + + private static ObjectId FindEntityForRow(Database database, DataLinksManager links, int rowId) + { + try + { + using var transaction = database.TransactionManager.StartOpenCloseTransaction(); + var table = (BlockTable)transaction.GetObject(database.BlockTableId, OpenMode.ForRead); + var model = (BlockTableRecord)transaction.GetObject(table[BlockTableRecord.ModelSpace], OpenMode.ForRead); + foreach (ObjectId id in model) + { + if (transaction.GetObject(id, OpenMode.ForRead) is not Entity) continue; + try { if (links.FindAcPpRowId(id) == rowId) return id; } + catch (DLException) { } + } + } + catch { } + return ObjectId.Null; + } + + private static void HighlightAndZoom(Document document, ObjectId id) + { + using var transaction = document.Database.TransactionManager.StartTransaction(); + if (transaction.GetObject(id, OpenMode.ForRead) is not Entity entity) return; + entity.Highlight(); + var extents = entity.GeometricExtents; + using var view = document.Editor.GetCurrentView(); + var transform = Matrix3d.PlaneToWorld(view.ViewDirection); + transform = Matrix3d.Displacement(view.Target - Point3d.Origin) * transform; + transform = Matrix3d.Rotation(-view.ViewTwist, view.ViewDirection, view.Target) * transform; + extents.TransformBy(transform.Inverse()); + view.Width = Math.Max(extents.MaxPoint.X - extents.MinPoint.X, 1d); + view.Height = Math.Max(extents.MaxPoint.Y - extents.MinPoint.Y, 1d); + view.CenterPoint = new Point2d((extents.MaxPoint.X + extents.MinPoint.X) / 2d, (extents.MaxPoint.Y + extents.MinPoint.Y) / 2d); + document.Editor.SetCurrentView(view); + transaction.Commit(); + } + + private static bool SamePath(string left, string right) => string.Equals(Path.GetFullPath(left), Path.GetFullPath(right), StringComparison.OrdinalIgnoreCase); +} diff --git a/Plant3D2026/P_Rec_Annot/tools/Build-Bundle.ps1 b/Plant3D2026/P_Rec_Annot/tools/Build-Bundle.ps1 index 06a4490..4527771 100644 --- a/Plant3D2026/P_Rec_Annot/tools/Build-Bundle.ps1 +++ b/Plant3D2026/P_Rec_Annot/tools/Build-Bundle.ps1 @@ -16,6 +16,6 @@ $moduleManifestPath=Join-Path $bundleRoot 'Contents\module-manifest.json' $files=Get-ChildItem $bundleRoot -Recurse -File | Where-Object { $_.FullName -ne $moduleManifestPath } | Sort-Object FullName | ForEach-Object { [ordered]@{ relativePath=$_.FullName.Substring($bundleRoot.Length + 1).Replace('\','/'); sha256=(Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant(); required=$true } } -$manifest=[ordered]@{ schemaVersion=2; package='P_Rec_Annot'; version='0.2.4'; modules=$modules; assemblies=$assemblies; files=$files } +$manifest=[ordered]@{ schemaVersion=2; package='P_Rec_Annot'; version='0.2.5'; modules=$modules; assemblies=$assemblies; files=$files } $manifest | ConvertTo-Json -Depth 5 | Set-Content (Join-Path $bundleRoot 'Contents\module-manifest.json') -Encoding utf8 Write-Host 'Bundle assembly build completed; deployment is intentionally not performed.'