feat: add ortho annotation removal and package integrity manifest
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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.3" ProductCode="b455ee6eaed79d76f084846a60ce3284" Author="NBIM">
|
<ApplicationPackage SchemaVersion="1.0" Name="P_Rec_Annot" Description="P_Rec_Annot Plant 3D 2026" AppVersion="0.2.4" ProductCode="b455ee6eaed79d76f084846a60ce3284" Author="NBIM">
|
||||||
<Components>
|
<Components>
|
||||||
<!-- SeriesMin/SeriesMax and platform token are pending official/runtime verification. -->
|
<!-- SeriesMin/SeriesMax and platform token are pending official/runtime verification. -->
|
||||||
<RuntimeRequirements OS="Win64" />
|
<RuntimeRequirements OS="Win64" />
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ public sealed class EntryPoint : IExtensionApplication
|
|||||||
[CommandMethod("PXP-IE")] public void InsertElevationCommand() => ElevationCommands.RunManual();
|
[CommandMethod("PXP-IE")] public void InsertElevationCommand() => ElevationCommands.RunManual();
|
||||||
[CommandMethod("PXP-COV")] public void CheckOrthoViewportCommand() => OrthoMaintenanceCommands.CheckViewport();
|
[CommandMethod("PXP-COV")] public void CheckOrthoViewportCommand() => OrthoMaintenanceCommands.CheckViewport();
|
||||||
[CommandMethod("PXP-UOB")] public void UpdateOrthoViewBlocksCommand() => PRecAnnot.Plant3D2026.UpdateOrthoViewBlocksCommand.Run();
|
[CommandMethod("PXP-UOB")] public void UpdateOrthoViewBlocksCommand() => PRecAnnot.Plant3D2026.UpdateOrthoViewBlocksCommand.Run();
|
||||||
|
[CommandMethod("PXP-RA", CommandFlags.Session)] public void RemoveOrthoAnnotationsCommand() => OrthoAnnotationRemovalCommand.Run();
|
||||||
[CommandMethod("PXP-AC", CommandFlags.UsePickSet)] public void SwapAttributesCommand() => PRecAnnot.Plant3D2026.SwapAttributesCommand.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("PXP-IOD", CommandFlags.Session)] public void UpdateOrthoDrawingsCommand() => PRecAnnot.Plant3D2026.UpdateOrthoDrawingsCommand.Run();
|
||||||
[CommandMethod("mto-ma")] public void ShowManualAnnotationPaletteCommand() => ManualAnnotationPalette.Show();
|
[CommandMethod("mto-ma")] public void ShowManualAnnotationPaletteCommand() => ManualAnnotationPalette.Show();
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using Autodesk.AutoCAD.ApplicationServices;
|
||||||
|
using Autodesk.AutoCAD.DatabaseServices;
|
||||||
|
using Autodesk.AutoCAD.EditorInput;
|
||||||
|
using Autodesk.ProcessPower.P3dProjectParts;
|
||||||
|
using Autodesk.ProcessPower.PlantInstance;
|
||||||
|
using PRecAnnot.Abstractions;
|
||||||
|
using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Core.Application;
|
||||||
|
|
||||||
|
namespace PRecAnnot.Plant3D2026;
|
||||||
|
|
||||||
|
// Source port of OrthoAutoAnnotate.Commands.RemoveAnnotations (PXP-RA).
|
||||||
|
// The modern writer records the viewport identity in PXP-OA XData, allowing
|
||||||
|
// deterministic removal without depending on the historical named-dictionary scan.
|
||||||
|
internal static class OrthoAnnotationRemovalCommand
|
||||||
|
{
|
||||||
|
private const string ApplicationName = "PXP-OA";
|
||||||
|
|
||||||
|
public static void Run()
|
||||||
|
{
|
||||||
|
var document = AcadApplication.DocumentManager.MdiActiveDocument;
|
||||||
|
if (document is null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var options = new PromptEntityOptions("\nSelect Ortho viewport: ");
|
||||||
|
options.SetRejectMessage("\nA viewport is required.");
|
||||||
|
options.AddAllowedClass(typeof(Viewport), true);
|
||||||
|
var selected = document.Editor.GetEntity(options);
|
||||||
|
if (selected.Status != PromptStatus.OK) return;
|
||||||
|
var viewport = Resolve(document, selected.ObjectId);
|
||||||
|
|
||||||
|
using var documentLock = document.LockDocument();
|
||||||
|
using var transaction = document.Database.TransactionManager.StartTransaction();
|
||||||
|
var blocks = (BlockTable)transaction.GetObject(document.Database.BlockTableId, OpenMode.ForRead);
|
||||||
|
var paper = (BlockTableRecord)transaction.GetObject(blocks[BlockTableRecord.PaperSpace], OpenMode.ForRead);
|
||||||
|
var removed = 0;
|
||||||
|
foreach (ObjectId id in paper)
|
||||||
|
{
|
||||||
|
var entity = transaction.GetObject(id, OpenMode.ForRead) as Entity;
|
||||||
|
if (entity is null || !OwnedByViewport(entity, viewport.Id)) continue;
|
||||||
|
entity.UpgradeOpen();
|
||||||
|
entity.Erase();
|
||||||
|
removed++;
|
||||||
|
}
|
||||||
|
transaction.Commit();
|
||||||
|
document.Editor.WriteMessage($"\nPXP-RA: removed={removed}; native AutoCAD UNDO is available.");
|
||||||
|
RuntimeDiagnostics.Write("OrthoAnnotationRemovalCompleted", commandName: "PXP-RA", resultStatus: "Completed", document: document, details: new Dictionary<string, object?> { ["Removed"] = removed, ["Viewport"] = viewport.Id });
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
RuntimeDiagnostics.Write("OrthoAnnotationRemovalFailed", "ERROR", commandName: "PXP-RA", resultStatus: exception.Message, exception: exception, document: document);
|
||||||
|
document.Editor.WriteMessage("\nPXP-RA: " + exception.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OrthoViewportInfo Resolve(Document document, ObjectId selectedId)
|
||||||
|
{
|
||||||
|
using var transaction = document.Database.TransactionManager.StartOpenCloseTransaction();
|
||||||
|
var selected = (Viewport)transaction.GetObject(selectedId, OpenMode.ForRead);
|
||||||
|
var project = PlantApplication.ProjectPart("Ortho") as OrthoProject ?? throw new InvalidOperationException("ORTHO_PROJECT_UNAVAILABLE");
|
||||||
|
var matches = new PlantOrthoViewportReader(project, document.Name).ReadViewports(CancellationToken.None)
|
||||||
|
.Where(x => Math.Abs(x.CenterX - selected.CenterPoint.X) < .001 && Math.Abs(x.CenterY - selected.CenterPoint.Y) < .001 && Math.Abs(x.Width - selected.Width) < .001 && Math.Abs(x.Height - selected.Height) < .001)
|
||||||
|
.ToArray();
|
||||||
|
return matches.Length == 1 ? matches[0] : throw new InvalidOperationException(matches.Length == 0 ? "ORTHO_VIEW_NOT_FOUND" : "ORTHO_VIEW_AMBIGUOUS");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool OwnedByViewport(Entity entity, string viewportId)
|
||||||
|
{
|
||||||
|
using var data = entity.GetXDataForApplication(ApplicationName);
|
||||||
|
var values = data?.AsArray().Where(value => value.TypeCode == (int)DxfCode.ExtendedDataAsciiString).Select(value => value.Value?.ToString()).ToArray();
|
||||||
|
return values is { Length: >= 2 } && string.Equals(values[0], "VIEWPORT", StringComparison.Ordinal) && string.Equals(values[1], viewportId, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ $ErrorActionPreference='Stop'
|
|||||||
dotnet build (Join-Path $PSScriptRoot '..\PRecAnnot2026.sln') --configuration $Configuration /p:AutoCAD2026ManagedDir=$AutoCAD2026ManagedDir /p:Plant3D2026ManagedDir=$Plant3D2026ManagedDir
|
dotnet build (Join-Path $PSScriptRoot '..\PRecAnnot2026.sln') --configuration $Configuration /p:AutoCAD2026ManagedDir=$AutoCAD2026ManagedDir /p:Plant3D2026ManagedDir=$Plant3D2026ManagedDir
|
||||||
if($LASTEXITCODE -ne 0){ throw "Build failed: $LASTEXITCODE" }
|
if($LASTEXITCODE -ne 0){ throw "Build failed: $LASTEXITCODE" }
|
||||||
$bundleRoot=Join-Path $PSScriptRoot '..\artifacts\P_Rec_Annot.bundle'
|
$bundleRoot=Join-Path $PSScriptRoot '..\artifacts\P_Rec_Annot.bundle'
|
||||||
|
$bundleRoot=(Resolve-Path $bundleRoot).Path
|
||||||
$contents=Join-Path $bundleRoot 'Contents\Windows'
|
$contents=Join-Path $bundleRoot 'Contents\Windows'
|
||||||
New-Item -ItemType Directory -Force -Path $contents | Out-Null
|
New-Item -ItemType Directory -Force -Path $contents | Out-Null
|
||||||
$output=Join-Path $PSScriptRoot "..\src\PRecAnnot.Plant3D2026\bin\$Configuration\net8.0-windows"
|
$output=Join-Path $PSScriptRoot "..\src\PRecAnnot.Plant3D2026\bin\$Configuration\net8.0-windows"
|
||||||
@@ -11,6 +12,10 @@ Copy-Item (Join-Path $PSScriptRoot '..\bundle\P_Rec_Annot.bundle\PackageContents
|
|||||||
Copy-Item (Join-Path $PSScriptRoot '..\bundle\P_Rec_Annot.bundle\Settings.xml') $bundleRoot -Force
|
Copy-Item (Join-Path $PSScriptRoot '..\bundle\P_Rec_Annot.bundle\Settings.xml') $bundleRoot -Force
|
||||||
$modules=@('PHP_REC_ANNOT','AnnotateLineNumbers','AutoLinenumberConnections','AutoElevation','InsertElevation','ExtentsIntersect','DataLinksRead')
|
$modules=@('PHP_REC_ANNOT','AnnotateLineNumbers','AutoLinenumberConnections','AutoElevation','InsertElevation','ExtentsIntersect','DataLinksRead')
|
||||||
$assemblies=Get-ChildItem $contents -File -Filter '*.dll' | Sort-Object Name | ForEach-Object { [ordered]@{ name=$_.Name; sha256=(Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant(); required=$true } }
|
$assemblies=Get-ChildItem $contents -File -Filter '*.dll' | Sort-Object Name | ForEach-Object { [ordered]@{ name=$_.Name; sha256=(Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant(); required=$true } }
|
||||||
$manifest=[ordered]@{ schemaVersion=1; package='P_Rec_Annot'; version='0.2.3'; modules=$modules; assemblies=$assemblies }
|
$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 | ConvertTo-Json -Depth 5 | Set-Content (Join-Path $bundleRoot 'Contents\module-manifest.json') -Encoding utf8
|
$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.'
|
Write-Host 'Bundle assembly build completed; deployment is intentionally not performed.'
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ if(-not (Test-Path $moduleManifestPath)){throw 'Composite module manifest is mis
|
|||||||
$moduleManifest=Get-Content $moduleManifestPath -Raw | ConvertFrom-Json
|
$moduleManifest=Get-Content $moduleManifestPath -Raw | ConvertFrom-Json
|
||||||
if($moduleManifest.package -ne 'P_Rec_Annot' -or $moduleManifest.version -ne $xml.ApplicationPackage.AppVersion){throw 'Composite module manifest identity/version mismatch.'}
|
if($moduleManifest.package -ne 'P_Rec_Annot' -or $moduleManifest.version -ne $xml.ApplicationPackage.AppVersion){throw 'Composite module manifest identity/version mismatch.'}
|
||||||
foreach($assembly in $moduleManifest.assemblies){$path=Join-Path $BundleRoot "Contents\Windows\$($assembly.name)"; if(-not (Test-Path $path)){throw "Manifest assembly missing: $($assembly.name)"}; if((Get-FileHash $path -Algorithm SHA256).Hash.ToLowerInvariant() -ne $assembly.sha256){throw "Manifest hash mismatch: $($assembly.name)"}}
|
foreach($assembly in $moduleManifest.assemblies){$path=Join-Path $BundleRoot "Contents\Windows\$($assembly.name)"; if(-not (Test-Path $path)){throw "Manifest assembly missing: $($assembly.name)"}; if((Get-FileHash $path -Algorithm SHA256).Hash.ToLowerInvariant() -ne $assembly.sha256){throw "Manifest hash mismatch: $($assembly.name)"}}
|
||||||
|
if($moduleManifest.schemaVersion -lt 2 -or @($moduleManifest.files).Count -eq 0){throw 'Complete package file manifest is missing.'}
|
||||||
|
foreach($file in $moduleManifest.files){$path=Join-Path $BundleRoot ($file.relativePath -replace '/', '\'); if(-not (Test-Path -LiteralPath $path)){throw "Manifest file missing: $($file.relativePath)"}; if((Get-FileHash $path -Algorithm SHA256).Hash.ToLowerInvariant() -ne $file.sha256){throw "Manifest hash mismatch: $($file.relativePath)"}}
|
||||||
$forbidden='^(AcMgd|AcDbMgd|AcCoreMgd|AdWindows|PnP.*)\.dll$'
|
$forbidden='^(AcMgd|AcDbMgd|AcCoreMgd|AdWindows|PnP.*)\.dll$'
|
||||||
$forbiddenFiles=Get-ChildItem $BundleRoot -Recurse -File | Where-Object { $_.Name -match $forbidden -or $_.Extension -in '.pdb','.cs' -or $_.FullName -match '\\(bin|obj)\\' }
|
$forbiddenFiles=Get-ChildItem $BundleRoot -Recurse -File | Where-Object { $_.Name -match $forbidden -or $_.Extension -in '.pdb','.cs' -or $_.FullName -match '\\(bin|obj)\\' }
|
||||||
if($forbiddenFiles){throw "Bundle contains forbidden files: $($forbiddenFiles.FullName -join ', ')"}
|
if($forbiddenFiles){throw "Bundle contains forbidden files: $($forbiddenFiles.FullName -join ', ')"}
|
||||||
|
|||||||
Reference in New Issue
Block a user