feat: add ortho annotation source navigation
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ApplicationPackage SchemaVersion="1.0" Name="P_Rec_Annot" Description="P_Rec_Annot Plant 3D 2026" AppVersion="0.2.4" ProductCode="b455ee6eaed79d76f084846a60ce3284" Author="NBIM">
|
||||
<ApplicationPackage SchemaVersion="1.0" Name="P_Rec_Annot" Description="P_Rec_Annot Plant 3D 2026" AppVersion="0.2.5" ProductCode="b455ee6eaed79d76f084846a60ce3284" Author="NBIM">
|
||||
<Components>
|
||||
<!-- SeriesMin/SeriesMax and platform token are pending official/runtime verification. -->
|
||||
<RuntimeRequirements OS="Win64" />
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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); }
|
||||
}
|
||||
|
||||
@@ -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<Document>()
|
||||
.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] <Open>: ") { 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);
|
||||
}
|
||||
@@ -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.'
|
||||
|
||||
Reference in New Issue
Block a user